{"version":3,"sources":["../src/FetchCollection.ts","../src/HijackedRestService.ts"],"names":[],"mappings":"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","sourcesContent":["declare global {\n    // eslint-disable-next-line no-var\n    var ___originalFetch: typeof fetch\n    // eslint-disable-next-line no-var\n    var ___fakeFetch: Array<typeof fetch>\n}\n\n// Using a pure singleton is not working once the cypress tests,\n// they work locally, but not in CI and I'm not sure for what exact reason.\n// Using a `globalThis` variable as a work around.\n// This will have the benefit is several versions of fake-rest get unintentionally\n// installed as we'll have a global singleton across all versions.\nglobalThis.___originalFetch = globalThis.___originalFetch || fetch\nglobalThis.___fakeFetch = globalThis.___fakeFetch || [fetch]\n\nexport class FetchCollection {\n    private static instance: FetchCollection\n    private originalFetch: typeof fetch = fetch\n\n    public static getInstance() {\n        if (!FetchCollection.instance) {\n            FetchCollection.instance = new FetchCollection()\n        }\n        return FetchCollection.instance\n    }\n\n    private get fetches() {\n        return globalThis.___fakeFetch\n    }\n\n    public push(instance: typeof fetch) {\n        this.fetches.push(instance)\n        globalThis.fetch = instance\n    }\n\n    public teardown() {\n        globalThis.fetch = this.originalFetch\n    }\n\n    public get instance() {\n        return this.fetches[this.fetches.length - 1]\n    }\n\n    public next(instance: typeof fetch) {\n        const index = this.fetches.findIndex(x => x === instance)\n        const next = this.fetches[index - 1]\n        if (next) {\n            return next\n        }\n        return this.originalFetch\n    }\n}\n","import { createRouter, isHandler } from './createRouter'\nimport { enableLogger, logger } from './logger'\nimport { type Methods } from './methods'\nimport { Response, Request, RestRouter, HttpHeader } from './types'\nimport { getMethod, getRouteParams, getUrl, handleServiceError } from './utils'\nimport { FetchCollection } from './FetchCollection'\n\nexport { createRouter }\nexport { enableLogger }\n\ntype HijackedRestServiceOptions = {\n    name: string\n    path: string\n    silent: boolean\n}\n\nexport const mockedFetch = (...args: Parameters<typeof fetch>) => {\n    return FetchCollection.getInstance().instance(...args)\n}\n\n/**\n * Hijacks the fetch/XmlRequest calls and returns the data from the router.\n */\nexport class HijackedRestService {\n    private options: Partial<HijackedRestServiceOptions>\n    private isActive = true\n    private readonly hijackedFetch: typeof fetch\n\n    constructor(\n        host: URL,\n        private router: RestRouter,\n        options?: Partial<HijackedRestServiceOptions>,\n    ) {\n        this.options = {\n            name: 'HijackedRestService',\n            ...options,\n        }\n\n        logger(`${this.options.name}: Starting...`)\n\n        this.hijackedFetch = globalThis.fetch = async (\n            input: RequestInfo | URL,\n            init?: RequestInit,\n        ) => {\n            if (!this.isActive) {\n                return FetchCollection.getInstance().next(this.hijackedFetch)(input, init)\n            }\n            const url = getUrl(input)\n            logger(`${this.options.name}: Fetching ${url}`)\n            if (`${host.protocol}` === url.protocol) {\n                if (host.host === url.host) {\n                    if (!host.pathname || url.pathname.startsWith(host.pathname)) {\n                        // hijack the call\n                        const method = getMethod(input, init)\n                        const hostUrl = url.pathname + url.search\n                        logger(`${this.options.name}:`, '->', method, hostUrl)\n\n                        const matchingRoutes = this.router.routes.filter(route => {\n                            if (!route.method) {\n                                // middleware\n                                return route.regexp.test(url.pathname)\n                            } else {\n                                return route.method === method && route.regexp.test(url.pathname)\n                            }\n                        })\n\n                        let status: number | undefined = undefined\n                        let send: unknown[] = []\n                        let complete = false\n                        const headers: Headers = new Headers()\n                        const response: Response = {\n                            setHeader(key, value) {\n                                headers.set(key, value)\n                                return response\n                            },\n                            status: code => {\n                                status = code\n                                return response\n                            },\n                            send: data => {\n                                if (complete) {\n                                    throw new Error('Response already completed')\n                                }\n                                send.push(data)\n                                complete = true\n                                return response\n                            },\n                            json: data => {\n                                headers.set('Content-Type', 'application/json; charset=utf-8')\n                                send = [data]\n                                if (!status) {\n                                    status = 200\n                                }\n                                complete = true\n                                return response\n                            },\n                            end: () => {\n                                if (!status) {\n                                    status = 200\n                                }\n                                complete = true\n                            },\n                        }\n\n                        const query = Object.fromEntries(url.searchParams.entries())\n                        const request: Omit<Request<string>, 'params'> = {\n                            host: host.host,\n                            query: query,\n                            headers: getHeaders(input, init) as Record<string, string>,\n                            method: method as Methods,\n                            url: hostUrl,\n                            body: getBody(input, init),\n                        }\n\n                        const promises: Promise<unknown>[] = []\n                        const errorHandlers = [...router.errorHandlers]\n\n                        const executeRoutes = async () => {\n                            const matchingRoute = matchingRoutes.shift()\n\n                            if (!matchingRoute || !isHandler(matchingRoute.handler))\n                                return Promise.reject('failed to find route')\n\n                            const params = getRouteParams(matchingRoute, url)\n                            await matchingRoute.handler(\n                                Object.assign(request, { params }),\n                                response,\n                                async () => {\n                                    promises.push(executeRoutes())\n                                },\n                            )\n                        }\n                        promises.push(executeRoutes())\n                        while (promises.length) {\n                            try {\n                                await promises.shift()\n                            } catch (e) {\n                                handleServiceError(\n                                    e,\n                                    errorHandlers,\n                                    Object.assign(request, { params: {} }),\n                                    response,\n                                )\n                            }\n                        }\n\n                        return Promise.resolve<Partial<globalThis.Response>>({\n                            get ok() {\n                                return Boolean(\n                                    `${status}`.startsWith('2') || `${status}`.startsWith('3'),\n                                )\n                            },\n                            status: status ?? 500,\n                            json: () => {\n                                if (request.method === 'HEAD') {\n                                    return Promise.reject(\n                                        new SyntaxError('Unexpected end of JSON input'),\n                                    )\n                                }\n                                return Promise.resolve(send[0])\n                            },\n                            headers: headers,\n                            text: () => {\n                                if (request.method === 'HEAD') {\n                                    return Promise.resolve('')\n                                }\n                                const result = send.map(x => {\n                                    switch (typeof x) {\n                                        case 'string':\n                                            return x\n                                        default:\n                                            return JSON.stringify(x)\n                                    }\n                                })\n                                return Promise.resolve(result.join(''))\n                            },\n                            url: url.toString(),\n                        }) as Promise<globalThis.Response>\n                    }\n                }\n            }\n            return FetchCollection.getInstance().next(this.hijackedFetch)(input, init)\n        }\n\n        FetchCollection.getInstance().push(this.hijackedFetch)\n    }\n\n    dispose() {\n        this.isActive = false\n        logger(`${this.options.name}: Disposed.`)\n    }\n}\n\nconst parseInput = (input: RequestInfo | URL, init?: RequestInit) => {\n    if (typeof input === 'string') {\n        return init?.body\n    } else if (input instanceof URL) {\n        return init?.body\n    } else {\n        return input.body\n    }\n}\n\nconst compareInstance = (\n    body: any,\n    klass: { new (): FormData } | { new (): URLSearchParams } | { new (): Blob },\n) => {\n    if (body instanceof klass) return true\n    // In cypress we are comparing across different window instances.\n    return 'constructor' in body && body.constructor.toString() === klass.toString()\n}\n\nconst isURLSearchParams = (body: any): body is URLSearchParams =>\n    compareInstance(body, URLSearchParams)\n\nconst isFormData = (body: any): body is FormData => compareInstance(body, FormData)\n\nconst isBlob = (body: any): body is Blob => compareInstance(body, Blob)\n\nconst getBody = (input: RequestInfo | URL, init?: RequestInit) => {\n    const body = parseInput(input, init)\n    if (body === undefined || body === null) {\n        return null\n    } else if (typeof body === 'string') {\n        return JSON.parse(body)\n    } else if (isFormData(body)) {\n        return Object.fromEntries(body.entries())\n    } else if (isURLSearchParams(body)) {\n        return Object.fromEntries((body as unknown as URLSearchParams).entries())\n    } else if (isBlob(body)) {\n        return body\n    } else if (typeof body === 'object') {\n        return body\n    }\n}\n\nconst getHeaders = (input: RequestInfo | URL, init?: RequestInit): HttpHeader => {\n    if (typeof input === 'string') {\n        return convertHeaders(init?.headers)\n    } else if (input instanceof URL) {\n        return convertHeaders(init?.headers)\n    } else {\n        return convertHeaders(input.headers)\n    }\n}\n\nconst convertHeaders = (headers?: HeadersInit): HttpHeader => {\n    if (!headers) return {}\n    if (isStringStringHeader(headers)) {\n        return headers.reduce((acc, [key, value]) => {\n            acc[key] = value\n            return acc\n        }, {} as HttpHeader)\n    } else if (headers instanceof Headers) {\n        return Object.fromEntries(headers.entries())\n    } else {\n        return headers\n    }\n}\n\nconst isStringStringHeader = (header: HeadersInit): header is [string, string][] => {\n    return Array.isArray(header)\n}\n"]}