{"version":3,"file":"WsListener.mjs","names":["message: SpyMessageStorage | undefined","data","spyAndJestMatchers: any","resolve: (value: void | PromiseLike<any>) => void","tracker: Tracker","timeoutPid: NodeJS.Timeout | undefined"],"sources":["../../listener/WsListener.ts"],"sourcesContent":["import { device } from 'aws-iot-device-sdk';\nimport { fragment, getConnection } from './iot-connection';\nimport { ServerlessSpyListener } from './ServerlessSpyListener';\nimport { ServerlessSpyListenerParams } from './ServerlessSpyListenerParams';\nimport { getTopic } from './topic';\nimport { WaitForParams } from './WaitForParams';\nimport { FunctionRequestSpyEvent } from '../common/spyEvents/FunctionRequestSpyEvent';\nimport { SpyMessage } from '../common/spyEvents/SpyMessage';\n\nexport class WsListener<TSpyEvents> {\n  private messages: SpyMessageStorage[] = [];\n  private trackers: Tracker[] = [];\n\n  private connectionOpenResolve?: () => void;\n  private connectionOpenReject?: (reason?: any) => void;\n  private closed = true;\n  private functionPrefix = 'waitFor';\n  private debugMode = false;\n  private connection: device | undefined;\n\n  private fragments = new Map<string, Map<number, fragment>>();\n\n  public async start(params: ServerlessSpyListenerParams) {\n    this.debugMode = !!params.debugMode;\n    try {\n      this.connection = await getConnection(\n        this.debugMode,\n        params.serverlessSpyWsUrl\n      );\n      this.closed = false;\n      const topic = getTopic(params.scope || '#');\n      this.log(`Subscribing to topic ${topic}`);\n      const connectionOpenResolve =\n        this.connectionOpenResolve || params.connectionOpenResolve;\n      const localConnection = this.connection;\n      this.connection.on('connect', () => {\n        this.closed = false;\n        this.log('Connection opened');\n        localConnection.subscribe(topic);\n        if (connectionOpenResolve) {\n          connectionOpenResolve();\n        }\n      });\n      this.connection.on('message', (_topic: string, data: Buffer) => {\n        if (this.closed) return;\n\n        this.log('Message received', data.toString());\n        const fragment = JSON.parse(data.toString());\n        let message: SpyMessageStorage | undefined = undefined;\n        if (!fragment.id) {\n          message = JSON.parse(fragment.data) as SpyMessageStorage;\n        }\n\n        let pending = this.fragments.get(fragment.id);\n        if (!pending) {\n          pending = new Map();\n          this.fragments.set(fragment.id, pending);\n        }\n        pending.set(fragment.index, fragment);\n\n        if (pending.size === fragment.count) {\n          const data = [...pending.values()]\n            .sort((a, b) => a.index - b.index)\n            .map((item) => item.data)\n            .join('');\n          this.fragments.delete(fragment.id);\n          message = JSON.parse(data) as SpyMessageStorage;\n        }\n\n        if (message) {\n          message.serviceKeyForFunction = message.serviceKey.replace(/#/g, '');\n\n          if (message.serviceKey.startsWith('Function')) {\n            message.functionContextAwsRequestId = (\n              message.data as FunctionRequestSpyEvent\n            ).context.awsRequestId;\n          }\n\n          this.messages.push(message);\n          this.resolveOldTrackerWithNewMessage(message);\n        }\n      });\n      this.connection.on('close', () => {\n        this.log('Connection closed');\n\n        this.closed = true;\n      });\n\n      const connectionOpenReject =\n        this.connectionOpenReject || params.connectionOpenReject;\n      this.connection.on('error', (error) => {\n        this.log('Connection error:', error);\n        connectionOpenReject?.(error);\n      });\n    } catch (e) {\n      console.error('Failed to get connection', e);\n      throw e;\n    }\n  }\n\n  public async stop() {\n    this.closed = true;\n    this.connection!.end(true);\n  }\n\n  private trackerMatchMessage(tracker: Tracker, message: SpyMessageStorage) {\n    if (tracker.finished) return;\n\n    if (\n      (tracker.serviceKey && tracker.serviceKey === message.serviceKey) ||\n      (tracker.serviceKeyForFunction &&\n        tracker.serviceKeyForFunction === message.serviceKeyForFunction)\n    ) {\n      if (this.trackerMatchCondition(tracker, message)) {\n        tracker.finished = true;\n\n        const spyAndJestMatchers: any = {\n          getData: () => message.data,\n        };\n\n        const serviceKeyForFunction = tracker.serviceKeyForFunction;\n        if (\n          serviceKeyForFunction &&\n          serviceKeyForFunction.startsWith('Function') &&\n          (serviceKeyForFunction.endsWith('Request') ||\n            serviceKeyForFunction.endsWith('Console'))\n        ) {\n          let serviceKeyForFunctionChain = serviceKeyForFunction;\n\n          if (serviceKeyForFunctionChain.endsWith('Request')) {\n            serviceKeyForFunctionChain = serviceKeyForFunctionChain.substring(\n              0,\n              serviceKeyForFunctionChain.length - 'Request'.length\n            );\n          } else if (serviceKeyForFunctionChain.endsWith('Console')) {\n            serviceKeyForFunctionChain = serviceKeyForFunctionChain.substring(\n              0,\n              serviceKeyForFunctionChain.length - 'Console'.length\n            );\n          }\n\n          spyAndJestMatchers.followedByConsole = (paramsW: WaitForParams) => {\n            return this.createWaitForXXXFunc(\n              `${serviceKeyForFunctionChain}Console`,\n              (message.data as FunctionRequestSpyEvent).context.awsRequestId\n            )(paramsW);\n          };\n\n          spyAndJestMatchers.followedByResponse = (paramsW: WaitForParams) => {\n            return this.createWaitForXXXFunc(\n              `${serviceKeyForFunctionChain}Response`,\n              (message.data as FunctionRequestSpyEvent).context.awsRequestId\n            )(paramsW);\n          };\n        }\n\n        const proxy = new Proxy(spyAndJestMatchers, {\n          get: function (target: any, objectKey: string) {\n            if (target.hasOwnProperty(objectKey)) {\n              return target[objectKey];\n            } else if (objectKey !== 'then') {\n              return function () {\n                const jestFunctionToExecute = (expect(message.data) as any)[\n                  objectKey\n                ];\n                jestFunctionToExecute.apply(undefined, arguments);\n                return proxy;\n              };\n            }\n          },\n        });\n\n        tracker.promiseResolve(proxy);\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private resolveTrackerInOldMessages(tracker: Tracker) {\n    for (const message of this.messages) {\n      if (this.trackerMatchMessage(tracker, message)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  private resolveOldTrackerWithNewMessage(message: SpyMessageStorage) {\n    for (let index = 0; index < this.trackers.length; index++) {\n      const tracker = this.trackers[index];\n      if (this.trackerMatchMessage(tracker, message)) {\n        this.trackers = this.trackers.splice(index, 1);\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  private trackerMatchCondition(tracker: Tracker, message: SpyMessageStorage) {\n    const matchCondition =\n      (tracker.condition && tracker.condition(message.data)) ||\n      !tracker.condition;\n\n    const matchRequestId =\n      (tracker.functionContextAwsRequestId &&\n        tracker.functionContextAwsRequestId ===\n          message.functionContextAwsRequestId) ||\n      !tracker.functionContextAwsRequestId;\n\n    if (matchCondition && matchRequestId) {\n      return true;\n    } else {\n      if (\n        !matchCondition &&\n        matchRequestId &&\n        !tracker.possibleSpyMessageDataForDebugging\n      ) {\n        tracker.possibleSpyMessageDataForDebugging = message.data;\n      }\n      return false;\n    }\n  }\n\n  private createWaitForXXXFunc(\n    serviceKeyForFunction: string,\n    functionContextAwsRequestId?: string\n  ) {\n    return (paramsW?: WaitForParams) => {\n      let resolve: (value: void | PromiseLike<any>) => void;\n      const promise = new Promise((res) => {\n        resolve = res;\n      });\n      const tracker: Tracker = {\n        finished: false,\n        // @ts-ignore\n        promiseResolve: resolve,\n        serviceKeyForFunction,\n        functionContextAwsRequestId,\n      };\n\n      tracker.condition = paramsW?.condition;\n\n      let timeoutPid: NodeJS.Timeout | undefined;\n      const timer = new Promise((_, reject) => {\n        timeoutPid = setTimeout(() => {\n          if (tracker.finished) return;\n          tracker.finished = true;\n          let message = `Timeout waiting for Serverless Spy message ${serviceKeyForFunction}.`;\n\n          if (tracker.possibleSpyMessageDataForDebugging) {\n            message += ` Similar matching spy event data: ${JSON.stringify(\n              tracker.possibleSpyMessageDataForDebugging,\n              null,\n              2\n            )}`;\n          }\n\n          reject(new Error(message));\n        }, paramsW?.timoutMs || 10000);\n      });\n\n      if (!this.resolveTrackerInOldMessages(tracker)) {\n        this.trackers.push(tracker);\n      }\n\n      return Promise.race([promise, timer]).finally(() => {\n        if (!!timeoutPid) {\n          clearTimeout(timeoutPid);\n        }\n      });\n    };\n  }\n\n  public createProxy() {\n    const spyListener = {} as ServerlessSpyListener<TSpyEvents>;\n\n    spyListener.stop = async () => {\n      await this.stop();\n    };\n\n    return new Proxy<ServerlessSpyListener<TSpyEvents>>(spyListener, {\n      get: (target: any, objectKey: string) => {\n        if (target.hasOwnProperty(objectKey)) {\n          return target[objectKey].bind(target);\n        } else if (\n          typeof objectKey === 'string' &&\n          objectKey.startsWith(this.functionPrefix)\n        ) {\n          const serviceKeyForFunction = objectKey.substring(\n            this.functionPrefix.length\n          );\n\n          return this.createWaitForXXXFunc(serviceKeyForFunction);\n        }\n      },\n    });\n  }\n\n  private log(message: string, ...optionalParams: any[]) {\n    if (this.debugMode && !this.closed) {\n      console.debug(\n        'SSPY',\n        message,\n        new Date().toISOString(),\n        ...optionalParams\n      );\n    }\n  }\n}\n\ntype Tracker = {\n  promiseResolve: (data: any) => void;\n  finished: boolean;\n  serviceKey?: string;\n  serviceKeyForFunction?: string;\n  condition?: (data: any) => boolean;\n  timoutMs?: number;\n  functionContextAwsRequestId?: string;\n  possibleSpyMessageDataForDebugging?: any;\n};\n\ntype SpyMessageStorage = SpyMessage & {\n  serviceKeyForFunction: string;\n  functionContextAwsRequestId?: string;\n};\n"],"mappings":"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"}