diff --git a/src/api/actors.ts b/src/api/actors.ts index ef5dcd1..1db974b 100644 --- a/src/api/actors.ts +++ b/src/api/actors.ts @@ -1,11 +1,12 @@ // A global singleton for our internet computer actors. import * as Agent from '@dfinity/agent'; import { InterfaceFactory } from '@dfinity/candid/lib/cjs/idl'; - -import type { All } from './idl/all.d'; -import { idlFactory as allIDL } from './idl/all'; import type { CandidUI } from './idl/candid-ui.d'; import { idlFactory as candidIDL } from './idl/candid-ui'; +import { sandboxEvalInterface } from '../services/sandbox'; +import { mapOptional } from '../services/common'; +import type { All } from './idl/all.d'; +import { idlFactory as allIDL } from './idl/all'; import DABCanisters from './idl/dab-canisters.did.d'; import { idlFactory as dabCanistersIDL } from './idl/dab-canisters.did'; import DABTokens from './idl/dab-tokens.did.d'; @@ -13,78 +14,117 @@ import { idlFactory as dabTokensIDL } from './idl/dab-tokens.did'; import DABNFTs from './idl/dab-nfts.did.d'; import { idlFactory as dabNFTsIDL } from './idl/dab-nfts.did'; -///////////// -// Config // -/////////// +// TODO: Clean up these error classes. We only user the parent, because introspecting error classes between sandbox and origin would require de/serialization that I don't feel like dealing with. +class CandidInterfaceError extends Error { } + +class CanisterExposesNoInterfaceError extends CandidInterfaceError { } + + +export class ActorHelper { + boundryUrl: URL; + isLocal: boolean; + interfaces: { [key: string]: 'ok' }; + constructor(url: URL) { + this.boundryUrl = new URL(url.origin); + this.isLocal = this.boundryUrl.toString().includes('localhost') || this.boundryUrl.toString().includes('127.0.0.1'); + this.interfaces = {}; + } -const canisters: { [key: string]: string } = { - candidUI: 'a4gq6-oaaaa-aaaab-qaa4q-cai', - dabCanisters: 'curr3-vaaaa-aaaah-abbdq-cai', - dabTokens: 'qwt65-nyaaa-aaaah-qcl4q-cai', - dabNFTs: 'aipdg-waaaa-aaaah-aaq5q-cai', -}; + createActor( + canisterId: string, + idl: InterfaceFactory, + config?: Agent.ActorConfig, + ): Agent.ActorSubclass { + const agent = new Agent.HttpAgent({ host: this.boundryUrl.toString() }); + return Agent.Actor.createActor(idl, { canisterId, agent, ...config }); + } -const host = 'https://ic0.app'; -const hostLocal = 'http://localhost:8000'; + createCandidUIActor(): Agent.ActorSubclass { + let canisterId = this.isLocal ? 'b77ix-eeaaa-aaaaa-qaada-cai' : 'a4gq6-oaaaa-aaaab-qaa4q-cai'; + return this.createActor(canisterId, candidIDL); + } -//////////// -// Agent // -////////// + createDABCansitersActor(): Agent.ActorSubclass | undefined { + if (this.isLocal) { + return undefined; + } + let canisterId = 'curr3-vaaaa-aaaah-abbdq-cai'; + return this.createActor(canisterId, dabCanistersIDL); + } -export const agent = new Agent.HttpAgent({ host }); -const agentLocal = new Agent.HttpAgent({ host: hostLocal }); -agentLocal.fetchRootKey(); + createDABTokensActor(): Agent.ActorSubclass | undefined { + if (this.isLocal) { + return undefined; + } + let canister = 'qwt65-nyaaa-aaaah-qcl4q-cai'; + return this.createActor(canister, dabTokensIDL); + } -///////////// -// Actors // -/////////// + createDABNFTsActor(): Agent.ActorSubclass | undefined { + if (this.isLocal) { + return undefined; + } + let canister = 'aipdg-waaaa-aaaah-aaq5q-cai'; + return this.createActor(canister, dabNFTsIDL); + } -export const candidUI = actor(canisters.candidUI, candidIDL); -export const dabCanisters = actor( - canisters.dabCanisters, - dabCanistersIDL, -); -export const dabTokens = actor(canisters.dabTokens, dabTokensIDL); -export const dabNFTs = actor(canisters.dabNFTs, dabNFTsIDL); -export const canister = generic(allIDL); + /** + * Get interface factory from memory, or attempt to import it. Throws an error if an interface factory can't be imported. + * @deprecated We can't transmit IDLs to and from the sandbox + */ + async getCanisterIDL( + canisterId: string, + ): Promise<'ok'> { + if (!(canisterId in this.interfaces)) { + const i = await this.importCandidInterface(canisterId); + if (!i) { + throw new CanisterExposesNoInterfaceError( + `Could not retrieve IDL for canister ${canisterId}`, + ); + } + this.interfaces[canisterId] = i; + } + return this.interfaces[canisterId]; + } -////////// -// Lib // -//////// + /** + * Attempts to import the interface bindings for a given canister, which can be used for effective candid decoding. Currently will not work for Rust canisters (will return undefined). + */ + async importCandidInterface( + canisterId: string, + ): Promise<'ok' | undefined> { + const candid = await this.fetchCandidInterface(canisterId); + if (!candid) return undefined; + const js = await this.convertCandidToJavascript(candid); + if (!js) return undefined; + return sandboxEvalInterface(canisterId, this.boundryUrl, js); + } -// Map of existing actors -// const actors: { -// [key: string]: { -// actor: Agent.ActorSubclass -// local: Agent.ActorSubclass -// idl: InterfaceFactory -// } -// } = {} + /** + * Attempts to retrieve the candid interface for a given canister. Relies on `canister.__get_candid_interface_tmp_hack`, which works for Motoko canisters, but not Rust canisters. + * @returns Candid interface in text format + */ + async fetchCandidInterface( + canisterId: string, + ): Promise { + try { + const canister = this.createActor(canisterId, allIDL); + return await canister.__get_candid_interface_tmp_hack(); + } catch (e) { + return undefined; + } + } -// Create an actor. -export function actor( - canisterId: string, - idl: InterfaceFactory, - isLocal = false, - config?: Agent.ActorConfig, -): Agent.ActorSubclass { - const actor = - // (actors[canisterId].actor as Agent.ActorSubclass) || - Agent.Actor.createActor(idl, { canisterId, agent, ...config }); - const local = - // (actors[canisterId].local as Agent.ActorSubclass) || - Agent.Actor.createActor(idl, { - canisterId, - agent: agentLocal, - ...config, - }); - // actors[canisterId] = { actor, idl, local } - return isLocal ? local : actor; -} + /** + * Attempts to convert a candid interface definition into a javascript interface definition, using the dfinity Candid UI canister. + * @param candid Candid interface in text format + * @returns Javascript interface in text format + */ + async convertCandidToJavascript( + candid: string, + ): Promise { + const candidUI = this.createCandidUIActor(); + return mapOptional(await candidUI.did_to_js(candid)); + } -// Create a function that creates an actor using a generic IDL. -function generic(idl: InterfaceFactory, local = false) { - return function (canisterId: string): Agent.ActorSubclass { - return actor(canisterId, idl, local); - }; -} +} \ No newline at end of file diff --git a/src/services/candid/decode.ts b/src/services/candid/decode.ts index 86bc9c5..96b5ce5 100644 --- a/src/services/candid/decode.ts +++ b/src/services/candid/decode.ts @@ -1,6 +1,7 @@ +import { ActorHelper } from '../../api/actors'; import { sandboxDecodeCandidArgs, sandboxDecodeCandidVals } from '../sandbox'; import { decodeNoInterface } from './decode-no-interface'; -import { CandidInterfaceError, getCanisterIDL } from './interfaces'; +import { CandidInterfaceError } from './errors'; export interface CandidDecodeResult { result: any; @@ -14,9 +15,10 @@ export async function decodeCandidArgs( canisterId: string, method: string, data: ArrayBuffer, + actorHelper: ActorHelper ): Promise { try { - await getCanisterIDL(canisterId); + await actorHelper.getCanisterIDL(canisterId); return { result: await sandboxDecodeCandidArgs(canisterId, method, data), withInterface: true, @@ -39,9 +41,10 @@ export async function decodeCandidVals( canisterId: string, method: string, data: ArrayBuffer, + actorHelper: ActorHelper ): Promise { try { - await getCanisterIDL(canisterId); + await actorHelper.getCanisterIDL(canisterId); return { result: await sandboxDecodeCandidVals(canisterId, method, data), withInterface: true, diff --git a/src/services/candid/errors.ts b/src/services/candid/errors.ts new file mode 100644 index 0000000..2c2b0d7 --- /dev/null +++ b/src/services/candid/errors.ts @@ -0,0 +1,5 @@ +export class CandidInterfaceError extends Error { } + +export class CanisterExposesNoInterfaceError extends CandidInterfaceError { } + +export class InterfaceMismatchError extends CandidInterfaceError { } \ No newline at end of file diff --git a/src/services/candid/index.ts b/src/services/candid/index.ts index 751d453..d816adb 100644 --- a/src/services/candid/index.ts +++ b/src/services/candid/index.ts @@ -4,8 +4,7 @@ export { CandidDecodeResult, } from './decode'; export { - fetchCandidInterface, CandidInterfaceError, InterfaceMismatchError, CanisterExposesNoInterfaceError, -} from './interfaces'; +} from './errors'; diff --git a/src/services/candid/interfaces.ts b/src/services/candid/interfaces.ts deleted file mode 100644 index 59ae11e..0000000 --- a/src/services/candid/interfaces.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { candidUI, canister } from '../../api/actors'; -import { mapOptional } from '../common'; -import { sandboxEvalInterface } from '../sandbox'; - -const interfaces: { [key: string]: 'ok' } = {}; - -// TODO: Clean up these error classes. We only user the parent, because introspecting error classes between sandbox and origin would require de/serialization that I don't feel like dealing with. -export class CandidInterfaceError extends Error {} - -export class CanisterExposesNoInterfaceError extends CandidInterfaceError {} - -export class InterfaceMismatchError extends CandidInterfaceError {} - -/** - * Get interface factory from memory, or attempt to import it. Throws an error if an interface factory can't be imported. - * @deprecated We can't transmit IDLs to and from the sandbox - */ -export async function getCanisterIDL(canisterId: string): Promise<'ok'> { - if (!(canisterId in interfaces)) { - const i = await importCandidInterface(canisterId); - if (!i) { - throw new CanisterExposesNoInterfaceError( - `Could not retrieve IDL for canister ${canisterId}`, - ); - } - interfaces[canisterId] = i; - } - return interfaces[canisterId]; -} - -/** - * Attempts to import the interface bindings for a given canister, which can be used for effective candid decoding. Currently will not work for Rust canisters (will return undefined). - */ -async function importCandidInterface( - canisterId: string, -): Promise<'ok' | undefined> { - const candid = await fetchCandidInterface(canisterId); - if (!candid) return undefined; - const js = await convertCandidToJavascript(candid); - if (!js) return undefined; - return sandboxEvalInterface(canisterId, js); -} - -/** - * Attempts to retrieve the candid interface for a given canister. Relies on `canister.__get_candid_interface_tmp_hack`, which works for Motoko canisters, but not Rust canisters. - * @returns Candid interface in text format - */ -export async function fetchCandidInterface( - canisterId: string, -): Promise { - try { - return await canister(canisterId).__get_candid_interface_tmp_hack(); - } catch (e) { - return undefined; - } -} - -/** - * Attempts to convert a candid interface definition into a javascript interface definition, using the dfinity Candid UI canister. - * @param candid Candid interface in text format - * @returns Javascript interface in text format - */ -async function convertCandidToJavascript( - candid: string, -): Promise { - return mapOptional(await candidUI.did_to_js(candid)); -} diff --git a/src/services/capture/handler.ts b/src/services/capture/handler.ts index 758d649..6d01236 100644 --- a/src/services/capture/handler.ts +++ b/src/services/capture/handler.ts @@ -1,3 +1,4 @@ +import { ActorHelper } from '../../api/actors'; import { DecodedRequest, decodeRequest } from './request'; import { DecodedResponse, decodeResponse } from './response'; import { shouldCapture } from './select'; @@ -9,17 +10,19 @@ export async function captureInternetComputerMessageFromNetworkEvent( event: chrome.devtools.network.Request, ): Promise< | { - request: DecodedRequest; - response: DecodedResponse; - } + request: DecodedRequest; + response: DecodedResponse; + } | undefined > { if (!shouldCapture(event)) return; console.debug('Full network event stub', event); + const request = await decodeRequest(event); - const response = await decodeResponse(event, request); + const actorHelper = new ActorHelper(request.boundary); + const response = await decodeResponse(event, request, actorHelper); return { request, response }; } diff --git a/src/services/capture/request.ts b/src/services/capture/request.ts index b35504b..3ac439e 100644 --- a/src/services/capture/request.ts +++ b/src/services/capture/request.ts @@ -4,6 +4,7 @@ import { ReadRequest, CallRequest, Expiry, requestIdOf } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; import { decodeCandidArgs } from '../candid'; import { base64ToBytes, asPrincipal } from '../common'; +import { ActorHelper } from '../../api/actors'; export type RequestType = 'query' | 'call' | 'read_state'; type InternetComputerRequest = ReadRequest | CallRequest; @@ -19,6 +20,7 @@ interface AbstractDecodedRequest { boundary: URL; size?: number; stack?: SimplifiedStack[]; + actorHelper: ActorHelper; } export interface DecodedCallRequest extends AbstractDecodedRequest { @@ -74,6 +76,7 @@ export async function decodeRequest( // Validate the request. const request = result.content; + console.log(request); if ( !('request_type' in request) || !['query', 'call', 'read_state'].includes(request.request_type) @@ -88,6 +91,7 @@ export async function decodeRequest( const ingressExpiry = request.ingress_expiry; const requestId = toHexString([...new Uint8Array(requestIdOf(request))]); const boundary = new URL(event.request.url); + const actorHelper = new ActorHelper(boundary); const size = event.request.bodySize > -1 ? event.request.bodySize : undefined; const stack = extractTrace(event); @@ -104,7 +108,7 @@ export async function decodeRequest( canisterId: canisterId, method, }; - const args = await decodeCandidArgs(canisterId, method, request.arg); + const args = await decodeCandidArgs(canisterId, method, request.arg, actorHelper); return { boundary, @@ -118,15 +122,25 @@ export async function decodeRequest( args, size, stack, + actorHelper }; } else { // These parameters only on "read_state" requests const paths = request.paths; - - // The lookup path provided in "read_state" request is the requestId of the original "call" request - const message = toHexString([...new Uint8Array(request.paths[0][1])]); - - const { method, canisterId } = messageDetails[message]; + let method; + let canisterId; + let message; + if (request.paths[0].length <= 1) { + throw new Error('Unexpected empty paths in read_state request'); + } else { + + // The lookup path provided in "read_state" request is the requestId of the original "call" request + message = toHexString([...new Uint8Array(request.paths[0][1])]); + + let info = messageDetails[message]; + method = info.method; + canisterId = info.canisterId; + } return { boundary, message, @@ -139,6 +153,7 @@ export async function decodeRequest( paths, size, stack, + actorHelper }; } } diff --git a/src/services/capture/response.ts b/src/services/capture/response.ts index 5aa2b10..1227623 100644 --- a/src/services/capture/response.ts +++ b/src/services/capture/response.ts @@ -13,6 +13,7 @@ import { messageDetails, DecodedCallRequest, } from './request'; +import { ActorHelper } from '../../api/actors'; interface AbstractDecodedResponse { size?: number; @@ -49,6 +50,7 @@ export type DecodedResponse = export async function decodeResponse( event: chrome.devtools.network.Request, request: DecodedRequest, + actorHelper: ActorHelper ): Promise { // We retrieve the response text through chrome's async api const content = (await new Promise((res) => @@ -103,6 +105,7 @@ export async function decodeResponse( canisterId, method, response.reply.arg, + actorHelper ); return { status, reply, size }; } else { @@ -137,6 +140,7 @@ export async function decodeResponse( const { canisterId, method } = details; + const cert = new Certificate(response, new HttpAgent()); // manipulating the private `verified` property to bypass BLS verification. This is fine for debugging purposes, but it breaks the security model of the IC, so logs in the extension will not be trustable. There's a pure js BLS lib, but it will take 8 seconds to verify each certificate. There's a much faster WASM lib, but chrome extensions make that a pain (could be something worth implementing in the sandbox.) (cert as any).verified = true; @@ -166,6 +170,7 @@ export async function decodeResponse( canisterId, method, buffer, + actorHelper ); return { status, reply, size } as RepliedReadStateResponse; } @@ -182,7 +187,7 @@ export async function decodeResponse( const message = new TextDecoder().decode( cert.lookup([...paths.flat(), 'reject_message'])!, ); - return { status, message, code, size }; + return { status: "rejected", message, code, size } as RejectedResponse; } case RequestStatusResponseStatus.Done: diff --git a/src/services/capture/select.ts b/src/services/capture/select.ts index ffcf7ea..ae3cca7 100644 --- a/src/services/capture/select.ts +++ b/src/services/capture/select.ts @@ -1,5 +1,5 @@ const boundaryNodeRegex = - /https?:\/\/(?:.+)?((?:ic0\.app|dfinity.network|icp-api.io|icp0.io|mainnet.plugwallet.ooo)|localhost:[0-9]+)\/api\/v2\/canister\/(.+)\/(query|call|read_state)/; + /https?:\/\/(?:.+)?((?:ic0\.app|dfinity.network|icp-api.io|icp0.io|mainnet.plugwallet.ooo)|(localhost|127.0.0.1):[0-9]+)\/api\/v2\/canister\/(.+)\/(query|call|read_state)/; /** * Determines whether a URL represents a request to an internet computer boundary node. @@ -31,6 +31,5 @@ export function isCBOR(event: chrome.devtools.network.Request): boolean { * proved less reliable. */ export function shouldCapture(event: chrome.devtools.network.Request): boolean { - // Let's try out a CBOR-only approach for now. - return isCBOR(event); + return isBoundaryNodeURL(event.request.url); } diff --git a/src/services/dab/canister-details.ts b/src/services/dab/canister-details.ts index 02fed96..af14769 100644 --- a/src/services/dab/canister-details.ts +++ b/src/services/dab/canister-details.ts @@ -2,14 +2,15 @@ import { sandboxDabLookup } from '../sandbox/dab'; export type DabLookupResponse = | { - name: string; - description: string; - thumbnail: string; - } + name: string; + description: string; + thumbnail: string; + } | undefined; export async function getDabCanisterData( canisterId: string, + boundryUrl: URL, ): Promise { - return await sandboxDabLookup(canisterId); + return await sandboxDabLookup(canisterId, boundryUrl); } diff --git a/src/services/logging/common.ts b/src/services/logging/common.ts index 711d948..cc075ed 100644 --- a/src/services/logging/common.ts +++ b/src/services/logging/common.ts @@ -1,7 +1,7 @@ import { Principal } from '@dfinity/principal'; -import { fetchCandidInterface } from '../candid'; import { DecodedRequest, DecodedResponse } from '../capture'; import { getDabCanisterData } from '../dab'; +import { ActorHelper } from '../../api/actors'; export let LOG_MAX = 100; export const increaseLogMax = () => (LOG_MAX += 50); @@ -23,6 +23,7 @@ export interface CanisterData { moduleHash?: string; controllers?: string[]; hasCandid: boolean; + canisterUIUrl?: string; } export interface MethodData { @@ -32,12 +33,13 @@ export interface MethodData { export async function getCanisterData( canisterId: string, + actorHelper: ActorHelper ): Promise { - const dab = await getDabCanisterData(canisterId); + const dab = await getDabCanisterData(canisterId, actorHelper.boundryUrl); const { subnet, moduleHash, controllers } = await getIcApiCanisterData( canisterId, ); - const hasCandid = Boolean(await fetchCandidInterface(canisterId)); + const hasCandid = Boolean(await actorHelper.fetchCandidInterface(canisterId)); return { identifier: canisterId, subnet, @@ -48,6 +50,9 @@ export async function getCanisterData( description: dab?.description, logoUrl: dab?.thumbnail, hasCandid, + canisterUIUrl: actorHelper.isLocal + ? `${actorHelper.boundryUrl}?canisterId=b77ix-eeaaa-aaaaa-qaada-cai&id=${canisterId}` + : `https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.ic0.app/?id=${canisterId}`, }; } diff --git a/src/services/logging/store.ts b/src/services/logging/store.ts index 1b58c0f..4565072 100644 --- a/src/services/logging/store.ts +++ b/src/services/logging/store.ts @@ -7,6 +7,7 @@ import { MessageId, getMessageRepositoryUpdate, } from './messages'; +import { ActorHelper } from '../../api/actors'; export default create<{ messages: MessageRepository; @@ -21,8 +22,10 @@ export default create<{ }, async log(request, response) { // We don't want async in out update logic because it could cause stale state in parallel updates, so we put it up here. + + const actorHelper = new ActorHelper(request.boundary); const asyncData = { - canister: await getCanisterData(request.canisterId), + canister: await getCanisterData(request.canisterId, actorHelper), }; const { messages } = get(); const update = getMessageRepositoryUpdate( diff --git a/src/services/sandbox/dab.ts b/src/services/sandbox/dab.ts index a7241c1..575093f 100644 --- a/src/services/sandbox/dab.ts +++ b/src/services/sandbox/dab.ts @@ -1,7 +1,9 @@ import { Principal } from '@dfinity/principal'; import { DabLookupResponse } from '../dab/canister-details'; import { sandboxRequest } from './handler'; -import { dabCanisters, dabNFTs } from '../../api/actors'; +import { ActorHelper } from '../../api/actors'; +import { CanisterMetadata } from '../../api/idl/dab-canisters.did.d'; +import { DABCollection } from '../../api/idl/dab-nfts.did.d'; // Copy pasted a bunch of dab-js code because their deps mess with my build export type DetailType = @@ -54,9 +56,9 @@ export const parseDetailValue = (detailValue: DetailValue): DetailType => { typeof value === 'number' ? v : parseDetailValue( - // @ts-ignore: psychedelic code - v, - ), + // @ts-ignore: psychedelic code + v, + ), ); } return value; @@ -71,20 +73,24 @@ export interface SandboxRequestDabLookup { type: 'dabLookup'; data: { canisterId: string; + boundryUrl: string; }; } export async function sandboxDabLookup( canisterId: string, + boundryUrl: URL ): Promise { if ((window as any)?.DISABLE_SANDBOX) - return sandboxHandleDabLookup({ - type: 'dabLookup', - data: { canisterId }, - }); + return sandboxHandleDabLookup( + { + type: 'dabLookup', + data: { canisterId, boundryUrl: boundryUrl.toString() }, + } + ); return sandboxRequest({ type: 'dabLookup', - data: { canisterId }, + data: { canisterId, boundryUrl: boundryUrl.toString() }, }).then((r) => r.data); } @@ -101,10 +107,10 @@ const formatRegistryDetails = (details: Metadata['details']): Details => { return formattedDetails; }; -const canisterDirectory = dabCanisters.get_all(); +let canisterDirectoryCache: CanisterMetadata[] | undefined; // DAB token directory isn't working at the moment // let tokenDirectory = dabTokens.get_all(); -const nftDirectory = dabNFTs.get_all(); +let nftDirectoryCache: DABCollection[] | undefined; /** * Handle an evaluate interface message. Stores the generated javascript interface in sandbox memory for future reference. @@ -112,16 +118,30 @@ const nftDirectory = dabNFTs.get_all(); export async function sandboxHandleDabLookup( request: SandboxRequestDabLookup, ): Promise { + const actorHelper = new ActorHelper(new URL(request.data.boundryUrl)); const { data: { canisterId }, } = request; - const canister = (await canisterDirectory).find( + if (canisterDirectoryCache == null) { + let canisterDirectoryActor = actorHelper.createDABCansitersActor(); + if (canisterDirectoryActor != null) { + canisterDirectoryCache = await canisterDirectoryActor.get_all(); + } + } + + if (nftDirectoryCache == null) { + let nftsActor = actorHelper.createDABNFTsActor(); + if (nftsActor != null) { + nftDirectoryCache = await nftsActor.get_all(); + } + } + const canister = canisterDirectoryCache?.find( (canister) => canister.principal_id.toText() === canisterId, ); if (canister) return formatMetadata(canister); // const token = (await tokenDirectory).find((token) => token.principal_id.toText() === canisterId); // if (token) return formatMetadata(token); - const nft = (await nftDirectory).find( + const nft = nftDirectoryCache?.find( (nft) => nft.principal_id.toText() === canisterId, ); if (nft) diff --git a/src/services/sandbox/handler.ts b/src/services/sandbox/handler.ts index b2f882e..2feb6b1 100644 --- a/src/services/sandbox/handler.ts +++ b/src/services/sandbox/handler.ts @@ -29,10 +29,10 @@ export const sandboxRepository = { interface SandboxRequest { request: - | SandboxRequestEvalInterface - | SandboxRequestDecodeCandidArgs - | SandboxRequestDecodeCandidVals - | SandboxRequestDabLookup; + | SandboxRequestEvalInterface + | SandboxRequestDecodeCandidArgs + | SandboxRequestDecodeCandidVals + | SandboxRequestDabLookup; requestId: string; } @@ -52,10 +52,10 @@ export async function sandboxRequest(request: SandboxRequest['request']) { interface SandboxResponse { response: - | SandboxResponseEvalInterface - | SandboxResponseDecodeCandidArgs - | SandboxResponsedecodeCandidVals - | SandboxResponseDabLookup; + | SandboxResponseEvalInterface + | SandboxResponseDecodeCandidArgs + | SandboxResponsedecodeCandidVals + | SandboxResponseDabLookup; requestId: string; } diff --git a/src/services/sandbox/interfaces.test.ts b/src/services/sandbox/interfaces.test.ts index 5e5c0c2..18c047a 100644 --- a/src/services/sandbox/interfaces.test.ts +++ b/src/services/sandbox/interfaces.test.ts @@ -12,6 +12,7 @@ test('Candid javascript interface evaluation', () => { type: 'evalInterface', data: { canisterId: name, + boundryUrl: "https://ic0.app/", javascriptAsString: stub, }, }; diff --git a/src/services/sandbox/interfaces.ts b/src/services/sandbox/interfaces.ts index 994a0eb..97de882 100644 --- a/src/services/sandbox/interfaces.ts +++ b/src/services/sandbox/interfaces.ts @@ -10,17 +10,19 @@ export interface SandboxRequestEvalInterface { type: 'evalInterface'; data: { canisterId: string; + boundryUrl: string; javascriptAsString: string; }; } export async function sandboxEvalInterface( canisterId: string, + boundryUrl: URL, javascriptAsString: string, ): Promise<'ok'> { return sandboxRequest({ type: 'evalInterface', - data: { canisterId, javascriptAsString }, + data: { canisterId, boundryUrl: boundryUrl.toString(), javascriptAsString }, }).then((r) => r.data); } diff --git a/src/ui/details.tsx b/src/ui/details.tsx index fd276ce..5b398aa 100644 --- a/src/ui/details.tsx +++ b/src/ui/details.tsx @@ -96,7 +96,7 @@ function Overview(props: { message: MessageEntry }) {
{message.canister.hasCandid ? ( @@ -108,14 +108,18 @@ function Overview(props: { message: MessageEntry }) {
Subnet
- - https://dashboard.internetcomputer.org/subnet/ - {message.canister.subnet} - + {message.canister.subnet ? ( + + https://dashboard.internetcomputer.org/subnet/ + {message.canister.subnet} + + ) : ( + `Could not determine` + )}
Description