Blocklet SDK for blocklet developer
Install
yarn add @blocklet/sdkor
npm install @blocklet/sdkWallet
const { getWallet } = require('@blocklet/sdk');
// wallet is an instance of @ocap/wallet
const wallet = getWallet();
const { address, secretKey, publicKey } = wallet;Auth
Get Client
const { AuthService } = require('@blocklet/sdk');
const client = new AuthService();getUser
client.getUser(did)
Get user by user did
- @param did
string - @return
{ code, user }
getOwner
client.getOwner()
Get the owner of the app
- @param did
string - @return
{ code, user }
getUsers
Get users of the app
By Default
client.getUsers();
client.getUsers({ paging: { page: 2 } });
client.getUsers({ query: { role: 'admin' } });
client.getUsers({ query: { approved: true } });
client.getUsers({ query: { search: 'Bob' } });
client.getUsers({ sort: { updatedAt: -1 } });- @param paging
Object - paging.pageSize The value of pageSize cannot exceed 100
- paging.page
- @param query
Object - query.role
StringMatch users by role name $none: Match users who do not have a role
- query.approved
BooleanMatch users by approved - query.search
StringMatch users by did or name - Search results by name are fuzzy matches
- Search results by did are exact matches
- @param sort
Object-1: The latest time is at first.1: The latest time is at last. Default sort is{ createdAt: -1 } - sort.createdAt
Number - sort.updatedAt
Number - sort.lastLoginAt
Number
- sort.createdAt
- @return
{ code, users, paging }
Paging {
total: number of users
pageSize: number of users per page
pageCount: number of page
page: current page number
}By User DID
client.getUsers({ dids: ['did1', 'did2', ...] });
client.getUsers({ dids: ['did1', 'did2', ...], query: { approved: true } });- @param Array
didsThe user did list - @param query
Object - query.approved
BooleanMatch users by approved - @return
{ code, users }
Tips:
- If you don't pass the
didsparameter, the API will run by default - If you pass in a non-existing DID, the API will not report an error
updateUserApproval
Enable or disable a user by DID. A disabled user will not login to the blocklet again.
- @param did
string - @param approved
boolean - @return
{ code, user }
client.updateUserApproval(did, true); // enable the user
client.updateUserApproval(did, false); // disable the usergetPermissionsByRole
client.getPermissionsByRole(role)
Get all permissions of a role
- @param role
string - @return
{ code, permissions }
getRoles
client.getRoles()
Get all roles of the app
- @return
{ code, roles }
createRole
client.createRole({ name, title, description, extra })
- @param name
stringthe key of the role, should be unique - @param title
string - @param description
string - @param extra
string, a json stringified extra object, may containdisplayandtypeskey - @return
{ code, role }
updateRole
client.updateRole(name, { title, description })
- @param name
stringthe key of the role - @param title
string - @param description
string - @return
{ code, role }
deleteRole
client.deleteRole(name, { title, description })
- @param name
stringthe key of the role - @return
{ code }
issuePassportToUser
client.issuePassportToUser({ userDid, role })
- @param userDid
string - @param role
stringthe key of the role. e.g.owner,admin,member - @param display
objectstandard nft display object{ type: string, content: string } - @param notify
boolshould we send notification to wallet - @return
{ code, user }
enableUserPassport
client.enableUserPassport({ userDid, passportId })
set passport status to valid
- @param userDid
string - @param passportId
stringpassportId (get from user.passports) - @return
{ code, user }
revokeUserPassport
client.revokeUserPassport({ userDid, passportId })
set passport status to revoked
- @param userDid
string - @param passportId
stringpassportId (get from user.passports) - @return
{ code, user }
grantPermissionForRole
client.grantPermissionForRole(role, permission)
- @param role
stringthe name of the role - @param permission
stringthe name of the permission - @return
{ code }
revokePermissionFromRole
client.revokePermissionFromRole(role, permission)
- @param role
stringthe name of the role - @param permission
stringthe name of the permission - @return
{ code }
updatePermissionsForRole
client.updatePermissionsForRole(role, permissions)
Full update permissions of a role
- @param role
stringthe name of the role - @param permissions
array<string>name of the permissions - @return
{ code, role }
hasPermission
client.hasPermission(role, permission)
- @param role
stringthe name of the role - @param permission
stringthe name of the permission - @return
{ code, result } - result
boolean
getPermissions
client.getPermissions()
Get all permissions of the app
- @return
{ code, permissions }
createPermission
client.createPermission({ name, title, description })
- @param name
Permissionthe key of the permission, should be unique - format:
<action>_<resource>. e.g.query_article,mutate_user - @param description
string - @return
{ code, role }
updatePermission
client.updatePermission(name, { title, description })
- @param name
stringthe key of the role - @param title
string - @param description
string - @return
{ code }
deletePermission
client.deletePermission(name, { title, description })
- @param name
stringthe key of the permission - @return
{ code }
login
client.login({ provider, did, pk, avatar, email, fullName, id, locale })
- @return
{ user, token, refreshToken }
refreshSession
client.refreshSession({ refreshToken })
- @param refreshToken
stringthe refresh token - @return
{ user, token, refreshToken }
Notification
const { Notification } = require('@blocklet/sdk');sendToUser
Notification.sendToUser(receiver, notification)
Send notification to an account
const userDid = 'xxxxxxxx';
const notification = {
title: 'xxx',
body: 'xxx',
attachments: [
{
type: 'asset',
data: {
did: 'xxx',
chainHost: 'https://chainhost',
},
},
],
actions: [
{
name: 'xxx',
title: 'Go To Website',
link: 'https://arcblock.io',
},
],
};
const content = { message: 'this is a message' };
const actions = [];
await Notification.sendToUser(userDid, notification);
await Notification.sendToUser(userDid, [notification, anotherNotification]);
await Notification.sendToUser([userDid, anotherUserDid], notification);
await Notification.sendToUser([userDid, anotherUserDid], [notification, anotherNotification]);- notification Notification
- receiver
string | array<string>required
broadcast
Notification.broadcast(notification, options)
Broadcast notification to a channel
const notification = {
title: 'xxx',
body: 'xxx',
};
await Notification.broadcast(notification);
await Notification.broadcast(notification, { socketDid: 'did' });- notification Notification
- options
- socketDid:
Stringsend notification to a specific socket by socketDid - socketId:
Stringsend notification to a specific socket by socketId - channel:
Stringsend notification to which channel (Default: app public channel) - event:
Stringsend notification to which event (Default: 'message')
Notification Type
- notification
object | array<object>required - notification.title
string - notification.body
string - notification.attachments
array<object> - attachment.type
enum'asset', 'vc', 'token' required - attachment.data
object - type: text
- type
string - message
string
- type
- type: asset
- did
string - chainHost
stringuri
- did
- type: vc
- credential
object - tag
string
- credential
- type: token
- address
stringdid - amount
string - symbol
string - senderDid
string - chainHost
string - decimal
integer
- address
- attachment.type
- notification.actions
array<object> - name
stringrequired - title
string - color
string - bgColor
string - link
stringuri
- name
on
Notification.on()
Listen for system notification
Notification.on('hi', () => {});off
Notification.off()
Cancel listening for system messages
const handler = () => {};
Notification.on('hi', handler);
Notification.off('hi', handler);System Events
'hi'
When the client joins the app public channel
Notification.on('hi', ({ sender: { socketId, did } }) => {});- sender
object - sender.socketId
string - sender.did
string
DID Connect
import AuthStorage from '@arcblock/did-auth-storage-nedb';
import { WalletAuthenticator, WalletHandlers } from '@blocklet/sdk';
const authenticator = new WalletAuthenticator();
const handlers = new WalletHandlers({
authenticator,
tokenGenerator: () => Date.now().toString(),
tokenStorage: new AuthStorage({
dbPath: path.join(process.env.BLOCKLET_DATA_DIR, 'auth.db'),
onload: (err) => {
if (err) {
// eslint-disable-next-line no-console
console.error(`Failed to load database from ${path.join(process.env.BLOCKLET_DATA_DIR, 'auth.db')}`, err);
}
},
}),
});Database
A database library for develop blocklet, it's a wrapper of nedb.
Supply a simpler way to use nedb. Just use new Database([dbName]), or you can pass a object option as second parameter to create a database as origin nedb way new Database([dbName], [options])
Supply full-promise and typescript support.
import { Database } from '@blocklet/sdk';
// Getting Started
(async () => {
const db = new Database('demo.db');
const inserted = await db.insert({ key: 'value' });
const docs = await db.find({});
const paginated = await db.cursor({}).skip(1).limit(10).exec();
})();
// Extend with class
(async () => {
class MyDatabase extends Database {
async extraFn() {
return 'extra';
}
}
const db = new MyDatabase('demo.db');
const inserted = await db.insert({ key: 'value' });
const docs = await db.find({});
const paginated = await db.cursor({}).skip(1).limit(10).exec();
const extra = await db.extraFn();
})();Environment
import { env } from '@blocklet/sdk';
const {
appId, // the did of the app
appPid, // the permenant did of the app
appIds, // all did's that the application has previously used
appName, // the title of the app, used to display to user
appDescription, // the description of the app
appUrl, // the web url of the app
appStorageEndpoint // the endpoint of the DID Spaces of the app
componentDid, // the did of the blocklet
dataDir, // the data dir of the blocklet
cacheDir, // the cache dir of the blocklet
mode, // in which mode the blocklet is running
serverVersion: // the version of the server where the app is running
preferences, // blocklet preferences. default: {}
} = env;Please reference Blocklet Preferences for how to change the structure and value in env.preferences.
mode
In which mode the blocklet is running
env.mode === 'development'; // The blocklet is running in the development mode
env.mode === 'production'; // The blocklet is running in the production modeConfig
Unlike Environment, the information in Config will be updated in real time, the application does not need to be restarted, and events will be thrown when updating
import { env, components, events, Events } from '@blocklet/sdk/lib/config'envsame as env in EnvironmentappIdthe did of the appappPidthe permenant did of the appappIdsall did's that the application has previously usedappNamethe title of the app, used to display to userappDescriptionthe description of the appappUrlthe web url of the appappStorageEndpointthe endpoint of the DID Spaces of the appcomponentDid组件 DIDdataDirthe data dir of the blockletcacheDirthe cache dir of the blockletmodein which mode the blocklet is runningserverVersionthe version of the server where the app is runningpreferencesblocklet preferences. default: {}componentsArray<object>titlecomponent titledidcomponent didnamecomponent nameversioncomponent versionmountPointe.g. '/', '/blog'statusimport(@blocklet/constant).BlockletStatusporte.g. 5678webEndpointe.g. http://127.0.0.1resourcesArray component resource path
events.on(Events.componentAdded, (components) => {});
events.on(Events.componentRemoved, (components) => {});
events.on(Events.componentStarted, (components) => {});
events.on(Events.componentStopped, (components) => {});
events.on(Events.componentUpdated, (components) => {});
events.on(Events.envUpdate, (envs: {key: string; value: string}[]) => {});Component
import { Component } from '@blocklet/sdk';getComponentWebEndpoint
Component.getComponentWebEndpoint(name)
Get endpoint of component of app
- @param name
stringthe name or title or did of the component bundle
If the blocklet.yml of component is
did: did1
name: demo-blocklet
title: Demo Blockletthe blocklet should use like this:
Component.getComponentWebEndpoint('did1')
Component.getComponentWebEndpoint('demo-blocklet')
Component.getComponentWebEndpoint('Demo Blocklet')- @return endpoint of the first-level component. e.g.
http://127.0.0.1:5678
getComponentMountPoint
Component.getComponentMountPoint(name)
Get mount point of component of app
- @param name
stringthe name or title or did of the component bundle
If the blocklet.yml of component is
did: did1
name: demo-blocklet
title: Demo Blockletthe blocklet should use like this:
Component.getComponentMountPoint('did1')
Component.getComponentMountPoint('demo-blocklet')
Component.getComponentMountPoint('Demo Blocklet')- @return mount point of the first-level component. e.g.
/abc
call
Communicate with component component safely
Component.call({ name, path, data })
- @param name
stringthe name or title or did of the component bundle - @param path
stringthe http api. e.g./api/xxx - @param data
objectthe payload - @param method
objecthttp method - @param responseType
undefined | 'stream'response type - @return
objectthe response of axios https://github.com/axios/axios#response-schema
e.g.
component-1:
import { Component, middlewares } from '@blocklet/sdk';
const app = express();
app.post(
'/api/component-2',
// You should use verifySig middleware to prevent unknown request
middlewares.component.verifySig,
(req, res) => {
// req.body is { msg: "ping from component-2" } if the request is from component-2
res.json({ msg: 'pong from component-1' });
}
);
// data: { msg: 'pong from component-2' }
const { data } = await Component.call({
name: 'component-1',
path: '/api/component-2',
data: { msg: 'ping from component-1' },
});component-2:
const app = express();
app.post(
'/api/component-2',
// You should use verifySig middleware to prevent unknown request
middlewares.component.verifySig,
(req, res) => {
// req.body is { msg: "ping from component-1" } if the request is from component-1
res.json({ msg: 'pong from component-2' });
}
);
// data: { msg: 'pong from component-1' }
const { data } = await Component.call({
path: '/api/component-1',
data: 'ping from component-2',
});Middlewares
Session
import express from 'express';
import { middlewares } from '@blocklet/sdk';
const app = express();
/* Once user is verified, req.user will be like
export type SessionUser = {
did: string;
role: string | undefined; // will be `component` when authenticated with componentCall or signedToken
provider: string;
fullName: string;
walletOS: string; // will be `embed` when authenticated with componentCall or signedToken
emailVerified: boolean; // will always be false when authenticated with componentCall or signedToken
phoneVerified: boolean; // will always be false when authenticated with componentCall or signedToken
kyc?: number;
method: 'loginToken' | 'componentCall' | 'signedToken';
[key: string]: any;
}; */
app.use(middlewares.session({ loginToken: true })); // only decode user from loginToken
app.use(middlewares.session({ componentCall: true }); // decode user from loginToken and componentCall
app.use(middlewares.session({ signedToken: true }); // decode user for short lived and signed jwt token
app.use(middlewares.session({ accessKey: true }); // decode user from accessKey
app.use(middlewares.session({ loginToken: true, strictMode: true })); // will throw when loginToken invalidAccess
import express from 'express';
import { middlewares } from '@blocklet/sdk';
const app = express();
app.get('/auth1', middlewares.auth(), (req, res) => {
// will return 401 if user is not connected
});
app.get('/auth2', middlewares.auth({ roles: ['admin', 'owner'] }), (req, res) => {
// will return 401 if user is not connected
// will return 403 if user role is neither owner nor admin
});
app.get('/auth3', middlewares.auth({ permissions: ['mutate_data', 'query_data'] }), (req, res) => {
// will return 401 if user is not connected
// will return 403 if neither 'mutate_data' nor 'query data' in user permissions
});
app.get(
'/auth4',
middlewares.auth({ roles: ['admin', 'owner'], permissions: ['mutate_data', 'query_data'] }),
(req, res) => {
// will return 401 if user is not connected
// will return 403 if user role is neither owner nor admin
// will return 403 if neither 'mutate_data' nor 'query data' in user permissions
}
);
app.get('/auth5', middlewares.auth({ kyc: ['email'] }), (req, res) => {
// will return 401 if user email is not verified
});
app.get('/auth6', middlewares.auth({ methods: ['componentCall'] }), (req, res) => {
// will return 401 if user is not authenticated with componentCall
});User(deprecated)
import express from 'express';
import { middlewares } from '@blocklet/sdk';
const app = express();
app.get('/', middlewares.user(), (req, res) => {
const { did, fullName, role } = req.user;
});Secure communication between components
import express from 'express';
import { middlewares } from '@blocklet/sdk';
const app = express();
app.post('/component-private-api', middlewares.component.verifySig, (req, res) => {
// will return 400 if sig not found in req
// will return 401 if verify sig failed
});Security
When blocklet needs to encrypt and decrypt sensitive information, the security module comes to help:
const assert = require('assert');
const { Security } = require('@blocklet/sdk');
const message = 'some sensitive info';
const encrypted = Security.encrypt(message);
const decrypted = Security.decrypt(encrypted);
assert.notEqual(encrypted, message);
assert.notEqual(encrypted, decrypted);
assert.equal(decrypted, message);Note
The security module uses AES encryption internally, all encryption params are derived from blocklet server secret key using a deterministic algorithm.