Welcome
Getting Started
How to Guides
Application vs Blocklet
Create Blocklet
Compose Blocklets
Develop Blocklet
User and Passport
Communicate with DID Wallet
Blocklet Storage
Using Blocklet Preferences
Using Blocklet Logger
Add PWA Integration to Blocklet
Build blocklet for profit [deprecated]
Bundle your blocklet
Manage Blocklet Versions
Publish your blocklet to the world
Deploy your blocklet
Read/Write blockchain in blocklet
Operation your blocklet
Reference Guides
DID Connect
blocklet.yml
blocklet.js
Blocklet SDK (Node.js)
Blocklet SDK (Browser)
Blocklet Service
Blocklet CLI
Blocklet Server CLI
Blocklet UI
Blocklet GitHub Actions
Blocklet Studio
Blocklet Manager
Security
Performance
Developer Best Practices.
Known Issues or Limitations
Setup Blocklet Server
WebHooks
OAuth Server
Access Key
MCP Servers
Conceptual Guides
Frequently Asked Questions
Blocklet SDK (Node.js)
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<string>
didsThe user did list - > The length of dids cannot exceed 100
- @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.1:5678resourcesArray<string> 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);