Blocklet SDK (Node.js)
Blocklet SDK for blocklet developers used in Node.js environments
Install#
yarn add @blocklet/sdk
or
npm install @blocklet/sdk
Wallet#
const { getWallet } = require('@blocklet/sdk');
// wallet is an instance of @ocap/wallet
const wallet = getWallet();
const { address, secretKey, publicKey } = wallet;
Auth#
Get Client#
const { Auth } = require('@blocklet/sdk');
const client = new Auth();
getUser#
client.getUser(did)
Get user by user did
- @param did
string
- @return
{ code, user }
getOwner#
client.getOwner()
Get 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
pageSize 的值不能超过 100 - paging.page
- @param query
Object
- query.role
String
通过角色名称匹配 $none
: 匹配没有角色的用户
- query.approved
Boolean
通过 approved 搜索 - query.search
String
通过 DID 或 用户名搜索 - 通过用户名的搜索结果是模糊匹配
- 通过 DID 的搜索结果是精确匹配
- @param sort
Object
-1
: 最近的时间是排在第一个。1
:最近的时间排在最后一个。 默认排序是{ createdAt: -1 }
- sort.createdAt
Number
- sort.updatedAt
Number
- sort.lastLoginAt
Number
- sort.createdAt
- @return
{ code, users, paging }
Paging {
total: 用户总数
pageSize: 每页用户数
pageCount: 页数
page: 当前是第几页
}
By User DID#
client.getUsers({ dids: ['did1', 'did2', ...] });
client.getUsers({ dids: ['did1', 'did2', ...], query: { approved: true } });
- @param Array<string>
dids
The user did list - > The length of dids cannot exceed 100
- @param query
Object
- query.approved
Boolean
Match users by approved - @return
{ code, users }
说明:
- 如果你不传入
dids
参数, 将会通过默认方式获取 - 如果你传入了不存在的 DID, 接口 不会 报错
updateUserApproval#
禁用或者起用某个用户,被禁用的用户无法再使用你的应用。
- @param did
string
- @param approved
boolean
- @return
{ code, user }
client.updateUserApproval(did, true); // enable the user
client.updateUserApproval(did, false); // disable the user
getPermissionsByRole#
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 })
- @param name
string
the key of the role, should be unique - @param title
string
- @param description
string
- @return
{ code, role }
updateRole#
client.updateRole(name, { title, description })
- @param name
string
the key of the role - @param title
string
- @param description
string
- @return
{ code, role }
deleteRole#
client.deleteRole(name, { title, description })
- @param name
string
the key of the role - @return
{ code }
issuePassportToUser#
client.issuePassportToUser({ userDid, role })
- @param userDid
string
- @param role
string
the key of the role. e.g.owner
,admin
,member
- @return
{ code, user }
enableUserPassport#
client.enableUserPassport({ userDid, passportId })
set passport status to valid
- @param userDid
string
- @param passportId
string
passportId (get from user.passports) - @return
{ code, user }
revokeUserPassport#
client.revokeUserPassport({ userDid, passportId })
set passport status to revoked
- @param userDid
string
- @param passportId
string
passportId (get from user.passports) - @return
{ code, user }
grantPermissionForRole#
client.grantPermissionForRole(role, permission)
- @param role
string
the name of the role - @param permission
string
the name of the permission - @return
{ code }
revokePermissionFromRole#
client.revokePermissionFromRole(role, permission)
- @param role
string
the name of the role - @param permission
string
the name of the permission - @return
{ code }
updatePermissionsForRole#
client.updatePermissionsForRole(role, permissions)
Full update permissions of a role
- @param role
string
the name of the role - @param permissions
array<string>
name of the permissions - @return
{ code, role }
hasPermission#
client.hasPermission(role, permission)
- @param role
string
the name of the role - @param permission
string
the 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
Permission
the 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
string
the key of the role - @param title
string
- @param description
string
- @return
{ code }
deletePermission#
client.deletePermission(name, { title, description })
- @param name
string
the 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
string
the 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:
String
send notification to a specific socket by socketDid - socketId:
String
send notification to a specific socket by socketId - channel:
String
send notification to which channel (Default: app public channel) - event:
String
send 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
string
uri
- did
- type: vc
- credential
object
- tag
string
- credential
- type: token
- address
string
did - amount
string
- symbol
string
- senderDid
string
- chainHost
string
- decimal
integer
- address
- attachment.type
- notification.actions
array<object>
- name
string
required - title
string
- color
string
- bgColor
string
- link
string
uri
- 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, // 应用 DID
appPid, // 应用永久 DID
appIds, // 应用曾使用过的所有 DID
appName, // 应用名称,用于显示给用户
appDescription, // 应用描述,用于显示给用户
appUrl, // 应用的的访问地址
appStorageEndpoint // 应用绑定的 DID Space 的 endpoint
componentDid, // 组件 DID
dataDir, // 组件 数据存放目录
cacheDir, // 组件 缓存数据路径
mode, // 组件 以什么模式运行
serverVersion: // 应用运行所在server的版本
preferences, // 应用的偏好设置。默认值: {}
} = env;
请参照 应用偏好 来了解如何修改 env.preferences
的结构和数据。
mode#
Blocklet 以什么模式运行
env.mode === 'development'; // Blocklet 以开发模式运行
env.mode === 'production'; // Blocklet 以正式模式运行
Config#
和 Environment 不同的是 Config 中的信息会实时更新,应用无需重启,且更新时会抛出事件
import { env, components } from '@blocklet/sdk/lib/config'
env
Environment 中的 env 相同appId
应用 DIDappPid
应用永久 DIDappIds
应用曾使用过的所有 DIDappName
应用名称,用于显示给用户appDescription
应用描述,用于显示给用户appUrl
应用的的访问地址appStorageEndpoint
应用绑定的 DID Space 的 endpointcomponentDid
组件 DIDdataDir
Blocklet 数据存放目录cacheDir
Blocklet 缓存数据路径mode
Blocklet 以什么模式运行serverVersion
应用运行所在server的版本preferences
应用 的偏好设置。默认值: {}components
Array\<object\>title
组件名称did
组件 DIDname
组件 nameversion
组件版本mountPoint
e.g. '/', '/blog'status
import(@blocklet/constant).BlockletStatusport
e.g. 5678webEndpoint
e.g. http://127.0.0.1:5678resources
Array<string> 组件的资源路径
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
string
the name or title or did of the component bundle
If the blocklet.yml of component is
did: did1
name: demo-blocklet
title: Demo Blocklet
the 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
string
the name or title or did of the component bundle
If the blocklet.yml of component is
did: did1
name: demo-blocklet
title: Demo Blocklet
the 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 other component safely
Component.call({ name, path, data })
- @param name
string
the name or title or did of the component bundle - @param path
string
the http api. e.g./api/xxx
- @param data
object
the payload - @param method
object
http method - @param responseType
undefined | 'stream'
response type - @return
object
the 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#
User#
import express from 'express';
import { middlewares } from '@blocklet/sdk';
const app = express();
app.get('/', middlewares.user(), (req, res) => {
const { did, fullName, role } = req.user;
});
Access#
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
}
);
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#
当 Blocklet 需要对敏感信息进行加密和解密时,Security 模块可直接使用。
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);