Skip to content

Commit

Permalink
Signer Implement global error handling [sc-13127] (#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitriiIdentityLabs authored Jun 19, 2024
1 parent 8dcaea3 commit a6a4a2a
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 152 deletions.
50 changes: 22 additions & 28 deletions examples/react-signer/src/hook/use-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { methodServices } from "../service/method/method.servcie"
import { RPCMessage } from "../type"
import { methodComponents } from "../component/method/method.component"
import { accountService } from "../service/account.service"
import { NotSupportedError, exceptionHandlerService } from "../service/exception-handler.service"

export type UseSignerResponse = {
component?: ReactNode
Expand All @@ -20,35 +21,28 @@ export const useSigner = (): UseSignerResponse => {
const [state, setState] = React.useState<State>(State.READY)

const handleMessage = React.useCallback(async (message: MessageEvent<RPCMessage>) => {
console.debug("useSigner handleMessage:", message)

if (!message.data.jsonrpc) {
return
try {
console.debug("useSigner handleMessage:", message)

if (!message.data.jsonrpc) {
return
}

const methodService = methodServices.get(message.data.method)

if (!methodService) {
throw new NotSupportedError()
}

const componentData = await methodService.invokeAndGetComponentData(message)
const methodComponent = componentData && methodComponents.get(componentData.method)
const component = methodComponent && methodComponent.getComponent(componentData, setState)
setComponent(component)
setState(State.PROCESSING)
} catch (error) {
setState(State.LOADING)
exceptionHandlerService.handle(error, message)
}

const methodService = methodServices.get(message.data.method)

if (!methodService) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code: 2000,
message: "Not supported",
},
},
message.origin
)
return
}

const componentData = await methodService.invokeAndGetComponentData(message)
const methodComponent = componentData && methodComponents.get(componentData.method)
const component = methodComponent && methodComponent.getComponent(componentData, setState)
setComponent(component)
setState(State.PROCESSING)
}, [])

React.useEffect(() => {
Expand Down
5 changes: 2 additions & 3 deletions examples/react-signer/src/service/call-canister.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
Cbor,
} from "@nfid/agent"
import { DelegationIdentity } from "@dfinity/identity"
import * as console from "console"
import { interfaceFactoryService } from "./interface-factory.service"
import { GenericError } from "./exception-handler.service"

// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(BigInt.prototype as any).toJSON = function () {
Expand Down Expand Up @@ -60,8 +60,7 @@ class CallCanisterService {
content,
}
} catch (error) {
console.error(`The call cannot be executed`)
throw error
throw new GenericError("The call cannot be executed")
}
}

Expand Down
7 changes: 3 additions & 4 deletions examples/react-signer/src/service/consent-message.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import {
} from "../idl/consent"
import { idlFactory as ConsentMessageCanisterIDL } from "../idl/consent_idl"
import { Agent } from "@nfid/agent"

class ConsentMessageError extends Error {}
import { GenericError } from "./exception-handler.service"

export const consentMessageService = {
async getConsentMessage(
Expand Down Expand Up @@ -35,11 +34,11 @@ export const consentMessageService = {

if ("Err" in consentMessageResult) {
const description = Object.values(consentMessageResult.Err)[0].description
throw new ConsentMessageError(description)
throw new GenericError(description)
}

if ("LineDisplayMessage" in consentMessageResult.Ok.consent_message) {
throw new ConsentMessageError("LineDisplayMessage is not supported")
throw new GenericError("LineDisplayMessage is not supported")
}

return consentMessageResult.Ok.consent_message.GenericDisplayMessage
Expand Down
41 changes: 41 additions & 0 deletions examples/react-signer/src/service/exception-handler.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { RPCMessage } from "../type"

export class NotSupportedError extends Error {}
export class GenericError extends Error {}

class ExceptionHandlerService {
public handle(error: unknown, message: MessageEvent<RPCMessage>) {
console.error("ExceptionHandlerService", error)

if (error instanceof NotSupportedError) {
this.postErrorMessage(message, 2000, "Not supported")
}

if (error instanceof GenericError) {
this.postErrorMessage(message, 1000, "Generic error", error.message)
}
}

private postErrorMessage(
message: MessageEvent<RPCMessage>,
code: number,
title: string,
text?: string
) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code,
message: title,
text,
},
},
message.origin
)
}
}

export const exceptionHandlerService = new ExceptionHandlerService()
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
LookupResultFound,
ReadStateResponse,
} from "@nfid/agent"
import { GenericError } from "./exception-handler.service"

const CANDID_UI_CANISTER = "a4gq6-oaaaa-aaaab-qaa4q-cai"

Expand All @@ -19,8 +20,7 @@ class InterfaceFactoryService {
): Promise<IDL.InterfaceFactory> {
const candidFile: string | undefined = await this.getCandidFile(canisterId, agent as never)
if (!candidFile) {
console.error(`Unable to retrieve candid from the canister ${canisterId}`)
throw Error("Unable to retrieve candid")
throw new GenericError(`Unable to retrieve candid file for the canister ${canisterId}`)
}
const candidJs = await this.transformDidToJs(candidFile, agent as never)
const dataUri = "data:text/javascript;charset=utf-8," + encodeURIComponent(candidJs)
Expand All @@ -41,7 +41,7 @@ class InterfaceFactoryService {
try {
responseCandid = await agent.readState(canister, { paths: [pathCandid] })
} catch (error) {
throw new Error(
throw new GenericError(
`Not possible to retrieve candid file from the canister ${canisterId} : ${error}`
)
}
Expand All @@ -66,7 +66,7 @@ class InterfaceFactoryService {
})
const result = await didJs["did_to_js"](candid)
if (!result) {
throw Error("DidtoJs transformation Error")
throw new GenericError("The didtoJs transformation error")
}
return (result as string[])[0]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RPCMessage, RPCSuccessResponse } from "../../../type"
import { ComponentData, InteractiveMethodService } from "./interactive-method.service"
import { Account, accountService } from "../../account.service"
import { GenericError } from "../../exception-handler.service"

export interface AccountsComponentData extends ComponentData {
accounts: Account[]
Expand Down Expand Up @@ -36,20 +37,7 @@ class Icrc27GetAccountsMethodService extends InteractiveMethodService {
public async getСomponentData(message: MessageEvent<RPCMessage>): Promise<AccountsComponentData> {
const accounts = await accountService.getAccounts()
if (!accounts) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code: 1000,
message: "Generic error",
text: "User data has not been found",
},
},
message.origin
)
throw Error("User is not found")
throw new GenericError("User data has not been found")
}

const baseData = await super.getСomponentData(message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DelegationChain, Ed25519PublicKey } from "@dfinity/identity"
import { Principal } from "@dfinity/principal"
import { fromHex } from "@dfinity/agent"
import { targetService } from "../../target.service"
import { GenericError } from "../../exception-handler.service"

export interface GetDelegationComponentData extends ComponentData {
accounts: Account[]
Expand All @@ -29,20 +30,7 @@ class Icrc34GetDelegationMethodService extends InteractiveMethodService {
let targets

if (!key) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code: 1000,
message: "Generic error",
text: "User data has not been found",
},
},
message.origin
)
throw Error("No key found")
throw new GenericError("User data has not been found")
}

const sessionPublicKey = Ed25519PublicKey.fromDer(fromHex(icrc34Dto.publicKey))
Expand All @@ -51,20 +39,8 @@ class Icrc34GetDelegationMethodService extends InteractiveMethodService {
try {
await targetService.validateTargets(icrc34Dto.targets, message.origin)
} catch (e: unknown) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code: 1000,
message: "Generic error",
text: (e as Error).message,
},
},
message.origin
)
throw e
const text = e instanceof Error ? e.message : "Unknown error"
throw new GenericError(text)
}
targets = icrc34Dto.targets.map((x) => Principal.fromText(x))
}
Expand Down Expand Up @@ -95,20 +71,7 @@ class Icrc34GetDelegationMethodService extends InteractiveMethodService {
const accounts = await accountService.getAccounts()
const isPublicAccountsAllowed = !icrc34Dto.targets || icrc34Dto.targets.length === 0
if (!accounts) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code: 1000,
message: "Generic error",
text: "User data has not been found",
},
},
message.origin
)
throw Error("User is not found")
throw new GenericError("User data has not been found")
}

const baseData = await super.getСomponentData(message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { consentMessageService } from "../../consent-message.service"
import { Agent, HttpAgent, Identity } from "@nfid/agent"
import { interfaceFactoryService } from "../../interface-factory.service"
import { IDL } from "@dfinity/candid"
import { GenericError } from "../../exception-handler.service"

const HOUR = 3_600_000
const IC_HOSTNAME = "https://ic0.app"
Expand Down Expand Up @@ -37,20 +38,7 @@ class Icrc49GetDelegationMethodService extends InteractiveMethodService {
const key = await accountService.getAccountKeyIdentityByPrincipal(icrc49Dto.sender)

if (!key) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code: 1000,
message: "Generic error",
text: "User data has not been found",
},
},
message.origin
)
throw Error("No key found")
throw new GenericError("User data has not been found")
}

const sessionKey = Ed25519KeyIdentity.generate()
Expand Down Expand Up @@ -95,20 +83,7 @@ class Icrc49GetDelegationMethodService extends InteractiveMethodService {
const key = await accountService.getAccountKeyIdentityByPrincipal(icrc49Dto.sender)

if (!key) {
window.parent.postMessage(
{
origin: message.data.origin,
jsonrpc: message.data.jsonrpc,
id: message.data.id,
error: {
code: 1000,
message: "Generic error",
text: "User data has not been found",
},
},
message.origin
)
throw Error("User is not found")
throw new GenericError("User data has not been found")
}

const sessionKey = Ed25519KeyIdentity.generate()
Expand Down
Loading

0 comments on commit a6a4a2a

Please sign in to comment.