Skip to content

UserInterface Implementation

Aaron Cox edited this page Feb 1, 2023 · 6 revisions

As of tag v0.3.0-ui-4 on Feb 1, 2023.

This WILL change rapidly, and this document will be out of date within weeks.

SessionKit UI Logic Description

  • The SessionKit accepts a ui parameter that implements UserInterface.
  • When .login() is called on the SessionKit, the .onLogin call is triggered on the UserInterface.
  • The SessionKit determines which other calls it needs to make, including:
    • Check the .walletPlugins array and if more than one, call .onSelectWallet() on the UserInterface.
    • The .walletPlugins converted to a WalletPluginMetadata array and is passed to this call via the LoginContext.
    • This is awaited until the UserInterface returns the index of the array that matches the WalletPlugin the user selects.
    • The .login() call continues and checks to see if the WalletPlugin requires the user to select a blockchain.
    • If no blockchain is preselected and the user is required to select, call .onSelectChain() on the UserInterface.
    • The list of blockchains is passed to the .onSelectChain() call again via the LoginContext.
    • This is awaited until the UserInterface returns the chain ID (Checksum256) of the blockchain the user selects.
  • The WalletPlugin.login() method is triggered asking the wallet to return a selected chain ID and permission level.
  • A Session is created and returned as part of the LoginResult object from the original .login() call.
  • The SessionKit makes a .onLoginResult call against the UserInterface to indicate that the login is completed.
  • The resulting Session object is configured to use the selected wallet, chain, and permission.
  • The .transact() call can now be used on the Session object by the app.
  • When .transact() is called on the Session, it interacts with the UI by doing the following:
    • ...more to come about transactions

UserInterface Interface

A UserInterface is an interface that species a number of methods that are called during either the login or transact calls on a Session.

Source

/**
 * Interface which a [[UserInteface]] plugins must implement.
 */
export interface UserInterface {
    // Inform the UI that a login call has started
    onLogin: (options?: LoginOptions) => void
    // Inform the UI that a login call has completed
    onLoginResult: () => void
    // Ask the user to select a blockchain, and return the chain id
    onSelectChain: (context: LoginContext) => Promise<Checksum256>
    // Ask the user to select an account, and return the PermissionLevel
    onSelectPermissionLevel: (context: LoginContext) => Promise<PermissionLevel>
    // Ask the user to select a wallet, and return the index based on the metadata
    onSelectWallet: (context: LoginContext) => Promise<number>
    // Inform the UI that a transact call has started
    onTransact: (context: TransactContext) => void
    // Inform the UI that a transact call has completed
    onTransactResult: (context: TransactResult) => void
    // Update the displayed modal status from a TransactPlugin
    status: (message: string) => void
}

LoginContext

The LoginContext is passed to many of the .login() calls to provide information to the UI about the login process and facilitate decision making.

Source

/**
 * Temporary context created for the duration of a [[Kit.login]] call.
 *
 * This context is used to store the state of the login request and
 * provide a way for plugins to add hooks into the process.
 */
export class LoginContext {
    // client: APIClient
    chain: ChainDefinition
    chains: ChainDefinition[] = []
    hooks: LoginHooks = {
        afterLogin: [],
        beforeLogin: [],
    }
    ui: UserInterface
    walletPlugins: WalletPluginMetadata[] = []
    constructor(options: LoginContextOptions) {
        // this.client = options.client
        if (options.chains) {
            this.chains = options.chains
        }
        this.chain = options.chain || this.chains[0]
        this.walletPlugins = options.walletPlugins || []
        this.ui = options.ui
        // options.loginPlugins?.forEach((plugin: AbstractLoginPlugin) => {
        //     plugin.register(this)
        // })
    }
    addHook(t: LoginHookTypes, hook: LoginHook) {
        this.hooks[t].push(hook)
    }
}

TransactContext

Just like the LoginContext being used in .login() calls, the TransactContext is used in the .transact() calls to facilitate UI interactions during a transaction.

Source

/**
 * Temporary context created for the duration of a [[Session.transact]] call.
 *
 * This context is used to store the state of the transact request and
 * provide a way for plugins to add hooks into the process.
 */
export class TransactContext {
    readonly abiProvider: AbiProvider
    readonly client: APIClient
    readonly fetch: Fetch
    readonly hooks: TransactHooks = {
        afterBroadcast: [],
        afterSign: [],
        beforeSign: [],
    }
    readonly permissionLevel: PermissionLevel
    readonly transactPluginsOptions: TransactPluginsOptions
    readonly ui: UserInterface


    constructor(options: TransactContextOptions) {
        this.abiProvider = options.abiProvider
        this.client = options.client
        this.fetch = options.fetch
        this.permissionLevel = options.permissionLevel
        this.transactPluginsOptions = options.transactPluginsOptions || {}
        this.ui = options.ui
        options.transactPlugins?.forEach((plugin: AbstractTransactPlugin) => {
            plugin.register(this)
        })
    }


    get accountName(): Name {
        return this.permissionLevel.actor
    }


    get permissionName(): Name {
        return this.permissionLevel.permission
    }


    get esrOptions(): SigningRequestEncodingOptions {
        return {
            abiProvider: this.abiProvider,
            zlib,
        }
    }


    addHook(t: TransactHookTypes, hook: TransactHook) {
        this.hooks[t].push(hook)
    }


    async resolve(request: SigningRequest, expireSeconds = 120): Promise<ResolvedSigningRequest> {
        // TODO: Cache the info/header first time the context resolves?
        // If multiple plugins resolve the same request and call get_info, tapos might change
        const info = await this.client.v1.chain.get_info()
        const header = info.getTransactionHeader(expireSeconds)


        // Load ABIs required to resolve this request
        const abis = await request.fetchAbis(this.abiProvider)


        // Resolve the request and return
        return request.resolve(abis, this.permissionLevel, header)
    }
}

UserInterfaceHeadless Example

This class implements UserInterface and primarily just adds some potential logging for testing purposes. It also serves as an example of how the interface above can be implemented.

Source

export class UserInterfaceHeadless implements UserInterface {
    public consoleLog = false
    public messages: string[] = []
    public log(message: string) {
        if (this.consoleLog) {
            // eslint-disable-next-line no-console
            console.info('UserInterfaceHeadless', message)
        }
    }
    onLogin(options?: LoginOptions) {
        this.log('onLogin: ' + JSON.stringify(options))
    }
    onLoginResult() {
        this.log('onLoginResult')
    }
    public async onSelectPermissionLevel(context: LoginContext): Promise<PermissionLevel> {
        throw new Error('The headless user interface does not support permission selection')
    }
    public async onSelectChain(context: LoginContext): Promise<Checksum256> {
        throw new Error('The headless user interface does not support chain selection')
    }
    public async onSelectWallet(context: LoginContext): Promise<number> {
        throw new Error('The headless user interface does not support wallet selection')
    }
    public async onTransact(context: TransactContext) {
        this.log('onTransact' + String(context.accountName))
    }
    public async onTransactResult(context: TransactResult) {
        this.log('onTransactResult' + String(context.transaction))
    }
    public status(message: string) {
        this.messages.push(message)
        this.log(message)
    }
}

Web UI Renderer

https://github.com/wharfkit/web-ui-renderer

The Web UI Renderer is a prototype that takes these interfaces and implements them as a SvelteKit rendered HTML element. All of the UserInterface methods are implemented here:

https://github.com/wharfkit/web-ui-renderer/blob/master/src/index.ts

In order to create elements on the page and react to the events happening within the SessionKit.

The web-ui-renderer package also has a live development mode that you can run after cloning down the repo:

yarn dev

This will serve the following static HTML project, which implements both the Session Kit and the Web UI Renderer.

https://github.com/wharfkit/web-ui-renderer/blob/master/test/public/index.html