@lightsparkdev/core 1.3.1 → 1.4.1
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 +26 -0
- package/dist/{chunk-23SS5EX2.js → chunk-NV53XYAS.js} +64 -24
- package/dist/{index-C7dqDM91.d.cts → index-DZbfPQqd.d.cts} +5 -5
- package/dist/{index-C7dqDM91.d.ts → index-DZbfPQqd.d.ts} +5 -5
- package/dist/index.cjs +104 -32
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +44 -12
- package/dist/utils/index.cjs +61 -21
- package/dist/utils/index.d.cts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +3 -3
- package/src/requester/Requester.ts +52 -11
- package/src/requester/tests/Requester.test.ts +347 -0
- package/src/utils/errors.ts +49 -7
- package/src/utils/localStorage.ts +19 -3
- package/src/utils/typeGuards.ts +10 -1
- package/src/utils/types.ts +0 -4
package/dist/utils/index.cjs
CHANGED
|
@@ -62,7 +62,7 @@ __export(utils_exports, {
|
|
|
62
62
|
isErrorWithMessage: () => isErrorWithMessage,
|
|
63
63
|
isNode: () => isNode,
|
|
64
64
|
isNumber: () => isNumber,
|
|
65
|
-
isObject: () =>
|
|
65
|
+
isObject: () => isObject,
|
|
66
66
|
isRecord: () => isRecord,
|
|
67
67
|
isSDKCurrencyAmount: () => isSDKCurrencyAmount,
|
|
68
68
|
isTest: () => isTest,
|
|
@@ -974,6 +974,18 @@ function localeToCurrencySymbol(locale) {
|
|
|
974
974
|
return symbol;
|
|
975
975
|
}
|
|
976
976
|
|
|
977
|
+
// src/utils/typeGuards.ts
|
|
978
|
+
function isUint8Array(value) {
|
|
979
|
+
return value instanceof Uint8Array;
|
|
980
|
+
}
|
|
981
|
+
function isObject(value) {
|
|
982
|
+
const type = typeof value;
|
|
983
|
+
return value != null && (type == "object" || type == "function");
|
|
984
|
+
}
|
|
985
|
+
function isRecord(value) {
|
|
986
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
|
|
987
|
+
}
|
|
988
|
+
|
|
977
989
|
// src/utils/errors.ts
|
|
978
990
|
var isError = (e) => {
|
|
979
991
|
return Boolean(
|
|
@@ -994,31 +1006,71 @@ var isErrorMsg = (e, msg) => {
|
|
|
994
1006
|
}
|
|
995
1007
|
return false;
|
|
996
1008
|
};
|
|
997
|
-
function
|
|
1009
|
+
function normalizeObject(obj) {
|
|
1010
|
+
const normalized = {};
|
|
1011
|
+
if (isObject(obj)) {
|
|
1012
|
+
const props = Object.getOwnPropertyNames(obj);
|
|
1013
|
+
for (const prop of props) {
|
|
1014
|
+
const objRecord = obj;
|
|
1015
|
+
normalized[prop] = objRecord[prop];
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return normalized;
|
|
1019
|
+
}
|
|
1020
|
+
function errorToJSON(err, stringifyObjects = false) {
|
|
998
1021
|
if (!err) {
|
|
999
1022
|
return null;
|
|
1000
1023
|
}
|
|
1001
|
-
if (
|
|
1024
|
+
if (isObject(err) && "toJSON" in err && typeof err.toJSON === "function") {
|
|
1025
|
+
if (stringifyObjects === true) {
|
|
1026
|
+
return objectToJSON(err.toJSON());
|
|
1027
|
+
}
|
|
1002
1028
|
return err.toJSON();
|
|
1003
1029
|
}
|
|
1004
1030
|
if (typeof err === "object" && /* This happens for certain errors like DOMException: */
|
|
1005
1031
|
Object.getOwnPropertyNames(err).length === 0 && "message" in err && typeof err.message === "string") {
|
|
1006
1032
|
return { message: err.message };
|
|
1007
1033
|
}
|
|
1034
|
+
return objectToJSON(err);
|
|
1035
|
+
}
|
|
1036
|
+
function objectToJSON(obj) {
|
|
1037
|
+
const normalizedObj = normalizeObject(obj);
|
|
1008
1038
|
return JSON.parse(
|
|
1009
|
-
JSON.stringify(
|
|
1039
|
+
JSON.stringify(normalizedObj, (key, value) => {
|
|
1040
|
+
if (key === "") {
|
|
1041
|
+
return value;
|
|
1042
|
+
}
|
|
1043
|
+
const objProps = Object.getOwnPropertyNames(normalizedObj);
|
|
1044
|
+
if (!objProps.includes(key)) {
|
|
1045
|
+
return void 0;
|
|
1046
|
+
}
|
|
1047
|
+
if (isObject(value)) {
|
|
1048
|
+
return JSON.stringify(value);
|
|
1049
|
+
}
|
|
1050
|
+
return value;
|
|
1051
|
+
})
|
|
1010
1052
|
);
|
|
1011
1053
|
}
|
|
1012
1054
|
|
|
1013
1055
|
// src/utils/localStorage.ts
|
|
1014
1056
|
function getLocalStorageConfigItem(key) {
|
|
1015
|
-
|
|
1057
|
+
const localStorageBoolean = getLocalStorageBoolean(key);
|
|
1058
|
+
if (localStorageBoolean == null) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
return localStorageBoolean;
|
|
1016
1062
|
}
|
|
1017
1063
|
function getLocalStorageBoolean(key) {
|
|
1018
1064
|
try {
|
|
1019
|
-
|
|
1065
|
+
if (localStorage.getItem(key) === "1") {
|
|
1066
|
+
return true;
|
|
1067
|
+
} else if (localStorage.getItem(key) == null) {
|
|
1068
|
+
return null;
|
|
1069
|
+
} else {
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1020
1072
|
} catch (e) {
|
|
1021
|
-
return
|
|
1073
|
+
return null;
|
|
1022
1074
|
}
|
|
1023
1075
|
}
|
|
1024
1076
|
function setLocalStorageBoolean(key, value) {
|
|
@@ -1092,11 +1144,11 @@ function baseGetTag(value) {
|
|
|
1092
1144
|
var baseGetTag_default = baseGetTag;
|
|
1093
1145
|
|
|
1094
1146
|
// ../../node_modules/lodash-es/isObject.js
|
|
1095
|
-
function
|
|
1147
|
+
function isObject2(value) {
|
|
1096
1148
|
var type = typeof value;
|
|
1097
1149
|
return value != null && (type == "object" || type == "function");
|
|
1098
1150
|
}
|
|
1099
|
-
var isObject_default =
|
|
1151
|
+
var isObject_default = isObject2;
|
|
1100
1152
|
|
|
1101
1153
|
// ../../node_modules/lodash-es/isFunction.js
|
|
1102
1154
|
var asyncTag = "[object AsyncFunction]";
|
|
@@ -1160,15 +1212,6 @@ function lsidToUUID(lsid) {
|
|
|
1160
1212
|
return lsid.replace(/^[^:]+:(.*)$/, "$1");
|
|
1161
1213
|
}
|
|
1162
1214
|
|
|
1163
|
-
// src/utils/typeGuards.ts
|
|
1164
|
-
function isUint8Array(value) {
|
|
1165
|
-
return value instanceof Uint8Array;
|
|
1166
|
-
}
|
|
1167
|
-
function isObject2(value) {
|
|
1168
|
-
const type = typeof value;
|
|
1169
|
-
return value != null && (type == "object" || type == "function");
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1215
|
// src/utils/types.ts
|
|
1173
1216
|
var isType = (typename) => (node) => {
|
|
1174
1217
|
return node?.__typename === typename;
|
|
@@ -1176,9 +1219,6 @@ var isType = (typename) => (node) => {
|
|
|
1176
1219
|
function notNullUndefined(value) {
|
|
1177
1220
|
return value !== null && value !== void 0;
|
|
1178
1221
|
}
|
|
1179
|
-
function isRecord(value) {
|
|
1180
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1181
|
-
}
|
|
1182
1222
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1183
1223
|
0 && (module.exports = {
|
|
1184
1224
|
CurrencyUnit,
|
package/dist/utils/index.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { A as AppendUnitsOptions,
|
|
1
|
+
export { A as AppendUnitsOptions, ab as ById, am as Complete, o as CurrencyAmountArg, k as CurrencyAmountInputObj, m as CurrencyAmountObj, n as CurrencyAmountPreferenceObj, V as CurrencyCodes, T as CurrencyLocales, j as CurrencyMap, f as CurrencyUnit, g as CurrencyUnitType, af as DeepPartial, D as DeprecatedCurrencyAmountObj, aa as ExpandRecursively, ae as ExtractByTypename, ag as JSONLiteral, ai as JSONObject, ah as JSONType, a9 as Maybe, aj as NN, ac as OmitTypename, al as PartialBy, an as RequiredKeys, S as SDKCurrencyAmountType, U as UmaCurrency, l as UmaCurrencyAmount, z as abbrCurrencyUnit, b as b64decode, a as b64encode, O as bytesToHex, $ as clamp, i as convertCurrencyAmount, h as convertCurrencyAmountValue, R as countryCodesToCurrencyCodes, c as createSha256Hash, d as defaultCurrencyCode, _ as deleteLocalStorageItem, e as ensureArray, N as errorToJSON, B as formatCurrencyStr, w as getCurrencyAmount, Q as getCurrentLocale, L as getErrorMsg, Y as getLocalStorageBoolean, X as getLocalStorageConfigItem, P as hexToBytes, G as isBrowser, p as isCurrencyAmountInputObj, s as isCurrencyAmountObj, t as isCurrencyAmountPreferenceObj, y as isCurrencyMap, r as isDeprecatedCurrencyAmountObj, J as isError, M as isErrorMsg, K as isErrorWithMessage, H as isNode, a2 as isNumber, a7 as isObject, a8 as isRecord, v as isSDKCurrencyAmount, I as isTest, ad as isType, a6 as isUint8Array, q as isUmaCurrencyAmount, a0 as linearInterpolate, W as localeToCurrencyCode, F as localeToCurrencySymbol, a5 as lsidToUUID, x as mapCurrencyAmount, ak as notNullUndefined, a3 as pollUntil, a1 as round, E as separateCurrencyStrParts, Z as setLocalStorageBoolean, a4 as sleep, u as urlsafe_b64decode } from '../index-DZbfPQqd.cjs';
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { A as AppendUnitsOptions,
|
|
1
|
+
export { A as AppendUnitsOptions, ab as ById, am as Complete, o as CurrencyAmountArg, k as CurrencyAmountInputObj, m as CurrencyAmountObj, n as CurrencyAmountPreferenceObj, V as CurrencyCodes, T as CurrencyLocales, j as CurrencyMap, f as CurrencyUnit, g as CurrencyUnitType, af as DeepPartial, D as DeprecatedCurrencyAmountObj, aa as ExpandRecursively, ae as ExtractByTypename, ag as JSONLiteral, ai as JSONObject, ah as JSONType, a9 as Maybe, aj as NN, ac as OmitTypename, al as PartialBy, an as RequiredKeys, S as SDKCurrencyAmountType, U as UmaCurrency, l as UmaCurrencyAmount, z as abbrCurrencyUnit, b as b64decode, a as b64encode, O as bytesToHex, $ as clamp, i as convertCurrencyAmount, h as convertCurrencyAmountValue, R as countryCodesToCurrencyCodes, c as createSha256Hash, d as defaultCurrencyCode, _ as deleteLocalStorageItem, e as ensureArray, N as errorToJSON, B as formatCurrencyStr, w as getCurrencyAmount, Q as getCurrentLocale, L as getErrorMsg, Y as getLocalStorageBoolean, X as getLocalStorageConfigItem, P as hexToBytes, G as isBrowser, p as isCurrencyAmountInputObj, s as isCurrencyAmountObj, t as isCurrencyAmountPreferenceObj, y as isCurrencyMap, r as isDeprecatedCurrencyAmountObj, J as isError, M as isErrorMsg, K as isErrorWithMessage, H as isNode, a2 as isNumber, a7 as isObject, a8 as isRecord, v as isSDKCurrencyAmount, I as isTest, ad as isType, a6 as isUint8Array, q as isUmaCurrencyAmount, a0 as linearInterpolate, W as localeToCurrencyCode, F as localeToCurrencySymbol, a5 as lsidToUUID, x as mapCurrencyAmount, ak as notNullUndefined, a3 as pollUntil, a1 as round, E as separateCurrencyStrParts, Z as setLocalStorageBoolean, a4 as sleep, u as urlsafe_b64decode } from '../index-DZbfPQqd.js';
|
package/dist/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightsparkdev/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Lightspark JS SDK",
|
|
5
5
|
"author": "Lightspark Inc.",
|
|
6
6
|
"keywords": [
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"lint": "eslint .",
|
|
54
54
|
"package:checks": "yarn publint && yarn attw --pack .",
|
|
55
55
|
"postversion": "yarn build",
|
|
56
|
-
"test": "node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --bail
|
|
56
|
+
"test-cmd": "node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --bail",
|
|
57
|
+
"test": "yarn test-cmd -- src/**/tests/**/*.test.ts",
|
|
57
58
|
"types:watch": "tsc-absolute --watch",
|
|
58
59
|
"types": "tsc"
|
|
59
60
|
},
|
|
60
61
|
"license": "Apache-2.0",
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"crypto-browserify": "^3.12.0",
|
|
63
63
|
"dayjs": "^1.11.7",
|
|
64
64
|
"graphql": "^16.6.0",
|
|
65
65
|
"graphql-ws": "^5.11.3",
|
|
@@ -5,7 +5,6 @@ import dayjs from "dayjs";
|
|
|
5
5
|
import utc from "dayjs/plugin/utc.js";
|
|
6
6
|
import type { Client as WsClient } from "graphql-ws";
|
|
7
7
|
import { createClient } from "graphql-ws";
|
|
8
|
-
import NodeWebSocket from "ws";
|
|
9
8
|
import { Observable } from "zen-observable-ts";
|
|
10
9
|
|
|
11
10
|
import type Query from "./Query.js";
|
|
@@ -33,7 +32,8 @@ type BodyData = {
|
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
class Requester {
|
|
36
|
-
private
|
|
35
|
+
private wsClient: Promise<WsClient>;
|
|
36
|
+
private resolveWsClient: ((value: WsClient) => void) | null = null;
|
|
37
37
|
constructor(
|
|
38
38
|
private readonly nodeKeyCache: NodeKeyCache,
|
|
39
39
|
private readonly schemaEndpoint: string,
|
|
@@ -44,22 +44,43 @@ class Requester {
|
|
|
44
44
|
private readonly signingKey?: SigningKey,
|
|
45
45
|
private readonly fetchImpl: typeof fetch = fetch,
|
|
46
46
|
) {
|
|
47
|
+
this.wsClient = new Promise<WsClient>((resolve) => {
|
|
48
|
+
this.resolveWsClient = resolve;
|
|
49
|
+
});
|
|
50
|
+
void this.initWsClient(baseUrl, authProvider);
|
|
51
|
+
autoBind(this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async initWsClient(baseUrl: string, authProvider: AuthProvider) {
|
|
55
|
+
if (!this.resolveWsClient) {
|
|
56
|
+
/* If resolveWsClient is null assume already initialized: */
|
|
57
|
+
return this.wsClient;
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
let websocketImpl;
|
|
48
|
-
if (
|
|
49
|
-
|
|
61
|
+
if (isNode && typeof WebSocket === "undefined") {
|
|
62
|
+
const wsModule = await import("ws");
|
|
63
|
+
websocketImpl = wsModule.default;
|
|
50
64
|
}
|
|
51
65
|
let websocketProtocol = "wss";
|
|
52
66
|
if (baseUrl.startsWith("http://")) {
|
|
53
67
|
websocketProtocol = "ws";
|
|
54
68
|
}
|
|
55
|
-
|
|
69
|
+
|
|
70
|
+
const wsClient = createClient({
|
|
56
71
|
url: `${websocketProtocol}://${this.stripProtocol(this.baseUrl)}/${
|
|
57
72
|
this.schemaEndpoint
|
|
58
73
|
}`,
|
|
59
74
|
connectionParams: () => authProvider.addWsConnectionParams({}),
|
|
60
75
|
webSocketImpl: websocketImpl,
|
|
61
76
|
});
|
|
62
|
-
|
|
77
|
+
|
|
78
|
+
if (this.resolveWsClient) {
|
|
79
|
+
this.resolveWsClient(wsClient);
|
|
80
|
+
this.resolveWsClient = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return wsClient;
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
public async executeQuery<T>(query: Query<T>): Promise<T | null> {
|
|
@@ -106,11 +127,31 @@ class Requester {
|
|
|
106
127
|
|
|
107
128
|
return new Observable<{ data: T }>((observer) => {
|
|
108
129
|
logger.trace(`Requester.subscribe observer`, observer);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
130
|
+
|
|
131
|
+
let cleanup: (() => void) | null = null;
|
|
132
|
+
let canceled = false;
|
|
133
|
+
|
|
134
|
+
void (async () => {
|
|
135
|
+
try {
|
|
136
|
+
const wsClient = await this.wsClient;
|
|
137
|
+
if (!canceled) {
|
|
138
|
+
cleanup = wsClient.subscribe(bodyData, {
|
|
139
|
+
next: (data) => observer.next(data as { data: T }),
|
|
140
|
+
error: (err) => observer.error(err),
|
|
141
|
+
complete: () => observer.complete(),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
observer.error(err);
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
canceled = true;
|
|
151
|
+
if (cleanup) {
|
|
152
|
+
cleanup();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
114
155
|
});
|
|
115
156
|
}
|
|
116
157
|
|
|
@@ -0,0 +1,347 @@
|
|
|
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
|
+
import LightsparkException from "../../LightsparkException.js";
|
|
10
|
+
import type Query from "../Query.js";
|
|
11
|
+
|
|
12
|
+
/* Mocking ESM modules (when running node with --experimental-vm-modules)
|
|
13
|
+
requires unstable_mockModule, see https://bit.ly/433nRV1 */
|
|
14
|
+
await jest.unstable_mockModule("graphql-ws", () => ({
|
|
15
|
+
__esModule: true,
|
|
16
|
+
createClient: jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
/* Since Requester uses graphql-ws we need a dynamic import after the above mock */
|
|
19
|
+
const { Requester } = await import("../index.js");
|
|
20
|
+
|
|
21
|
+
describe("Requester", () => {
|
|
22
|
+
const schemaEndpoint = "graphql";
|
|
23
|
+
const sdkUserAgent = "test-agent";
|
|
24
|
+
const baseUrl = "https://api.example.com";
|
|
25
|
+
|
|
26
|
+
let nodeKeyCache: NodeKeyCache;
|
|
27
|
+
let authProvider: AuthProvider;
|
|
28
|
+
let signingKey: SigningKey;
|
|
29
|
+
let cryptoImpl: CryptoInterface;
|
|
30
|
+
let fetchImpl: typeof fetch;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
nodeKeyCache = {
|
|
34
|
+
getKey: jest.fn(),
|
|
35
|
+
hasKey: jest.fn(),
|
|
36
|
+
} as unknown as NodeKeyCache;
|
|
37
|
+
|
|
38
|
+
authProvider = {
|
|
39
|
+
addAuthHeaders: jest.fn(async (headers: Record<string, string>) => ({
|
|
40
|
+
...headers,
|
|
41
|
+
"X-Test": "1",
|
|
42
|
+
})),
|
|
43
|
+
isAuthorized: jest.fn(async () => true),
|
|
44
|
+
addWsConnectionParams: jest.fn(
|
|
45
|
+
async (params: Record<string, unknown>) => ({
|
|
46
|
+
...params,
|
|
47
|
+
ws: true,
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
} satisfies AuthProvider;
|
|
51
|
+
|
|
52
|
+
signingKey = {
|
|
53
|
+
type: SigningKeyType.RSASigningKey,
|
|
54
|
+
sign: jest.fn(async (data: Uint8Array) => new Uint8Array([1, 2, 3])),
|
|
55
|
+
} satisfies SigningKey;
|
|
56
|
+
|
|
57
|
+
cryptoImpl = {
|
|
58
|
+
decryptSecretWithNodePassword: jest.fn(async () => new ArrayBuffer(0)),
|
|
59
|
+
generateSigningKeyPair: jest.fn(async () => ({
|
|
60
|
+
publicKey: "",
|
|
61
|
+
privateKey: "",
|
|
62
|
+
})),
|
|
63
|
+
serializeSigningKey: jest.fn(async () => new ArrayBuffer(0)),
|
|
64
|
+
getNonce: jest.fn(async () => 123),
|
|
65
|
+
sign: jest.fn(async () => new ArrayBuffer(0)),
|
|
66
|
+
importPrivateSigningKey: jest.fn(async () => ""),
|
|
67
|
+
} satisfies CryptoInterface;
|
|
68
|
+
|
|
69
|
+
fetchImpl = jest.fn(
|
|
70
|
+
async () =>
|
|
71
|
+
({
|
|
72
|
+
ok: true,
|
|
73
|
+
json: async () => ({ data: { foo: "bar" }, errors: undefined }),
|
|
74
|
+
statusText: "OK",
|
|
75
|
+
}) as Response,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("constructs without error", () => {
|
|
80
|
+
expect(
|
|
81
|
+
() =>
|
|
82
|
+
new Requester(
|
|
83
|
+
nodeKeyCache,
|
|
84
|
+
schemaEndpoint,
|
|
85
|
+
sdkUserAgent,
|
|
86
|
+
authProvider,
|
|
87
|
+
baseUrl,
|
|
88
|
+
cryptoImpl,
|
|
89
|
+
signingKey,
|
|
90
|
+
fetchImpl,
|
|
91
|
+
),
|
|
92
|
+
).not.toThrow();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("executeQuery", () => {
|
|
96
|
+
it("calls makeRawRequest and returns constructed object", async () => {
|
|
97
|
+
const requester = new Requester(
|
|
98
|
+
nodeKeyCache,
|
|
99
|
+
schemaEndpoint,
|
|
100
|
+
sdkUserAgent,
|
|
101
|
+
authProvider,
|
|
102
|
+
baseUrl,
|
|
103
|
+
cryptoImpl,
|
|
104
|
+
signingKey,
|
|
105
|
+
fetchImpl,
|
|
106
|
+
);
|
|
107
|
+
const query: Query<{ foo: string }> = {
|
|
108
|
+
queryPayload: "query TestQuery { foo }",
|
|
109
|
+
variables: { a: 1 },
|
|
110
|
+
constructObject: (rawData) => ({
|
|
111
|
+
foo: (rawData as { foo: string }).foo,
|
|
112
|
+
}),
|
|
113
|
+
};
|
|
114
|
+
jest.spyOn(requester, "makeRawRequest").mockResolvedValue({ foo: "bar" });
|
|
115
|
+
const result = await requester.executeQuery(query);
|
|
116
|
+
expect(result).toEqual({ foo: "bar" });
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("makeRawRequest", () => {
|
|
121
|
+
it("makes a successful request and returns data", async () => {
|
|
122
|
+
const requester = new Requester(
|
|
123
|
+
nodeKeyCache,
|
|
124
|
+
schemaEndpoint,
|
|
125
|
+
sdkUserAgent,
|
|
126
|
+
authProvider,
|
|
127
|
+
baseUrl,
|
|
128
|
+
cryptoImpl,
|
|
129
|
+
signingKey,
|
|
130
|
+
fetchImpl,
|
|
131
|
+
);
|
|
132
|
+
const result = await requester.makeRawRequest("query TestQuery { foo }", {
|
|
133
|
+
a: 1,
|
|
134
|
+
});
|
|
135
|
+
expect(result).toEqual({ foo: "bar" });
|
|
136
|
+
expect(fetchImpl).toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("throws on invalid query", async () => {
|
|
140
|
+
const requester = new Requester(
|
|
141
|
+
nodeKeyCache,
|
|
142
|
+
schemaEndpoint,
|
|
143
|
+
sdkUserAgent,
|
|
144
|
+
authProvider,
|
|
145
|
+
baseUrl,
|
|
146
|
+
cryptoImpl,
|
|
147
|
+
signingKey,
|
|
148
|
+
fetchImpl,
|
|
149
|
+
);
|
|
150
|
+
await expect(requester.makeRawRequest("invalid", {})).rejects.toThrow(
|
|
151
|
+
LightsparkException,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("throws on subscription query", async () => {
|
|
156
|
+
const requester = new Requester(
|
|
157
|
+
nodeKeyCache,
|
|
158
|
+
schemaEndpoint,
|
|
159
|
+
sdkUserAgent,
|
|
160
|
+
authProvider,
|
|
161
|
+
baseUrl,
|
|
162
|
+
cryptoImpl,
|
|
163
|
+
signingKey,
|
|
164
|
+
fetchImpl,
|
|
165
|
+
);
|
|
166
|
+
await expect(
|
|
167
|
+
requester.makeRawRequest("subscription TestSub { foo }", {}),
|
|
168
|
+
).rejects.toThrow(LightsparkException);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("throws on failed response", async () => {
|
|
172
|
+
fetchImpl = jest.fn(
|
|
173
|
+
async () =>
|
|
174
|
+
({
|
|
175
|
+
ok: false,
|
|
176
|
+
statusText: "Bad Request",
|
|
177
|
+
json: async () => ({
|
|
178
|
+
errors: [
|
|
179
|
+
{ message: "fail", extensions: { error_name: "TestError" } },
|
|
180
|
+
],
|
|
181
|
+
}),
|
|
182
|
+
}) as Response,
|
|
183
|
+
);
|
|
184
|
+
const requester = new Requester(
|
|
185
|
+
nodeKeyCache,
|
|
186
|
+
schemaEndpoint,
|
|
187
|
+
sdkUserAgent,
|
|
188
|
+
authProvider,
|
|
189
|
+
baseUrl,
|
|
190
|
+
cryptoImpl,
|
|
191
|
+
signingKey,
|
|
192
|
+
fetchImpl,
|
|
193
|
+
);
|
|
194
|
+
await expect(
|
|
195
|
+
requester.makeRawRequest("query TestQuery { foo }", {}),
|
|
196
|
+
).rejects.toThrow(LightsparkException);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("throws if response has no data and errors", async () => {
|
|
200
|
+
fetchImpl = jest.fn(
|
|
201
|
+
async () =>
|
|
202
|
+
({
|
|
203
|
+
ok: true,
|
|
204
|
+
json: async () => ({
|
|
205
|
+
data: undefined,
|
|
206
|
+
errors: [
|
|
207
|
+
{ message: "fail", extensions: { error_name: "TestError" } },
|
|
208
|
+
],
|
|
209
|
+
}),
|
|
210
|
+
statusText: "OK",
|
|
211
|
+
}) as Response,
|
|
212
|
+
);
|
|
213
|
+
const requester = new Requester(
|
|
214
|
+
nodeKeyCache,
|
|
215
|
+
schemaEndpoint,
|
|
216
|
+
sdkUserAgent,
|
|
217
|
+
authProvider,
|
|
218
|
+
baseUrl,
|
|
219
|
+
cryptoImpl,
|
|
220
|
+
signingKey,
|
|
221
|
+
fetchImpl,
|
|
222
|
+
);
|
|
223
|
+
await expect(
|
|
224
|
+
requester.makeRawRequest("query TestQuery { foo }", {}),
|
|
225
|
+
).rejects.toThrow(LightsparkException);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("subscribe", () => {
|
|
230
|
+
it("throws on mutation query", () => {
|
|
231
|
+
const requester = new Requester(
|
|
232
|
+
nodeKeyCache,
|
|
233
|
+
schemaEndpoint,
|
|
234
|
+
sdkUserAgent,
|
|
235
|
+
authProvider,
|
|
236
|
+
baseUrl,
|
|
237
|
+
cryptoImpl,
|
|
238
|
+
signingKey,
|
|
239
|
+
fetchImpl,
|
|
240
|
+
);
|
|
241
|
+
expect(() =>
|
|
242
|
+
requester.subscribe("mutation TestMutation { foo }"),
|
|
243
|
+
).toThrow(LightsparkException);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("throws on invalid query", () => {
|
|
247
|
+
const requester = new Requester(
|
|
248
|
+
nodeKeyCache,
|
|
249
|
+
schemaEndpoint,
|
|
250
|
+
sdkUserAgent,
|
|
251
|
+
authProvider,
|
|
252
|
+
baseUrl,
|
|
253
|
+
cryptoImpl,
|
|
254
|
+
signingKey,
|
|
255
|
+
fetchImpl,
|
|
256
|
+
);
|
|
257
|
+
expect(() => requester.subscribe("invalid")).toThrow(LightsparkException);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("returns an Observable for a valid subscription", async () => {
|
|
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
|
+
|
|
280
|
+
const requester = new Requester(
|
|
281
|
+
nodeKeyCache,
|
|
282
|
+
schemaEndpoint,
|
|
283
|
+
sdkUserAgent,
|
|
284
|
+
authProvider,
|
|
285
|
+
baseUrl,
|
|
286
|
+
cryptoImpl,
|
|
287
|
+
signingKey,
|
|
288
|
+
fetchImpl,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const observable = requester.subscribe<{ foo: string }>(
|
|
292
|
+
"subscription TestSub { foo }",
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const results: { foo: string }[] = [];
|
|
296
|
+
await new Promise<void>((resolve) => {
|
|
297
|
+
observable.subscribe({
|
|
298
|
+
next: (data) => {
|
|
299
|
+
results.push(data.data);
|
|
300
|
+
},
|
|
301
|
+
complete: () => {
|
|
302
|
+
expect(results).toEqual([{ foo: "bar" }]);
|
|
303
|
+
resolve();
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
expect(wsClient.subscribe).toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe("signing logic", () => {
|
|
313
|
+
it("adds signing headers if signingNodeId is provided", async () => {
|
|
314
|
+
(nodeKeyCache.getKey as jest.Mock).mockReturnValue(signingKey);
|
|
315
|
+
const requester = new Requester(
|
|
316
|
+
nodeKeyCache,
|
|
317
|
+
schemaEndpoint,
|
|
318
|
+
sdkUserAgent,
|
|
319
|
+
authProvider,
|
|
320
|
+
baseUrl,
|
|
321
|
+
cryptoImpl,
|
|
322
|
+
undefined,
|
|
323
|
+
fetchImpl,
|
|
324
|
+
);
|
|
325
|
+
const spy = jest.spyOn(signingKey, "sign");
|
|
326
|
+
await requester.makeRawRequest("query TestQuery { foo }", {}, "node123");
|
|
327
|
+
expect(spy).toHaveBeenCalled();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("throws if signingKey is missing", async () => {
|
|
331
|
+
(nodeKeyCache.getKey as jest.Mock).mockReturnValue(undefined);
|
|
332
|
+
const requester = new Requester(
|
|
333
|
+
nodeKeyCache,
|
|
334
|
+
schemaEndpoint,
|
|
335
|
+
sdkUserAgent,
|
|
336
|
+
authProvider,
|
|
337
|
+
baseUrl,
|
|
338
|
+
cryptoImpl,
|
|
339
|
+
undefined,
|
|
340
|
+
fetchImpl,
|
|
341
|
+
);
|
|
342
|
+
await expect(
|
|
343
|
+
requester.makeRawRequest("query TestQuery { foo }", {}, "node123"),
|
|
344
|
+
).rejects.toThrow();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
});
|