@tollara/service-sdk 0.0.1 → 0.0.2
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/README.md +15 -10
- package/dist/index.d.mts +123 -58
- package/dist/index.d.ts +123 -58
- package/dist/index.js +329 -157
- package/dist/index.mjs +318 -155
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,9 @@ __export(index_exports, {
|
|
|
25
25
|
DEFAULT_CORE_PATH_PREFIX: () => DEFAULT_CORE_PATH_PREFIX,
|
|
26
26
|
DEFAULT_GATEWAY_PATH_PREFIX: () => DEFAULT_GATEWAY_PATH_PREFIX,
|
|
27
27
|
DEFAULT_USAGE_PATH_PREFIX: () => DEFAULT_USAGE_PATH_PREFIX,
|
|
28
|
+
ECS_CORE_PATH_PREFIX: () => ECS_CORE_PATH_PREFIX,
|
|
29
|
+
ECS_GATEWAY_PATH_PREFIX: () => ECS_GATEWAY_PATH_PREFIX,
|
|
30
|
+
ECS_USAGE_PATH_PREFIX: () => ECS_USAGE_PATH_PREFIX,
|
|
28
31
|
ENV_API_URL: () => ENV_API_URL,
|
|
29
32
|
ENV_SERVICE_ID: () => ENV_SERVICE_ID,
|
|
30
33
|
ENV_SERVICE_SECRET: () => ENV_SERVICE_SECRET,
|
|
@@ -32,6 +35,7 @@ __export(index_exports, {
|
|
|
32
35
|
TollaraHeaders: () => TollaraHeaders,
|
|
33
36
|
buildGatewayUserContextString: () => buildGatewayUserContextString,
|
|
34
37
|
buildGatewayUserContextStringV2: () => buildGatewayUserContextStringV2,
|
|
38
|
+
buildGatewayUserContextStringV3: () => buildGatewayUserContextStringV3,
|
|
35
39
|
buildUsageReportUrl: () => buildUsageReportUrl,
|
|
36
40
|
calculateHmac: () => calculateHmac,
|
|
37
41
|
calculateHmacWithTimestamp: () => calculateHmacWithTimestamp,
|
|
@@ -42,14 +46,19 @@ __export(index_exports, {
|
|
|
42
46
|
getRequestResult: () => getRequestResult,
|
|
43
47
|
getRequestStatus: () => getRequestStatus,
|
|
44
48
|
getUserContext: () => getUserContext,
|
|
49
|
+
grantAccess: () => grantAccess,
|
|
45
50
|
invokeService: () => invokeService,
|
|
51
|
+
isHostedTollaraApiOrigin: () => isHostedTollaraApiOrigin,
|
|
52
|
+
parseUsageBreakdown: () => parseUsageBreakdown,
|
|
46
53
|
reportCompletion: () => reportCompletion,
|
|
47
|
-
reportCompletionFull: () => reportCompletionFull,
|
|
48
|
-
reportCompletionWithResult: () => reportCompletionWithResult,
|
|
49
54
|
reportProgress: () => reportProgress,
|
|
50
55
|
reportUsage: () => reportUsage,
|
|
56
|
+
resolveCorePathPrefix: () => resolveCorePathPrefix,
|
|
57
|
+
resolveGatewayPathPrefix: () => resolveGatewayPathPrefix,
|
|
58
|
+
resolveUsagePathPrefix: () => resolveUsagePathPrefix,
|
|
51
59
|
validateHmacSignature: () => validateHmacSignature,
|
|
52
60
|
validateServiceKey: () => validateServiceKey,
|
|
61
|
+
validateServiceKeyWithOutcome: () => validateServiceKeyWithOutcome,
|
|
53
62
|
verifyInboundHmac: () => verifyInboundHmac,
|
|
54
63
|
verifySignature: () => verifySignature,
|
|
55
64
|
verifySignatureFromHeaders: () => verifySignatureFromHeaders,
|
|
@@ -62,10 +71,15 @@ var TollaraHeaders = {
|
|
|
62
71
|
SIGNATURE: "X-Tollara-Signature",
|
|
63
72
|
TIMESTAMP: "X-Tollara-Timestamp",
|
|
64
73
|
USER_ID: "X-Tollara-User-ID",
|
|
74
|
+
/** @deprecated v1/v2 only; v3 uses SERVICE_PRODUCT_ID */
|
|
65
75
|
PLAN: "X-Tollara-Plan",
|
|
76
|
+
SERVICE_PRODUCT_ID: "X-Tollara-Service-Product-ID",
|
|
66
77
|
ROLES: "X-Tollara-Roles",
|
|
78
|
+
/** @deprecated v1 only */
|
|
67
79
|
QUOTA_REMAINING: "X-Tollara-Quota-Remaining",
|
|
80
|
+
/** @deprecated v1/v2 only; v3 uses SUBSCRIPTION_STATUS */
|
|
68
81
|
SUBSCRIPTION_ACTIVE: "X-Tollara-Subscription-Active",
|
|
82
|
+
SUBSCRIPTION_STATUS: "X-Tollara-Subscription-Status",
|
|
69
83
|
BILLING_MODEL: "X-Tollara-Billing-Model",
|
|
70
84
|
MEASUREMENT_TYPE: "X-Tollara-Measurement-Type",
|
|
71
85
|
UNIT_LABEL: "X-Tollara-Unit-Label",
|
|
@@ -106,6 +120,13 @@ function validateHmacSignature(signature, payloadString, key) {
|
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
122
|
|
|
123
|
+
// src/grantAccess.ts
|
|
124
|
+
var ACCESS_STATUSES = /* @__PURE__ */ new Set(["ACTIVE", "TRIAL", "CANCELLING", "CANCELLING_PENDING"]);
|
|
125
|
+
function grantAccess(subscriptionStatus) {
|
|
126
|
+
if (subscriptionStatus == null || subscriptionStatus === "") return false;
|
|
127
|
+
return ACCESS_STATUSES.has(subscriptionStatus.trim().toUpperCase());
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
// src/verifier.ts
|
|
110
131
|
function headerGet(headers, canonicalName) {
|
|
111
132
|
const target = canonicalName.toLowerCase();
|
|
@@ -143,42 +164,69 @@ function buildGatewayUserContextStringV2(userId, plan, roles, subscriptionActive
|
|
|
143
164
|
const ul = unitLabel ?? "";
|
|
144
165
|
return "2" + u + p + r + sub + b + m + ul;
|
|
145
166
|
}
|
|
146
|
-
function
|
|
167
|
+
function buildGatewayUserContextStringV3(userId, serviceProductId, roles, subscriptionStatus, billingModelType, measurementType, unitLabel) {
|
|
168
|
+
const u = userId ?? "";
|
|
169
|
+
const sp = serviceProductId ?? "";
|
|
170
|
+
const r = roles?.length ? roles.join(",") : "";
|
|
171
|
+
const st = subscriptionStatus ?? "";
|
|
172
|
+
const b = billingModelType ?? "";
|
|
173
|
+
const m = measurementType ?? "";
|
|
174
|
+
const ul = unitLabel ?? "";
|
|
175
|
+
return "3" + u + sp + r + st + b + m + ul;
|
|
176
|
+
}
|
|
177
|
+
function buildUserContextString(input) {
|
|
147
178
|
const {
|
|
148
|
-
signature,
|
|
149
|
-
timestamp,
|
|
150
|
-
payload,
|
|
151
179
|
userId,
|
|
180
|
+
serviceProductId,
|
|
152
181
|
plan,
|
|
153
182
|
roles,
|
|
154
183
|
quotaRemaining,
|
|
184
|
+
subscriptionStatus,
|
|
155
185
|
subscriptionActive,
|
|
156
186
|
billingModelType,
|
|
157
187
|
measurementType,
|
|
158
188
|
unitLabel,
|
|
159
189
|
signingVersion
|
|
160
190
|
} = input;
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
const payloadString = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
164
|
-
const userContextString = signingVersion === "2" ? buildGatewayUserContextStringV2(
|
|
191
|
+
if (signingVersion === "3") {
|
|
192
|
+
return buildGatewayUserContextStringV3(
|
|
165
193
|
userId,
|
|
166
|
-
|
|
194
|
+
serviceProductId ?? null,
|
|
167
195
|
roles ?? [],
|
|
168
|
-
|
|
196
|
+
subscriptionStatus ?? null,
|
|
169
197
|
billingModelType ?? null,
|
|
170
198
|
measurementType ?? null,
|
|
171
199
|
unitLabel ?? null
|
|
172
|
-
)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (signingVersion === "2") {
|
|
203
|
+
return buildGatewayUserContextStringV2(
|
|
173
204
|
userId,
|
|
174
|
-
plan,
|
|
205
|
+
plan ?? null,
|
|
175
206
|
roles ?? [],
|
|
176
|
-
|
|
177
|
-
subscriptionActive,
|
|
207
|
+
subscriptionActive ?? false,
|
|
178
208
|
billingModelType ?? null,
|
|
179
209
|
measurementType ?? null,
|
|
180
210
|
unitLabel ?? null
|
|
181
211
|
);
|
|
212
|
+
}
|
|
213
|
+
return buildGatewayUserContextString(
|
|
214
|
+
userId,
|
|
215
|
+
plan ?? null,
|
|
216
|
+
roles ?? [],
|
|
217
|
+
quotaRemaining ?? null,
|
|
218
|
+
subscriptionActive ?? false,
|
|
219
|
+
billingModelType ?? null,
|
|
220
|
+
measurementType ?? null,
|
|
221
|
+
unitLabel ?? null
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function verifySignature(serviceSecret, input) {
|
|
225
|
+
const { signature, timestamp, payload } = input;
|
|
226
|
+
if (!signature || !timestamp || !serviceSecret) return false;
|
|
227
|
+
try {
|
|
228
|
+
const payloadString = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
229
|
+
const userContextString = buildUserContextString(input);
|
|
182
230
|
const dataToSign = payloadString + timestamp + userContextString;
|
|
183
231
|
const expectedSignature = calculateHmac(dataToSign, serviceSecret);
|
|
184
232
|
return constantTimeEquals(expectedSignature, signature);
|
|
@@ -193,9 +241,11 @@ function verifyInboundHmac(serviceSecret, request) {
|
|
|
193
241
|
timestamp: request.timestamp,
|
|
194
242
|
payload: request.payload,
|
|
195
243
|
userId: s.userId,
|
|
244
|
+
serviceProductId: s.serviceProductId,
|
|
196
245
|
plan: s.plan,
|
|
197
246
|
roles: s.roles ?? [],
|
|
198
247
|
quotaRemaining: s.quotaRemaining,
|
|
248
|
+
subscriptionStatus: s.subscriptionStatus,
|
|
199
249
|
subscriptionActive: s.subscriptionActive,
|
|
200
250
|
billingModelType: s.billingModelType,
|
|
201
251
|
measurementType: s.measurementType,
|
|
@@ -203,10 +253,7 @@ function verifyInboundHmac(serviceSecret, request) {
|
|
|
203
253
|
signingVersion: request.signingVersion
|
|
204
254
|
});
|
|
205
255
|
}
|
|
206
|
-
function
|
|
207
|
-
const signature = headerGet(headers, TollaraHeaders.SIGNATURE);
|
|
208
|
-
const timestamp = headerGet(headers, TollaraHeaders.TIMESTAMP);
|
|
209
|
-
if (!signature || !timestamp) return false;
|
|
256
|
+
function parseSignedUserContextFromHeaders(headers) {
|
|
210
257
|
const rolesHeader = headerGet(headers, TollaraHeaders.ROLES);
|
|
211
258
|
const roles = rolesHeader ? rolesHeader.split(",").map((x) => x.trim()).filter(Boolean) : [];
|
|
212
259
|
let quotaRemaining = headerGet(headers, TollaraHeaders.QUOTA_REMAINING);
|
|
@@ -221,16 +268,24 @@ function verifySignatureFromHeaders(serviceSecret, headers, payload) {
|
|
|
221
268
|
const billing = headerGet(headers, TollaraHeaders.BILLING_MODEL);
|
|
222
269
|
const measurement = headerGet(headers, TollaraHeaders.MEASUREMENT_TYPE);
|
|
223
270
|
const unit = headerGet(headers, TollaraHeaders.UNIT_LABEL);
|
|
224
|
-
|
|
271
|
+
return {
|
|
225
272
|
userId: headerGet(headers, TollaraHeaders.USER_ID),
|
|
273
|
+
serviceProductId: headerGet(headers, TollaraHeaders.SERVICE_PRODUCT_ID),
|
|
226
274
|
plan: headerGet(headers, TollaraHeaders.PLAN),
|
|
227
275
|
roles,
|
|
228
276
|
quotaRemaining,
|
|
277
|
+
subscriptionStatus: headerGet(headers, TollaraHeaders.SUBSCRIPTION_STATUS),
|
|
229
278
|
subscriptionActive,
|
|
230
279
|
billingModelType: billing && billing !== "" ? billing : null,
|
|
231
280
|
measurementType: measurement && measurement !== "" ? measurement : null,
|
|
232
281
|
unitLabel: unit && unit !== "" ? unit : null
|
|
233
282
|
};
|
|
283
|
+
}
|
|
284
|
+
function verifySignatureFromHeaders(serviceSecret, headers, payload) {
|
|
285
|
+
const signature = headerGet(headers, TollaraHeaders.SIGNATURE);
|
|
286
|
+
const timestamp = headerGet(headers, TollaraHeaders.TIMESTAMP);
|
|
287
|
+
if (!signature || !timestamp) return false;
|
|
288
|
+
const signedUserContext = parseSignedUserContextFromHeaders(headers);
|
|
234
289
|
const signingVersion = headerGet(headers, TollaraHeaders.SIGNING_VERSION);
|
|
235
290
|
return verifyInboundHmac(serviceSecret, {
|
|
236
291
|
signature,
|
|
@@ -245,28 +300,42 @@ function verifySignatureFromHeadersAndGetUserContext(serviceSecret, headers, pay
|
|
|
245
300
|
return getUserContext(headers);
|
|
246
301
|
}
|
|
247
302
|
function getUserContext(headers) {
|
|
248
|
-
const
|
|
249
|
-
const roles = rolesHeader ? rolesHeader.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
250
|
-
let quotaRemaining = null;
|
|
251
|
-
const q = headerGet(headers, TollaraHeaders.QUOTA_REMAINING);
|
|
252
|
-
if (q != null && q !== "") {
|
|
253
|
-
const n = Number(q);
|
|
254
|
-
if (!Number.isNaN(n)) quotaRemaining = n;
|
|
255
|
-
}
|
|
256
|
-
const sub = headerGet(headers, TollaraHeaders.SUBSCRIPTION_ACTIVE);
|
|
257
|
-
const subscriptionActive = parseSubscriptionActive(sub);
|
|
258
|
-
const bm = headerGet(headers, TollaraHeaders.BILLING_MODEL);
|
|
259
|
-
const mt = headerGet(headers, TollaraHeaders.MEASUREMENT_TYPE);
|
|
260
|
-
const ul = headerGet(headers, TollaraHeaders.UNIT_LABEL);
|
|
303
|
+
const s = parseSignedUserContextFromHeaders(headers);
|
|
261
304
|
return {
|
|
262
|
-
userId:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
305
|
+
userId: s.userId,
|
|
306
|
+
serviceProductId: s.serviceProductId ?? null,
|
|
307
|
+
plan: s.plan ?? null,
|
|
308
|
+
roles: s.roles ?? [],
|
|
309
|
+
quotaRemaining: typeof s.quotaRemaining === "number" ? s.quotaRemaining : s.quotaRemaining != null && s.quotaRemaining !== "" ? Number(s.quotaRemaining) : null,
|
|
310
|
+
subscriptionStatus: s.subscriptionStatus ?? null,
|
|
311
|
+
subscriptionActive: s.subscriptionActive ?? false,
|
|
312
|
+
billingModelType: s.billingModelType ?? null,
|
|
313
|
+
measurementType: s.measurementType ?? null,
|
|
314
|
+
unitLabel: s.unitLabel ?? null
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/usageBreakdown.ts
|
|
319
|
+
function parseUsageBreakdown(raw) {
|
|
320
|
+
if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
321
|
+
const o = raw;
|
|
322
|
+
const num = (k) => typeof o[k] === "number" ? o[k] : void 0;
|
|
323
|
+
const bool = (k) => typeof o[k] === "boolean" ? o[k] : void 0;
|
|
324
|
+
return {
|
|
325
|
+
unitsUsed: num("unitsUsed"),
|
|
326
|
+
baseUnitsUsed: num("baseUnitsUsed"),
|
|
327
|
+
overageUnits: num("overageUnits"),
|
|
328
|
+
chargeableOverageUnits: num("chargeableOverageUnits"),
|
|
329
|
+
surplusOverageUnits: num("surplusOverageUnits"),
|
|
330
|
+
overageCost: num("overageCost"),
|
|
331
|
+
totalOverageCost: num("totalOverageCost"),
|
|
332
|
+
unitsRemaining: num("unitsRemaining"),
|
|
333
|
+
remainingCredits: num("remainingCredits"),
|
|
334
|
+
remainingSpendingCap: num("remainingSpendingCap"),
|
|
335
|
+
totalUnitsUsedThisCycle: num("totalUnitsUsedThisCycle"),
|
|
336
|
+
isOverLimit: bool("isOverLimit"),
|
|
337
|
+
isOverage: bool("isOverage"),
|
|
338
|
+
isOverageAllowed: bool("isOverageAllowed")
|
|
270
339
|
};
|
|
271
340
|
}
|
|
272
341
|
|
|
@@ -275,6 +344,9 @@ var DEFAULT_API_URL = "https://api.tollara.ai";
|
|
|
275
344
|
var DEFAULT_CORE_PATH_PREFIX = "/api/v1";
|
|
276
345
|
var DEFAULT_GATEWAY_PATH_PREFIX = "/api";
|
|
277
346
|
var DEFAULT_USAGE_PATH_PREFIX = "/api/usage";
|
|
347
|
+
var ECS_CORE_PATH_PREFIX = "/core/api/v1";
|
|
348
|
+
var ECS_GATEWAY_PATH_PREFIX = "/gateway/api/v1";
|
|
349
|
+
var ECS_USAGE_PATH_PREFIX = "/usage/api/v1";
|
|
278
350
|
|
|
279
351
|
// src/urls.ts
|
|
280
352
|
function trimTrailingSlashes(s) {
|
|
@@ -293,13 +365,88 @@ function resolveBaseUrl(override, fallback) {
|
|
|
293
365
|
return trimTrailingSlashes(t || fallback);
|
|
294
366
|
}
|
|
295
367
|
|
|
368
|
+
// src/pathPrefixes.ts
|
|
369
|
+
function isHostedTollaraApiOrigin(origin) {
|
|
370
|
+
try {
|
|
371
|
+
const host = new URL(origin).hostname.toLowerCase();
|
|
372
|
+
if (host === "api.tollara.ai" || host.endsWith(".api.tollara.ai")) {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
if (host === "api.ppe.tollara.ai" || host.endsWith(".api.ppe.tollara.ai")) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function resolveGatewayPathPrefix(baseUrl, override) {
|
|
384
|
+
const explicit = override?.trim();
|
|
385
|
+
if (explicit) {
|
|
386
|
+
return explicit;
|
|
387
|
+
}
|
|
388
|
+
const origin = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
389
|
+
return isHostedTollaraApiOrigin(origin) ? ECS_GATEWAY_PATH_PREFIX : DEFAULT_GATEWAY_PATH_PREFIX;
|
|
390
|
+
}
|
|
391
|
+
function resolveCorePathPrefix(baseUrl, override) {
|
|
392
|
+
const explicit = override?.trim();
|
|
393
|
+
if (explicit) {
|
|
394
|
+
return explicit;
|
|
395
|
+
}
|
|
396
|
+
const origin = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
397
|
+
return isHostedTollaraApiOrigin(origin) ? ECS_CORE_PATH_PREFIX : DEFAULT_CORE_PATH_PREFIX;
|
|
398
|
+
}
|
|
399
|
+
function resolveUsagePathPrefix(baseUrl, override) {
|
|
400
|
+
const explicit = override?.trim();
|
|
401
|
+
if (explicit) {
|
|
402
|
+
return explicit;
|
|
403
|
+
}
|
|
404
|
+
const origin = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
405
|
+
return isHostedTollaraApiOrigin(origin) ? ECS_USAGE_PATH_PREFIX : DEFAULT_USAGE_PATH_PREFIX;
|
|
406
|
+
}
|
|
407
|
+
|
|
296
408
|
// src/validationClient.ts
|
|
297
|
-
|
|
298
|
-
|
|
409
|
+
function invalidKeyFromUnsignedErrorBody(responseText, httpStatus) {
|
|
410
|
+
if (httpStatus !== 401 && httpStatus !== 403) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const data = JSON.parse(responseText);
|
|
415
|
+
if (data.valid === false) {
|
|
416
|
+
return {
|
|
417
|
+
ok: false,
|
|
418
|
+
code: "INVALID_KEY",
|
|
419
|
+
message: typeof data.error === "string" ? data.error : void 0,
|
|
420
|
+
httpStatus
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
function parseValidationResult(data, serviceId) {
|
|
428
|
+
const subscriptionStatus = typeof data.subscriptionStatus === "string" ? data.subscriptionStatus : null;
|
|
429
|
+
return {
|
|
430
|
+
userId: data.userId ?? null,
|
|
431
|
+
serviceId: data.serviceId ?? serviceId ?? null,
|
|
432
|
+
serviceKeyId: typeof data.serviceKeyId === "string" && data.serviceKeyId.length > 0 ? data.serviceKeyId : null,
|
|
433
|
+
serviceProductId: typeof data.serviceProductId === "string" ? data.serviceProductId : null,
|
|
434
|
+
roles: Array.isArray(data.roles) ? data.roles : [],
|
|
435
|
+
subscriptionStatus,
|
|
436
|
+
validationSchemaVersion: typeof data.validationSchemaVersion === "number" ? data.validationSchemaVersion : 0,
|
|
437
|
+
billingModelType: data.billingModelType ?? null,
|
|
438
|
+
measurementType: data.measurementType ?? null,
|
|
439
|
+
unitLabel: data.unitLabel ?? null,
|
|
440
|
+
grantAccess: grantAccess(subscriptionStatus)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
async function validateServiceKeyWithOutcome(params) {
|
|
299
444
|
const { baseUrl, serviceKey, serviceId, serviceSecret, fetch: fetchFn = fetch } = params;
|
|
300
|
-
if (!serviceKey?.trim())
|
|
445
|
+
if (!serviceKey?.trim()) {
|
|
446
|
+
return { ok: false, code: "MISSING_KEY" };
|
|
447
|
+
}
|
|
301
448
|
const origin = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
302
|
-
const url = `${joinUrl(origin,
|
|
449
|
+
const url = `${joinUrl(origin, resolveCorePathPrefix(baseUrl))}/service-keys/validate`;
|
|
303
450
|
const body = JSON.stringify({ serviceKey, serviceId, serviceSecret });
|
|
304
451
|
let res;
|
|
305
452
|
try {
|
|
@@ -308,35 +455,61 @@ async function validateServiceKey(params) {
|
|
|
308
455
|
headers: { "Content-Type": "application/json" },
|
|
309
456
|
body
|
|
310
457
|
});
|
|
311
|
-
} catch
|
|
312
|
-
return
|
|
458
|
+
} catch {
|
|
459
|
+
return { ok: false, code: "NETWORK" };
|
|
313
460
|
}
|
|
314
|
-
|
|
461
|
+
const httpStatus = res.status;
|
|
315
462
|
const responseText = await res.text();
|
|
463
|
+
if (!res.ok) {
|
|
464
|
+
const unsignedInvalid = invalidKeyFromUnsignedErrorBody(responseText, httpStatus);
|
|
465
|
+
if (unsignedInvalid) {
|
|
466
|
+
return unsignedInvalid;
|
|
467
|
+
}
|
|
468
|
+
return { ok: false, code: "HTTP_ERROR", httpStatus };
|
|
469
|
+
}
|
|
316
470
|
const signature = res.headers.get(TollaraHeaders.SIGNATURE);
|
|
317
471
|
const timestamp = res.headers.get(TollaraHeaders.TIMESTAMP);
|
|
318
|
-
if (!signature || !timestamp)
|
|
472
|
+
if (!signature || !timestamp) {
|
|
473
|
+
return { ok: false, code: "MISSING_SIGNATURE_HEADERS", httpStatus };
|
|
474
|
+
}
|
|
319
475
|
const dataToVerify = responseText + timestamp;
|
|
320
476
|
const expectedSig = calculateHmac(dataToVerify, serviceSecret);
|
|
321
|
-
if (!constantTimeEquals(expectedSig, signature))
|
|
477
|
+
if (!constantTimeEquals(expectedSig, signature)) {
|
|
478
|
+
return { ok: false, code: "HMAC_MISMATCH", httpStatus };
|
|
479
|
+
}
|
|
322
480
|
let data;
|
|
323
481
|
try {
|
|
324
482
|
data = JSON.parse(responseText);
|
|
325
483
|
} catch {
|
|
326
|
-
return
|
|
484
|
+
return { ok: false, code: "PARSE_ERROR", httpStatus };
|
|
327
485
|
}
|
|
328
|
-
if (!data.valid)
|
|
486
|
+
if (!data.valid) {
|
|
487
|
+
return {
|
|
488
|
+
ok: false,
|
|
489
|
+
code: "INVALID_KEY",
|
|
490
|
+
message: typeof data.error === "string" ? data.error : void 0,
|
|
491
|
+
httpStatus
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return { ok: true, result: parseValidationResult(data, serviceId) };
|
|
495
|
+
}
|
|
496
|
+
async function validateServiceKey(params) {
|
|
497
|
+
const outcome = await validateServiceKeyWithOutcome(params);
|
|
498
|
+
return outcome.ok ? outcome.result : null;
|
|
499
|
+
}
|
|
500
|
+
function parseEstimateResult(data, httpStatus) {
|
|
329
501
|
return {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
502
|
+
sufficientCredits: Boolean(data.sufficientCredits),
|
|
503
|
+
wouldExceedCap: Boolean(data.wouldExceedCap),
|
|
504
|
+
wouldAllow: Boolean(data.wouldAllow),
|
|
505
|
+
estimatedCost: typeof data.estimatedCost === "number" ? data.estimatedCost : null,
|
|
506
|
+
billingModelType: typeof data.billingModelType === "string" ? data.billingModelType : null,
|
|
507
|
+
measurementType: typeof data.measurementType === "string" ? data.measurementType : null,
|
|
508
|
+
unitLabel: typeof data.unitLabel === "string" ? data.unitLabel : null,
|
|
509
|
+
breakdown: parseUsageBreakdown(data.breakdown),
|
|
510
|
+
estimateSchemaVersion: typeof data.estimateSchemaVersion === "number" ? data.estimateSchemaVersion : 0,
|
|
511
|
+
timestamp: typeof data.timestamp === "number" ? data.timestamp : 0,
|
|
512
|
+
httpStatus
|
|
340
513
|
};
|
|
341
514
|
}
|
|
342
515
|
async function estimateUsage(params) {
|
|
@@ -344,7 +517,7 @@ async function estimateUsage(params) {
|
|
|
344
517
|
if (!serviceKey?.trim()) return null;
|
|
345
518
|
if (estimatedUnits == null || !Number.isFinite(estimatedUnits) || estimatedUnits <= 0) return null;
|
|
346
519
|
const origin = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
347
|
-
const url = `${joinUrl(origin,
|
|
520
|
+
const url = `${joinUrl(origin, resolveCorePathPrefix(baseUrl))}/service-keys/estimate-usage`;
|
|
348
521
|
const body = JSON.stringify({ serviceKey, serviceId, serviceSecret, estimatedUnits });
|
|
349
522
|
let res;
|
|
350
523
|
try {
|
|
@@ -370,23 +543,7 @@ async function estimateUsage(params) {
|
|
|
370
543
|
} catch {
|
|
371
544
|
return null;
|
|
372
545
|
}
|
|
373
|
-
|
|
374
|
-
const breakdown = br != null && typeof br === "object" && !Array.isArray(br) ? br : null;
|
|
375
|
-
return {
|
|
376
|
-
sufficientCredits: Boolean(data.sufficientCredits),
|
|
377
|
-
wouldExceedCap: Boolean(data.wouldExceedCap),
|
|
378
|
-
wouldAllow: Boolean(data.wouldAllow),
|
|
379
|
-
estimatedCost: typeof data.estimatedCost === "number" ? data.estimatedCost : null,
|
|
380
|
-
remainingCredits: typeof data.remainingCredits === "number" ? data.remainingCredits : null,
|
|
381
|
-
remainingSpendingCap: typeof data.remainingSpendingCap === "number" ? data.remainingSpendingCap : null,
|
|
382
|
-
billingModelType: typeof data.billingModelType === "string" ? data.billingModelType : null,
|
|
383
|
-
measurementType: typeof data.measurementType === "string" ? data.measurementType : null,
|
|
384
|
-
unitLabel: typeof data.unitLabel === "string" ? data.unitLabel : null,
|
|
385
|
-
breakdown,
|
|
386
|
-
estimateSchemaVersion: typeof data.estimateSchemaVersion === "number" ? data.estimateSchemaVersion : 0,
|
|
387
|
-
timestamp: typeof data.timestamp === "number" ? data.timestamp : 0,
|
|
388
|
-
httpStatus: code
|
|
389
|
-
};
|
|
546
|
+
return parseEstimateResult(data, code);
|
|
390
547
|
}
|
|
391
548
|
async function estimateUsageWithJwt(params) {
|
|
392
549
|
const {
|
|
@@ -401,7 +558,7 @@ async function estimateUsageWithJwt(params) {
|
|
|
401
558
|
if (!bearerToken?.trim() || !userId?.trim() || !serviceId?.trim()) return null;
|
|
402
559
|
if (estimatedUnits == null || !Number.isFinite(estimatedUnits) || estimatedUnits <= 0) return null;
|
|
403
560
|
const origin = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
404
|
-
const prefix = (corePathPrefix
|
|
561
|
+
const prefix = resolveCorePathPrefix(baseUrl, corePathPrefix);
|
|
405
562
|
const url = `${joinUrl(origin, prefix)}/billing/usage/estimate`;
|
|
406
563
|
const body = JSON.stringify({ userId, serviceId, estimatedUnits });
|
|
407
564
|
let res;
|
|
@@ -427,24 +584,9 @@ async function estimateUsageWithJwt(params) {
|
|
|
427
584
|
} catch {
|
|
428
585
|
return null;
|
|
429
586
|
}
|
|
430
|
-
|
|
431
|
-
const breakdown = br != null && typeof br === "object" && !Array.isArray(br) ? br : null;
|
|
432
|
-
return {
|
|
433
|
-
sufficientCredits: Boolean(data.sufficientCredits),
|
|
434
|
-
wouldExceedCap: Boolean(data.wouldExceedCap),
|
|
435
|
-
wouldAllow: Boolean(data.wouldAllow),
|
|
436
|
-
estimatedCost: typeof data.estimatedCost === "number" ? data.estimatedCost : null,
|
|
437
|
-
remainingCredits: typeof data.remainingCredits === "number" ? data.remainingCredits : null,
|
|
438
|
-
remainingSpendingCap: typeof data.remainingSpendingCap === "number" ? data.remainingSpendingCap : null,
|
|
439
|
-
billingModelType: typeof data.billingModelType === "string" ? data.billingModelType : null,
|
|
440
|
-
measurementType: typeof data.measurementType === "string" ? data.measurementType : null,
|
|
441
|
-
unitLabel: typeof data.unitLabel === "string" ? data.unitLabel : null,
|
|
442
|
-
breakdown,
|
|
443
|
-
estimateSchemaVersion: typeof data.estimateSchemaVersion === "number" ? data.estimateSchemaVersion : 0,
|
|
444
|
-
timestamp: typeof data.timestamp === "number" ? data.timestamp : 0,
|
|
445
|
-
httpStatus: code
|
|
446
|
-
};
|
|
587
|
+
return parseEstimateResult(data, code);
|
|
447
588
|
}
|
|
589
|
+
var CACHE_TTL_MS = 6e4;
|
|
448
590
|
function createValidationCache() {
|
|
449
591
|
const cache = /* @__PURE__ */ new Map();
|
|
450
592
|
return {
|
|
@@ -486,12 +628,15 @@ function usageReportInstantAndEpochSeconds(timestamp) {
|
|
|
486
628
|
}
|
|
487
629
|
function buildUsageReportUrl(baseUrl) {
|
|
488
630
|
const base = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
489
|
-
let p =
|
|
631
|
+
let p = resolveUsagePathPrefix(baseUrl).trim();
|
|
490
632
|
if (!p.startsWith("/")) p = `/${p}`;
|
|
491
633
|
p = p.replace(/\/$/, "");
|
|
492
634
|
return `${base}${p}/report`;
|
|
493
635
|
}
|
|
494
636
|
function parseUrlParams(url) {
|
|
637
|
+
if (url == null || typeof url !== "string") {
|
|
638
|
+
return { baseUrl: "", signature: null, timestamp: null };
|
|
639
|
+
}
|
|
495
640
|
let baseUrl = url;
|
|
496
641
|
let signature = null;
|
|
497
642
|
let timestamp = null;
|
|
@@ -504,17 +649,16 @@ function parseUrlParams(url) {
|
|
|
504
649
|
}
|
|
505
650
|
return { baseUrl, signature, timestamp };
|
|
506
651
|
}
|
|
507
|
-
async function
|
|
508
|
-
const {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const bodyString = JSON.stringify(body);
|
|
652
|
+
async function postSignedUsageCallback(urlWithQuery, bodyString, serviceSecret, fetchFn) {
|
|
653
|
+
const { baseUrl, timestamp } = parseUrlParams(urlWithQuery);
|
|
654
|
+
if (!timestamp) {
|
|
655
|
+
return {
|
|
656
|
+
success: false,
|
|
657
|
+
httpStatus: 0,
|
|
658
|
+
httpStatusText: urlWithQuery ? "Missing timestamp query parameter in URL" : "Missing or invalid callback/progress URL",
|
|
659
|
+
requestUrl: baseUrl
|
|
660
|
+
};
|
|
661
|
+
}
|
|
518
662
|
const signature = calculateHmacWithTimestamp(bodyString, timestamp, serviceSecret);
|
|
519
663
|
try {
|
|
520
664
|
const res = await fetchFn(baseUrl, {
|
|
@@ -526,21 +670,37 @@ async function reportProgress(params) {
|
|
|
526
670
|
},
|
|
527
671
|
body: bodyString
|
|
528
672
|
});
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
673
|
+
const responseBody = await res.text();
|
|
674
|
+
return {
|
|
675
|
+
success: res.ok,
|
|
676
|
+
httpStatus: res.status,
|
|
677
|
+
httpStatusText: res.statusText,
|
|
678
|
+
requestUrl: baseUrl,
|
|
679
|
+
responseBody: responseBody || void 0
|
|
680
|
+
};
|
|
681
|
+
} catch (err) {
|
|
682
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
683
|
+
return {
|
|
684
|
+
success: false,
|
|
685
|
+
httpStatus: 0,
|
|
686
|
+
httpStatusText: "Network error",
|
|
687
|
+
requestUrl: baseUrl,
|
|
688
|
+
networkError: message
|
|
689
|
+
};
|
|
532
690
|
}
|
|
533
691
|
}
|
|
534
|
-
async function
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
692
|
+
async function reportProgress(params) {
|
|
693
|
+
const { progressUrl, stage, percentageComplete, errorMessage, serviceSecret, fetch: fetchFn = fetch } = params;
|
|
694
|
+
const body = {
|
|
695
|
+
stage,
|
|
696
|
+
percentageComplete,
|
|
697
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
698
|
+
};
|
|
699
|
+
if (errorMessage != null) body.errorMessage = errorMessage;
|
|
700
|
+
return postSignedUsageCallback(progressUrl, JSON.stringify(body), serviceSecret, fetchFn);
|
|
539
701
|
}
|
|
540
|
-
async function
|
|
702
|
+
async function reportCompletion(params) {
|
|
541
703
|
const { callbackUrl, status, result, resultUrl, contentType, units, serviceSecret, fetch: fetchFn = fetch } = params;
|
|
542
|
-
const { baseUrl, timestamp } = parseUrlParams(callbackUrl);
|
|
543
|
-
if (!timestamp) return false;
|
|
544
704
|
const body = {
|
|
545
705
|
status,
|
|
546
706
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -549,22 +709,7 @@ async function reportCompletionFull(params) {
|
|
|
549
709
|
if (result != null) body.result = result;
|
|
550
710
|
if (resultUrl != null) body.resultUrl = resultUrl;
|
|
551
711
|
if (contentType != null) body.contentType = contentType;
|
|
552
|
-
|
|
553
|
-
const signature = calculateHmacWithTimestamp(bodyString, timestamp, serviceSecret);
|
|
554
|
-
try {
|
|
555
|
-
const res = await fetchFn(baseUrl, {
|
|
556
|
-
method: "POST",
|
|
557
|
-
headers: {
|
|
558
|
-
"Content-Type": "application/json",
|
|
559
|
-
[TollaraHeaders.SIGNATURE]: signature,
|
|
560
|
-
[TollaraHeaders.TIMESTAMP]: timestamp
|
|
561
|
-
},
|
|
562
|
-
body: bodyString
|
|
563
|
-
});
|
|
564
|
-
return res.ok;
|
|
565
|
-
} catch {
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
712
|
+
return postSignedUsageCallback(callbackUrl, JSON.stringify(body), serviceSecret, fetchFn);
|
|
568
713
|
}
|
|
569
714
|
async function reportUsage(params) {
|
|
570
715
|
const {
|
|
@@ -593,7 +738,18 @@ async function reportUsage(params) {
|
|
|
593
738
|
if (!res.ok) {
|
|
594
739
|
throw new Error(`Usage report failed: ${res.status} ${res.statusText}`);
|
|
595
740
|
}
|
|
596
|
-
|
|
741
|
+
const json = await res.json();
|
|
742
|
+
return {
|
|
743
|
+
reportSchemaVersion: typeof json.reportSchemaVersion === "number" ? json.reportSchemaVersion : void 0,
|
|
744
|
+
status: typeof json.status === "string" ? json.status : void 0,
|
|
745
|
+
warning: typeof json.warning === "string" ? json.warning : json.warning === null ? null : void 0,
|
|
746
|
+
userId: typeof json.userId === "string" ? json.userId : void 0,
|
|
747
|
+
serviceId: typeof json.serviceId === "string" ? json.serviceId : void 0,
|
|
748
|
+
billingModelType: typeof json.billingModelType === "string" ? json.billingModelType : null,
|
|
749
|
+
measurementType: typeof json.measurementType === "string" ? json.measurementType : null,
|
|
750
|
+
unitLabel: typeof json.unitLabel === "string" ? json.unitLabel : null,
|
|
751
|
+
breakdown: parseUsageBreakdown(json.breakdown)
|
|
752
|
+
};
|
|
597
753
|
}
|
|
598
754
|
|
|
599
755
|
// src/gatewayClient.ts
|
|
@@ -608,7 +764,11 @@ function buildUrl(origin, gatewayPathPrefix, suffix) {
|
|
|
608
764
|
}
|
|
609
765
|
async function getRequestStatus(params) {
|
|
610
766
|
const { baseUrl, requestId, serviceKey, fetch: fetchFn = fetch } = params;
|
|
611
|
-
const url = buildUrl(
|
|
767
|
+
const url = buildUrl(
|
|
768
|
+
baseUrl ?? DEFAULT_API_URL,
|
|
769
|
+
resolveGatewayPathPrefix(baseUrl),
|
|
770
|
+
`/requests/${requestId}/status`
|
|
771
|
+
);
|
|
612
772
|
try {
|
|
613
773
|
const res = await fetchFn(url, {
|
|
614
774
|
method: "GET",
|
|
@@ -622,7 +782,11 @@ async function getRequestStatus(params) {
|
|
|
622
782
|
}
|
|
623
783
|
async function getRequestResult(params) {
|
|
624
784
|
const { baseUrl, requestId, serviceKey, fetch: fetchFn = fetch } = params;
|
|
625
|
-
const url = buildUrl(
|
|
785
|
+
const url = buildUrl(
|
|
786
|
+
baseUrl ?? DEFAULT_API_URL,
|
|
787
|
+
resolveGatewayPathPrefix(baseUrl),
|
|
788
|
+
`/requests/${requestId}/result`
|
|
789
|
+
);
|
|
626
790
|
try {
|
|
627
791
|
const res = await fetchFn(url, {
|
|
628
792
|
method: "GET",
|
|
@@ -654,7 +818,7 @@ async function invokeService(params) {
|
|
|
654
818
|
fetch: fetchFn = fetch
|
|
655
819
|
} = params;
|
|
656
820
|
const origin = resolveBaseUrl(baseUrl, DEFAULT_API_URL);
|
|
657
|
-
const prefix = normalizePrefix2((gatewayPathPrefix
|
|
821
|
+
const prefix = normalizePrefix2(resolveGatewayPathPrefix(baseUrl, gatewayPathPrefix));
|
|
658
822
|
const path = `${prefix}/service/${serviceId}/endpoint/${endpointId}/invoke${isAsync ? "/async" : ""}`;
|
|
659
823
|
const url = `${origin}${path}`;
|
|
660
824
|
const m = method.toUpperCase();
|
|
@@ -731,6 +895,15 @@ var TollaraClient = class {
|
|
|
731
895
|
fetch: this.fetchFn
|
|
732
896
|
});
|
|
733
897
|
}
|
|
898
|
+
async validateServiceKeyWithOutcome(serviceKey) {
|
|
899
|
+
return validateServiceKeyWithOutcome({
|
|
900
|
+
baseUrl: this.apiOrigin,
|
|
901
|
+
serviceKey,
|
|
902
|
+
serviceId: this.serviceId,
|
|
903
|
+
serviceSecret: this.serviceSecret,
|
|
904
|
+
fetch: this.fetchFn
|
|
905
|
+
});
|
|
906
|
+
}
|
|
734
907
|
async estimateUsage(serviceKey, estimatedUnits) {
|
|
735
908
|
return estimateUsage({
|
|
736
909
|
baseUrl: this.apiOrigin,
|
|
@@ -792,23 +965,13 @@ var TollaraClient = class {
|
|
|
792
965
|
}
|
|
793
966
|
async sendCompletion(callbackUrl, requestId, status, units, options) {
|
|
794
967
|
const { result, resultUrl, contentType } = options ?? {};
|
|
795
|
-
if (result != null || resultUrl != null || contentType != null) {
|
|
796
|
-
return reportCompletionFull({
|
|
797
|
-
callbackUrl,
|
|
798
|
-
requestId,
|
|
799
|
-
status,
|
|
800
|
-
result,
|
|
801
|
-
resultUrl,
|
|
802
|
-
contentType,
|
|
803
|
-
units,
|
|
804
|
-
serviceSecret: this.serviceSecret,
|
|
805
|
-
fetch: this.fetchFn
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
968
|
return reportCompletion({
|
|
809
969
|
callbackUrl,
|
|
810
970
|
requestId,
|
|
811
971
|
status,
|
|
972
|
+
result,
|
|
973
|
+
resultUrl,
|
|
974
|
+
contentType,
|
|
812
975
|
units,
|
|
813
976
|
serviceSecret: this.serviceSecret,
|
|
814
977
|
fetch: this.fetchFn
|
|
@@ -845,6 +1008,9 @@ TollaraClient.DEFAULT_USAGE_PATH_PREFIX = DEFAULT_USAGE_PATH_PREFIX;
|
|
|
845
1008
|
DEFAULT_CORE_PATH_PREFIX,
|
|
846
1009
|
DEFAULT_GATEWAY_PATH_PREFIX,
|
|
847
1010
|
DEFAULT_USAGE_PATH_PREFIX,
|
|
1011
|
+
ECS_CORE_PATH_PREFIX,
|
|
1012
|
+
ECS_GATEWAY_PATH_PREFIX,
|
|
1013
|
+
ECS_USAGE_PATH_PREFIX,
|
|
848
1014
|
ENV_API_URL,
|
|
849
1015
|
ENV_SERVICE_ID,
|
|
850
1016
|
ENV_SERVICE_SECRET,
|
|
@@ -852,6 +1018,7 @@ TollaraClient.DEFAULT_USAGE_PATH_PREFIX = DEFAULT_USAGE_PATH_PREFIX;
|
|
|
852
1018
|
TollaraHeaders,
|
|
853
1019
|
buildGatewayUserContextString,
|
|
854
1020
|
buildGatewayUserContextStringV2,
|
|
1021
|
+
buildGatewayUserContextStringV3,
|
|
855
1022
|
buildUsageReportUrl,
|
|
856
1023
|
calculateHmac,
|
|
857
1024
|
calculateHmacWithTimestamp,
|
|
@@ -862,14 +1029,19 @@ TollaraClient.DEFAULT_USAGE_PATH_PREFIX = DEFAULT_USAGE_PATH_PREFIX;
|
|
|
862
1029
|
getRequestResult,
|
|
863
1030
|
getRequestStatus,
|
|
864
1031
|
getUserContext,
|
|
1032
|
+
grantAccess,
|
|
865
1033
|
invokeService,
|
|
1034
|
+
isHostedTollaraApiOrigin,
|
|
1035
|
+
parseUsageBreakdown,
|
|
866
1036
|
reportCompletion,
|
|
867
|
-
reportCompletionFull,
|
|
868
|
-
reportCompletionWithResult,
|
|
869
1037
|
reportProgress,
|
|
870
1038
|
reportUsage,
|
|
1039
|
+
resolveCorePathPrefix,
|
|
1040
|
+
resolveGatewayPathPrefix,
|
|
1041
|
+
resolveUsagePathPrefix,
|
|
871
1042
|
validateHmacSignature,
|
|
872
1043
|
validateServiceKey,
|
|
1044
|
+
validateServiceKeyWithOutcome,
|
|
873
1045
|
verifyInboundHmac,
|
|
874
1046
|
verifySignature,
|
|
875
1047
|
verifySignatureFromHeaders,
|