Used to check for browser translation.
用于检测浏览器翻译。
ブラウザの翻訳を検出する

Blocklet SDK (Node.js)

ArcBlock
2024-09-18 07:11
· edited

Blocklet SDK for blocklet developer

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
    The value of pageSize cannot exceed 100
  • paging.page
  • @param query Object
  • query.role String Match users by role name
    • $none: Match users which does not have a role
  • query.approved Boolean Match users by approved
  • query.search String Match 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
  • @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> 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 }

Tips:

  • If you don't pass the dids parameter, 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 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: asset
      • did string
      • chainHost string uri
    • type: vc
      • credential object
      • tag string
    • type: token
      • address string did
      • amount string
      • symbol string
      • senderDid string
      • chainHost string
      • decimal integer
  • notification.actions array<object>
    • name string required
    • title string
    • color string
    • bgColor string
    • link string uri

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 mode

Config#

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'
  • env same as env in Environment
  • 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 组件 DID
  • 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: {}
  • components Array\<object\>
  • title component title
  • did component did
  • name component name
  • version component version
  • mountPoint e.g. '/', '/blog'
  • status import(@blocklet/constant).BlockletStatus
  • port e.g. 5678
  • webEndpoint e.g. http://127.0.0.1:5678
  • resources Array<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 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')

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 component 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#

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

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
}
);

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);




Sticker