{"version":3,"sources":["../../src/dappCore/setupClientChannel.ts"],"sourcesContent":["import {safeAssertUnreachable} from '../utils/assertion'\nimport {logger} from '../utils/logging'\n\nimport {messageDirectionMatches} from './nufiMessage'\nimport type {\n  InitChannelMessage,\n  RequestArgument,\n  RequestMessage,\n  SuccessResponse,\n  ErrorResponse,\n  MessageToClient,\n  ScriptContext,\n  UntypedConnectorKind,\n  InitChannelData,\n  ConnectorPlatform,\n} from './types'\nimport {getRandomUUID} from './utils'\n\nconst cachedGetPort = <T extends MessagePort>(\n  getPort: (invalidatePort: () => void) => T,\n): (() => T) => {\n  let port: null | T = null\n  const invalidatePort = () => {\n    port = null\n  }\n  return () => {\n    if (port === null) port = getPort(invalidatePort)\n    return port\n  }\n}\n\ntype SetupClientChannelParams<ConnectorKind extends UntypedConnectorKind> = {\n  appId: string\n  connectorPlatform: ConnectorPlatform\n  currentContext: ScriptContext\n  targetContext: ScriptContext\n  eventHandler: (\n    kind: ConnectorKind | undefined,\n    method: string,\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    args: any[],\n  ) => Promise<void>\n  sendPortPostMessage: (message: unknown, transfer: Transferable[]) => void\n  onBeforeFirstSend?: () => Promise<void>\n  onBeforeRequest?: (args: {\n    connectorKind: null | ConnectorKind\n    method: string\n    args: RequestArgument[]\n  }) => unknown\n  initChannelData?: InitChannelData\n}\n\n// This sets up a connection from the dapp script to the nufi script.\n// Note that the connection is only actually built up on the first request.\n// When a method is called, a request is sent, and a promise is returned. This\n// promise will be resolved whenever a response to the request arrives.\nexport const setupClientChannel = <ConnectorKind extends UntypedConnectorKind>({\n  appId,\n  connectorPlatform,\n  currentContext,\n  targetContext,\n  eventHandler,\n  sendPortPostMessage,\n  onBeforeFirstSend,\n  onBeforeRequest,\n  initChannelData,\n}: SetupClientChannelParams<ConnectorKind>) => {\n  logger.debug('\"setupClientChannel\"')\n  const {\n    // to be called by the listener waiting for the channel to respond\n    setChannelReady,\n    channelReady,\n  } = (() => {\n    let _setChannelReady: (value: unknown) => void\n\n    const channelReady = new Promise((resolve) => {\n      _setChannelReady = resolve\n    })\n\n    return {\n      channelReady,\n      setChannelReady: () => {\n        logger.debug('\"setupClientChannel\" setting channel ready')\n        _setChannelReady(true)\n      },\n    }\n  })()\n\n  // Note that it is important that the logic of this function stays synchronous,\n  // as some dappConnectors (extension) relies on it.\n  if (onBeforeFirstSend != null) {\n    logger.debug('\"setupClientChannel\" calling onBeforeFirstSend')\n    onBeforeFirstSend().then(() => {\n      logger.debug('\"onBeforeFirstSend\" finished')\n      setChannelReady()\n    })\n  } else {\n    setChannelReady()\n  }\n\n  const activeRequests = new Map<\n    string,\n    {resolve: (_: SuccessResponse) => void; reject: (_: ErrorResponse) => void}\n  >()\n\n  const getPort = cachedGetPort(() => {\n    logger.debug('\"setupClientChannel\" calling cachedGetPort')\n    const channel = new MessageChannel()\n    const initChannelMessage: InitChannelMessage = {\n      appId,\n      connectorPlatform,\n      method: 'initChannel',\n      data: initChannelData,\n    }\n    // Note that even if port message is being sent sooner than this call is processed,\n    // the listener on the other end will anyway receive the messages.\n    sendPortPostMessage(initChannelMessage, [channel.port2])\n    channel.port1.onmessage = (\n      e: MessageEvent<MessageToClient<ConnectorKind>>,\n    ) => {\n      // We are only checking message structure here, not the origin,\n      // as this code anyways runs in untrusted environment.\n      const msg = e.data\n\n      if (!messageDirectionMatches(msg, targetContext, currentContext)) return\n\n      logger.debug('\"setupClientChannel\" received port message', msg)\n\n      switch (msg.type) {\n        case 'response': {\n          const {id, result} = msg\n          const msgPromiseCallbacks = activeRequests.get(id)\n          if (msgPromiseCallbacks) {\n            activeRequests.delete(id)\n            if (result.kind === 'success')\n              msgPromiseCallbacks.resolve(result.value)\n            else msgPromiseCallbacks.reject(result.value)\n          }\n          break\n        }\n        case 'event':\n          // \"stray\" events related to connections managed from other frames may arrive\n          // and we just want to ignore them as they are not related to \"our\" frame\n          if (msg.targetOrigin !== window.location.origin) return\n\n          eventHandler(msg.connectorKind, msg.method, msg.args)\n          break\n        default:\n          safeAssertUnreachable(msg)\n      }\n    }\n    return channel.port1\n  })\n  // Manually curried, as some websites such as https://io.gidonline.fun\n  // override `Function.prototype.bind`, making the usage of `bind` broken inside them.\n  return (connectorKind: null | ConnectorKind) =>\n    async (method: string, args: RequestArgument[]) => {\n      const priorityTimestamp = new Date().toISOString()\n      logger.debug('\"setupClientChannel\" calling API', {\n        connectorKind,\n        method,\n        args,\n      })\n\n      // this ensures that messages don't collide even if we inject the\n      // connector objects into multiple (i)frames within the same page (i.e. tab)\n      const id = getRandomUUID()\n      const request: RequestMessage<ConnectorKind> = {\n        senderContext: currentContext,\n        targetContext,\n        id,\n        connectorKind,\n        method,\n        args,\n        priorityTimestamp,\n      }\n\n      onBeforeRequest?.({connectorKind, method, args})\n\n      await channelReady\n\n      logger.debug('\"setupClientChannel\" posting port message', request)\n\n      getPort().postMessage(request)\n      return new Promise<SuccessResponse>((resolve, reject) =>\n        activeRequests.set(id, {resolve, reject}),\n      )\n    }\n}\n"],"mappings":"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","names":["channelReady"]}