Skip to content

Commit

Permalink
Localization (#25)
Browse files Browse the repository at this point in the history
* enhancement: using esr-anchor instead of esr scheme

Closes issue #3

* chore: added localization

* fix: got tests passing again

* fix: importing translations in plugin

* Add rollup json plugin

---------

Co-authored-by: Aaron Cox <[email protected]>
  • Loading branch information
dafuga and aaroncox authored Apr 18, 2023
1 parent 6a3b49e commit 8c7ab3d
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 30 deletions.
5 changes: 3 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import typescript from '@rollup/plugin-typescript'
import replace from '@rollup/plugin-replace'

import pkg from './package.json'
import json from '@rollup/plugin-json'

const replaceVersion = replace({
preventAssignment: true,
Expand Down Expand Up @@ -35,7 +36,7 @@ export default [
sourcemap: true,
exports: 'named',
},
plugins: [replaceVersion, typescript({target: 'es6'})],
plugins: [replaceVersion, typescript({target: 'es6'}), json()],
external,
},
{
Expand All @@ -46,7 +47,7 @@ export default [
format: 'esm',
sourcemap: true,
},
plugins: [replaceVersion, typescript({target: 'es2020'})],
plugins: [replaceVersion, typescript({target: 'es2020'}), json()],
external,
},
{
Expand Down
74 changes: 52 additions & 22 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {LinkInfo} from './anchor-types'

import {extractSignaturesFromCallback, isCallback} from './esr'

import defaultTranslations from './translations'

interface WalletPluginOptions {
buoyUrl?: string
buoyWs?: WebSocket
Expand All @@ -47,17 +49,23 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
buoyUrl: string
buoyWs: WebSocket | undefined

/**
* The unique identifier for the wallet plugin.
*/
id = 'anchor'

/**
* The translations for this plugin
*/
translations = defaultTranslations

constructor(options?: WalletPluginOptions) {
super()

this.buoyUrl = options?.buoyUrl || 'https://cb.anchor.link'
this.buoyWs = options?.buoyWs
}

public get id(): string {
return 'anchor'
}

/**
* The logic configuration for the wallet plugin.
*/
Expand Down Expand Up @@ -96,7 +104,14 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
}

async handleLogin(context: LoginContext): Promise<WalletPluginLoginResponse> {
context.ui?.status('Preparing request for Anchor...')
if (!context.ui) {
throw new Error('No UI available')
}

// Retrieve translation helper from the UI, passing the app ID
const t = context.ui.getTranslate(this.id)

context.ui?.status(t('login.preparing', {default: 'Preparing request for Anchor...'}))

// Create the identity request to be presented to the user
const {callback, request, requestKey, privateKey} = await createIdentityRequest(
Expand All @@ -105,19 +120,22 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
)
// Tell Wharf we need to prompt the user with a QR code and a button
const promptResponse = context.ui?.prompt({
title: 'Login with Anchor',
body: 'Scan the QR-code with Anchor on another device or use the button to open it here.',
title: t('login.preparing', {default: 'Login with Anchor'}),
body: t('login.preparing', {
default:
'Scan the QR-code with Anchor on another device or use the button to open it here.',
}),
elements: [
{
type: 'qr',
data: String(request),
},
{
type: 'link',
label: 'Open Anchor',
label: t('login.link', {default: 'Open Anchor'}),
data: {
href: String(request),
label: 'Open Anchor',
label: t('login.link', {default: 'Open Anchor'}),
},
},
],
Expand All @@ -131,7 +149,7 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
})

// Await a promise race to wait for either the wallet response or the cancel
const callbackResponse: CallbackPayload = await waitForCallback(callback, this.buoyWs)
const callbackResponse: CallbackPayload = await waitForCallback(callback, this.buoyWs, t)

if (
callbackResponse.link_ch &&
Expand Down Expand Up @@ -160,7 +178,10 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
promptResponse.cancel('Invalid response from Anchor.')

throw new Error(
'Invalid response from Anchor, must contain link_ch, link_key, link_name and cid flags.'
t('error.invalid_response', {
default:
'Invalid response from Anchor, must contain link_ch, link_key, link_name and cid flags.',
})
)
}
}
Expand All @@ -187,7 +208,11 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
if (!context.ui) {
throw new Error('No UI available')
}
context.ui.status('Preparing request for Anchor...')

// Retrieve translation helper from the UI, passing the app ID
const t = context.ui.getTranslate(this.id)

context.ui?.status(t('shared.preparing', {default: 'Preparing request for Anchor...'}))

// Set expiration time frames for the request
const expiration = resolved.transaction.expiration.toDate()
Expand All @@ -199,19 +224,22 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {

// Tell Wharf we need to prompt the user with a QR code and a button
const promptPromise: Cancelable<PromptResponse> = context.ui.prompt({
title: 'Sign',
body: `Please open the Anchor Wallet on "${this.data.channelName}" to review and sign the transaction.`,
title: t('transact.title', {default: 'Sign'}),
body: t('shared.body', {
channelName: this.data.channelName,
default: `Please open the Anchor Wallet on "${this.data.channelName}" to review and sign the transaction.`,
}),
elements: [
{
type: 'countdown',
data: expiration.toISOString(),
},
{
type: 'link',
label: 'Sign manually or with another device',
label: t('shared.label', {default: 'Sign manually or with another device'}),
data: {
href: resolved.request.encode(true, false, 'esr-anchor'),
label: 'Trigger Manually',
label: t('shared.link', {default: 'Trigger Manually'}),
},
},
],
Expand All @@ -220,9 +248,11 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
// Create a timer to test the external cancelation of the prompt, if defined
const timer = setTimeout(() => {
if (!context.ui) {
throw new Error('No UI defined')
throw new Error('No UI available')
}
promptPromise.cancel('The request expired, please try again.')
promptPromise.cancel(
t('error.expired', {default: 'The request expired, please try again.'})
)
}, expiresIn)

// Clear the timeout if the UI throws (which generally means it closed)
Expand All @@ -237,7 +267,7 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
)

// Wait for the callback from the wallet
const callbackPromise = waitForCallback(callback, this.buoyWs)
const callbackPromise = waitForCallback(callback, this.buoyWs, t)

// Assemble and send the payload to the wallet
const service = new URL(this.data.channelUrl).origin
Expand Down Expand Up @@ -286,7 +316,7 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
}
}

const errorString = 'The request was not completed.'
const errorString = t('error.not_completed', {default: 'The request was not completed.'})

promptPromise.cancel(errorString)

Expand All @@ -295,7 +325,7 @@ export class WalletPluginAnchor extends AbstractWalletPlugin {
}
}

async function waitForCallback(callbackArgs, buoyWs): Promise<CallbackPayload> {
async function waitForCallback(callbackArgs, buoyWs, t): Promise<CallbackPayload> {
// Use the buoy-client to create a promise and wait for a response to the identity request
const callbackResponse = await receive({...callbackArgs, WebSocket: buoyWs || WebSocket})

Expand All @@ -313,7 +343,7 @@ async function waitForCallback(callbackArgs, buoyWs): Promise<CallbackPayload> {
const payload = JSON.parse(callbackResponse) as CallbackPayload

if (payload.sa === undefined || payload.sp === undefined || payload.cid === undefined) {
throw new Error('The request was cancelled from Anchor.')
throw new Error(t('error.cancelled', {default: 'The request was cancelled from Anchor.'}))
}

return payload
Expand Down
21 changes: 21 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"shared": {
"preparing": "Preparing request for Anchor..."
},
"login": {
"title": "Login with Anchor",
"body": "Scan the QR-code with Anchor on another device or use the button to open it here.",
"link": "Open Anchor"
},
"transact": {
"title": "Sign transaction with Anchor",
"body": "Please open the Anchor Wallet on \"{{channelName}}\" to review and sign the transaction.",
"label": "Sign manually or with another device",
"link": "Trigger Manually"
},
"error": {
"expired": "The request expired, please try again.",
"invalid_response": "Invalid response from Anchor, must contain link_ch, link_key, link_name and cid flags.",
"not_completed": "The request was not completed."
}
}
11 changes: 11 additions & 0 deletions src/translations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import en from './en.json'
import ko from './ko.json'
import zh_hans from './zh-hans.json'
import zh_hant from './zh-hant.json'

export default {
en,
ko,
'zh-Hans': zh_hans,
'zh-Hant': zh_hant,
}
1 change: 1 addition & 0 deletions src/translations/ko.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions src/translations/zh-hans.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions src/translations/zh-hant.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 0 additions & 5 deletions test/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ import {mockSessionKitOptions} from '$test/utils/mock-session'
import {mockCallbackPayload} from '$test/utils/mock-esr'
import {mockChainId} from '$test/utils/mock-config'

const mockChainDefinition = {
id: '73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d',
url: 'https://jungle4.greymass.com',
}

const mockPermissionLevel = PermissionLevel.from('wharfkit1115@test')

suite('wallet plugin', function () {
Expand Down
4 changes: 4 additions & 0 deletions test/utils/mock-userinterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class MockUserInterface extends AbstractUserInterface implements UserInte
readonly logging = false
public messages: string[] = []

translate(key) {
return key
}

log(message: string) {
this.messages.push(message)
if (this.logging) {
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"noImplicitAny": false,
"sourceMap": true,
"strict": true,
"target": "es2020"
"target": "es2020",
"resolveJsonModule": true,
},
"include": ["src/**/*"]
}

0 comments on commit 8c7ab3d

Please sign in to comment.