@licensr/sdk 0.1.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 ADDED
@@ -0,0 +1,143 @@
1
+ # @licensr/sdk
2
+
3
+ Official JavaScript/TypeScript SDK for the [Licensr](https://licensr.app) license API: validation, seat/domain activation, offline EdDSA token verification, and hosted checkout. Ships as dual ESM/CJS with bundled types, zero runtime dependencies beyond [`jose`](https://github.com/panva/jose) (for offline token verification).
4
+
5
+ See the [plugin integration guide](https://docs.licensr.app/plugin-integration) for the underlying API's concepts (activation modes, entitlement/fallback, rate limits, the embedded-key doctrine) — this README covers the JS-specific surface.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @licensr/sdk
11
+ ```
12
+
13
+ ## Quickstart
14
+
15
+ ```ts
16
+ import {LicensrClient} from '@licensr/sdk';
17
+
18
+ const client = new LicensrClient({
19
+ apiKey: 'pk_live_...', // safe to embed in a distributed binary/webview — see §6.1 of plugin-integration.md
20
+ pluginSlug: 'my-plugin',
21
+ });
22
+
23
+ const result = await client.validate({licenseKey: userEnteredKey});
24
+ if (result.valid) {
25
+ unlockFullFeatures();
26
+ } else if (result.entitlement === 'limited') {
27
+ unlockDegradedMode(); // perpetual-fallback: expired, but the plan grants a limited tier
28
+ }
29
+ ```
30
+
31
+ ## API
32
+
33
+ Every method returns a camelCase-mapped response and throws on failure — see [Errors](#errors).
34
+
35
+ | Method | Wraps | Notes |
36
+ | ------------------------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
37
+ | `client.validate({licenseKey})` | `POST /v1/license/validate` | Always read `valid`/`entitlement`; never branch on HTTP status — an unknown key returns `200 {valid: false}`, not `404`. |
38
+ | `client.token({licenseKey})` | `POST /v1/license/token` | Same check as `validate`, plus a short-lived signed offline token. See [Offline verification](#offline-verification). |
39
+ | `client.activate({licenseKey, activationType, identifier, label?})` | `POST /v1/license/activate` | `activationType` is `'seat'` (per-machine) or `'domain'`, fixed by the plugin's configured mode. |
40
+ | `client.deactivate({licenseKey, activationId})` | `POST /v1/license/deactivate` | Frees a seat/domain slot. |
41
+ | `client.activations({licenseKey})` | `GET /v1/license/activations` | Lists every current activation. |
42
+ | `client.checkout({planId, customerEmail, successUrl?, cancelUrl?})` | `POST /v1/billing/checkout` | Returns `checkoutUrl` — redirect the user there. Requires a key with the `checkout` scope. |
43
+
44
+ ### Client options
45
+
46
+ ```ts
47
+ new LicensrClient({
48
+ apiKey: 'pk_live_...',
49
+ pluginSlug: 'my-plugin',
50
+ baseUrl: 'https://api.licensr.app', // default; override for self-hosted/staging
51
+ deviceId: stableHwid, // buckets rate limits per installation instead of per key — reuse your activate() identifier
52
+ origin: 'app://my-plugin', // non-browser runtimes only; native SDKs should set the plugin's client_kind to "native" instead
53
+ timeoutMs: 10_000,
54
+ retry: {maxRetries: 2, baseDelayMs: 300, maxDelayMs: 5000}, // network errors / 429 / 5xx, exponential backoff + jitter, honors Retry-After
55
+ });
56
+ ```
57
+
58
+ ### Events
59
+
60
+ ```ts
61
+ client.events.on('validated', (result) => console.log('validated', result));
62
+ client.events.on('retry', ({method, attempt, delayMs}) =>
63
+ console.log(`retrying ${method}, attempt ${attempt} in ${delayMs}ms`)
64
+ );
65
+ client.events.on('error', ({method, error}) => reportToCrashlytics(method, error));
66
+ ```
67
+
68
+ Available events: `validated`, `activated`, `deactivated`, `tokenIssued`, `retry`, `error`.
69
+
70
+ ### Offline verification
71
+
72
+ `client.token()` mints an EdDSA-signed JWT whose claims mirror `validate()`'s response. Verify it fully offline (no network beyond the first JWKS fetch, which is cached for the process lifetime):
73
+
74
+ ```ts
75
+ import {verifyOfflineToken} from '@licensr/sdk';
76
+
77
+ const {token, jwksUrl} = await client.token({licenseKey});
78
+ const claims = await verifyOfflineToken(token, jwksUrl);
79
+ // claims.valid, claims.entitlement, claims.exp, ...
80
+ ```
81
+
82
+ Offline tokens carry no revocation signal — they prove the token was genuinely issued and hasn't expired, not that the license is still active _right now_. Re-validate online before `expiresAt`.
83
+
84
+ To boot fully offline (no network at all, e.g. on first launch before any successful `/token` call), persist the last-known-good token yourself using the `OfflineTokenStore` interface (a `InMemoryOfflineTokenStore` is included, but doesn't survive a restart — back it with `localStorage`, a config file, or your platform's keychain):
85
+
86
+ ```ts
87
+ import {InMemoryOfflineTokenStore, verifyOfflineToken} from '@licensr/sdk';
88
+
89
+ const store = new InMemoryOfflineTokenStore(); // swap for your own persistent implementation
90
+
91
+ const cached = store.get(licenseKey);
92
+ if (cached) {
93
+ try {
94
+ await verifyOfflineToken(cached.token, jwksUrl); // still valid — usable while offline
95
+ } catch {
96
+ /* expired or tampered — fall through to an online check */
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Errors
102
+
103
+ | Class | When |
104
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
105
+ | `LicensrApiError` | Non-2xx response. Has `status`, `code` (see `LicensrErrorCode` — matches `contract/conformance.yaml`), `message`, and `retryAfterSeconds`. Branch on `code`, not `message`. |
106
+ | `LicensrNetworkError` | The request never got a response (network failure, timeout, retries exhausted). |
107
+ | `LicensrTokenVerificationError` | `verifyOfflineToken` rejected a bad signature, wrong algorithm, or expired token. |
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ pnpm install
113
+ pnpm build # tsup → dist/ (ESM + CJS + .d.ts)
114
+ pnpm test # vitest
115
+ pnpm test:coverage
116
+ pnpm typecheck
117
+ pnpm lint
118
+ pnpm format
119
+ ```
120
+
121
+ ### Conformance suite
122
+
123
+ `test/conformance.test.ts` runs every case in [`contract/conformance.yaml`](../../contract/README.md) against a real, running backend — the same contract `backend/tests/test_conformance_contract.py` enforces on the Python side. It's skipped by default (no live backend in a normal `pnpm test` run); to run it locally:
124
+
125
+ ```bash
126
+ # 1. Start Postgres + migrate (from repo root)
127
+ docker compose up -d postgres
128
+ cd backend && APP_ENV=dev poetry run alembic upgrade head
129
+
130
+ # 2. Start the backend (BILLING_PROVIDER=mock avoids needing real Stripe creds)
131
+ APP_ENV=dev BILLING_PROVIDER=mock poetry run uvicorn main:app --port 8080
132
+
133
+ # 3. Seed fixtures for every case (in another shell)
134
+ poetry run python scripts/seed_conformance_fixtures.py --out /tmp/conformance_fixtures.json
135
+
136
+ # 4. Run the conformance suite (from sdks/js)
137
+ cd ../sdks/js
138
+ CONFORMANCE_BASE_URL=http://localhost:8080 \
139
+ CONFORMANCE_FIXTURES_PATH=/tmp/conformance_fixtures.json \
140
+ pnpm exec vitest run test/conformance.test.ts
141
+ ```
142
+
143
+ CI wires up the same four steps as one job — see `.github/workflows/test-sdk-js.yaml`.
package/dist/index.cjs ADDED
@@ -0,0 +1,413 @@
1
+ 'use strict';
2
+
3
+ var jose = require('jose');
4
+
5
+ // src/events.ts
6
+ var LicensrEventEmitter = class {
7
+ constructor() {
8
+ this.listeners = /* @__PURE__ */ new Map();
9
+ }
10
+ /** Returns an unsubscribe function. */
11
+ on(event, listener) {
12
+ let set = this.listeners.get(event);
13
+ if (!set) {
14
+ set = /* @__PURE__ */ new Set();
15
+ this.listeners.set(event, set);
16
+ }
17
+ set.add(listener);
18
+ return () => this.off(event, listener);
19
+ }
20
+ off(event, listener) {
21
+ this.listeners.get(event)?.delete(listener);
22
+ }
23
+ emit(event, payload) {
24
+ const set = this.listeners.get(event);
25
+ if (!set) return;
26
+ for (const listener of set) listener(payload);
27
+ }
28
+ };
29
+
30
+ // src/errors.ts
31
+ var LicensrApiError = class extends Error {
32
+ constructor(status, code, message, retryAfterSeconds = null) {
33
+ super(message);
34
+ this.name = "LicensrApiError";
35
+ this.status = status;
36
+ this.code = code;
37
+ this.retryAfterSeconds = retryAfterSeconds;
38
+ }
39
+ };
40
+ var LicensrNetworkError = class extends Error {
41
+ constructor(message, cause) {
42
+ super(message);
43
+ this.name = "LicensrNetworkError";
44
+ this.cause = cause;
45
+ }
46
+ };
47
+ var LicensrTokenVerificationError = class extends Error {
48
+ constructor(message) {
49
+ super(message);
50
+ this.name = "LicensrTokenVerificationError";
51
+ }
52
+ };
53
+
54
+ // src/http.ts
55
+ var DEVICE_ID_HEADER = "X-Licensr-Device-Id";
56
+ var DEFAULT_RETRY = {
57
+ maxRetries: 2,
58
+ baseDelayMs: 300,
59
+ maxDelayMs: 5e3
60
+ };
61
+ function buildUrl(baseUrl, path, query) {
62
+ const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
63
+ if (query) {
64
+ for (const [key, value] of Object.entries(query)) {
65
+ if (value !== void 0) url.searchParams.set(key, value);
66
+ }
67
+ }
68
+ return url.toString();
69
+ }
70
+ function sleep(ms) {
71
+ return new Promise((resolve) => setTimeout(resolve, ms));
72
+ }
73
+ function jitterDelayMs(attempt, baseDelayMs, maxDelayMs) {
74
+ const cap = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
75
+ return Math.random() * cap;
76
+ }
77
+ function parseRetryAfterSeconds(header) {
78
+ if (!header) return null;
79
+ const seconds = Number(header);
80
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
81
+ }
82
+ async function parseErrorDetail(response) {
83
+ let json;
84
+ try {
85
+ json = await response.json();
86
+ } catch {
87
+ return { code: "unknown_error", message: response.statusText || `HTTP ${response.status}` };
88
+ }
89
+ const detail = json?.detail;
90
+ if (detail && typeof detail === "object" && !Array.isArray(detail) && "error" in detail) {
91
+ const { error, message } = detail;
92
+ return {
93
+ code: typeof error === "string" ? error : "unknown_error",
94
+ message: typeof message === "string" ? message : response.statusText
95
+ };
96
+ }
97
+ if (Array.isArray(detail) && detail.length > 0) {
98
+ const first = detail[0];
99
+ return {
100
+ code: "validation_error",
101
+ message: typeof first?.msg === "string" ? first.msg : "Request validation failed"
102
+ };
103
+ }
104
+ return { code: "unknown_error", message: response.statusText || `HTTP ${response.status}` };
105
+ }
106
+ async function requestJson(config, spec) {
107
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch;
108
+ if (!fetchImpl) {
109
+ throw new LicensrNetworkError(
110
+ "No fetch implementation available. Pass `fetchImpl` explicitly on Node < 18.",
111
+ void 0
112
+ );
113
+ }
114
+ const retry = { ...DEFAULT_RETRY, ...config.retry };
115
+ const url = buildUrl(config.baseUrl, spec.path, spec.query);
116
+ for (let attempt = 0; ; attempt += 1) {
117
+ const headers = new Headers({ Authorization: `Bearer ${config.apiKey}` });
118
+ if (spec.body !== void 0) headers.set("Content-Type", "application/json");
119
+ if (config.deviceId) headers.set(DEVICE_ID_HEADER, config.deviceId);
120
+ if (config.origin) {
121
+ try {
122
+ headers.set("Origin", config.origin);
123
+ } catch {
124
+ }
125
+ }
126
+ const controller = config.timeoutMs !== void 0 ? new AbortController() : void 0;
127
+ const timeoutHandle = controller && config.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), config.timeoutMs) : void 0;
128
+ let response;
129
+ try {
130
+ response = await fetchImpl(url, {
131
+ method: spec.method,
132
+ headers,
133
+ body: spec.body !== void 0 ? JSON.stringify(spec.body) : void 0,
134
+ signal: controller?.signal
135
+ });
136
+ } catch (cause) {
137
+ if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
138
+ if (attempt < retry.maxRetries) {
139
+ const delayMs = jitterDelayMs(attempt, retry.baseDelayMs, retry.maxDelayMs);
140
+ spec.onRetry?.(attempt + 1, delayMs);
141
+ await sleep(delayMs);
142
+ continue;
143
+ }
144
+ throw new LicensrNetworkError(`Request to ${spec.path} failed after ${attempt + 1} attempt(s)`, cause);
145
+ }
146
+ if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
147
+ if (response.ok) {
148
+ if (response.status === 204) return void 0;
149
+ return await response.json();
150
+ }
151
+ const retriable = response.status === 429 || response.status >= 500;
152
+ if (retriable && attempt < retry.maxRetries) {
153
+ const retryAfterSeconds = parseRetryAfterSeconds(response.headers.get("Retry-After"));
154
+ const delayMs = retryAfterSeconds !== null ? retryAfterSeconds * 1e3 : jitterDelayMs(attempt, retry.baseDelayMs, retry.maxDelayMs);
155
+ spec.onRetry?.(attempt + 1, delayMs);
156
+ await sleep(delayMs);
157
+ continue;
158
+ }
159
+ const { code, message } = await parseErrorDetail(response);
160
+ throw new LicensrApiError(
161
+ response.status,
162
+ code,
163
+ message,
164
+ parseRetryAfterSeconds(response.headers.get("Retry-After"))
165
+ );
166
+ }
167
+ }
168
+
169
+ // src/wire.ts
170
+ function fromValidateWire(wire) {
171
+ return {
172
+ valid: wire.valid,
173
+ status: wire.status,
174
+ planId: wire.plan_id,
175
+ planName: wire.plan_name,
176
+ activationMode: wire.activation_mode,
177
+ maxSeats: wire.max_seats,
178
+ maxDomains: wire.max_domains,
179
+ seatsInUse: wire.seats_in_use,
180
+ expiresAt: wire.expires_at,
181
+ entitlement: wire.entitlement,
182
+ fallback: wire.fallback,
183
+ featureFlags: wire.feature_flags
184
+ };
185
+ }
186
+ function fromTokenWire(wire) {
187
+ return {
188
+ token: wire.token,
189
+ algorithm: wire.algorithm,
190
+ kid: wire.kid,
191
+ issuedAt: wire.issued_at,
192
+ expiresAt: wire.expires_at,
193
+ jwksUrl: wire.jwks_url
194
+ };
195
+ }
196
+ function fromActivateWire(wire) {
197
+ return {
198
+ activationId: wire.activation_id,
199
+ activationType: wire.activation_type,
200
+ identifier: wire.identifier,
201
+ featureFlags: wire.feature_flags
202
+ };
203
+ }
204
+ function fromDeactivateWire(wire) {
205
+ return { deactivated: wire.deactivated };
206
+ }
207
+ function fromActivationListItemWire(wire) {
208
+ return {
209
+ activationId: wire.activation_id,
210
+ activationType: wire.activation_type,
211
+ identifier: wire.identifier,
212
+ label: wire.label,
213
+ firstSeenAt: wire.first_seen_at,
214
+ lastSeenAt: wire.last_seen_at
215
+ };
216
+ }
217
+ function fromActivationsWire(wire) {
218
+ return {
219
+ licenseId: wire.license_id,
220
+ activations: wire.activations.map(fromActivationListItemWire)
221
+ };
222
+ }
223
+ function fromCheckoutWire(wire) {
224
+ return { checkoutUrl: wire.checkout_url };
225
+ }
226
+
227
+ // src/client.ts
228
+ var LicensrClient = class {
229
+ constructor(config) {
230
+ this.events = new LicensrEventEmitter();
231
+ this.pluginSlug = config.pluginSlug;
232
+ this.http = {
233
+ baseUrl: config.baseUrl ?? "https://api.licensr.app",
234
+ apiKey: config.apiKey,
235
+ origin: config.origin,
236
+ deviceId: config.deviceId,
237
+ fetchImpl: config.fetchImpl,
238
+ timeoutMs: config.timeoutMs,
239
+ retry: config.retry
240
+ };
241
+ }
242
+ /** Check whether a license is valid for this plugin, without minting an offline token. */
243
+ async validate(req) {
244
+ return this.run("validate", async () => {
245
+ const wire = await requestJson(this.http, {
246
+ method: "POST",
247
+ path: "v1/license/validate",
248
+ body: { license_key: req.licenseKey, plugin_slug: this.pluginSlug },
249
+ onRetry: this.onRetry("validate")
250
+ });
251
+ return fromValidateWire(wire);
252
+ });
253
+ }
254
+ /**
255
+ * Run the same check as {@link validate}, and on success mint a
256
+ * short-lived EdDSA-signed offline token. Verify it with
257
+ * {@link import('./offline.js').verifyOfflineToken} — no network call
258
+ * needed after the first successful fetch of the plugin's JWKS.
259
+ */
260
+ async token(req) {
261
+ return this.run("token", async () => {
262
+ const wire = await requestJson(this.http, {
263
+ method: "POST",
264
+ path: "v1/license/token",
265
+ body: { license_key: req.licenseKey, plugin_slug: this.pluginSlug },
266
+ onRetry: this.onRetry("token")
267
+ });
268
+ return fromTokenWire(wire);
269
+ });
270
+ }
271
+ /** Activate a seat (per-machine HWID) or domain for a license. */
272
+ async activate(req) {
273
+ return this.run("activate", async () => {
274
+ const wire = await requestJson(this.http, {
275
+ method: "POST",
276
+ path: "v1/license/activate",
277
+ body: {
278
+ license_key: req.licenseKey,
279
+ plugin_slug: this.pluginSlug,
280
+ activation_type: req.activationType,
281
+ identifier: req.identifier,
282
+ label: req.label ?? null
283
+ },
284
+ onRetry: this.onRetry("activate")
285
+ });
286
+ return fromActivateWire(wire);
287
+ });
288
+ }
289
+ /** Release a previously created activation, freeing its seat/domain slot. */
290
+ async deactivate(req) {
291
+ return this.run("deactivate", async () => {
292
+ const wire = await requestJson(this.http, {
293
+ method: "POST",
294
+ path: "v1/license/deactivate",
295
+ body: { license_key: req.licenseKey, activation_id: req.activationId },
296
+ onRetry: this.onRetry("deactivate")
297
+ });
298
+ return fromDeactivateWire(wire);
299
+ });
300
+ }
301
+ /** List every current activation for a license (seats/domains in use). */
302
+ async activations(req) {
303
+ return this.run("activations", async () => {
304
+ const wire = await requestJson(this.http, {
305
+ method: "GET",
306
+ path: "v1/license/activations",
307
+ query: { license_key: req.licenseKey, plugin_slug: this.pluginSlug },
308
+ onRetry: this.onRetry("activations")
309
+ });
310
+ return fromActivationsWire(wire);
311
+ });
312
+ }
313
+ /** Start a hosted checkout session for a plan; redirect the user to the returned URL. */
314
+ async checkout(req) {
315
+ return this.run("checkout", async () => {
316
+ const wire = await requestJson(this.http, {
317
+ method: "POST",
318
+ path: "v1/billing/checkout",
319
+ body: {
320
+ plan_id: req.planId,
321
+ customer_email: req.customerEmail,
322
+ success_url: req.successUrl ?? null,
323
+ cancel_url: req.cancelUrl ?? null
324
+ },
325
+ onRetry: this.onRetry("checkout")
326
+ });
327
+ return fromCheckoutWire(wire);
328
+ });
329
+ }
330
+ onRetry(method) {
331
+ return (attempt, delayMs) => this.events.emit("retry", { method, attempt, delayMs });
332
+ }
333
+ async run(method, fn) {
334
+ try {
335
+ const result = await fn();
336
+ this.emitResult(method, result);
337
+ return result;
338
+ } catch (error) {
339
+ this.events.emit("error", { method, error });
340
+ throw error;
341
+ }
342
+ }
343
+ emitResult(method, result) {
344
+ switch (method) {
345
+ case "validate":
346
+ this.events.emit("validated", result);
347
+ break;
348
+ case "activate":
349
+ this.events.emit("activated", result);
350
+ break;
351
+ case "deactivate":
352
+ this.events.emit("deactivated", result);
353
+ break;
354
+ case "token":
355
+ this.events.emit("tokenIssued", result);
356
+ break;
357
+ }
358
+ }
359
+ };
360
+ var jwksSetCache = /* @__PURE__ */ new Map();
361
+ function getJwks(jwksUrl, fetchImpl) {
362
+ let jwks = jwksSetCache.get(jwksUrl);
363
+ if (!jwks) {
364
+ jwks = jose.createRemoteJWKSet(
365
+ new URL(jwksUrl),
366
+ fetchImpl ? { [jose.customFetch]: (url, options) => fetchImpl(url, options) } : void 0
367
+ );
368
+ jwksSetCache.set(jwksUrl, jwks);
369
+ }
370
+ return jwks;
371
+ }
372
+ function clearJwksCache() {
373
+ jwksSetCache.clear();
374
+ }
375
+ async function verifyOfflineToken(token, jwksUrl, options = {}) {
376
+ const jwks = getJwks(jwksUrl, options.fetchImpl);
377
+ try {
378
+ const { payload } = await jose.jwtVerify(token, jwks, {
379
+ algorithms: ["EdDSA"],
380
+ clockTolerance: options.clockToleranceSeconds ?? 60
381
+ });
382
+ return payload;
383
+ } catch (cause) {
384
+ const reason = cause instanceof Error ? cause.message : String(cause);
385
+ throw new LicensrTokenVerificationError(`Offline token verification failed: ${reason}`);
386
+ }
387
+ }
388
+ var InMemoryOfflineTokenStore = class {
389
+ constructor() {
390
+ this.entries = /* @__PURE__ */ new Map();
391
+ }
392
+ get(licenseKey) {
393
+ return this.entries.get(licenseKey) ?? null;
394
+ }
395
+ set(licenseKey, entry) {
396
+ this.entries.set(licenseKey, entry);
397
+ }
398
+ clear(licenseKey) {
399
+ this.entries.delete(licenseKey);
400
+ }
401
+ };
402
+
403
+ exports.DEVICE_ID_HEADER = DEVICE_ID_HEADER;
404
+ exports.InMemoryOfflineTokenStore = InMemoryOfflineTokenStore;
405
+ exports.LicensrApiError = LicensrApiError;
406
+ exports.LicensrClient = LicensrClient;
407
+ exports.LicensrEventEmitter = LicensrEventEmitter;
408
+ exports.LicensrNetworkError = LicensrNetworkError;
409
+ exports.LicensrTokenVerificationError = LicensrTokenVerificationError;
410
+ exports.clearJwksCache = clearJwksCache;
411
+ exports.verifyOfflineToken = verifyOfflineToken;
412
+ //# sourceMappingURL=index.cjs.map
413
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/events.ts","../src/errors.ts","../src/http.ts","../src/wire.ts","../src/client.ts","../src/offline.ts"],"names":["createRemoteJWKSet","customFetch","jwtVerify"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {ActivateResponse, DeactivateResponse, TokenResponse, ValidateResponse} from './types.js';\n\n/** Lifecycle events emitted by {@link import('./client.js').LicensrClient}. */\nexport interface LicensrEventMap {\n validated: ValidateResponse;\n activated: ActivateResponse;\n deactivated: DeactivateResponse;\n tokenIssued: TokenResponse;\n /** Fired on every retried request, before the retry delay. */\n retry: {method: string; attempt: number; delayMs: number};\n /** Fired whenever a client method throws, right before the error propagates. */\n error: {method: string; error: unknown};\n}\n\ntype Listener<T> = (payload: T) => void;\n\n/**\n * Minimal, dependency-free typed event emitter for {@link LicensrEventMap}\n * — no Node `EventEmitter` import so the SDK stays usable unmodified in\n * browsers.\n */\nexport class LicensrEventEmitter {\n private readonly listeners = new Map<keyof LicensrEventMap, Set<Listener<never>>>();\n\n /** Returns an unsubscribe function. */\n on<K extends keyof LicensrEventMap>(event: K, listener: Listener<LicensrEventMap[K]>): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener as Listener<never>);\n return () => this.off(event, listener);\n }\n\n off<K extends keyof LicensrEventMap>(event: K, listener: Listener<LicensrEventMap[K]>): void {\n this.listeners.get(event)?.delete(listener as Listener<never>);\n }\n\n emit<K extends keyof LicensrEventMap>(event: K, payload: LicensrEventMap[K]): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const listener of set) (listener as Listener<LicensrEventMap[K]>)(payload);\n }\n}\n","import type {LicensrErrorCode} from './types.js';\n\n/**\n * The API responded with a non-2xx status and a parsed `detail.error` body.\n *\n * `code` is one of the documented error codes in `contract/conformance.yaml`\n * (e.g. `license_inactive`, `origin_not_allowed`, `rate_limit_exceeded`).\n * Branch on `code`, not on `message` — the message is for humans and may\n * change without notice.\n */\nexport class LicensrApiError extends Error {\n readonly status: number;\n readonly code: LicensrErrorCode;\n /** Present on 429 responses when the server sends `Retry-After`. */\n readonly retryAfterSeconds: number | null;\n\n constructor(status: number, code: LicensrErrorCode, message: string, retryAfterSeconds: number | null = null) {\n super(message);\n this.name = 'LicensrApiError';\n this.status = status;\n this.code = code;\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\n/** The request never got a response — network failure, timeout, or abort. */\nexport class LicensrNetworkError extends Error {\n readonly cause: unknown;\n\n constructor(message: string, cause: unknown) {\n super(message);\n this.name = 'LicensrNetworkError';\n this.cause = cause;\n }\n}\n\n/** An offline token failed EdDSA verification, was malformed, or has expired. */\nexport class LicensrTokenVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LicensrTokenVerificationError';\n }\n}\n","import {LicensrApiError, LicensrNetworkError} from './errors.js';\n\n/** SDKs already compute a stable per-machine HWID for activation — send it\n * here for free so the server buckets rate limits per *installation*\n * rather than per API key (see backend/services/rate_limit_key.py). */\nexport const DEVICE_ID_HEADER = 'X-Licensr-Device-Id';\n\nexport interface RetryOptions {\n /** Extra attempts after the first, on network errors / 429 / 5xx. Default 2. */\n maxRetries?: number;\n /** Base delay for exponential backoff with full jitter. Default 300ms. */\n baseDelayMs?: number;\n /** Ceiling for the backoff delay. Default 5000ms. */\n maxDelayMs?: number;\n}\n\nexport interface HttpClientConfig {\n baseUrl: string;\n apiKey: string;\n /**\n * Explicitly set the `Origin` header. Real browsers control this header\n * themselves — the fetch spec forbids scripts from overriding it, and\n * that's fine, since the browser already sends the caller's true origin.\n * This option exists for non-browser runtimes (Node, Electron main\n * process) that want the domain-origin guard (see docs/sdks) to see a\n * specific origin. Native/desktop plugins should instead set the\n * plugin's client type to \"native\" in the admin UI and leave this unset.\n */\n origin?: string;\n /** Stable per-install identifier — see {@link DEVICE_ID_HEADER}. */\n deviceId?: string;\n /** Defaults to `globalThis.fetch`. Override for testing or non-standard runtimes. */\n fetchImpl?: typeof fetch;\n /** Per-request timeout. Unset by default (no timeout). */\n timeoutMs?: number;\n retry?: RetryOptions;\n}\n\ninterface RequestSpec {\n method: 'GET' | 'POST';\n path: string;\n query?: Record<string, string | undefined>;\n body?: unknown;\n /** Called right before each retry's backoff delay — wired to the `retry` event in {@link import('./client.js').LicensrClient}. */\n onRetry?: (attempt: number, delayMs: number) => void;\n}\n\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n maxRetries: 2,\n baseDelayMs: 300,\n maxDelayMs: 5000,\n};\n\nfunction buildUrl(baseUrl: string, path: string, query?: Record<string, string | undefined>): string {\n const url = new URL(path, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) url.searchParams.set(key, value);\n }\n }\n return url.toString();\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Exponential backoff with full jitter (AWS-style): `random(0, min(max, base * 2^attempt))`. */\nfunction jitterDelayMs(attempt: number, baseDelayMs: number, maxDelayMs: number): number {\n const cap = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);\n return Math.random() * cap;\n}\n\nfunction parseRetryAfterSeconds(header: string | null): number | null {\n if (!header) return null;\n const seconds = Number(header);\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;\n}\n\nasync function parseErrorDetail(response: Response): Promise<{code: string; message: string}> {\n let json: unknown;\n try {\n json = await response.json();\n } catch {\n return {code: 'unknown_error', message: response.statusText || `HTTP ${response.status}`};\n }\n\n const detail = (json as Record<string, unknown> | null)?.detail;\n\n // `{\"detail\": {\"error\": \"...\", \"message\": \"...\"}}` — every hand-written\n // HTTPException in the license/billing routers uses this shape.\n if (detail && typeof detail === 'object' && !Array.isArray(detail) && 'error' in detail) {\n const {error, message} = detail as {error?: unknown; message?: unknown};\n return {\n code: typeof error === 'string' ? error : 'unknown_error',\n message: typeof message === 'string' ? message : response.statusText,\n };\n }\n\n // `{\"detail\": [{\"msg\": \"...\", \"loc\": [...]}, ...]}` — FastAPI/Pydantic's\n // own 422 shape for request validation errors, not a hand-written one.\n if (Array.isArray(detail) && detail.length > 0) {\n const first = detail[0] as {msg?: unknown} | undefined;\n return {\n code: 'validation_error',\n message: typeof first?.msg === 'string' ? first.msg : 'Request validation failed',\n };\n }\n\n return {code: 'unknown_error', message: response.statusText || `HTTP ${response.status}`};\n}\n\n/**\n * Perform one logical API call, retrying network errors / 429 / 5xx with\n * exponential backoff + jitter, and honouring a `Retry-After` header when\n * present. Throws {@link LicensrApiError} for any other non-2xx response,\n * or {@link LicensrNetworkError} once retries are exhausted.\n */\nexport async function requestJson<T>(config: HttpClientConfig, spec: RequestSpec): Promise<T> {\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n if (!fetchImpl) {\n throw new LicensrNetworkError(\n 'No fetch implementation available. Pass `fetchImpl` explicitly on Node < 18.',\n undefined\n );\n }\n const retry = {...DEFAULT_RETRY, ...config.retry};\n const url = buildUrl(config.baseUrl, spec.path, spec.query);\n\n for (let attempt = 0; ; attempt += 1) {\n const headers = new Headers({Authorization: `Bearer ${config.apiKey}`});\n if (spec.body !== undefined) headers.set('Content-Type', 'application/json');\n if (config.deviceId) headers.set(DEVICE_ID_HEADER, config.deviceId);\n if (config.origin) {\n try {\n headers.set('Origin', config.origin);\n } catch {\n // Forbidden header name in this runtime (real browsers) — the\n // browser already sends the caller's true Origin, so this is a\n // no-op rather than an error.\n }\n }\n\n const controller = config.timeoutMs !== undefined ? new AbortController() : undefined;\n const timeoutHandle =\n controller && config.timeoutMs !== undefined ? setTimeout(() => controller.abort(), config.timeoutMs) : undefined;\n\n let response: Response;\n try {\n response = await fetchImpl(url, {\n method: spec.method,\n headers,\n body: spec.body !== undefined ? JSON.stringify(spec.body) : undefined,\n signal: controller?.signal,\n });\n } catch (cause) {\n if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);\n if (attempt < retry.maxRetries) {\n const delayMs = jitterDelayMs(attempt, retry.baseDelayMs, retry.maxDelayMs);\n spec.onRetry?.(attempt + 1, delayMs);\n await sleep(delayMs);\n continue;\n }\n throw new LicensrNetworkError(`Request to ${spec.path} failed after ${attempt + 1} attempt(s)`, cause);\n }\n if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n const retriable = response.status === 429 || response.status >= 500;\n if (retriable && attempt < retry.maxRetries) {\n const retryAfterSeconds = parseRetryAfterSeconds(response.headers.get('Retry-After'));\n const delayMs =\n retryAfterSeconds !== null\n ? retryAfterSeconds * 1000\n : jitterDelayMs(attempt, retry.baseDelayMs, retry.maxDelayMs);\n spec.onRetry?.(attempt + 1, delayMs);\n await sleep(delayMs);\n continue;\n }\n\n const {code, message} = await parseErrorDetail(response);\n throw new LicensrApiError(\n response.status,\n code,\n message,\n parseRetryAfterSeconds(response.headers.get('Retry-After'))\n );\n }\n}\n","/**\n * snake_case wire shapes for the license API and their mappers to/from the\n * SDK's public camelCase types. Kept explicit (no generic deep-casing) so\n * opaque, plugin-defined maps like `featureFlags` are never mangled.\n */\nimport type {\n ActivateResponse,\n ActivationListItem,\n ActivationsResponse,\n ActivationType,\n CheckoutResponse,\n DeactivateResponse,\n Entitlement,\n LicenseStatus,\n TokenResponse,\n ValidateResponse,\n} from './types.js';\n\nexport interface ValidateResponseWire {\n valid: boolean;\n status: LicenseStatus;\n plan_id: string | null;\n plan_name: string | null;\n activation_mode: ActivationType | null;\n max_seats: number | null;\n max_domains: number | null;\n seats_in_use: number | null;\n expires_at: string | null;\n entitlement: Entitlement;\n fallback: boolean;\n feature_flags: Record<string, unknown> | null;\n}\n\nexport function fromValidateWire(wire: ValidateResponseWire): ValidateResponse {\n return {\n valid: wire.valid,\n status: wire.status,\n planId: wire.plan_id,\n planName: wire.plan_name,\n activationMode: wire.activation_mode,\n maxSeats: wire.max_seats,\n maxDomains: wire.max_domains,\n seatsInUse: wire.seats_in_use,\n expiresAt: wire.expires_at,\n entitlement: wire.entitlement,\n fallback: wire.fallback,\n featureFlags: wire.feature_flags,\n };\n}\n\nexport interface TokenResponseWire {\n token: string;\n algorithm: string;\n kid: string;\n issued_at: string;\n expires_at: string;\n jwks_url: string;\n}\n\nexport function fromTokenWire(wire: TokenResponseWire): TokenResponse {\n return {\n token: wire.token,\n algorithm: wire.algorithm,\n kid: wire.kid,\n issuedAt: wire.issued_at,\n expiresAt: wire.expires_at,\n jwksUrl: wire.jwks_url,\n };\n}\n\nexport interface ActivateResponseWire {\n activation_id: string;\n activation_type: ActivationType;\n identifier: string;\n feature_flags: Record<string, unknown> | null;\n}\n\nexport function fromActivateWire(wire: ActivateResponseWire): ActivateResponse {\n return {\n activationId: wire.activation_id,\n activationType: wire.activation_type,\n identifier: wire.identifier,\n featureFlags: wire.feature_flags,\n };\n}\n\nexport interface DeactivateResponseWire {\n deactivated: boolean;\n}\n\nexport function fromDeactivateWire(wire: DeactivateResponseWire): DeactivateResponse {\n return {deactivated: wire.deactivated};\n}\n\nexport interface ActivationListItemWire {\n activation_id: string;\n activation_type: ActivationType;\n identifier: string;\n label: string | null;\n first_seen_at: string;\n last_seen_at: string;\n}\n\nexport interface ActivationsResponseWire {\n license_id: string;\n activations: ActivationListItemWire[];\n}\n\nfunction fromActivationListItemWire(wire: ActivationListItemWire): ActivationListItem {\n return {\n activationId: wire.activation_id,\n activationType: wire.activation_type,\n identifier: wire.identifier,\n label: wire.label,\n firstSeenAt: wire.first_seen_at,\n lastSeenAt: wire.last_seen_at,\n };\n}\n\nexport function fromActivationsWire(wire: ActivationsResponseWire): ActivationsResponse {\n return {\n licenseId: wire.license_id,\n activations: wire.activations.map(fromActivationListItemWire),\n };\n}\n\nexport interface CheckoutResponseWire {\n checkout_url: string;\n}\n\nexport function fromCheckoutWire(wire: CheckoutResponseWire): CheckoutResponse {\n return {checkoutUrl: wire.checkout_url};\n}\n","import {LicensrEventEmitter} from './events.js';\nimport {requestJson, type HttpClientConfig, type RetryOptions} from './http.js';\nimport type {\n ActivateRequest,\n ActivateResponse,\n ActivationsResponse,\n CheckoutRequest,\n CheckoutResponse,\n DeactivateRequest,\n DeactivateResponse,\n TokenResponse,\n ValidateRequest,\n ValidateResponse,\n} from './types.js';\nimport {\n fromActivateWire,\n fromActivationsWire,\n fromCheckoutWire,\n fromDeactivateWire,\n fromTokenWire,\n fromValidateWire,\n type ActivateResponseWire,\n type ActivationsResponseWire,\n type CheckoutResponseWire,\n type DeactivateResponseWire,\n type TokenResponseWire,\n type ValidateResponseWire,\n} from './wire.js';\n\nexport interface LicensrClientConfig {\n /** Plugin API key (`pk_live_...` / `pk_test_...`) — see docs/plugin-integration.md. */\n apiKey: string;\n /** The plugin this key belongs to (matches the slug in the admin dashboard). */\n pluginSlug: string;\n /** Defaults to `https://api.licensr.app`. Override for self-hosted/staging. */\n baseUrl?: string;\n /** See {@link HttpClientConfig.origin}. */\n origin?: string;\n /** Stable per-install identifier, e.g. the same HWID used for `activate()`. Buckets rate limits per installation instead of per key. */\n deviceId?: string;\n /** Defaults to `globalThis.fetch`. */\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n retry?: RetryOptions;\n}\n\n/**\n * Thin client for the Licensr public license API\n * (`/v1/license/*`, `/v1/billing/checkout`).\n *\n * ```ts\n * const client = new LicensrClient({apiKey: 'pk_live_...', pluginSlug: 'my-plugin'});\n * const result = await client.validate({licenseKey});\n * if (result.valid) { ... }\n * ```\n *\n * Subscribe to lifecycle events via `client.events`:\n *\n * ```ts\n * client.events.on('error', ({method, error}) => log(method, error));\n * ```\n */\nexport class LicensrClient {\n readonly events = new LicensrEventEmitter();\n private readonly http: HttpClientConfig;\n private readonly pluginSlug: string;\n\n constructor(config: LicensrClientConfig) {\n this.pluginSlug = config.pluginSlug;\n this.http = {\n baseUrl: config.baseUrl ?? 'https://api.licensr.app',\n apiKey: config.apiKey,\n origin: config.origin,\n deviceId: config.deviceId,\n fetchImpl: config.fetchImpl,\n timeoutMs: config.timeoutMs,\n retry: config.retry,\n };\n }\n\n /** Check whether a license is valid for this plugin, without minting an offline token. */\n async validate(req: ValidateRequest): Promise<ValidateResponse> {\n return this.run('validate', async () => {\n const wire = await requestJson<ValidateResponseWire>(this.http, {\n method: 'POST',\n path: 'v1/license/validate',\n body: {license_key: req.licenseKey, plugin_slug: this.pluginSlug},\n onRetry: this.onRetry('validate'),\n });\n return fromValidateWire(wire);\n });\n }\n\n /**\n * Run the same check as {@link validate}, and on success mint a\n * short-lived EdDSA-signed offline token. Verify it with\n * {@link import('./offline.js').verifyOfflineToken} — no network call\n * needed after the first successful fetch of the plugin's JWKS.\n */\n async token(req: ValidateRequest): Promise<TokenResponse> {\n return this.run('token', async () => {\n const wire = await requestJson<TokenResponseWire>(this.http, {\n method: 'POST',\n path: 'v1/license/token',\n body: {license_key: req.licenseKey, plugin_slug: this.pluginSlug},\n onRetry: this.onRetry('token'),\n });\n return fromTokenWire(wire);\n });\n }\n\n /** Activate a seat (per-machine HWID) or domain for a license. */\n async activate(req: ActivateRequest): Promise<ActivateResponse> {\n return this.run('activate', async () => {\n const wire = await requestJson<ActivateResponseWire>(this.http, {\n method: 'POST',\n path: 'v1/license/activate',\n body: {\n license_key: req.licenseKey,\n plugin_slug: this.pluginSlug,\n activation_type: req.activationType,\n identifier: req.identifier,\n label: req.label ?? null,\n },\n onRetry: this.onRetry('activate'),\n });\n return fromActivateWire(wire);\n });\n }\n\n /** Release a previously created activation, freeing its seat/domain slot. */\n async deactivate(req: DeactivateRequest): Promise<DeactivateResponse> {\n return this.run('deactivate', async () => {\n const wire = await requestJson<DeactivateResponseWire>(this.http, {\n method: 'POST',\n path: 'v1/license/deactivate',\n body: {license_key: req.licenseKey, activation_id: req.activationId},\n onRetry: this.onRetry('deactivate'),\n });\n return fromDeactivateWire(wire);\n });\n }\n\n /** List every current activation for a license (seats/domains in use). */\n async activations(req: {licenseKey: string}): Promise<ActivationsResponse> {\n return this.run('activations', async () => {\n const wire = await requestJson<ActivationsResponseWire>(this.http, {\n method: 'GET',\n path: 'v1/license/activations',\n query: {license_key: req.licenseKey, plugin_slug: this.pluginSlug},\n onRetry: this.onRetry('activations'),\n });\n return fromActivationsWire(wire);\n });\n }\n\n /** Start a hosted checkout session for a plan; redirect the user to the returned URL. */\n async checkout(req: CheckoutRequest): Promise<CheckoutResponse> {\n return this.run('checkout', async () => {\n const wire = await requestJson<CheckoutResponseWire>(this.http, {\n method: 'POST',\n path: 'v1/billing/checkout',\n body: {\n plan_id: req.planId,\n customer_email: req.customerEmail,\n success_url: req.successUrl ?? null,\n cancel_url: req.cancelUrl ?? null,\n },\n onRetry: this.onRetry('checkout'),\n });\n return fromCheckoutWire(wire);\n });\n }\n\n private onRetry(method: string): (attempt: number, delayMs: number) => void {\n return (attempt, delayMs) => this.events.emit('retry', {method, attempt, delayMs});\n }\n\n private async run<T>(method: string, fn: () => Promise<T>): Promise<T> {\n try {\n const result = await fn();\n this.emitResult(method, result);\n return result;\n } catch (error) {\n this.events.emit('error', {method, error});\n throw error;\n }\n }\n\n private emitResult(method: string, result: unknown): void {\n switch (method) {\n case 'validate':\n this.events.emit('validated', result as ValidateResponse);\n break;\n case 'activate':\n this.events.emit('activated', result as ActivateResponse);\n break;\n case 'deactivate':\n this.events.emit('deactivated', result as DeactivateResponse);\n break;\n case 'token':\n this.events.emit('tokenIssued', result as TokenResponse);\n break;\n default:\n break;\n }\n }\n}\n","import {createRemoteJWKSet, customFetch, jwtVerify} from 'jose';\nimport {LicensrTokenVerificationError} from './errors.js';\nimport type {OfflineTokenClaims} from './types.js';\n\nexport interface VerifyOfflineTokenOptions {\n /** Override the fetch used for the JWKS lookup — mainly for testing. */\n fetchImpl?: typeof fetch;\n /** Reject if the token's `iat`/`nbf` are further in the future than this many seconds (clock skew tolerance). Default 60. */\n clockToleranceSeconds?: number;\n}\n\n// `createRemoteJWKSet` already caches keys in-memory with a cooldown, so\n// reusing one instance per JWKS URL for the process lifetime avoids an\n// extra fetch on every verification while still picking up key rotation.\nconst jwksSetCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(jwksUrl: string, fetchImpl?: typeof fetch): ReturnType<typeof createRemoteJWKSet> {\n let jwks = jwksSetCache.get(jwksUrl);\n if (!jwks) {\n jwks = createRemoteJWKSet(\n new URL(jwksUrl),\n fetchImpl ? {[customFetch]: (url, options) => fetchImpl(url, options as RequestInit)} : undefined\n );\n jwksSetCache.set(jwksUrl, jwks);\n }\n return jwks;\n}\n\n/** Drop cached JWKS lookups — call after rotating a plugin's signing key in tests, or to force a fresh fetch. */\nexport function clearJwksCache(): void {\n jwksSetCache.clear();\n}\n\n/**\n * Verify an offline license token (from {@link import('./client.js').LicensrClient.token})\n * fully offline: no network call beyond fetching (and caching) the plugin's\n * public JWKS. Throws {@link LicensrTokenVerificationError} on a bad\n * signature, wrong algorithm, or an expired/not-yet-valid token.\n *\n * Tokens carry no revocation signal — this only proves the token was\n * genuinely issued by Licensr and hasn't expired, not that the license is\n * still active right now. Re-validate online (`validate()`/`token()`)\n * before the token's `expiresAt`.\n */\nexport async function verifyOfflineToken(\n token: string,\n jwksUrl: string,\n options: VerifyOfflineTokenOptions = {}\n): Promise<OfflineTokenClaims> {\n const jwks = getJwks(jwksUrl, options.fetchImpl);\n try {\n const {payload} = await jwtVerify(token, jwks, {\n algorithms: ['EdDSA'],\n clockTolerance: options.clockToleranceSeconds ?? 60,\n });\n return payload as unknown as OfflineTokenClaims;\n } catch (cause) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n throw new LicensrTokenVerificationError(`Offline token verification failed: ${reason}`);\n }\n}\n\nexport interface CachedOfflineToken {\n token: string;\n claims: OfflineTokenClaims;\n /** `Date.now()` at the time this entry was cached. */\n cachedAtMs: number;\n}\n\n/**\n * Pluggable persistence for the last-known-good offline token per license,\n * so an app can boot fully offline (no network at all) before its first\n * successful `/token` call in a given session. The default\n * {@link InMemoryOfflineTokenStore} does not persist across process\n * restarts — provide your own (backed by `localStorage`, a config file,\n * OS keychain, etc.) for that.\n */\nexport interface OfflineTokenStore {\n get(licenseKey: string): CachedOfflineToken | null | Promise<CachedOfflineToken | null>;\n set(licenseKey: string, entry: CachedOfflineToken): void | Promise<void>;\n clear(licenseKey: string): void | Promise<void>;\n}\n\nexport class InMemoryOfflineTokenStore implements OfflineTokenStore {\n private readonly entries = new Map<string, CachedOfflineToken>();\n\n get(licenseKey: string): CachedOfflineToken | null {\n return this.entries.get(licenseKey) ?? null;\n }\n\n set(licenseKey: string, entry: CachedOfflineToken): void {\n this.entries.set(licenseKey, entry);\n }\n\n clear(licenseKey: string): void {\n this.entries.delete(licenseKey);\n }\n}\n"]}