@lightsparkdev/core 1.4.3 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +0 -2
- package/dist/{chunk-36QHRQJC.js → chunk-ADHQHZNM.js} +11 -9
- package/dist/chunk-VY7NND44.js +713 -0
- package/dist/{index-gUXiTlhb.d.cts → index-CFQtMxrx.d.cts} +2 -1
- package/dist/{index-gUXiTlhb.d.ts → index-CFQtMxrx.d.ts} +2 -1
- package/dist/index.cjs +2585 -2573
- package/dist/index.d.cts +8 -171
- package/dist/index.d.ts +8 -171
- package/dist/index.js +25 -679
- package/dist/react-native/index.cjs +3111 -0
- package/dist/react-native/index.d.cts +175 -0
- package/dist/react-native/index.d.ts +175 -0
- package/dist/react-native/index.js +154 -0
- package/dist/utils/index.cjs +3 -0
- package/dist/utils/index.d.cts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +3 -1
- package/package.json +15 -1
- package/src/Logger.ts +2 -1
- package/src/crypto/SigningKey.ts +4 -2
- package/src/index.ts +3 -11
- package/src/react-native/index.ts +2 -0
- package/src/requester/DefaultRequester.ts +46 -0
- package/src/requester/Requester.ts +11 -44
- package/src/requester/tests/DefaultRequester.test.ts +129 -0
- package/src/requester/tests/Requester.test.ts +23 -32
- package/src/shared.ts +10 -0
- package/src/utils/environment.ts +3 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type AuthProvider from "../auth/AuthProvider.js";
|
|
2
|
+
import { isBare, isNode } from "../utils/environment.js";
|
|
3
|
+
import Requester from "./Requester.js";
|
|
4
|
+
|
|
5
|
+
export class DefaultRequester extends Requester {
|
|
6
|
+
protected async initWsClient(baseUrl: string, authProvider: AuthProvider) {
|
|
7
|
+
if (!this.resolveWsClient) {
|
|
8
|
+
/* If resolveWsClient is null assume already initialized: */
|
|
9
|
+
return this.wsClient;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (isBare) {
|
|
13
|
+
/* graphql-ws library is currently not supported in Bare environment, see LIG-7942 */
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let websocketImpl;
|
|
18
|
+
if (isNode && typeof WebSocket === "undefined") {
|
|
19
|
+
const wsModule = await import("ws");
|
|
20
|
+
websocketImpl = wsModule.default;
|
|
21
|
+
}
|
|
22
|
+
let websocketProtocol = "wss";
|
|
23
|
+
if (baseUrl.startsWith("http://")) {
|
|
24
|
+
websocketProtocol = "ws";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const graphqlWsModule = await import("graphql-ws");
|
|
28
|
+
const { createClient } = graphqlWsModule;
|
|
29
|
+
|
|
30
|
+
const wsClient = createClient({
|
|
31
|
+
url: `${websocketProtocol}://${this.stripProtocol(this.baseUrl)}/${
|
|
32
|
+
this.schemaEndpoint
|
|
33
|
+
}`,
|
|
34
|
+
connectionParams: () => authProvider.addWsConnectionParams({}),
|
|
35
|
+
webSocketImpl: websocketImpl,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (this.resolveWsClient) {
|
|
39
|
+
this.resolveWsClient(wsClient);
|
|
40
|
+
this.resolveWsClient = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return wsClient;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export default DefaultRequester;
|
|
@@ -17,7 +17,7 @@ import type { SigningKey } from "../crypto/SigningKey.js";
|
|
|
17
17
|
import LightsparkException from "../LightsparkException.js";
|
|
18
18
|
import { logger } from "../Logger.js";
|
|
19
19
|
import { b64encode } from "../utils/base64.js";
|
|
20
|
-
import {
|
|
20
|
+
import { isNode } from "../utils/environment.js";
|
|
21
21
|
|
|
22
22
|
const DEFAULT_BASE_URL = "api.lightspark.com";
|
|
23
23
|
dayjs.extend(utc);
|
|
@@ -31,14 +31,14 @@ type BodyData = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
class Requester {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
protected wsClient: Promise<WsClient | null>;
|
|
35
|
+
protected resolveWsClient: ((value: WsClient | null) => void) | null = null;
|
|
36
36
|
constructor(
|
|
37
37
|
private readonly nodeKeyCache: NodeKeyCache,
|
|
38
|
-
|
|
38
|
+
protected readonly schemaEndpoint: string,
|
|
39
39
|
private readonly sdkUserAgent: string,
|
|
40
40
|
private readonly authProvider: AuthProvider = new StubAuthProvider(),
|
|
41
|
-
|
|
41
|
+
protected readonly baseUrl: string = DEFAULT_BASE_URL,
|
|
42
42
|
private readonly cryptoImpl: CryptoInterface = DefaultCrypto,
|
|
43
43
|
private readonly signingKey?: SigningKey,
|
|
44
44
|
private readonly fetchImpl: typeof fetch = fetch,
|
|
@@ -50,44 +50,11 @@ class Requester {
|
|
|
50
50
|
autoBind(this);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (isBare) {
|
|
60
|
-
/* graphql-ws library is currently not supported in Bare environment, see LIG-7942 */
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let websocketImpl;
|
|
65
|
-
if (isNode && typeof WebSocket === "undefined") {
|
|
66
|
-
const wsModule = await import("ws");
|
|
67
|
-
websocketImpl = wsModule.default;
|
|
68
|
-
}
|
|
69
|
-
let websocketProtocol = "wss";
|
|
70
|
-
if (baseUrl.startsWith("http://")) {
|
|
71
|
-
websocketProtocol = "ws";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const graphqlWsModule = await import("graphql-ws");
|
|
75
|
-
const { createClient } = graphqlWsModule;
|
|
76
|
-
|
|
77
|
-
const wsClient = createClient({
|
|
78
|
-
url: `${websocketProtocol}://${this.stripProtocol(this.baseUrl)}/${
|
|
79
|
-
this.schemaEndpoint
|
|
80
|
-
}`,
|
|
81
|
-
connectionParams: () => authProvider.addWsConnectionParams({}),
|
|
82
|
-
webSocketImpl: websocketImpl,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (this.resolveWsClient) {
|
|
86
|
-
this.resolveWsClient(wsClient);
|
|
87
|
-
this.resolveWsClient = null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return wsClient;
|
|
53
|
+
protected initWsClient(
|
|
54
|
+
baseUrl: string,
|
|
55
|
+
authProvider: AuthProvider,
|
|
56
|
+
): Promise<WsClient | null> {
|
|
57
|
+
return Promise.resolve(null);
|
|
91
58
|
}
|
|
92
59
|
|
|
93
60
|
public async executeQuery<T>(query: Query<T>): Promise<T | null> {
|
|
@@ -293,7 +260,7 @@ class Requester {
|
|
|
293
260
|
return `${this.sdkUserAgent} ${platform}/${platformVersion}`;
|
|
294
261
|
}
|
|
295
262
|
|
|
296
|
-
|
|
263
|
+
protected stripProtocol(url: string): string {
|
|
297
264
|
return url.replace(/.*?:\/\//g, "");
|
|
298
265
|
}
|
|
299
266
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { beforeEach, jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
import type { Client as WsClient } from "graphql-ws";
|
|
4
|
+
import type AuthProvider from "../../auth/AuthProvider.js";
|
|
5
|
+
import type { CryptoInterface } from "../../crypto/crypto.js";
|
|
6
|
+
import type NodeKeyCache from "../../crypto/NodeKeyCache.js";
|
|
7
|
+
import type { SigningKey } from "../../crypto/SigningKey.js";
|
|
8
|
+
import { SigningKeyType } from "../../crypto/types.js";
|
|
9
|
+
|
|
10
|
+
/* Mocking ESM modules (when running node with --experimental-vm-modules)
|
|
11
|
+
requires unstable_mockModule, see https://bit.ly/433nRV1 */
|
|
12
|
+
await jest.unstable_mockModule("graphql-ws", () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
createClient: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
/* Since Requester uses graphql-ws we need a dynamic import after the above mock */
|
|
17
|
+
const { DefaultRequester } = await import("../DefaultRequester.js");
|
|
18
|
+
|
|
19
|
+
describe("DefaultRequester", () => {
|
|
20
|
+
const schemaEndpoint = "graphql";
|
|
21
|
+
const sdkUserAgent = "test-agent";
|
|
22
|
+
const baseUrl = "https://api.example.com";
|
|
23
|
+
|
|
24
|
+
let nodeKeyCache: NodeKeyCache;
|
|
25
|
+
let authProvider: AuthProvider;
|
|
26
|
+
let signingKey: SigningKey;
|
|
27
|
+
let cryptoImpl: CryptoInterface;
|
|
28
|
+
let fetchImpl: typeof fetch;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
nodeKeyCache = {
|
|
32
|
+
getKey: jest.fn(),
|
|
33
|
+
hasKey: jest.fn(),
|
|
34
|
+
} as unknown as NodeKeyCache;
|
|
35
|
+
|
|
36
|
+
authProvider = {
|
|
37
|
+
addAuthHeaders: jest.fn(async (headers: Record<string, string>) => ({
|
|
38
|
+
...headers,
|
|
39
|
+
"X-Test": "1",
|
|
40
|
+
})),
|
|
41
|
+
isAuthorized: jest.fn(async () => true),
|
|
42
|
+
addWsConnectionParams: jest.fn(
|
|
43
|
+
async (params: Record<string, unknown>) => ({
|
|
44
|
+
...params,
|
|
45
|
+
ws: true,
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
} satisfies AuthProvider;
|
|
49
|
+
|
|
50
|
+
signingKey = {
|
|
51
|
+
type: SigningKeyType.RSASigningKey,
|
|
52
|
+
sign: jest.fn(async (data: Uint8Array) => new Uint8Array([1, 2, 3])),
|
|
53
|
+
} satisfies SigningKey;
|
|
54
|
+
|
|
55
|
+
cryptoImpl = {
|
|
56
|
+
decryptSecretWithNodePassword: jest.fn(async () => new ArrayBuffer(0)),
|
|
57
|
+
generateSigningKeyPair: jest.fn(async () => ({
|
|
58
|
+
publicKey: "",
|
|
59
|
+
privateKey: "",
|
|
60
|
+
})),
|
|
61
|
+
serializeSigningKey: jest.fn(async () => new ArrayBuffer(0)),
|
|
62
|
+
getNonce: jest.fn(async () => 123),
|
|
63
|
+
sign: jest.fn(async () => new ArrayBuffer(0)),
|
|
64
|
+
importPrivateSigningKey: jest.fn(async () => ""),
|
|
65
|
+
} satisfies CryptoInterface;
|
|
66
|
+
|
|
67
|
+
fetchImpl = jest.fn(
|
|
68
|
+
async () =>
|
|
69
|
+
({
|
|
70
|
+
ok: true,
|
|
71
|
+
json: async () => ({ data: { foo: "bar" }, errors: undefined }),
|
|
72
|
+
statusText: "OK",
|
|
73
|
+
}) as Response,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("subscribe", () => {
|
|
78
|
+
it("returns an Observable for a valid subscription", async () => {
|
|
79
|
+
// Mock wsClient and its subscribe method
|
|
80
|
+
const wsClient = {
|
|
81
|
+
subscribe: jest.fn(
|
|
82
|
+
(
|
|
83
|
+
_body,
|
|
84
|
+
handlers: { next?: (data: unknown) => void; complete?: () => void },
|
|
85
|
+
) => {
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
handlers.next?.({ data: { foo: "bar" } });
|
|
88
|
+
handlers.complete?.();
|
|
89
|
+
}, 10);
|
|
90
|
+
return jest.fn();
|
|
91
|
+
},
|
|
92
|
+
),
|
|
93
|
+
} as unknown as WsClient;
|
|
94
|
+
|
|
95
|
+
const { createClient } = await import("graphql-ws");
|
|
96
|
+
(createClient as jest.Mock).mockReturnValue(wsClient);
|
|
97
|
+
|
|
98
|
+
const requester = new DefaultRequester(
|
|
99
|
+
nodeKeyCache,
|
|
100
|
+
schemaEndpoint,
|
|
101
|
+
sdkUserAgent,
|
|
102
|
+
authProvider,
|
|
103
|
+
baseUrl,
|
|
104
|
+
cryptoImpl,
|
|
105
|
+
signingKey,
|
|
106
|
+
fetchImpl,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const observable = requester.subscribe<{ foo: string }>(
|
|
110
|
+
"subscription TestSub { foo }",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const results: { foo: string }[] = [];
|
|
114
|
+
await new Promise<void>((resolve) => {
|
|
115
|
+
observable.subscribe({
|
|
116
|
+
next: (data: { data: { foo: string } }) => {
|
|
117
|
+
results.push(data.data);
|
|
118
|
+
},
|
|
119
|
+
complete: () => {
|
|
120
|
+
expect(results).toEqual([{ foo: "bar" }]);
|
|
121
|
+
resolve();
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(wsClient.subscribe).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { beforeEach, jest } from "@jest/globals";
|
|
2
2
|
|
|
3
|
-
import type { Client as WsClient } from "graphql-ws";
|
|
4
3
|
import type AuthProvider from "../../auth/AuthProvider.js";
|
|
5
4
|
import type { CryptoInterface } from "../../crypto/crypto.js";
|
|
6
5
|
import type NodeKeyCache from "../../crypto/NodeKeyCache.js";
|
|
@@ -16,7 +15,7 @@ await jest.unstable_mockModule("graphql-ws", () => ({
|
|
|
16
15
|
createClient: jest.fn(),
|
|
17
16
|
}));
|
|
18
17
|
/* Since Requester uses graphql-ws we need a dynamic import after the above mock */
|
|
19
|
-
const { Requester } = await import("../
|
|
18
|
+
const { default: Requester } = await import("../Requester.js");
|
|
20
19
|
|
|
21
20
|
describe("Requester", () => {
|
|
22
21
|
const schemaEndpoint = "graphql";
|
|
@@ -257,26 +256,7 @@ describe("Requester", () => {
|
|
|
257
256
|
expect(() => requester.subscribe("invalid")).toThrow(LightsparkException);
|
|
258
257
|
});
|
|
259
258
|
|
|
260
|
-
it("
|
|
261
|
-
// Mock wsClient and its subscribe method
|
|
262
|
-
const wsClient = {
|
|
263
|
-
subscribe: jest.fn(
|
|
264
|
-
(
|
|
265
|
-
_body,
|
|
266
|
-
handlers: { next?: (data: unknown) => void; complete?: () => void },
|
|
267
|
-
) => {
|
|
268
|
-
setTimeout(() => {
|
|
269
|
-
handlers.next?.({ data: { foo: "bar" } });
|
|
270
|
-
handlers.complete?.();
|
|
271
|
-
}, 10);
|
|
272
|
-
return jest.fn();
|
|
273
|
-
},
|
|
274
|
-
),
|
|
275
|
-
} as unknown as WsClient;
|
|
276
|
-
|
|
277
|
-
const { createClient } = await import("graphql-ws");
|
|
278
|
-
(createClient as jest.Mock).mockReturnValue(wsClient);
|
|
279
|
-
|
|
259
|
+
it("emits error when wsClient is not initialized", async () => {
|
|
280
260
|
const requester = new Requester(
|
|
281
261
|
nodeKeyCache,
|
|
282
262
|
schemaEndpoint,
|
|
@@ -287,25 +267,36 @@ describe("Requester", () => {
|
|
|
287
267
|
signingKey,
|
|
288
268
|
fetchImpl,
|
|
289
269
|
);
|
|
270
|
+
// Resolve internal wsClient promise to null so the observable emits an error.
|
|
271
|
+
(
|
|
272
|
+
requester as unknown as {
|
|
273
|
+
resolveWsClient: ((v: unknown) => void) | null;
|
|
274
|
+
}
|
|
275
|
+
).resolveWsClient?.(null);
|
|
290
276
|
|
|
291
|
-
const observable = requester.subscribe
|
|
292
|
-
"subscription TestSub { foo }",
|
|
293
|
-
);
|
|
277
|
+
const observable = requester.subscribe("subscription TestSub { foo }");
|
|
294
278
|
|
|
295
|
-
const results: { foo: string }[] = [];
|
|
296
279
|
await new Promise<void>((resolve) => {
|
|
297
280
|
observable.subscribe({
|
|
298
|
-
next: (
|
|
299
|
-
|
|
281
|
+
next: () => {
|
|
282
|
+
throw new Error(
|
|
283
|
+
"Should not emit next when wsClient is uninitialized",
|
|
284
|
+
);
|
|
300
285
|
},
|
|
301
|
-
|
|
302
|
-
expect(
|
|
286
|
+
error: (err) => {
|
|
287
|
+
expect(err).toBeInstanceOf(LightsparkException);
|
|
288
|
+
expect(String((err as Error).message)).toMatch(
|
|
289
|
+
/WebSocket client is not initialized/,
|
|
290
|
+
);
|
|
303
291
|
resolve();
|
|
304
292
|
},
|
|
293
|
+
complete: () => {
|
|
294
|
+
throw new Error(
|
|
295
|
+
"Should not complete when wsClient is uninitialized",
|
|
296
|
+
);
|
|
297
|
+
},
|
|
305
298
|
});
|
|
306
299
|
});
|
|
307
|
-
|
|
308
|
-
expect(wsClient.subscribe).toHaveBeenCalled();
|
|
309
300
|
});
|
|
310
301
|
});
|
|
311
302
|
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./auth/index.js";
|
|
2
|
+
export * from "./constants/index.js";
|
|
3
|
+
export * from "./crypto/index.js";
|
|
4
|
+
export { default as LightsparkException } from "./LightsparkException.js";
|
|
5
|
+
export { Logger, LoggingLevel, logger } from "./Logger.js";
|
|
6
|
+
export {
|
|
7
|
+
default as ServerEnvironment,
|
|
8
|
+
apiDomainForEnvironment,
|
|
9
|
+
} from "./ServerEnvironment.js";
|
|
10
|
+
export * from "./utils/index.js";
|
package/src/utils/environment.ts
CHANGED
|
@@ -14,3 +14,6 @@ export const isTest = isNode && process.env.NODE_ENV === "test";
|
|
|
14
14
|
|
|
15
15
|
/* https://github.com/holepunchto/which-runtime/blob/main/index.js */
|
|
16
16
|
export const isBare = typeof Bare !== "undefined";
|
|
17
|
+
|
|
18
|
+
export const isReactNative =
|
|
19
|
+
typeof navigator !== "undefined" && navigator.product === "ReactNative";
|