跳到主要内容

Blocklet SDK (Node.js)

Blocklet SDK for blocklet developers used in Node.js environments

Install

javascript
yarn add @blocklet/sdk

or

javascript
npm install @blocklet/sdk

Wallet

javascript
const { getWallet } = require('@blocklet/sdk');

// wallet is an instance of @ocap/wallet
const wallet = getWallet();
const { address, secretKey, publicKey } = wallet;

Auth

Get Client

javascript
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

js
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
  • @return { code, users, paging }
javascript
Paging {
  total: 用户总数
  pageSize: 每页用户数
  pageCount: 页数
  page: 当前是第几页
}

By User DID

javascript
client.getUsers({ dids: ['did1', 'did2', ...] });
client.getUsers({ dids: ['did1', 'did2', ...], query: { approved: true } });
  • @param Array dids The user did list
  • @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 }
js
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

javascript
const { Notification } = require('@blocklet/sdk');

sendToUser

Notification.sendToUser(receiver, notification)

Send notification to an account

javascript
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

javascript
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

javascript
Notification.on('hi', () => {});

off

Notification.off()

Cancel listening for system messages

javascript
const handler = () => {};

Notification.on('hi', handler);
Notification.off('hi', handler);

System Events

'hi'

When the client joins the app public channel

js
Notification.on('hi', ({ sender: { socketId, did } }) => {});
  • sender object
  • sender.socketId string
  • sender.did string

DID Connect

javascript
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.

javascript
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

javascript
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 以什么模式运行

js
env.mode === 'development'; // Blocklet 以开发模式运行
env.mode === 'production'; // Blocklet 以正式模式运行

Config

和 Environment 不同的是 Config 中的信息会实时更新,应用无需重启,且更新时会抛出事件

javascript
import { env, components } from '@blocklet/sdk/lib/config'
  • env Environment 中的 env 相同
  • appId 应用 DID
  • appPid 应用永久 DID
  • appIds 应用曾使用过的所有 DID
  • appName 应用名称,用于显示给用户
  • appDescription 应用描述,用于显示给用户
  • appUrl 应用的的访问地址
  • appStorageEndpoint 应用绑定的 DID Space 的 endpoint
  • componentDid 组件 DID
  • dataDir Blocklet 数据存放目录
  • cacheDir Blocklet 缓存数据路径
  • mode Blocklet 以什么模式运行
  • serverVersion 应用运行所在server的版本
  • preferences 应用 的偏好设置。默认值: {}
  • components Array<object>
  • title 组件名称
  • did 组件 DID
  • name 组件 name
  • version 组件版本
  • mountPoint e.g. '/', '/blog'
  • status import(@blocklet/constant).BlockletStatus
  • port e.g. 5678
  • webEndpoint e.g. http://127.0.0.1
  • resources Array 组件的资源路径
typescript
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

javascript
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

javascript
did: did1
name: demo-blocklet
title: Demo Blocklet

the blocklet should use like this:

javascript
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

javascript
did: did1
name: demo-blocklet
title: Demo Blocklet

the blocklet should use like this:

javascript
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:

js
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:

js
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

javascript
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 invalid

Access

javascript
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(将要废弃)

javascript
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

javascript
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 模块可直接使用。

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

说明

Security 模块在内部使用了 AES 加密,所有的参数都是使用确定的算法从 Blocklet Server 私钥计算得到。