@onesub/server 0.19.0 → 0.20.0
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 +1 -0
- package/dist/__tests__/apple-jws-cache.test.d.ts +16 -0
- package/dist/__tests__/apple-jws-cache.test.d.ts.map +1 -0
- package/dist/__tests__/metrics-aggregate.test.d.ts +10 -0
- package/dist/__tests__/metrics-aggregate.test.d.ts.map +1 -0
- package/dist/__tests__/metrics-cache.test.d.ts +11 -0
- package/dist/__tests__/metrics-cache.test.d.ts.map +1 -0
- package/dist/__tests__/verified-key-cache.test.d.ts +18 -0
- package/dist/__tests__/verified-key-cache.test.d.ts.map +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/index.cjs +287 -190
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +288 -192
- package/dist/index.js.map +1 -1
- package/dist/metrics-aggregate.d.ts +82 -0
- package/dist/metrics-aggregate.d.ts.map +1 -0
- package/dist/metrics-cache.d.ts +20 -0
- package/dist/metrics-cache.d.ts.map +1 -0
- package/dist/providers/apple.d.ts +2 -0
- package/dist/providers/apple.d.ts.map +1 -1
- package/dist/providers/verified-key-cache.d.ts +75 -0
- package/dist/providers/verified-key-cache.d.ts.map +1 -0
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/entitlements.d.ts +18 -1
- package/dist/routes/entitlements.d.ts.map +1 -1
- package/dist/routes/metrics.d.ts +8 -3
- package/dist/routes/metrics.d.ts.map +1 -1
- package/dist/store.d.ts +3 -2
- package/dist/store.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -97,6 +97,6 @@ export type { OpenAPIDoc } from './openapi.js';
|
|
|
97
97
|
export { withSpan } from './tracing.js';
|
|
98
98
|
export { validateAppleReceipt, fetchAppleSubscriptionStatus, fetchAppleTransactionHistory, signApplePromotionalOffer, } from './providers/apple.js';
|
|
99
99
|
export { validateGoogleReceipt } from './providers/google.js';
|
|
100
|
-
export { evaluateEntitlement } from './routes/entitlements.js';
|
|
100
|
+
export { evaluateEntitlement, evaluateEntitlementFrom } from './routes/entitlements.js';
|
|
101
101
|
export { setLogger, log } from './logger.js';
|
|
102
102
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAiD7E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAW7F;AAGD,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC5G,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAK/E,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE;;;OAGG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAiD7E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAW7F;AAGD,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC5G,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAK/E,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIpE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAO9D,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAIxF,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import express, { Router } from 'express';
|
|
|
2
2
|
import { PURCHASE_TYPE, ONESUB_ERROR_CODE, ROUTES, DEFAULT_PORT, SUBSCRIPTION_STATUS, MOCK_RECEIPT_PREFIX } from '@onesub/shared';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { decodeJwt, decodeProtectedHeader, importX509, jwtVerify, importPKCS8, SignJWT, createRemoteJWKSet } from 'jose';
|
|
5
|
-
import { X509Certificate, randomUUID, timingSafeEqual, createSign
|
|
5
|
+
import { X509Certificate, createHash, randomUUID, timingSafeEqual, createSign } from 'crypto';
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
|
|
8
8
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -173,9 +173,13 @@ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
|
173
173
|
async function fetchWithTimeout(input, init, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
|
|
174
174
|
const controller = new AbortController();
|
|
175
175
|
const callerSignal = init?.signal;
|
|
176
|
+
let onCallerAbort;
|
|
176
177
|
if (callerSignal) {
|
|
177
178
|
if (callerSignal.aborted) controller.abort(callerSignal.reason);
|
|
178
|
-
else
|
|
179
|
+
else {
|
|
180
|
+
onCallerAbort = () => controller.abort(callerSignal.reason);
|
|
181
|
+
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
182
|
+
}
|
|
179
183
|
}
|
|
180
184
|
const timer = setTimeout(() => {
|
|
181
185
|
controller.abort(new Error(`[onesub] fetch timed out after ${timeoutMs}ms`));
|
|
@@ -184,6 +188,7 @@ async function fetchWithTimeout(input, init, timeoutMs = DEFAULT_FETCH_TIMEOUT_M
|
|
|
184
188
|
return await fetch(input, { ...init, signal: controller.signal });
|
|
185
189
|
} finally {
|
|
186
190
|
clearTimeout(timer);
|
|
191
|
+
if (onCallerAbort) callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
187
192
|
}
|
|
188
193
|
}
|
|
189
194
|
|
|
@@ -236,6 +241,70 @@ function getDefaultCache() {
|
|
|
236
241
|
function setDefaultCache(adapter) {
|
|
237
242
|
defaultAdapter = adapter;
|
|
238
243
|
}
|
|
244
|
+
var DEFAULT_MAX_ENTRIES = 32;
|
|
245
|
+
var DEFAULT_MAX_TTL_MS = 60 * 60 * 1e3;
|
|
246
|
+
var VerifiedKeyCache = class {
|
|
247
|
+
entries = /* @__PURE__ */ new Map();
|
|
248
|
+
maxEntries;
|
|
249
|
+
maxTtlMs;
|
|
250
|
+
now;
|
|
251
|
+
constructor(opts = {}) {
|
|
252
|
+
this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
253
|
+
this.maxTtlMs = opts.maxTtlMs ?? DEFAULT_MAX_TTL_MS;
|
|
254
|
+
this.now = opts.now ?? Date.now;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Return the key for `parts`, verifying via `produce` only on a miss.
|
|
258
|
+
*
|
|
259
|
+
* `parts` must contain every input the verification depends on — for Apple
|
|
260
|
+
* that is the JWS `alg` plus the full x5c chain. Anything left out would let
|
|
261
|
+
* one input's result be served for a different input.
|
|
262
|
+
*/
|
|
263
|
+
async resolve(parts, produce) {
|
|
264
|
+
const cacheKey = digest(parts);
|
|
265
|
+
const now = this.now();
|
|
266
|
+
const hit = this.entries.get(cacheKey);
|
|
267
|
+
if (hit) {
|
|
268
|
+
if (hit.expiresAt > now) return hit.key;
|
|
269
|
+
this.entries.delete(cacheKey);
|
|
270
|
+
}
|
|
271
|
+
const verified = await produce();
|
|
272
|
+
const expiresAt = Math.min(now + this.maxTtlMs, verified.notAfter);
|
|
273
|
+
if (expiresAt > now) {
|
|
274
|
+
this.prune(now);
|
|
275
|
+
this.entries.set(cacheKey, { key: verified.key, expiresAt });
|
|
276
|
+
}
|
|
277
|
+
return verified.key;
|
|
278
|
+
}
|
|
279
|
+
/** Entries currently held, including any not yet pruned. Test/diagnostic use. */
|
|
280
|
+
get size() {
|
|
281
|
+
return this.entries.size;
|
|
282
|
+
}
|
|
283
|
+
/** Drop everything. Test/diagnostic use. */
|
|
284
|
+
clear() {
|
|
285
|
+
this.entries.clear();
|
|
286
|
+
}
|
|
287
|
+
/** Make room: expired entries first, then the oldest insertions. */
|
|
288
|
+
prune(now) {
|
|
289
|
+
for (const [key, entry] of this.entries) {
|
|
290
|
+
if (entry.expiresAt <= now) this.entries.delete(key);
|
|
291
|
+
}
|
|
292
|
+
while (this.entries.size >= this.maxEntries) {
|
|
293
|
+
const oldest = this.entries.keys().next();
|
|
294
|
+
if (oldest.done) break;
|
|
295
|
+
this.entries.delete(oldest.value);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
function digest(parts) {
|
|
300
|
+
const hash = createHash("sha256");
|
|
301
|
+
for (const part of parts) {
|
|
302
|
+
hash.update(String(part.length));
|
|
303
|
+
hash.update(":");
|
|
304
|
+
hash.update(part);
|
|
305
|
+
}
|
|
306
|
+
return hash.digest("base64");
|
|
307
|
+
}
|
|
239
308
|
function classifyMockReceipt(receipt) {
|
|
240
309
|
if (receipt.startsWith(MOCK_RECEIPT_PREFIX.NETWORK_ERROR)) return { kind: "network-error" };
|
|
241
310
|
if (receipt.startsWith(MOCK_RECEIPT_PREFIX.REVOKED)) return { kind: "revoked" };
|
|
@@ -255,8 +324,8 @@ function outcomePasses(outcome, tag) {
|
|
|
255
324
|
return false;
|
|
256
325
|
}
|
|
257
326
|
function deterministicTransactionId(prefix, receipt) {
|
|
258
|
-
const
|
|
259
|
-
return `${prefix}_${
|
|
327
|
+
const digest2 = createHash("sha256").update(receipt).digest("hex").slice(0, 24);
|
|
328
|
+
return `${prefix}_${digest2}`;
|
|
260
329
|
}
|
|
261
330
|
function extractBoundToken(receipt) {
|
|
262
331
|
return receipt.match(/#token=([^#]+)/)?.[1];
|
|
@@ -346,11 +415,15 @@ function verifyAppleCertChain(x5c) {
|
|
|
346
415
|
}
|
|
347
416
|
const chain = x5c.map((der) => new X509Certificate(derBase64ToPem(der)));
|
|
348
417
|
const now = /* @__PURE__ */ new Date();
|
|
418
|
+
let notAfter = Infinity;
|
|
349
419
|
for (let i = 0; i < chain.length; i++) {
|
|
350
420
|
const cert = chain[i];
|
|
351
|
-
|
|
421
|
+
const validTo = new Date(cert.validTo).getTime();
|
|
422
|
+
if (new Date(cert.validFrom) > now || validTo < now.getTime()) {
|
|
352
423
|
throw new Error(`[onesub/apple] cert[${i}] outside validity window`);
|
|
353
424
|
}
|
|
425
|
+
if (Number.isNaN(validTo)) notAfter = 0;
|
|
426
|
+
else if (validTo < notAfter) notAfter = validTo;
|
|
354
427
|
if (i >= 1) assertIssuerCanSign(cert, i);
|
|
355
428
|
if (i + 1 < chain.length) {
|
|
356
429
|
if (!cert.checkIssued(chain[i + 1]) || !cert.verify(chain[i + 1].publicKey)) {
|
|
@@ -373,8 +446,9 @@ function verifyAppleCertChain(x5c) {
|
|
|
373
446
|
if (!trustsRoot) {
|
|
374
447
|
throw new Error("[onesub/apple] cert chain does not terminate at a trusted Apple root");
|
|
375
448
|
}
|
|
376
|
-
return chain[0].toString();
|
|
449
|
+
return { leafPem: chain[0].toString(), notAfter };
|
|
377
450
|
}
|
|
451
|
+
var appleLeafKeys = new VerifiedKeyCache();
|
|
378
452
|
async function decodeJws(jws, skipVerification = false) {
|
|
379
453
|
if (skipVerification) {
|
|
380
454
|
if (process.env["NODE_ENV"] === "production") {
|
|
@@ -389,9 +463,11 @@ async function decodeJws(jws, skipVerification = false) {
|
|
|
389
463
|
if (!x5c || x5c.length === 0) {
|
|
390
464
|
throw new Error("[onesub/apple] JWS header missing x5c certificate chain");
|
|
391
465
|
}
|
|
392
|
-
const leafPem = verifyAppleCertChain(x5c);
|
|
393
466
|
const alg = header.alg ?? "ES256";
|
|
394
|
-
const key = await
|
|
467
|
+
const key = await appleLeafKeys.resolve([alg, ...x5c], async () => {
|
|
468
|
+
const { leafPem, notAfter } = verifyAppleCertChain(x5c);
|
|
469
|
+
return { key: await importX509(leafPem, alg), notAfter };
|
|
470
|
+
});
|
|
395
471
|
const { payload } = await jwtVerify(jws, key);
|
|
396
472
|
return payload;
|
|
397
473
|
}
|
|
@@ -2064,10 +2140,8 @@ function createPurchaseRouter(config, purchaseStore) {
|
|
|
2064
2140
|
});
|
|
2065
2141
|
return router;
|
|
2066
2142
|
}
|
|
2067
|
-
|
|
2143
|
+
function evaluateEntitlementFrom(subs, purchases, entitlement, now = Date.now()) {
|
|
2068
2144
|
const productIdSet = new Set(entitlement.productIds);
|
|
2069
|
-
const now = Date.now();
|
|
2070
|
-
const subs = await store.getAllByUserId(userId);
|
|
2071
2145
|
for (const sub of subs) {
|
|
2072
2146
|
if (!productIdSet.has(sub.productId)) continue;
|
|
2073
2147
|
const statusAllows = sub.status === SUBSCRIPTION_STATUS.ACTIVE || sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD;
|
|
@@ -2080,7 +2154,6 @@ async function evaluateEntitlement(userId, entitlement, store, purchaseStore) {
|
|
|
2080
2154
|
expiresAt: sub.expiresAt
|
|
2081
2155
|
};
|
|
2082
2156
|
}
|
|
2083
|
-
const purchases = await purchaseStore.getPurchasesByUserId(userId);
|
|
2084
2157
|
for (const p of purchases) {
|
|
2085
2158
|
if (p.type !== PURCHASE_TYPE.NON_CONSUMABLE) continue;
|
|
2086
2159
|
if (!productIdSet.has(p.productId)) continue;
|
|
@@ -2092,6 +2165,14 @@ async function evaluateEntitlement(userId, entitlement, store, purchaseStore) {
|
|
|
2092
2165
|
}
|
|
2093
2166
|
return { active: false, source: null };
|
|
2094
2167
|
}
|
|
2168
|
+
async function evaluateEntitlement(userId, entitlement, store, purchaseStore) {
|
|
2169
|
+
const now = Date.now();
|
|
2170
|
+
const subs = await store.getAllByUserId(userId);
|
|
2171
|
+
const fromSubs = evaluateEntitlementFrom(subs, [], entitlement, now);
|
|
2172
|
+
if (fromSubs.active) return fromSubs;
|
|
2173
|
+
const purchases = await purchaseStore.getPurchasesByUserId(userId);
|
|
2174
|
+
return evaluateEntitlementFrom([], purchases, entitlement, now);
|
|
2175
|
+
}
|
|
2095
2176
|
var userIdSchema = z.string().min(1).max(256);
|
|
2096
2177
|
var entitlementIdSchema = z.string().min(1).max(128);
|
|
2097
2178
|
function createEntitlementRouter(config, store, purchaseStore) {
|
|
@@ -2133,11 +2214,13 @@ function createEntitlementRouter(config, store, purchaseStore) {
|
|
|
2133
2214
|
return;
|
|
2134
2215
|
}
|
|
2135
2216
|
try {
|
|
2136
|
-
const
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2217
|
+
const [subs, purchases] = await Promise.all([
|
|
2218
|
+
store.getAllByUserId(userId),
|
|
2219
|
+
purchaseStore.getPurchasesByUserId(userId)
|
|
2220
|
+
]);
|
|
2221
|
+
const now = Date.now();
|
|
2222
|
+
const entries = Object.entries(entitlements).map(
|
|
2223
|
+
([id, entitlement]) => [id, evaluateEntitlementFrom(subs, purchases, entitlement, now)]
|
|
2141
2224
|
);
|
|
2142
2225
|
const response = {
|
|
2143
2226
|
entitlements: Object.fromEntries(entries)
|
|
@@ -2328,9 +2411,10 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2328
2411
|
]);
|
|
2329
2412
|
let entitlements;
|
|
2330
2413
|
if (config.entitlements && Object.keys(config.entitlements).length > 0) {
|
|
2414
|
+
const now = Date.now();
|
|
2331
2415
|
entitlements = {};
|
|
2332
2416
|
for (const [id, def] of Object.entries(config.entitlements)) {
|
|
2333
|
-
entitlements[id] =
|
|
2417
|
+
entitlements[id] = evaluateEntitlementFrom(subscriptions, purchases, def, now);
|
|
2334
2418
|
}
|
|
2335
2419
|
}
|
|
2336
2420
|
const response = {
|
|
@@ -2442,11 +2526,135 @@ function createAdminRouter(config, purchaseStore, store, webhookQueue) {
|
|
|
2442
2526
|
}
|
|
2443
2527
|
return router;
|
|
2444
2528
|
}
|
|
2529
|
+
var MS_PER_DAY = 864e5;
|
|
2530
|
+
function bump(map, key) {
|
|
2531
|
+
map[key] = (map[key] ?? 0) + 1;
|
|
2532
|
+
}
|
|
2533
|
+
function utcDateKey(ms) {
|
|
2534
|
+
const d = new Date(ms);
|
|
2535
|
+
const yyyy = d.getUTCFullYear();
|
|
2536
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
2537
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
2538
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
2539
|
+
}
|
|
2540
|
+
function emptyDailyBuckets(fromMs, toMs) {
|
|
2541
|
+
const out = [];
|
|
2542
|
+
const start = new Date(fromMs);
|
|
2543
|
+
start.setUTCHours(0, 0, 0, 0);
|
|
2544
|
+
let cur = start.getTime();
|
|
2545
|
+
while (cur <= toMs) {
|
|
2546
|
+
out.push({ date: utcDateKey(cur), count: 0 });
|
|
2547
|
+
cur += MS_PER_DAY;
|
|
2548
|
+
}
|
|
2549
|
+
return out;
|
|
2550
|
+
}
|
|
2551
|
+
function aggregateRange(records, opts) {
|
|
2552
|
+
const byProduct = {};
|
|
2553
|
+
const byPlatform = {};
|
|
2554
|
+
let total = 0;
|
|
2555
|
+
const buckets = opts.groupBy === "day" ? emptyDailyBuckets(opts.fromMs, opts.toMs) : null;
|
|
2556
|
+
const bucketIndex = buckets ? new Map(buckets.map((b, i) => [b.date, i])) : null;
|
|
2557
|
+
for (const record of records) {
|
|
2558
|
+
if (opts.include && !opts.include(record)) continue;
|
|
2559
|
+
const anchorMs = new Date(opts.anchor(record)).getTime();
|
|
2560
|
+
if (Number.isNaN(anchorMs)) continue;
|
|
2561
|
+
if (anchorMs < opts.fromMs || anchorMs > opts.toMs) continue;
|
|
2562
|
+
total++;
|
|
2563
|
+
bump(byProduct, record.productId);
|
|
2564
|
+
bump(byPlatform, record.platform);
|
|
2565
|
+
if (buckets && bucketIndex) {
|
|
2566
|
+
const idx = bucketIndex.get(utcDateKey(anchorMs));
|
|
2567
|
+
if (idx !== void 0) buckets[idx].count++;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
return { total, byProduct, byPlatform, ...buckets ? { buckets } : {} };
|
|
2571
|
+
}
|
|
2572
|
+
function isActiveSubscription(sub, nowMs) {
|
|
2573
|
+
const statusAllows = sub.status === SUBSCRIPTION_STATUS.ACTIVE || sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD;
|
|
2574
|
+
return statusAllows && new Date(sub.expiresAt).getTime() > nowMs;
|
|
2575
|
+
}
|
|
2576
|
+
function isEndedSubscription(sub) {
|
|
2577
|
+
return sub.status === SUBSCRIPTION_STATUS.EXPIRED || sub.status === SUBSCRIPTION_STATUS.CANCELED;
|
|
2578
|
+
}
|
|
2579
|
+
function isNonConsumable(purchase) {
|
|
2580
|
+
return purchase.type === PURCHASE_TYPE.NON_CONSUMABLE;
|
|
2581
|
+
}
|
|
2582
|
+
function aggregateActive(subs, purchases, nowMs) {
|
|
2583
|
+
const byProduct = {};
|
|
2584
|
+
const byProductPurchases = {};
|
|
2585
|
+
const byPlatform = {};
|
|
2586
|
+
let activeSubscriptions = 0;
|
|
2587
|
+
let gracePeriodSubscriptions = 0;
|
|
2588
|
+
let nonConsumablePurchases = 0;
|
|
2589
|
+
for (const sub of subs) {
|
|
2590
|
+
if (!isActiveSubscription(sub, nowMs)) continue;
|
|
2591
|
+
activeSubscriptions++;
|
|
2592
|
+
if (sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD) gracePeriodSubscriptions++;
|
|
2593
|
+
bump(byProduct, sub.productId);
|
|
2594
|
+
bump(byPlatform, sub.platform);
|
|
2595
|
+
}
|
|
2596
|
+
for (const purchase of purchases) {
|
|
2597
|
+
if (!isNonConsumable(purchase)) continue;
|
|
2598
|
+
nonConsumablePurchases++;
|
|
2599
|
+
bump(byProductPurchases, purchase.productId);
|
|
2600
|
+
bump(byPlatform, purchase.platform);
|
|
2601
|
+
}
|
|
2602
|
+
return {
|
|
2603
|
+
total: activeSubscriptions + nonConsumablePurchases,
|
|
2604
|
+
activeSubscriptions,
|
|
2605
|
+
gracePeriodSubscriptions,
|
|
2606
|
+
nonConsumablePurchases,
|
|
2607
|
+
byProduct,
|
|
2608
|
+
byProductPurchases,
|
|
2609
|
+
byPlatform
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
// src/metrics-cache.ts
|
|
2614
|
+
function isDisabled(ttlSeconds) {
|
|
2615
|
+
return !Number.isFinite(ttlSeconds) || ttlSeconds <= 0;
|
|
2616
|
+
}
|
|
2617
|
+
function createMetricsCache(ttlSeconds, storage = new InMemoryCacheAdapter()) {
|
|
2618
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
2619
|
+
const gridMs = Math.max(1, Math.floor(ttlSeconds * 1e3));
|
|
2620
|
+
return {
|
|
2621
|
+
quantizeToWindow(ms) {
|
|
2622
|
+
if (isDisabled(ttlSeconds)) return ms;
|
|
2623
|
+
return Math.floor(ms / gridMs) * gridMs;
|
|
2624
|
+
},
|
|
2625
|
+
async resolve(keyParts, produce) {
|
|
2626
|
+
if (isDisabled(ttlSeconds)) return produce();
|
|
2627
|
+
const key = `metrics:${keyParts.join(":")}`;
|
|
2628
|
+
const cache = storage;
|
|
2629
|
+
const hit = await cache.get(key);
|
|
2630
|
+
if (hit !== null && hit !== void 0) return hit;
|
|
2631
|
+
const pending = inflight.get(key);
|
|
2632
|
+
if (pending) return pending;
|
|
2633
|
+
const promise = (async () => {
|
|
2634
|
+
const value = await produce();
|
|
2635
|
+
await cache.set(key, value, ttlSeconds);
|
|
2636
|
+
return value;
|
|
2637
|
+
})();
|
|
2638
|
+
inflight.set(key, promise);
|
|
2639
|
+
try {
|
|
2640
|
+
return await promise;
|
|
2641
|
+
} finally {
|
|
2642
|
+
inflight.delete(key);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// src/routes/metrics.ts
|
|
2445
2649
|
var ADMIN_SECRET_HEADER2 = "x-admin-secret";
|
|
2650
|
+
var DEFAULT_METRICS_CACHE_TTL_SECONDS = 30;
|
|
2446
2651
|
function createMetricsRouter(config, store, purchaseStore) {
|
|
2447
2652
|
if (!config.adminSecret) return null;
|
|
2448
2653
|
const router = Router();
|
|
2449
2654
|
const adminSecret = config.adminSecret;
|
|
2655
|
+
const metricsCache = createMetricsCache(
|
|
2656
|
+
config.metricsCacheTtlSeconds ?? DEFAULT_METRICS_CACHE_TTL_SECONDS
|
|
2657
|
+
);
|
|
2450
2658
|
router.use("/onesub/metrics", (req, res, next) => {
|
|
2451
2659
|
const provided = req.headers[ADMIN_SECRET_HEADER2];
|
|
2452
2660
|
if (typeof provided !== "string" || !secretsEqual(provided, adminSecret)) {
|
|
@@ -2455,50 +2663,15 @@ function createMetricsRouter(config, store, purchaseStore) {
|
|
|
2455
2663
|
}
|
|
2456
2664
|
next();
|
|
2457
2665
|
});
|
|
2458
|
-
const isActiveSub = (sub, now) => {
|
|
2459
|
-
const statusAllows = sub.status === SUBSCRIPTION_STATUS.ACTIVE || sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD;
|
|
2460
|
-
return statusAllows && new Date(sub.expiresAt).getTime() > now;
|
|
2461
|
-
};
|
|
2462
|
-
const bump = (map, key) => {
|
|
2463
|
-
map[key] = (map[key] ?? 0) + 1;
|
|
2464
|
-
};
|
|
2465
2666
|
router.get(ROUTES.METRICS_ACTIVE, async (_req, res) => {
|
|
2466
2667
|
try {
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
const byProduct = {};
|
|
2473
|
-
const byProductPurchases = {};
|
|
2474
|
-
const byPlatform = {};
|
|
2475
|
-
let activeSubscriptions = 0;
|
|
2476
|
-
let gracePeriodSubscriptions = 0;
|
|
2477
|
-
let nonConsumablePurchases = 0;
|
|
2478
|
-
for (const sub of subs) {
|
|
2479
|
-
if (!isActiveSub(sub, now)) continue;
|
|
2480
|
-
activeSubscriptions++;
|
|
2481
|
-
if (sub.status === SUBSCRIPTION_STATUS.GRACE_PERIOD) {
|
|
2482
|
-
gracePeriodSubscriptions++;
|
|
2668
|
+
const response = await metricsCache.resolve(
|
|
2669
|
+
["active", metricsCache.quantizeToWindow(Date.now())],
|
|
2670
|
+
async () => {
|
|
2671
|
+
const [subs, purchases] = await Promise.all([store.listAll(), purchaseStore.listAll()]);
|
|
2672
|
+
return aggregateActive(subs, purchases, Date.now());
|
|
2483
2673
|
}
|
|
2484
|
-
|
|
2485
|
-
bump(byPlatform, sub.platform);
|
|
2486
|
-
}
|
|
2487
|
-
for (const p of purchases) {
|
|
2488
|
-
if (p.type !== PURCHASE_TYPE.NON_CONSUMABLE) continue;
|
|
2489
|
-
nonConsumablePurchases++;
|
|
2490
|
-
bump(byProductPurchases, p.productId);
|
|
2491
|
-
bump(byPlatform, p.platform);
|
|
2492
|
-
}
|
|
2493
|
-
const response = {
|
|
2494
|
-
total: activeSubscriptions + nonConsumablePurchases,
|
|
2495
|
-
activeSubscriptions,
|
|
2496
|
-
gracePeriodSubscriptions,
|
|
2497
|
-
nonConsumablePurchases,
|
|
2498
|
-
byProduct,
|
|
2499
|
-
byProductPurchases,
|
|
2500
|
-
byPlatform
|
|
2501
|
-
};
|
|
2674
|
+
);
|
|
2502
2675
|
res.status(200).json(response);
|
|
2503
2676
|
} catch (err2) {
|
|
2504
2677
|
log.error("[onesub/metrics/active] error:", err2);
|
|
@@ -2529,140 +2702,63 @@ function createMetricsRouter(config, store, purchaseStore) {
|
|
|
2529
2702
|
}
|
|
2530
2703
|
return { fromMs, toMs, groupBy };
|
|
2531
2704
|
}
|
|
2532
|
-
function
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
}
|
|
2539
|
-
function emptyDailyBuckets(fromMs, toMs) {
|
|
2540
|
-
const out = [];
|
|
2541
|
-
const start = new Date(fromMs);
|
|
2542
|
-
start.setUTCHours(0, 0, 0, 0);
|
|
2543
|
-
let cur = start.getTime();
|
|
2544
|
-
while (cur <= toMs) {
|
|
2545
|
-
out.push({ date: utcDateKey(cur), count: 0 });
|
|
2546
|
-
cur += 864e5;
|
|
2547
|
-
}
|
|
2548
|
-
return out;
|
|
2549
|
-
}
|
|
2550
|
-
router.get(ROUTES.METRICS_STARTED, async (req, res) => {
|
|
2551
|
-
const range = parseRange(req);
|
|
2552
|
-
if ("error" in range) {
|
|
2553
|
-
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, range.error);
|
|
2554
|
-
return;
|
|
2555
|
-
}
|
|
2556
|
-
try {
|
|
2557
|
-
const subs = await store.listAll();
|
|
2558
|
-
const byProduct = {};
|
|
2559
|
-
const byPlatform = {};
|
|
2560
|
-
let total = 0;
|
|
2561
|
-
const buckets = range.groupBy === "day" ? emptyDailyBuckets(range.fromMs, range.toMs) : null;
|
|
2562
|
-
const bucketIndex = buckets ? new Map(buckets.map((b, i) => [b.date, i])) : null;
|
|
2563
|
-
for (const sub of subs) {
|
|
2564
|
-
const purchasedMs = new Date(sub.purchasedAt).getTime();
|
|
2565
|
-
if (purchasedMs < range.fromMs || purchasedMs > range.toMs) continue;
|
|
2566
|
-
total++;
|
|
2567
|
-
bump(byProduct, sub.productId);
|
|
2568
|
-
bump(byPlatform, sub.platform);
|
|
2569
|
-
if (buckets && bucketIndex) {
|
|
2570
|
-
const idx = bucketIndex.get(utcDateKey(purchasedMs));
|
|
2571
|
-
if (idx !== void 0) buckets[idx].count++;
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
|
-
const response = {
|
|
2575
|
-
from: req.query["from"],
|
|
2576
|
-
to: req.query["to"],
|
|
2577
|
-
total,
|
|
2578
|
-
byProduct,
|
|
2579
|
-
byPlatform,
|
|
2580
|
-
...buckets ? { buckets } : {}
|
|
2581
|
-
};
|
|
2582
|
-
res.status(200).json(response);
|
|
2583
|
-
} catch (err2) {
|
|
2584
|
-
log.error("[onesub/metrics/started] error:", err2);
|
|
2585
|
-
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2586
|
-
}
|
|
2587
|
-
});
|
|
2588
|
-
router.get(ROUTES.METRICS_EXPIRED, async (req, res) => {
|
|
2589
|
-
const range = parseRange(req);
|
|
2590
|
-
if ("error" in range) {
|
|
2591
|
-
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, range.error);
|
|
2592
|
-
return;
|
|
2593
|
-
}
|
|
2594
|
-
try {
|
|
2595
|
-
const subs = await store.listAll();
|
|
2596
|
-
const byProduct = {};
|
|
2597
|
-
const byPlatform = {};
|
|
2598
|
-
let total = 0;
|
|
2599
|
-
const buckets = range.groupBy === "day" ? emptyDailyBuckets(range.fromMs, range.toMs) : null;
|
|
2600
|
-
const bucketIndex = buckets ? new Map(buckets.map((b, i) => [b.date, i])) : null;
|
|
2601
|
-
for (const sub of subs) {
|
|
2602
|
-
if (sub.status !== SUBSCRIPTION_STATUS.EXPIRED && sub.status !== SUBSCRIPTION_STATUS.CANCELED) continue;
|
|
2603
|
-
const expiredMs = new Date(sub.expiresAt).getTime();
|
|
2604
|
-
if (expiredMs < range.fromMs || expiredMs > range.toMs) continue;
|
|
2605
|
-
total++;
|
|
2606
|
-
bump(byProduct, sub.productId);
|
|
2607
|
-
bump(byPlatform, sub.platform);
|
|
2608
|
-
if (buckets && bucketIndex) {
|
|
2609
|
-
const idx = bucketIndex.get(utcDateKey(expiredMs));
|
|
2610
|
-
if (idx !== void 0) buckets[idx].count++;
|
|
2611
|
-
}
|
|
2705
|
+
function windowed(name, load, anchor, include) {
|
|
2706
|
+
return async (req, res) => {
|
|
2707
|
+
const range = parseRange(req);
|
|
2708
|
+
if ("error" in range) {
|
|
2709
|
+
sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, range.error);
|
|
2710
|
+
return;
|
|
2612
2711
|
}
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
const buckets = range.groupBy === "day" ? emptyDailyBuckets(range.fromMs, range.toMs) : null;
|
|
2639
|
-
const bucketIndex = buckets ? new Map(buckets.map((b, i) => [b.date, i])) : null;
|
|
2640
|
-
for (const p of purchases) {
|
|
2641
|
-
if (p.type !== PURCHASE_TYPE.NON_CONSUMABLE) continue;
|
|
2642
|
-
const purchasedMs = new Date(p.purchasedAt).getTime();
|
|
2643
|
-
if (purchasedMs < range.fromMs || purchasedMs > range.toMs) continue;
|
|
2644
|
-
total++;
|
|
2645
|
-
bump(byProduct, p.productId);
|
|
2646
|
-
bump(byPlatform, p.platform);
|
|
2647
|
-
if (buckets && bucketIndex) {
|
|
2648
|
-
const idx = bucketIndex.get(utcDateKey(purchasedMs));
|
|
2649
|
-
if (idx !== void 0) buckets[idx].count++;
|
|
2650
|
-
}
|
|
2712
|
+
try {
|
|
2713
|
+
const aggregate = await metricsCache.resolve(
|
|
2714
|
+
[
|
|
2715
|
+
name,
|
|
2716
|
+
metricsCache.quantizeToWindow(range.fromMs),
|
|
2717
|
+
metricsCache.quantizeToWindow(range.toMs),
|
|
2718
|
+
range.groupBy
|
|
2719
|
+
],
|
|
2720
|
+
async () => aggregateRange(await load(), {
|
|
2721
|
+
fromMs: range.fromMs,
|
|
2722
|
+
toMs: range.toMs,
|
|
2723
|
+
groupBy: range.groupBy,
|
|
2724
|
+
anchor,
|
|
2725
|
+
...include ? { include } : {}
|
|
2726
|
+
})
|
|
2727
|
+
);
|
|
2728
|
+
const response = {
|
|
2729
|
+
from: req.query["from"],
|
|
2730
|
+
to: req.query["to"],
|
|
2731
|
+
...aggregate
|
|
2732
|
+
};
|
|
2733
|
+
res.status(200).json(response);
|
|
2734
|
+
} catch (err2) {
|
|
2735
|
+
log.error(`[onesub/metrics/${name}] error:`, err2);
|
|
2736
|
+
sendError(res, 500, ONESUB_ERROR_CODE.STORE_ERROR, "Internal server error");
|
|
2651
2737
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
router.get(
|
|
2741
|
+
ROUTES.METRICS_STARTED,
|
|
2742
|
+
windowed("started", () => store.listAll(), (sub) => sub.purchasedAt)
|
|
2743
|
+
);
|
|
2744
|
+
router.get(
|
|
2745
|
+
ROUTES.METRICS_EXPIRED,
|
|
2746
|
+
windowed(
|
|
2747
|
+
"expired",
|
|
2748
|
+
() => store.listAll(),
|
|
2749
|
+
(sub) => sub.expiresAt,
|
|
2750
|
+
isEndedSubscription
|
|
2751
|
+
)
|
|
2752
|
+
);
|
|
2753
|
+
router.get(
|
|
2754
|
+
ROUTES.METRICS_PURCHASES_STARTED,
|
|
2755
|
+
windowed(
|
|
2756
|
+
"purchases-started",
|
|
2757
|
+
() => purchaseStore.listAll(),
|
|
2758
|
+
(purchase) => purchase.purchasedAt,
|
|
2759
|
+
isNonConsumable
|
|
2760
|
+
)
|
|
2761
|
+
);
|
|
2666
2762
|
return router;
|
|
2667
2763
|
}
|
|
2668
2764
|
var OFFER_SECRET_HEADER = "x-onesub-offer-secret";
|
|
@@ -4126,6 +4222,6 @@ if (isMain) {
|
|
|
4126
4222
|
});
|
|
4127
4223
|
}
|
|
4128
4224
|
|
|
4129
|
-
export { BullMQWebhookQueue, CacheWebhookEventStore, InMemoryCacheAdapter, InMemoryPurchaseStore, InMemorySubscriptionStore, InMemoryWebhookEventStore, InProcessWebhookQueue, ONESUB_OPENAPI, PostgresPurchaseStore, PostgresSubscriptionStore, RedisCacheAdapter, RedisPurchaseStore, RedisSubscriptionStore, RedisWebhookEventStore, createOneSubMiddleware, createOneSubServer, evaluateEntitlement, fetchAppleSubscriptionStatus, fetchAppleTransactionHistory, getDefaultCache, log, openapiHandler, processAppleNotification, processGoogleNotification, setDefaultCache, setLogger, signApplePromotionalOffer, unmarkWebhookEvent, validateAppleReceipt, validateGoogleReceipt, withSpan };
|
|
4225
|
+
export { BullMQWebhookQueue, CacheWebhookEventStore, InMemoryCacheAdapter, InMemoryPurchaseStore, InMemorySubscriptionStore, InMemoryWebhookEventStore, InProcessWebhookQueue, ONESUB_OPENAPI, PostgresPurchaseStore, PostgresSubscriptionStore, RedisCacheAdapter, RedisPurchaseStore, RedisSubscriptionStore, RedisWebhookEventStore, createOneSubMiddleware, createOneSubServer, evaluateEntitlement, evaluateEntitlementFrom, fetchAppleSubscriptionStatus, fetchAppleTransactionHistory, getDefaultCache, log, openapiHandler, processAppleNotification, processGoogleNotification, setDefaultCache, setLogger, signApplePromotionalOffer, unmarkWebhookEvent, validateAppleReceipt, validateGoogleReceipt, withSpan };
|
|
4130
4226
|
//# sourceMappingURL=index.js.map
|
|
4131
4227
|
//# sourceMappingURL=index.js.map
|