メインコンテンツへスキップ

Blocklet SDK (Node.js)

Blocklet SDK for blocklet developer

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 { 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

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 The value of pageSize cannot exceed 100
  • paging.page
  • @param query Object
  • query.role String Match users by role name
    • $none: Match users who do 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 }
javascript
Paging {
  total: number of users
  pageSize: number of users per page
  pageCount: number of page
  page: current page number
}

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 }

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 }
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, extra })

  • @param name string the key of the role, should be unique
  • @param title string
  • @param description string
  • @param extra string, a json stringified extra object, may contain display and types key
  • @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
  • @param display object standard nft display object { type: string, content: string }
  • @param notify bool should we send notification to wallet
  • @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, // 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

js
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

javascript
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
  • resources Array component resource path
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 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:

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(deprecated)

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

When blocklet needs to encrypt and decrypt sensitive information, the security module comes to help:

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

Note

The security module uses AES encryption internally, all encryption params are derived from blocklet server secret key using a deterministic algorithm.