@findkit/fetch
Advanced tools
| {"version":3,"file":"fetch.test.d.ts","sourceRoot":"","sources":["../../__tests__/fetch.test.ts"],"names":[],"mappings":""} |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B;AAED;;GAEG;AACH,oBAAY,YAAY,GAAG;IAC1B,CAAC,WAAW,EAAE,MAAM,GACjB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAC/B;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAClC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACjC,SAAS,CAAC;CACb,CAAC;AAEF;;GAEG;AACH,UAAU,QAAQ;IACjB,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC/D;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,IAAI,CAAC,EAAE,aAAa,GAAG,aAAa,GAAG,uBAAuB,CAAC;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAmBD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,CAAC,OAAO,EAAE;QACT,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACb;AAmBD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,gBAAgB;;;;EA6H3D;AAiBD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,UAEvD;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,oBAAY,qBAAqB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE;QACV,IAAI,EAAE,QAAQ,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KAChB,EAAE,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,YAAY,EAAE,YAAY,CAAC;KAC3B,EAAE,CAAC;CACJ"} |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"} |
+21
| MIT License | ||
| Copyright (c) 2022 Valu Digital Oy | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+138
-173
| import { expect, test, describe, beforeEach, afterEach, vi } from "vitest"; | ||
| import { findkitFetch } from ".."; | ||
| import { rest } from "msw"; | ||
@@ -11,4 +10,4 @@ import { sign } from "jsonwebtoken"; | ||
| createFindkitFetcher, | ||
| getRequestBody, | ||
| JwtErrorResponse, | ||
| FindkitSearchResponse, | ||
| FindkitErrorResponse, | ||
| } from "../src/index"; | ||
@@ -37,99 +36,11 @@ | ||
| type FindkitFetchResponse = Awaited<ReturnType<typeof findkitFetch>>; | ||
| describe("request body generation", () => { | ||
| test("without tags", () => { | ||
| const body = getRequestBody({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [[]], | ||
| size: 5, | ||
| from: 0, | ||
| createdDecay: undefined, | ||
| modifiedDecay: undefined, | ||
| decayScale: undefined, | ||
| highlightLength: undefined, | ||
| lang: undefined, | ||
| }, | ||
| ], | ||
| describe("fetch", () => { | ||
| test("empty fetch", async () => { | ||
| const { findkitFetch } = createFindkitFetcher({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| }); | ||
| expect(body).toEqual({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [[]], | ||
| size: 5, | ||
| from: 0, | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
| test("without tags", () => { | ||
| const body = getRequestBody({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| size: 5, | ||
| from: 0, | ||
| createdDecay: undefined, | ||
| modifiedDecay: undefined, | ||
| decayScale: undefined, | ||
| highlightLength: undefined, | ||
| lang: undefined, | ||
| }, | ||
| ], | ||
| }); | ||
| expect(body).toEqual({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| size: 5, | ||
| from: 0, | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
| test("with lang", () => { | ||
| const body = getRequestBody({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| size: 5, | ||
| from: 0, | ||
| lang: "de", | ||
| createdDecay: undefined, | ||
| modifiedDecay: undefined, | ||
| decayScale: undefined, | ||
| highlightLength: undefined, | ||
| }, | ||
| ], | ||
| }); | ||
| expect(body).toEqual({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| lang: "de", | ||
| size: 5, | ||
| from: 0, | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
| }); | ||
| describe("fetch", () => { | ||
| test("empty fetch", async () => { | ||
| server.use( | ||
| rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| const resData: FindkitFetchResponse = { | ||
| const resData: FindkitSearchResponse = { | ||
| groups: [], | ||
@@ -141,4 +52,4 @@ duration: 123, | ||
| ); | ||
| const res = await findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| q: "", | ||
@@ -150,29 +61,25 @@ groups: [], | ||
| }); | ||
| }); | ||
| describe("jwt", () => { | ||
| test("calls getJwtToken() and passes the token as bearer", async () => { | ||
| test("can use publicToken to generate the endpoint", async () => { | ||
| const spy = vi.fn(); | ||
| const { findkitFetch } = createFindkitFetcher({ | ||
| publicToken: "thetoken", | ||
| }); | ||
| server.use( | ||
| rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| spy(req.headers.get("authorization")); | ||
| const resData: FindkitFetchResponse = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| }) | ||
| rest.post( | ||
| "https://search.findkit.com/c/thetoken/search", | ||
| (req, res, ctx) => { | ||
| spy(req.url.toString()); | ||
| const resData: FindkitSearchResponse = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| } | ||
| ) | ||
| ); | ||
| const fetcher = createFindkitFetcher({ | ||
| async getJwtToken() { | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| const res = await fetcher.findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| const res = await findkitFetch({ | ||
| q: "", | ||
@@ -182,13 +89,19 @@ groups: [], | ||
| expect(spy).toHaveBeenCalledTimes(1); | ||
| expect(spy).toHaveBeenCalledWith("Bearer " + jwt); | ||
| expect(res).toEqual({ duration: 123, groups: [] }); | ||
| expect(spy).toHaveBeenCalledWith( | ||
| "https://search.findkit.com/c/thetoken/search?p=thetoken" | ||
| ); | ||
| }); | ||
| test("calls getJwtToken() only once for multiple fetches", async () => { | ||
| const spy = vi.fn(); | ||
| test("when no groups are defined add a default that searches everything", async () => { | ||
| const { findkitFetch } = createFindkitFetcher({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| }); | ||
| server.use( | ||
| rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| const resData: FindkitFetchResponse = { | ||
| rest.post("https://test.invalid/multi-search2", async (req, res, ctx) => { | ||
| const requestBody = await req.json(); | ||
| expect(requestBody).toEqual({ q: "test", groups: [{ tagQuery: [] }] }); | ||
| const resData: FindkitSearchResponse = { | ||
| groups: [], | ||
@@ -200,72 +113,124 @@ duration: 123, | ||
| ); | ||
| const fetcher = createFindkitFetcher({ | ||
| async getJwtToken() { | ||
| spy(); | ||
| return { jwt }; | ||
| }, | ||
| const res = await findkitFetch({ | ||
| q: "test", | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| expect(res).toEqual({ duration: 123, groups: [] }); | ||
| }); | ||
| for await (const i of Array(5).keys()) { | ||
| await fetcher.findkitFetch({ | ||
| describe("jwt", () => { | ||
| test("calls getJwtToken() and passes the token in the p query string", async () => { | ||
| const spy = vi.fn(); | ||
| server.use( | ||
| rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| spy(req.url.searchParams.get("p")); | ||
| const resData: FindkitSearchResponse = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| }) | ||
| ); | ||
| const { findkitFetch } = createFindkitFetcher({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| q: i.toString(), | ||
| async getJwtToken() { | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| const res = await findkitFetch({ | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| } | ||
| expect(spy).toHaveBeenCalledTimes(1); | ||
| }); | ||
| expect(spy).toHaveBeenCalledTimes(1); | ||
| expect(spy).toHaveBeenCalledWith("jwt:" + jwt); | ||
| expect(res).toEqual({ duration: 123, groups: [] }); | ||
| }); | ||
| test("calls getJwtToken() again when server responds with expired error", async () => { | ||
| let expired = false; | ||
| test("calls getJwtToken() only once for multiple fetches", async () => { | ||
| const spy = vi.fn(); | ||
| server.use( | ||
| rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| if (expired) { | ||
| expired = false; | ||
| const data: JwtErrorResponse = { | ||
| error: { type: "jwt-expired" }, | ||
| server.use( | ||
| rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| const resData: FindkitSearchResponse = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.status(403), ctx.json(data)); | ||
| } | ||
| return res(ctx.json(resData)); | ||
| }) | ||
| ); | ||
| const resData: FindkitFetchResponse = { | ||
| const fetcher = createFindkitFetcher({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| async getJwtToken() { | ||
| spy(); | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| for await (const i of Array(5).keys()) { | ||
| await fetcher.findkitFetch({ | ||
| q: i.toString(), | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| }) | ||
| ); | ||
| }); | ||
| } | ||
| const spy = vi.fn(); | ||
| const fetcher = createFindkitFetcher({ | ||
| async getJwtToken() { | ||
| spy(); | ||
| const jwt = createJwtToken(); | ||
| return { jwt }; | ||
| }, | ||
| expect(spy).toHaveBeenCalledTimes(1); | ||
| }); | ||
| await fetcher.findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| test("calls getJwtToken() again when server responds with expired error", async () => { | ||
| let expired = false; | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| server.use( | ||
| rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| if (expired) { | ||
| expired = false; | ||
| const data: FindkitErrorResponse = { | ||
| code: "jwt-expired", | ||
| }; | ||
| return res(ctx.status(403), ctx.json(data)); | ||
| } | ||
| expired = true; | ||
| const resData: FindkitSearchResponse = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| }) | ||
| ); | ||
| await fetcher.findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| const spy = vi.fn(); | ||
| q: "", | ||
| groups: [], | ||
| const fetcher = createFindkitFetcher({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| async getJwtToken() { | ||
| spy(); | ||
| const jwt = createJwtToken(); | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| await fetcher.findkitFetch({ | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| expired = true; | ||
| await fetcher.findkitFetch({ | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| expect(spy).toBeCalledTimes(2); | ||
| }); | ||
| expect(spy).toBeCalledTimes(2); | ||
| }); | ||
| }); |
| export {}; | ||
| //# sourceMappingURL=fetch.test.d.ts.map |
+120
-164
@@ -1,8 +0,9 @@ | ||
| import { expect, test, describe, beforeEach, afterEach, vi } from "vitest"; | ||
| import { findkitFetch } from ".."; | ||
| import { rest } from "msw"; | ||
| import { sign } from "jsonwebtoken"; | ||
| import fetch from "node-fetch"; | ||
| import { setupServer } from "msw/node"; | ||
| import { createFindkitFetcher, getRequestBody, } from "../src/index"; | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const vitest_1 = require("vitest"); | ||
| const msw_1 = require("msw"); | ||
| const jsonwebtoken_1 = require("jsonwebtoken"); | ||
| const node_fetch_1 = require("node-fetch"); | ||
| const node_1 = require("msw/node"); | ||
| const index_1 = require("../src/index"); | ||
| /** | ||
@@ -13,100 +14,19 @@ * The token content does not matter in these tests since @findkit/fetch does | ||
| function createJwtToken() { | ||
| return sign({ foo: "bar" }, "secret"); | ||
| return (0, jsonwebtoken_1.sign)({ foo: "bar" }, "secret"); | ||
| } | ||
| Object.assign(global, { fetch }); | ||
| const server = setupServer(); | ||
| beforeEach(async () => { | ||
| Object.assign(global, { fetch: node_fetch_1.default }); | ||
| const server = (0, node_1.setupServer)(); | ||
| (0, vitest_1.beforeEach)(async () => { | ||
| server.listen(); | ||
| }); | ||
| afterEach(async () => { | ||
| (0, vitest_1.afterEach)(async () => { | ||
| server.close(); | ||
| server.resetHandlers(); | ||
| }); | ||
| describe("request body generation", () => { | ||
| test("without tags", () => { | ||
| const body = getRequestBody({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [[]], | ||
| size: 5, | ||
| from: 0, | ||
| createdDecay: undefined, | ||
| modifiedDecay: undefined, | ||
| decayScale: undefined, | ||
| highlightLength: undefined, | ||
| lang: undefined, | ||
| }, | ||
| ], | ||
| (0, vitest_1.describe)("fetch", () => { | ||
| (0, vitest_1.test)("empty fetch", async () => { | ||
| const { findkitFetch } = (0, index_1.createFindkitFetcher)({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| }); | ||
| expect(body).toEqual({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [[]], | ||
| size: 5, | ||
| from: 0, | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
| test("without tags", () => { | ||
| const body = getRequestBody({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| size: 5, | ||
| from: 0, | ||
| createdDecay: undefined, | ||
| modifiedDecay: undefined, | ||
| decayScale: undefined, | ||
| highlightLength: undefined, | ||
| lang: undefined, | ||
| }, | ||
| ], | ||
| }); | ||
| expect(body).toEqual({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| size: 5, | ||
| from: 0, | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
| test("with lang", () => { | ||
| const body = getRequestBody({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| size: 5, | ||
| from: 0, | ||
| lang: "de", | ||
| createdDecay: undefined, | ||
| modifiedDecay: undefined, | ||
| decayScale: undefined, | ||
| highlightLength: undefined, | ||
| }, | ||
| ], | ||
| }); | ||
| expect(body).toEqual({ | ||
| q: "test", | ||
| groups: [ | ||
| { | ||
| tagQuery: [["tag"]], | ||
| lang: "de", | ||
| size: 5, | ||
| from: 0, | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
| }); | ||
| describe("fetch", () => { | ||
| test("empty fetch", async () => { | ||
| server.use(rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| server.use(msw_1.rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| const resData = { | ||
@@ -119,14 +39,14 @@ groups: [], | ||
| const res = await findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| expect(res).toEqual({ duration: 123, groups: [] }); | ||
| (0, vitest_1.expect)(res).toEqual({ duration: 123, groups: [] }); | ||
| }); | ||
| }); | ||
| describe("jwt", () => { | ||
| test("calls getJwtToken() and passes the token as bearer", async () => { | ||
| const spy = vi.fn(); | ||
| server.use(rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| spy(req.headers.get("authorization")); | ||
| (0, vitest_1.test)("can use publicToken to generate the endpoint", async () => { | ||
| const spy = vitest_1.vi.fn(); | ||
| const { findkitFetch } = (0, index_1.createFindkitFetcher)({ | ||
| publicToken: "thetoken", | ||
| }); | ||
| server.use(msw_1.rest.post("https://search.findkit.com/c/thetoken/search", (req, res, ctx) => { | ||
| spy(req.url.toString()); | ||
| const resData = { | ||
@@ -138,20 +58,16 @@ groups: [], | ||
| })); | ||
| const fetcher = createFindkitFetcher({ | ||
| async getJwtToken() { | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| const res = await fetcher.findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| const res = await findkitFetch({ | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| expect(spy).toHaveBeenCalledTimes(1); | ||
| expect(spy).toHaveBeenCalledWith("Bearer " + jwt); | ||
| expect(res).toEqual({ duration: 123, groups: [] }); | ||
| (0, vitest_1.expect)(res).toEqual({ duration: 123, groups: [] }); | ||
| (0, vitest_1.expect)(spy).toHaveBeenCalledWith("https://search.findkit.com/c/thetoken/search?p=thetoken"); | ||
| }); | ||
| test("calls getJwtToken() only once for multiple fetches", async () => { | ||
| const spy = vi.fn(); | ||
| server.use(rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| (0, vitest_1.test)("when no groups are defined add a default that searches everything", async () => { | ||
| const { findkitFetch } = (0, index_1.createFindkitFetcher)({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| }); | ||
| server.use(msw_1.rest.post("https://test.invalid/multi-search2", async (req, res, ctx) => { | ||
| const requestBody = await req.json(); | ||
| (0, vitest_1.expect)(requestBody).toEqual({ q: "test", groups: [{ tagQuery: [] }] }); | ||
| const resData = { | ||
@@ -163,55 +79,95 @@ groups: [], | ||
| })); | ||
| const fetcher = createFindkitFetcher({ | ||
| async getJwtToken() { | ||
| spy(); | ||
| return { jwt }; | ||
| }, | ||
| const res = await findkitFetch({ | ||
| q: "test", | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| for await (const i of Array(5).keys()) { | ||
| await fetcher.findkitFetch({ | ||
| (0, vitest_1.expect)(res).toEqual({ duration: 123, groups: [] }); | ||
| }); | ||
| (0, vitest_1.describe)("jwt", () => { | ||
| (0, vitest_1.test)("calls getJwtToken() and passes the token in the p query string", async () => { | ||
| const spy = vitest_1.vi.fn(); | ||
| server.use(msw_1.rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| spy(req.url.searchParams.get("p")); | ||
| const resData = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| })); | ||
| const { findkitFetch } = (0, index_1.createFindkitFetcher)({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| q: i.toString(), | ||
| async getJwtToken() { | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| const res = await findkitFetch({ | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| } | ||
| expect(spy).toHaveBeenCalledTimes(1); | ||
| }); | ||
| test("calls getJwtToken() again when server responds with expired error", async () => { | ||
| let expired = false; | ||
| server.use(rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| if (expired) { | ||
| expired = false; | ||
| const data = { | ||
| error: { type: "jwt-expired" }, | ||
| (0, vitest_1.expect)(spy).toHaveBeenCalledTimes(1); | ||
| (0, vitest_1.expect)(spy).toHaveBeenCalledWith("jwt:" + jwt); | ||
| (0, vitest_1.expect)(res).toEqual({ duration: 123, groups: [] }); | ||
| }); | ||
| (0, vitest_1.test)("calls getJwtToken() only once for multiple fetches", async () => { | ||
| const spy = vitest_1.vi.fn(); | ||
| server.use(msw_1.rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| const resData = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.status(403), ctx.json(data)); | ||
| return res(ctx.json(resData)); | ||
| })); | ||
| const fetcher = (0, index_1.createFindkitFetcher)({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| async getJwtToken() { | ||
| spy(); | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| const jwt = createJwtToken(); | ||
| for await (const i of Array(5).keys()) { | ||
| await fetcher.findkitFetch({ | ||
| q: i.toString(), | ||
| groups: [], | ||
| }); | ||
| } | ||
| const resData = { | ||
| (0, vitest_1.expect)(spy).toHaveBeenCalledTimes(1); | ||
| }); | ||
| (0, vitest_1.test)("calls getJwtToken() again when server responds with expired error", async () => { | ||
| let expired = false; | ||
| server.use(msw_1.rest.post("https://test.invalid/multi-search2", (req, res, ctx) => { | ||
| if (expired) { | ||
| expired = false; | ||
| const data = { | ||
| code: "jwt-expired", | ||
| }; | ||
| return res(ctx.status(403), ctx.json(data)); | ||
| } | ||
| const resData = { | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| })); | ||
| const spy = vitest_1.vi.fn(); | ||
| const fetcher = (0, index_1.createFindkitFetcher)({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| async getJwtToken() { | ||
| spy(); | ||
| const jwt = createJwtToken(); | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| await fetcher.findkitFetch({ | ||
| q: "", | ||
| groups: [], | ||
| duration: 123, | ||
| }; | ||
| return res(ctx.json(resData)); | ||
| })); | ||
| const spy = vi.fn(); | ||
| const fetcher = createFindkitFetcher({ | ||
| async getJwtToken() { | ||
| spy(); | ||
| const jwt = createJwtToken(); | ||
| return { jwt }; | ||
| }, | ||
| }); | ||
| expired = true; | ||
| await fetcher.findkitFetch({ | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| (0, vitest_1.expect)(spy).toBeCalledTimes(2); | ||
| }); | ||
| await fetcher.findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| expired = true; | ||
| await fetcher.findkitFetch({ | ||
| searchEndpoint: "https://test.invalid/multi-search2", | ||
| q: "", | ||
| groups: [], | ||
| }); | ||
| expect(spy).toBeCalledTimes(2); | ||
| }); | ||
| }); |
+22
-22
| /** | ||
| * @public | ||
| */ | ||
| export interface FindKitDeveloperOptions { | ||
| export interface FindkitFetchInit { | ||
| staging?: boolean; | ||
| logResponseTimes?: boolean; | ||
| publicToken?: string; | ||
| searchEndpoint?: string; | ||
| getJwtToken?: GetJwtToken; | ||
| } | ||
@@ -32,11 +35,4 @@ /** | ||
| */ | ||
| export interface FindkitFetchOptions extends FindkitSearchParams, FindKitDeveloperOptions { | ||
| publicToken?: string; | ||
| searchEndpoint?: string; | ||
| } | ||
| /** | ||
| * @public | ||
| */ | ||
| export interface FindkitFetch { | ||
| (options: FindkitFetchOptions): Promise<FindkitSearchResponse>; | ||
| (options: FindkitSearchParams): Promise<FindkitSearchResponse>; | ||
| } | ||
@@ -46,6 +42,5 @@ /** | ||
| */ | ||
| export interface JwtErrorResponse { | ||
| error: { | ||
| type?: "jwt-expired" | "jwt-invalid"; | ||
| }; | ||
| export interface FindkitErrorResponse { | ||
| code?: "jwt-expired" | "jwt-invalid" | "invalid-response-body"; | ||
| message?: string; | ||
| } | ||
@@ -56,3 +51,6 @@ /** | ||
| export interface GetJwtToken { | ||
| (): Promise<JwtToken>; | ||
| (options: { | ||
| publicToken?: string; | ||
| searchEndpoint: string; | ||
| }): Promise<JwtToken>; | ||
| } | ||
@@ -74,5 +72,3 @@ /** | ||
| */ | ||
| export declare function createFindkitFetcher(props?: { | ||
| getJwtToken?: GetJwtToken; | ||
| }): { | ||
| export declare function createFindkitFetcher(init?: FindkitFetchInit): { | ||
| findkitFetch: FindkitFetch; | ||
@@ -82,8 +78,6 @@ clear: () => void; | ||
| }; | ||
| export declare function getRequestBody(options: FindkitFetchOptions): FindkitSearchParams; | ||
| export declare function getProjectSearchEndpoint(publicToken: string): string; | ||
| /** | ||
| * @public | ||
| */ | ||
| export declare const findkitFetch: FindkitFetch; | ||
| export declare function createSearchEndpoint(publicToken: string): string; | ||
| /** | ||
@@ -94,3 +88,4 @@ * @public | ||
| q: string; | ||
| groups: FindkitSearchGroupParams[]; | ||
| groups?: FindkitSearchGroupParams[]; | ||
| signal?: AbortSignal; | ||
| } | ||
@@ -114,4 +109,8 @@ /** | ||
| export declare type FindkitSearchResponse = { | ||
| duration: number; | ||
| groups: GroupSearchResults[]; | ||
| duration: number; | ||
| messages?: { | ||
| code: "string"; | ||
| message: string; | ||
| }[]; | ||
| }; | ||
@@ -138,1 +137,2 @@ /** | ||
| export {}; | ||
| //# sourceMappingURL=index.d.ts.map |
+91
-45
@@ -0,5 +1,28 @@ | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createSearchEndpoint = exports.createFindkitFetcher = void 0; | ||
| async function safeErrorJson(response) { | ||
| const text = await response.text().catch(() => { | ||
| return "Failed to read response body"; | ||
| }); | ||
| try { | ||
| return JSON.parse(text); | ||
| } | ||
| catch { | ||
| return { | ||
| code: "invalid-response-body", | ||
| message: `body: ${text.slice(0, 500)}`, | ||
| }; | ||
| } | ||
| } | ||
| let logResponseTimes = false; | ||
| if (typeof window !== "undefined") { | ||
| logResponseTimes = | ||
| window.localStorage.getItem("findkit-log-response-times") === "true"; | ||
| try { | ||
| logResponseTimes = | ||
| window.localStorage.getItem("findkit-log-response-times") === "true"; | ||
| } | ||
| catch { | ||
| // local storage access can throw in some enviroments such a | ||
| // codesandox.io due to permission issues | ||
| } | ||
| } | ||
@@ -9,3 +32,3 @@ /** | ||
| */ | ||
| export function createFindkitFetcher(props) { | ||
| function createFindkitFetcher(init) { | ||
| let currentJwtTokenPromise = null; | ||
@@ -18,12 +41,27 @@ /** | ||
| } | ||
| function refresh() { | ||
| if (typeof props?.getJwtToken === "function") { | ||
| currentJwtTokenPromise = props.getJwtToken(); | ||
| const searchEndpointString = inferSearchEndpoint(init); | ||
| const refresh = () => { | ||
| const getJwtTokenArg = { | ||
| searchEndpoint: searchEndpointString, | ||
| }; | ||
| if (typeof (init === null || init === void 0 ? void 0 : init.getJwtToken) === "function") { | ||
| currentJwtTokenPromise = init.getJwtToken(getJwtTokenArg); | ||
| } | ||
| } | ||
| else if (typeof FINDKIT_GET_JWT_TOKEN === "function") { | ||
| currentJwtTokenPromise = FINDKIT_GET_JWT_TOKEN(getJwtTokenArg); | ||
| } | ||
| }; | ||
| const findkitFetch = async (options) => { | ||
| // new implementation | ||
| const fetchUrl = getSearchEndpoint(options); | ||
| var _a, _b; | ||
| if (!options.groups) { | ||
| options = { | ||
| ...options, | ||
| groups: [ | ||
| { | ||
| tagQuery: [], | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| const started = Date.now(); | ||
| const requestBody = getRequestBody(options); | ||
| if (!currentJwtTokenPromise) { | ||
@@ -33,38 +71,49 @@ refresh(); | ||
| const token = await currentJwtTokenPromise; | ||
| const headers = { | ||
| // This looks wrong but is intentional. We want to make "Simple CORS | ||
| // requests" eg. requests without the OPTIONS preflight and | ||
| // application/json is not allowed for those. So we just have to use | ||
| // one of the allowed ones. | ||
| // See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
| "Content-Type": "text/plain", | ||
| }; | ||
| const fetchOptions = { | ||
| method: "POST", | ||
| signal: (_a = options.signal) !== null && _a !== void 0 ? _a : null, | ||
| mode: "cors", | ||
| credentials: "omit", | ||
| headers: { | ||
| // This looks wrong but is intentional. We want to make "Simple CORS | ||
| // requests" eg. requests without the OPTIONS preflight and | ||
| // application/json is not allowed for those. So we just have to use | ||
| // one of the allowed ones. | ||
| // See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
| "Content-Type": "text/plain", | ||
| }, | ||
| body: JSON.stringify(requestBody), | ||
| headers, | ||
| body: JSON.stringify({ | ||
| q: options.q, | ||
| groups: options.groups, | ||
| }), | ||
| }; | ||
| if (token && fetchOptions.headers) { | ||
| fetchOptions.headers["authorization"] = "Bearer " + token.jwt; | ||
| const endpoint = new URL(searchEndpointString); | ||
| if (token) { | ||
| if (typeof token.jwt !== "string") { | ||
| throw new Error("[findkit] Expected GetJwtToken response to contain a 'jwt' property"); | ||
| } | ||
| endpoint.searchParams.set("p", `jwt:${token.jwt}`); | ||
| } | ||
| const res = await fetch(fetchUrl, fetchOptions); | ||
| const res = await fetch(endpoint.toString(), fetchOptions); | ||
| if (res.status === 403) { | ||
| const error = await res.json(); | ||
| if (error.error.type === "jwt-expired") { | ||
| const error = await safeErrorJson(res); | ||
| if (error.code === "jwt-expired") { | ||
| refresh(); | ||
| return findkitFetch(options); | ||
| } | ||
| throw new Error("[findkit] Permission denied: " + error.error.type); | ||
| throw new Error("[findkit] Permission denied: " + error.message); | ||
| } | ||
| if (!res.ok) { | ||
| throw new Error("[findkit] Bad response from search: " + res.status); | ||
| const error = await safeErrorJson(res); | ||
| throw new Error(`[findkit] Bad response ${res.status} from search: ${error.message}`); | ||
| } | ||
| const responses = await res.json(); | ||
| if (options.logResponseTimes || logResponseTimes) { | ||
| if ((init === null || init === void 0 ? void 0 : init.logResponseTimes) || logResponseTimes) { | ||
| const total = Date.now() - started; | ||
| const backendDuration = responses.duration; | ||
| console.log(`[findkit] Response total ${total}ms, backend ${backendDuration}ms, network ${total - backendDuration}ms`); | ||
| options.groups.forEach((group, index) => { | ||
| const duration = responses.groups[index]?.duration ?? 0; | ||
| (_b = options.groups) === null || _b === void 0 ? void 0 : _b.forEach((group, index) => { | ||
| var _a, _b; | ||
| const duration = (_b = (_a = responses.groups[index]) === null || _a === void 0 ? void 0 : _a.duration) !== null && _b !== void 0 ? _b : 0; | ||
| console.log(`[findkit] Group response ${duration}ms for group "${index}"`, group); | ||
@@ -81,25 +130,22 @@ }); | ||
| } | ||
| export function getRequestBody(options) { | ||
| return { | ||
| q: options.q, | ||
| groups: options.groups, | ||
| }; | ||
| } | ||
| function getSearchEndpoint(options) { | ||
| if (options.searchEndpoint) { | ||
| return options.searchEndpoint; | ||
| exports.createFindkitFetcher = createFindkitFetcher; | ||
| function inferSearchEndpoint(options) { | ||
| if (options === null || options === void 0 ? void 0 : options.searchEndpoint) { | ||
| return options === null || options === void 0 ? void 0 : options.searchEndpoint; | ||
| } | ||
| else if (options.publicToken) { | ||
| return getProjectSearchEndpoint(options.publicToken); | ||
| else if (options === null || options === void 0 ? void 0 : options.publicToken) { | ||
| return createSearchEndpoint(options.publicToken); | ||
| } | ||
| else { | ||
| throw new Error("Unable to determine search endpoint"); | ||
| const msg = "[findkit] Unable to determine search endpoint"; | ||
| console.error(`${msg}. The options object must contain either a 'publicToken' or a 'searchEndpoint' property`, options); | ||
| throw new Error(`${msg}. See logs for details`); | ||
| } | ||
| } | ||
| export function getProjectSearchEndpoint(publicToken) { | ||
| return `https://search.findkit.com/c/${publicToken}/search?p=${publicToken}`; | ||
| } | ||
| /** | ||
| * @public | ||
| */ | ||
| export const findkitFetch = createFindkitFetcher().findkitFetch; | ||
| function createSearchEndpoint(publicToken) { | ||
| return `https://search.findkit.com/c/${publicToken}/search?p=${publicToken}`; | ||
| } | ||
| exports.createSearchEndpoint = createSearchEndpoint; |
+22
-22
| /** | ||
| * @public | ||
| */ | ||
| export interface FindKitDeveloperOptions { | ||
| export interface FindkitFetchInit { | ||
| staging?: boolean; | ||
| logResponseTimes?: boolean; | ||
| publicToken?: string; | ||
| searchEndpoint?: string; | ||
| getJwtToken?: GetJwtToken; | ||
| } | ||
@@ -32,11 +35,4 @@ /** | ||
| */ | ||
| export interface FindkitFetchOptions extends FindkitSearchParams, FindKitDeveloperOptions { | ||
| publicToken?: string; | ||
| searchEndpoint?: string; | ||
| } | ||
| /** | ||
| * @public | ||
| */ | ||
| export interface FindkitFetch { | ||
| (options: FindkitFetchOptions): Promise<FindkitSearchResponse>; | ||
| (options: FindkitSearchParams): Promise<FindkitSearchResponse>; | ||
| } | ||
@@ -46,6 +42,5 @@ /** | ||
| */ | ||
| export interface JwtErrorResponse { | ||
| error: { | ||
| type?: "jwt-expired" | "jwt-invalid"; | ||
| }; | ||
| export interface FindkitErrorResponse { | ||
| code?: "jwt-expired" | "jwt-invalid" | "invalid-response-body"; | ||
| message?: string; | ||
| } | ||
@@ -56,3 +51,6 @@ /** | ||
| export interface GetJwtToken { | ||
| (): Promise<JwtToken>; | ||
| (options: { | ||
| publicToken?: string; | ||
| searchEndpoint: string; | ||
| }): Promise<JwtToken>; | ||
| } | ||
@@ -74,5 +72,3 @@ /** | ||
| */ | ||
| export declare function createFindkitFetcher(props?: { | ||
| getJwtToken?: GetJwtToken; | ||
| }): { | ||
| export declare function createFindkitFetcher(init?: FindkitFetchInit): { | ||
| findkitFetch: FindkitFetch; | ||
@@ -82,8 +78,6 @@ clear: () => void; | ||
| }; | ||
| export declare function getRequestBody(options: FindkitFetchOptions): FindkitSearchParams; | ||
| export declare function getProjectSearchEndpoint(publicToken: string): string; | ||
| /** | ||
| * @public | ||
| */ | ||
| export declare const findkitFetch: FindkitFetch; | ||
| export declare function createSearchEndpoint(publicToken: string): string; | ||
| /** | ||
@@ -94,3 +88,4 @@ * @public | ||
| q: string; | ||
| groups: FindkitSearchGroupParams[]; | ||
| groups?: FindkitSearchGroupParams[]; | ||
| signal?: AbortSignal; | ||
| } | ||
@@ -114,4 +109,8 @@ /** | ||
| export declare type FindkitSearchResponse = { | ||
| duration: number; | ||
| groups: GroupSearchResults[]; | ||
| duration: number; | ||
| messages?: { | ||
| code: "string"; | ||
| message: string; | ||
| }[]; | ||
| }; | ||
@@ -138,1 +137,2 @@ /** | ||
| export {}; | ||
| //# sourceMappingURL=index.d.ts.map |
+91
-45
@@ -0,5 +1,28 @@ | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createSearchEndpoint = exports.createFindkitFetcher = void 0; | ||
| async function safeErrorJson(response) { | ||
| const text = await response.text().catch(() => { | ||
| return "Failed to read response body"; | ||
| }); | ||
| try { | ||
| return JSON.parse(text); | ||
| } | ||
| catch { | ||
| return { | ||
| code: "invalid-response-body", | ||
| message: `body: ${text.slice(0, 500)}`, | ||
| }; | ||
| } | ||
| } | ||
| let logResponseTimes = false; | ||
| if (typeof window !== "undefined") { | ||
| logResponseTimes = | ||
| window.localStorage.getItem("findkit-log-response-times") === "true"; | ||
| try { | ||
| logResponseTimes = | ||
| window.localStorage.getItem("findkit-log-response-times") === "true"; | ||
| } | ||
| catch { | ||
| // local storage access can throw in some enviroments such a | ||
| // codesandox.io due to permission issues | ||
| } | ||
| } | ||
@@ -9,3 +32,3 @@ /** | ||
| */ | ||
| export function createFindkitFetcher(props) { | ||
| function createFindkitFetcher(init) { | ||
| let currentJwtTokenPromise = null; | ||
@@ -18,12 +41,27 @@ /** | ||
| } | ||
| function refresh() { | ||
| if (typeof props?.getJwtToken === "function") { | ||
| currentJwtTokenPromise = props.getJwtToken(); | ||
| const searchEndpointString = inferSearchEndpoint(init); | ||
| const refresh = () => { | ||
| const getJwtTokenArg = { | ||
| searchEndpoint: searchEndpointString, | ||
| }; | ||
| if (typeof (init === null || init === void 0 ? void 0 : init.getJwtToken) === "function") { | ||
| currentJwtTokenPromise = init.getJwtToken(getJwtTokenArg); | ||
| } | ||
| } | ||
| else if (typeof FINDKIT_GET_JWT_TOKEN === "function") { | ||
| currentJwtTokenPromise = FINDKIT_GET_JWT_TOKEN(getJwtTokenArg); | ||
| } | ||
| }; | ||
| const findkitFetch = async (options) => { | ||
| // new implementation | ||
| const fetchUrl = getSearchEndpoint(options); | ||
| var _a, _b; | ||
| if (!options.groups) { | ||
| options = { | ||
| ...options, | ||
| groups: [ | ||
| { | ||
| tagQuery: [], | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| const started = Date.now(); | ||
| const requestBody = getRequestBody(options); | ||
| if (!currentJwtTokenPromise) { | ||
@@ -33,38 +71,49 @@ refresh(); | ||
| const token = await currentJwtTokenPromise; | ||
| const headers = { | ||
| // This looks wrong but is intentional. We want to make "Simple CORS | ||
| // requests" eg. requests without the OPTIONS preflight and | ||
| // application/json is not allowed for those. So we just have to use | ||
| // one of the allowed ones. | ||
| // See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
| "Content-Type": "text/plain", | ||
| }; | ||
| const fetchOptions = { | ||
| method: "POST", | ||
| signal: (_a = options.signal) !== null && _a !== void 0 ? _a : null, | ||
| mode: "cors", | ||
| credentials: "omit", | ||
| headers: { | ||
| // This looks wrong but is intentional. We want to make "Simple CORS | ||
| // requests" eg. requests without the OPTIONS preflight and | ||
| // application/json is not allowed for those. So we just have to use | ||
| // one of the allowed ones. | ||
| // See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
| "Content-Type": "text/plain", | ||
| }, | ||
| body: JSON.stringify(requestBody), | ||
| headers, | ||
| body: JSON.stringify({ | ||
| q: options.q, | ||
| groups: options.groups, | ||
| }), | ||
| }; | ||
| if (token && fetchOptions.headers) { | ||
| fetchOptions.headers["authorization"] = "Bearer " + token.jwt; | ||
| const endpoint = new URL(searchEndpointString); | ||
| if (token) { | ||
| if (typeof token.jwt !== "string") { | ||
| throw new Error("[findkit] Expected GetJwtToken response to contain a 'jwt' property"); | ||
| } | ||
| endpoint.searchParams.set("p", `jwt:${token.jwt}`); | ||
| } | ||
| const res = await fetch(fetchUrl, fetchOptions); | ||
| const res = await fetch(endpoint.toString(), fetchOptions); | ||
| if (res.status === 403) { | ||
| const error = await res.json(); | ||
| if (error.error.type === "jwt-expired") { | ||
| const error = await safeErrorJson(res); | ||
| if (error.code === "jwt-expired") { | ||
| refresh(); | ||
| return findkitFetch(options); | ||
| } | ||
| throw new Error("[findkit] Permission denied: " + error.error.type); | ||
| throw new Error("[findkit] Permission denied: " + error.message); | ||
| } | ||
| if (!res.ok) { | ||
| throw new Error("[findkit] Bad response from search: " + res.status); | ||
| const error = await safeErrorJson(res); | ||
| throw new Error(`[findkit] Bad response ${res.status} from search: ${error.message}`); | ||
| } | ||
| const responses = await res.json(); | ||
| if (options.logResponseTimes || logResponseTimes) { | ||
| if ((init === null || init === void 0 ? void 0 : init.logResponseTimes) || logResponseTimes) { | ||
| const total = Date.now() - started; | ||
| const backendDuration = responses.duration; | ||
| console.log(`[findkit] Response total ${total}ms, backend ${backendDuration}ms, network ${total - backendDuration}ms`); | ||
| options.groups.forEach((group, index) => { | ||
| const duration = responses.groups[index]?.duration ?? 0; | ||
| (_b = options.groups) === null || _b === void 0 ? void 0 : _b.forEach((group, index) => { | ||
| var _a, _b; | ||
| const duration = (_b = (_a = responses.groups[index]) === null || _a === void 0 ? void 0 : _a.duration) !== null && _b !== void 0 ? _b : 0; | ||
| console.log(`[findkit] Group response ${duration}ms for group "${index}"`, group); | ||
@@ -81,25 +130,22 @@ }); | ||
| } | ||
| export function getRequestBody(options) { | ||
| return { | ||
| q: options.q, | ||
| groups: options.groups, | ||
| }; | ||
| } | ||
| function getSearchEndpoint(options) { | ||
| if (options.searchEndpoint) { | ||
| return options.searchEndpoint; | ||
| exports.createFindkitFetcher = createFindkitFetcher; | ||
| function inferSearchEndpoint(options) { | ||
| if (options === null || options === void 0 ? void 0 : options.searchEndpoint) { | ||
| return options === null || options === void 0 ? void 0 : options.searchEndpoint; | ||
| } | ||
| else if (options.publicToken) { | ||
| return getProjectSearchEndpoint(options.publicToken); | ||
| else if (options === null || options === void 0 ? void 0 : options.publicToken) { | ||
| return createSearchEndpoint(options.publicToken); | ||
| } | ||
| else { | ||
| throw new Error("Unable to determine search endpoint"); | ||
| const msg = "[findkit] Unable to determine search endpoint"; | ||
| console.error(`${msg}. The options object must contain either a 'publicToken' or a 'searchEndpoint' property`, options); | ||
| throw new Error(`${msg}. See logs for details`); | ||
| } | ||
| } | ||
| export function getProjectSearchEndpoint(publicToken) { | ||
| return `https://search.findkit.com/c/${publicToken}/search?p=${publicToken}`; | ||
| } | ||
| /** | ||
| * @public | ||
| */ | ||
| export const findkitFetch = createFindkitFetcher().findkitFetch; | ||
| function createSearchEndpoint(publicToken) { | ||
| return `https://search.findkit.com/c/${publicToken}/search?p=${publicToken}`; | ||
| } | ||
| exports.createSearchEndpoint = createSearchEndpoint; |
+3
-3
| { | ||
| "name": "@findkit/fetch", | ||
| "version": "2.0.0-dev.6f4e684575", | ||
| "version": "2.0.0-dev.6f6a1c6044", | ||
| "description": "minimal fetch for findkit", | ||
| "main": "dist/index.js", | ||
| "author": "Valu Digital Oy", | ||
| "license": "ISC", | ||
| "license": "MIT", | ||
| "devDependencies": { | ||
| "@types/jsonwebtoken": "^8.5.8", | ||
| "@valu/npm-tools": "^1.1.2", | ||
| "@valu/npm-tools": "^1.5.0", | ||
| "eslint": "8.21.0", | ||
@@ -12,0 +12,0 @@ "eslint-plugin-react": "^7.28.0", |
+121
-68
| /** | ||
| * @public | ||
| */ | ||
| export interface FindKitDeveloperOptions { | ||
| export interface FindkitFetchInit { | ||
| staging?: boolean; | ||
| logResponseTimes?: boolean; | ||
| publicToken?: string; | ||
| searchEndpoint?: string; | ||
| getJwtToken?: GetJwtToken; | ||
| } | ||
@@ -30,7 +33,4 @@ | ||
| */ | ||
| export interface FindkitFetchOptions | ||
| extends FindkitSearchParams, | ||
| FindKitDeveloperOptions { | ||
| publicToken?: string; | ||
| searchEndpoint?: string; | ||
| export interface FindkitFetch { | ||
| (options: FindkitSearchParams): Promise<FindkitSearchResponse>; | ||
| } | ||
@@ -41,13 +41,22 @@ | ||
| */ | ||
| export interface FindkitFetch { | ||
| (options: FindkitFetchOptions): Promise<FindkitSearchResponse>; | ||
| export interface FindkitErrorResponse { | ||
| code?: "jwt-expired" | "jwt-invalid" | "invalid-response-body"; | ||
| message?: string; | ||
| } | ||
| /** | ||
| * @public | ||
| */ | ||
| export interface JwtErrorResponse { | ||
| error: { | ||
| type?: "jwt-expired" | "jwt-invalid"; | ||
| }; | ||
| async function safeErrorJson( | ||
| response: Response | ||
| ): Promise<FindkitErrorResponse> { | ||
| const text = await response.text().catch(() => { | ||
| return "Failed to read response body"; | ||
| }); | ||
| try { | ||
| return JSON.parse(text); | ||
| } catch { | ||
| return { | ||
| code: "invalid-response-body", | ||
| message: `body: ${text.slice(0, 500)}`, | ||
| }; | ||
| } | ||
| } | ||
@@ -59,3 +68,6 @@ | ||
| export interface GetJwtToken { | ||
| (): Promise<JwtToken>; | ||
| (options: { | ||
| publicToken?: string; | ||
| searchEndpoint: string; | ||
| }): Promise<JwtToken>; | ||
| } | ||
@@ -77,10 +89,20 @@ | ||
| if (typeof window !== "undefined") { | ||
| logResponseTimes = | ||
| window.localStorage.getItem("findkit-log-response-times") === "true"; | ||
| try { | ||
| logResponseTimes = | ||
| window.localStorage.getItem("findkit-log-response-times") === "true"; | ||
| } catch { | ||
| // local storage access can throw in some enviroments such a | ||
| // codesandox.io due to permission issues | ||
| } | ||
| } | ||
| /** | ||
| * Global JWT token fethcer | ||
| */ | ||
| declare const FINDKIT_GET_JWT_TOKEN: GetJwtToken | undefined; | ||
| /** | ||
| * @public | ||
| */ | ||
| export function createFindkitFetcher(props?: { getJwtToken?: GetJwtToken }) { | ||
| export function createFindkitFetcher(init?: FindkitFetchInit) { | ||
| let currentJwtTokenPromise: Promise<JwtToken> | null = null; | ||
@@ -95,13 +117,29 @@ | ||
| function refresh() { | ||
| if (typeof props?.getJwtToken === "function") { | ||
| currentJwtTokenPromise = props.getJwtToken(); | ||
| const searchEndpointString = inferSearchEndpoint(init); | ||
| const refresh = () => { | ||
| const getJwtTokenArg: Parameters<GetJwtToken>[0] = { | ||
| searchEndpoint: searchEndpointString, | ||
| }; | ||
| if (typeof init?.getJwtToken === "function") { | ||
| currentJwtTokenPromise = init.getJwtToken(getJwtTokenArg); | ||
| } else if (typeof FINDKIT_GET_JWT_TOKEN === "function") { | ||
| currentJwtTokenPromise = FINDKIT_GET_JWT_TOKEN(getJwtTokenArg); | ||
| } | ||
| } | ||
| }; | ||
| const findkitFetch: FindkitFetch = async (options: FindkitFetchOptions) => { | ||
| // new implementation | ||
| const fetchUrl = getSearchEndpoint(options); | ||
| const findkitFetch: FindkitFetch = async (options: FindkitSearchParams) => { | ||
| if (!options.groups) { | ||
| options = { | ||
| ...options, | ||
| groups: [ | ||
| { | ||
| tagQuery: [], | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| const started = Date.now(); | ||
| const requestBody = getRequestBody(options); | ||
@@ -114,27 +152,40 @@ if (!currentJwtTokenPromise) { | ||
| const fetchOptions: PostRequestInit = { | ||
| const headers: Record<string, string> = { | ||
| // This looks wrong but is intentional. We want to make "Simple CORS | ||
| // requests" eg. requests without the OPTIONS preflight and | ||
| // application/json is not allowed for those. So we just have to use | ||
| // one of the allowed ones. | ||
| // See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
| "Content-Type": "text/plain", | ||
| }; | ||
| const fetchOptions: RequestInit = { | ||
| method: "POST", | ||
| signal: options.signal ?? null, | ||
| mode: "cors", | ||
| credentials: "omit", | ||
| headers: { | ||
| // This looks wrong but is intentional. We want to make "Simple CORS | ||
| // requests" eg. requests without the OPTIONS preflight and | ||
| // application/json is not allowed for those. So we just have to use | ||
| // one of the allowed ones. | ||
| // See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
| "Content-Type": "text/plain", | ||
| }, | ||
| body: JSON.stringify(requestBody), | ||
| headers, | ||
| body: JSON.stringify({ | ||
| q: options.q, | ||
| groups: options.groups, | ||
| }), | ||
| }; | ||
| if (token && fetchOptions.headers) { | ||
| fetchOptions.headers["authorization"] = "Bearer " + token.jwt; | ||
| const endpoint = new URL(searchEndpointString); | ||
| if (token) { | ||
| if (typeof token.jwt !== "string") { | ||
| throw new Error( | ||
| "[findkit] Expected GetJwtToken response to contain a 'jwt' property" | ||
| ); | ||
| } | ||
| endpoint.searchParams.set("p", `jwt:${token.jwt}`); | ||
| } | ||
| const res = await fetch(fetchUrl, fetchOptions); | ||
| const res = await fetch(endpoint.toString(), fetchOptions); | ||
| if (res.status === 403) { | ||
| const error: JwtErrorResponse = await res.json(); | ||
| const error = await safeErrorJson(res); | ||
| if (error.error.type === "jwt-expired") { | ||
| if (error.code === "jwt-expired") { | ||
| refresh(); | ||
@@ -144,7 +195,10 @@ return findkitFetch(options); | ||
| throw new Error("[findkit] Permission denied: " + error.error.type); | ||
| throw new Error("[findkit] Permission denied: " + error.message); | ||
| } | ||
| if (!res.ok) { | ||
| throw new Error("[findkit] Bad response from search: " + res.status); | ||
| const error = await safeErrorJson(res); | ||
| throw new Error( | ||
| `[findkit] Bad response ${res.status} from search: ${error.message}` | ||
| ); | ||
| } | ||
@@ -154,3 +208,3 @@ | ||
| if (options.logResponseTimes || logResponseTimes) { | ||
| if (init?.logResponseTimes || logResponseTimes) { | ||
| const total = Date.now() - started; | ||
@@ -165,3 +219,3 @@ const backendDuration = responses.duration; | ||
| options.groups.forEach((group, index) => { | ||
| options.groups?.forEach((group, index) => { | ||
| const duration = responses.groups[index]?.duration ?? 0; | ||
@@ -185,29 +239,23 @@ console.log( | ||
| export function getRequestBody( | ||
| options: FindkitFetchOptions | ||
| ): FindkitSearchParams { | ||
| return { | ||
| q: options.q, | ||
| groups: options.groups, | ||
| }; | ||
| } | ||
| function getSearchEndpoint(options: FindkitFetchOptions) { | ||
| if (options.searchEndpoint) { | ||
| return options.searchEndpoint; | ||
| } else if (options.publicToken) { | ||
| return getProjectSearchEndpoint(options.publicToken); | ||
| function inferSearchEndpoint(options?: FindkitFetchInit) { | ||
| if (options?.searchEndpoint) { | ||
| return options?.searchEndpoint; | ||
| } else if (options?.publicToken) { | ||
| return createSearchEndpoint(options.publicToken); | ||
| } else { | ||
| throw new Error("Unable to determine search endpoint"); | ||
| const msg = "[findkit] Unable to determine search endpoint"; | ||
| console.error( | ||
| `${msg}. The options object must contain either a 'publicToken' or a 'searchEndpoint' property`, | ||
| options | ||
| ); | ||
| throw new Error(`${msg}. See logs for details`); | ||
| } | ||
| } | ||
| export function getProjectSearchEndpoint(publicToken: string) { | ||
| return `https://search.findkit.com/c/${publicToken}/search?p=${publicToken}`; | ||
| } | ||
| /** | ||
| * @public | ||
| */ | ||
| export const findkitFetch: FindkitFetch = createFindkitFetcher().findkitFetch; | ||
| export function createSearchEndpoint(publicToken: string) { | ||
| return `https://search.findkit.com/c/${publicToken}/search?p=${publicToken}`; | ||
| } | ||
@@ -219,3 +267,4 @@ /** | ||
| q: string; | ||
| groups: FindkitSearchGroupParams[]; | ||
| groups?: FindkitSearchGroupParams[]; | ||
| signal?: AbortSignal; | ||
| } | ||
@@ -241,4 +290,8 @@ | ||
| export type FindkitSearchResponse = { | ||
| duration: number; | ||
| groups: GroupSearchResults[]; | ||
| duration: number; | ||
| messages?: { | ||
| code: "string"; | ||
| message: string; | ||
| }[]; | ||
| }; | ||
@@ -245,0 +298,0 @@ |
+12
-10
| { | ||
| "compilerOptions": { | ||
| "target": "es2020", | ||
| "strict": true, | ||
| "moduleResolution": "node", | ||
| "noUncheckedIndexedAccess": true, | ||
| "declaration": true, | ||
| "declarationDir": "./dist", | ||
| "skipLibCheck": true, | ||
| "outDir": "dist" | ||
| } | ||
| "compilerOptions": { | ||
| "target": "es2019", | ||
| "strict": true, | ||
| "module": "commonjs", | ||
| "moduleResolution": "node", | ||
| "noUncheckedIndexedAccess": true, | ||
| "declaration": true, | ||
| "declarationDir": "./dist", | ||
| "declarationMap": true, | ||
| "skipLibCheck": true, | ||
| "outDir": "dist" | ||
| } | ||
| } |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
43203
34.48%18
28.57%1192
5.11%15
-6.25%