@akira-io/billing-js 0.1.9 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/desktop/browser.ts","../src/desktop/fingerprint.ts","../src/desktop/keyring.ts","../src/desktop/session.ts","../src/desktop/use-auth.ts"],"names":["spawn","hostname","platform","createHash"],"mappings":"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","file":"desktop.cjs","sourcesContent":["import { spawn } from 'node:child_process';\n\n/** Launch the system default browser. Node-only. */\nexport function openBrowser(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n let cmd: string;\n let args: string[];\n switch (process.platform) {\n case 'darwin':\n cmd = 'open';\n args = [url];\n break;\n case 'win32':\n cmd = 'rundll32';\n args = ['url.dll,FileProtocolHandler', url];\n break;\n default:\n cmd = 'xdg-open';\n args = [url];\n }\n const child = spawn(cmd, args, { stdio: 'ignore', detached: true });\n child.once('error', reject);\n child.unref();\n resolve();\n });\n}\n","import { createHash } from 'node:crypto';\nimport { hostname, platform } from 'node:os';\n\nexport interface DeviceFingerprint {\n fingerprint: string;\n platform: string;\n app_version: string;\n}\n\nasync function machineId(): Promise<string> {\n const dyn = new Function('m', 'return import(m)') as (m: string) => Promise<unknown>;\n try {\n const mod = (await dyn('node-machine-id').catch(() => null)) as { machineIdSync?: (orig?: boolean) => string } | null;\n if (mod?.machineIdSync) return mod.machineIdSync(true);\n } catch {\n // fallthrough\n }\n return hostname();\n}\n\nexport async function deviceFingerprint(appVersion: string): Promise<DeviceFingerprint> {\n const id = await machineId();\n const plat = platform();\n const hash = createHash('sha256');\n hash.update(id);\n hash.update('::');\n hash.update(plat);\n hash.update('::');\n hash.update(appVersion);\n return {\n fingerprint: hash.digest('hex'),\n platform: plat,\n app_version: appVersion,\n };\n}\n","/**\n * Thin wrapper around `keytar` for desktop apps. Consumers install `keytar` as\n * an optional dependency; this module dynamically imports it so server-side\n * bundles never see it.\n */\nexport interface TokenKeyringOptions {\n service: string;\n account: string;\n}\n\ntype Keytar = {\n getPassword: (service: string, account: string) => Promise<string | null>;\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n};\n\nasync function loadKeytar(): Promise<Keytar> {\n // Use Function constructor so tsup/typescript don't try to resolve the\n // optional module at build time. Consumers add `keytar` as a peer/optional\n // dependency.\n const dyn = new Function('m', 'return import(m)') as (m: string) => Promise<unknown>;\n const mod = (await dyn('keytar').catch(() => null)) as { default?: Keytar } | null;\n if (!mod) throw new Error('TokenKeyring: install `keytar` to use the desktop keyring helper.');\n return mod.default ?? (mod as unknown as Keytar);\n}\n\nexport class TokenKeyring {\n private readonly service: string;\n private readonly account: string;\n\n constructor(opts: TokenKeyringOptions) {\n this.service = opts.service;\n this.account = opts.account;\n }\n\n async get(): Promise<string | null> {\n const k = await loadKeytar();\n return k.getPassword(this.service, this.account);\n }\n\n async set(value: string): Promise<void> {\n const k = await loadKeytar();\n await k.setPassword(this.service, this.account, value);\n }\n\n async delete(): Promise<void> {\n const k = await loadKeytar();\n await k.deletePassword(this.service, this.account);\n }\n}\n","import { BillingClient } from '../client';\nimport { TokenKeyring } from './keyring';\n\nexport class SessionStore {\n constructor(private readonly keyring: TokenKeyring) {}\n\n async hydrate(client: BillingClient): Promise<boolean> {\n const token = await this.keyring.get();\n if (!token) return false;\n client.setCustomerToken(token);\n return true;\n }\n\n async persist(client: BillingClient, token: string): Promise<void> {\n await this.keyring.set(token);\n client.setCustomerToken(token);\n }\n\n async clear(client: BillingClient): Promise<void> {\n await this.keyring.delete();\n client.setCustomerToken('');\n }\n\n async hasToken(): Promise<boolean> {\n const v = await this.keyring.get();\n return !!v;\n }\n}\n","import type { BillingClient } from '../client';\nimport type { Customer, OauthExchangeResponse } from '../client-types';\nimport type { LoopbackOutcome } from '../loopback';\nimport type { SessionStore } from './session';\n\nexport type AuthStatusState =\n | { state: 'loading' }\n | { state: 'guest' }\n | { state: 'authenticated'; customer: Customer; licensed: boolean };\n\nexport interface AuthControllerOptions {\n client: BillingClient;\n session: SessionStore;\n product: string;\n feature?: string;\n /** Loopback flow used by `oauthLogin`. */\n loopbackLogin: (provider: string) => Promise<LoopbackOutcome>;\n /** Override the runtime license check (e.g. cache, custom feature key). */\n checkLicense?: (client: BillingClient) => Promise<boolean>;\n}\n\nexport class AuthController {\n private listeners = new Set<(s: AuthStatusState) => void>();\n private current: AuthStatusState = { state: 'loading' };\n\n constructor(private readonly opts: AuthControllerOptions) {}\n\n subscribe(listener: (s: AuthStatusState) => void): () => void {\n this.listeners.add(listener);\n listener(this.current);\n return () => this.listeners.delete(listener);\n }\n\n snapshot(): AuthStatusState {\n return this.current;\n }\n\n private emit(next: AuthStatusState): void {\n this.current = next;\n this.listeners.forEach((l) => l(next));\n }\n\n async bootstrap(): Promise<void> {\n await this.opts.session.hydrate(this.opts.client);\n await this.refresh();\n }\n\n async refresh(): Promise<void> {\n if (!(await this.opts.session.hasToken())) {\n this.emit({ state: 'guest' });\n return;\n }\n try {\n const customer = await this.opts.client.customerMe();\n const licensed = this.opts.checkLicense\n ? await this.opts.checkLicense(this.opts.client)\n : await this.runDefaultLicenseCheck();\n this.emit({ state: 'authenticated', customer, licensed });\n } catch (e) {\n if ((e as { status?: number }).status === 401) {\n await this.opts.session.clear(this.opts.client);\n this.emit({ state: 'guest' });\n return;\n }\n throw e;\n }\n }\n\n private async runDefaultLicenseCheck(): Promise<boolean> {\n try {\n const resp = await this.opts.client.licenseCheck({\n product: this.opts.product,\n feature: this.opts.feature ?? 'general',\n });\n return resp.allowed;\n } catch {\n return false;\n }\n }\n\n async requestOtp(email: string, deviceFp?: string): Promise<void> {\n await this.opts.client.requestOtp({ email, device_fp: deviceFp ?? '' });\n }\n\n async verifyOtp(email: string, code: string, deviceFp?: string): Promise<Customer> {\n const resp = await this.opts.client.verifyOtp({\n email,\n code,\n device_fp: deviceFp ?? '',\n });\n await this.opts.session.persist(this.opts.client, resp.access_token);\n await this.refresh();\n return (await this.opts.client.customerMe());\n }\n\n async oauthLogin(provider: string): Promise<OauthExchangeResponse> {\n const outcome = await this.opts.loopbackLogin(provider);\n await this.opts.session.persist(this.opts.client, outcome.exchange.access_token);\n await this.refresh();\n return outcome.exchange;\n }\n\n async logout(): Promise<void> {\n await this.opts.session.clear(this.opts.client);\n this.emit({ state: 'guest' });\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ export { A as AuthController, a as AuthControllerOptions, b as AuthStatusState, S as SessionStore, T as TokenKeyring, c as TokenKeyringOptions } from './use-auth-BzN6Wcpm.cjs';
2
+ import './client.cjs';
3
+ import './client-types-CtRkHWna.cjs';
4
+ import './loopback.cjs';
5
+
6
+ /** Launch the system default browser. Node-only. */
7
+ declare function openBrowser(url: string): Promise<void>;
8
+
9
+ interface DeviceFingerprint {
10
+ fingerprint: string;
11
+ platform: string;
12
+ app_version: string;
13
+ }
14
+ declare function deviceFingerprint(appVersion: string): Promise<DeviceFingerprint>;
15
+
16
+ export { type DeviceFingerprint, deviceFingerprint, openBrowser };
@@ -0,0 +1,16 @@
1
+ export { A as AuthController, a as AuthControllerOptions, b as AuthStatusState, S as SessionStore, T as TokenKeyring, c as TokenKeyringOptions } from './use-auth-Bf4NrNdE.js';
2
+ import './client.js';
3
+ import './client-types-CtRkHWna.js';
4
+ import './loopback.js';
5
+
6
+ /** Launch the system default browser. Node-only. */
7
+ declare function openBrowser(url: string): Promise<void>;
8
+
9
+ interface DeviceFingerprint {
10
+ fingerprint: string;
11
+ platform: string;
12
+ app_version: string;
13
+ }
14
+ declare function deviceFingerprint(appVersion: string): Promise<DeviceFingerprint>;
15
+
16
+ export { type DeviceFingerprint, deviceFingerprint, openBrowser };
@@ -0,0 +1,188 @@
1
+ import { spawn } from 'child_process';
2
+ import { createHash } from 'crypto';
3
+ import { platform, hostname } from 'os';
4
+
5
+ // src/desktop/browser.ts
6
+ function openBrowser(url) {
7
+ return new Promise((resolve, reject) => {
8
+ let cmd;
9
+ let args;
10
+ switch (process.platform) {
11
+ case "darwin":
12
+ cmd = "open";
13
+ args = [url];
14
+ break;
15
+ case "win32":
16
+ cmd = "rundll32";
17
+ args = ["url.dll,FileProtocolHandler", url];
18
+ break;
19
+ default:
20
+ cmd = "xdg-open";
21
+ args = [url];
22
+ }
23
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
24
+ child.once("error", reject);
25
+ child.unref();
26
+ resolve();
27
+ });
28
+ }
29
+ async function machineId() {
30
+ const dyn = new Function("m", "return import(m)");
31
+ try {
32
+ const mod = await dyn("node-machine-id").catch(() => null);
33
+ if (mod?.machineIdSync) return mod.machineIdSync(true);
34
+ } catch {
35
+ }
36
+ return hostname();
37
+ }
38
+ async function deviceFingerprint(appVersion) {
39
+ const id = await machineId();
40
+ const plat = platform();
41
+ const hash = createHash("sha256");
42
+ hash.update(id);
43
+ hash.update("::");
44
+ hash.update(plat);
45
+ hash.update("::");
46
+ hash.update(appVersion);
47
+ return {
48
+ fingerprint: hash.digest("hex"),
49
+ platform: plat,
50
+ app_version: appVersion
51
+ };
52
+ }
53
+
54
+ // src/desktop/keyring.ts
55
+ async function loadKeytar() {
56
+ const dyn = new Function("m", "return import(m)");
57
+ const mod = await dyn("keytar").catch(() => null);
58
+ if (!mod) throw new Error("TokenKeyring: install `keytar` to use the desktop keyring helper.");
59
+ return mod.default ?? mod;
60
+ }
61
+ var TokenKeyring = class {
62
+ service;
63
+ account;
64
+ constructor(opts) {
65
+ this.service = opts.service;
66
+ this.account = opts.account;
67
+ }
68
+ async get() {
69
+ const k = await loadKeytar();
70
+ return k.getPassword(this.service, this.account);
71
+ }
72
+ async set(value) {
73
+ const k = await loadKeytar();
74
+ await k.setPassword(this.service, this.account, value);
75
+ }
76
+ async delete() {
77
+ const k = await loadKeytar();
78
+ await k.deletePassword(this.service, this.account);
79
+ }
80
+ };
81
+
82
+ // src/desktop/session.ts
83
+ var SessionStore = class {
84
+ constructor(keyring) {
85
+ this.keyring = keyring;
86
+ }
87
+ keyring;
88
+ async hydrate(client) {
89
+ const token = await this.keyring.get();
90
+ if (!token) return false;
91
+ client.setCustomerToken(token);
92
+ return true;
93
+ }
94
+ async persist(client, token) {
95
+ await this.keyring.set(token);
96
+ client.setCustomerToken(token);
97
+ }
98
+ async clear(client) {
99
+ await this.keyring.delete();
100
+ client.setCustomerToken("");
101
+ }
102
+ async hasToken() {
103
+ const v = await this.keyring.get();
104
+ return !!v;
105
+ }
106
+ };
107
+
108
+ // src/desktop/use-auth.ts
109
+ var AuthController = class {
110
+ constructor(opts) {
111
+ this.opts = opts;
112
+ }
113
+ opts;
114
+ listeners = /* @__PURE__ */ new Set();
115
+ current = { state: "loading" };
116
+ subscribe(listener) {
117
+ this.listeners.add(listener);
118
+ listener(this.current);
119
+ return () => this.listeners.delete(listener);
120
+ }
121
+ snapshot() {
122
+ return this.current;
123
+ }
124
+ emit(next) {
125
+ this.current = next;
126
+ this.listeners.forEach((l) => l(next));
127
+ }
128
+ async bootstrap() {
129
+ await this.opts.session.hydrate(this.opts.client);
130
+ await this.refresh();
131
+ }
132
+ async refresh() {
133
+ if (!await this.opts.session.hasToken()) {
134
+ this.emit({ state: "guest" });
135
+ return;
136
+ }
137
+ try {
138
+ const customer = await this.opts.client.customerMe();
139
+ const licensed = this.opts.checkLicense ? await this.opts.checkLicense(this.opts.client) : await this.runDefaultLicenseCheck();
140
+ this.emit({ state: "authenticated", customer, licensed });
141
+ } catch (e) {
142
+ if (e.status === 401) {
143
+ await this.opts.session.clear(this.opts.client);
144
+ this.emit({ state: "guest" });
145
+ return;
146
+ }
147
+ throw e;
148
+ }
149
+ }
150
+ async runDefaultLicenseCheck() {
151
+ try {
152
+ const resp = await this.opts.client.licenseCheck({
153
+ product: this.opts.product,
154
+ feature: this.opts.feature ?? "general"
155
+ });
156
+ return resp.allowed;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+ async requestOtp(email, deviceFp) {
162
+ await this.opts.client.requestOtp({ email, device_fp: deviceFp ?? "" });
163
+ }
164
+ async verifyOtp(email, code, deviceFp) {
165
+ const resp = await this.opts.client.verifyOtp({
166
+ email,
167
+ code,
168
+ device_fp: deviceFp ?? ""
169
+ });
170
+ await this.opts.session.persist(this.opts.client, resp.access_token);
171
+ await this.refresh();
172
+ return await this.opts.client.customerMe();
173
+ }
174
+ async oauthLogin(provider) {
175
+ const outcome = await this.opts.loopbackLogin(provider);
176
+ await this.opts.session.persist(this.opts.client, outcome.exchange.access_token);
177
+ await this.refresh();
178
+ return outcome.exchange;
179
+ }
180
+ async logout() {
181
+ await this.opts.session.clear(this.opts.client);
182
+ this.emit({ state: "guest" });
183
+ }
184
+ };
185
+
186
+ export { AuthController, SessionStore, TokenKeyring, deviceFingerprint, openBrowser };
187
+ //# sourceMappingURL=desktop.js.map
188
+ //# sourceMappingURL=desktop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/desktop/browser.ts","../src/desktop/fingerprint.ts","../src/desktop/keyring.ts","../src/desktop/session.ts","../src/desktop/use-auth.ts"],"names":[],"mappings":"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","file":"desktop.js","sourcesContent":["import { spawn } from 'node:child_process';\n\n/** Launch the system default browser. Node-only. */\nexport function openBrowser(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n let cmd: string;\n let args: string[];\n switch (process.platform) {\n case 'darwin':\n cmd = 'open';\n args = [url];\n break;\n case 'win32':\n cmd = 'rundll32';\n args = ['url.dll,FileProtocolHandler', url];\n break;\n default:\n cmd = 'xdg-open';\n args = [url];\n }\n const child = spawn(cmd, args, { stdio: 'ignore', detached: true });\n child.once('error', reject);\n child.unref();\n resolve();\n });\n}\n","import { createHash } from 'node:crypto';\nimport { hostname, platform } from 'node:os';\n\nexport interface DeviceFingerprint {\n fingerprint: string;\n platform: string;\n app_version: string;\n}\n\nasync function machineId(): Promise<string> {\n const dyn = new Function('m', 'return import(m)') as (m: string) => Promise<unknown>;\n try {\n const mod = (await dyn('node-machine-id').catch(() => null)) as { machineIdSync?: (orig?: boolean) => string } | null;\n if (mod?.machineIdSync) return mod.machineIdSync(true);\n } catch {\n // fallthrough\n }\n return hostname();\n}\n\nexport async function deviceFingerprint(appVersion: string): Promise<DeviceFingerprint> {\n const id = await machineId();\n const plat = platform();\n const hash = createHash('sha256');\n hash.update(id);\n hash.update('::');\n hash.update(plat);\n hash.update('::');\n hash.update(appVersion);\n return {\n fingerprint: hash.digest('hex'),\n platform: plat,\n app_version: appVersion,\n };\n}\n","/**\n * Thin wrapper around `keytar` for desktop apps. Consumers install `keytar` as\n * an optional dependency; this module dynamically imports it so server-side\n * bundles never see it.\n */\nexport interface TokenKeyringOptions {\n service: string;\n account: string;\n}\n\ntype Keytar = {\n getPassword: (service: string, account: string) => Promise<string | null>;\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n};\n\nasync function loadKeytar(): Promise<Keytar> {\n // Use Function constructor so tsup/typescript don't try to resolve the\n // optional module at build time. Consumers add `keytar` as a peer/optional\n // dependency.\n const dyn = new Function('m', 'return import(m)') as (m: string) => Promise<unknown>;\n const mod = (await dyn('keytar').catch(() => null)) as { default?: Keytar } | null;\n if (!mod) throw new Error('TokenKeyring: install `keytar` to use the desktop keyring helper.');\n return mod.default ?? (mod as unknown as Keytar);\n}\n\nexport class TokenKeyring {\n private readonly service: string;\n private readonly account: string;\n\n constructor(opts: TokenKeyringOptions) {\n this.service = opts.service;\n this.account = opts.account;\n }\n\n async get(): Promise<string | null> {\n const k = await loadKeytar();\n return k.getPassword(this.service, this.account);\n }\n\n async set(value: string): Promise<void> {\n const k = await loadKeytar();\n await k.setPassword(this.service, this.account, value);\n }\n\n async delete(): Promise<void> {\n const k = await loadKeytar();\n await k.deletePassword(this.service, this.account);\n }\n}\n","import { BillingClient } from '../client';\nimport { TokenKeyring } from './keyring';\n\nexport class SessionStore {\n constructor(private readonly keyring: TokenKeyring) {}\n\n async hydrate(client: BillingClient): Promise<boolean> {\n const token = await this.keyring.get();\n if (!token) return false;\n client.setCustomerToken(token);\n return true;\n }\n\n async persist(client: BillingClient, token: string): Promise<void> {\n await this.keyring.set(token);\n client.setCustomerToken(token);\n }\n\n async clear(client: BillingClient): Promise<void> {\n await this.keyring.delete();\n client.setCustomerToken('');\n }\n\n async hasToken(): Promise<boolean> {\n const v = await this.keyring.get();\n return !!v;\n }\n}\n","import type { BillingClient } from '../client';\nimport type { Customer, OauthExchangeResponse } from '../client-types';\nimport type { LoopbackOutcome } from '../loopback';\nimport type { SessionStore } from './session';\n\nexport type AuthStatusState =\n | { state: 'loading' }\n | { state: 'guest' }\n | { state: 'authenticated'; customer: Customer; licensed: boolean };\n\nexport interface AuthControllerOptions {\n client: BillingClient;\n session: SessionStore;\n product: string;\n feature?: string;\n /** Loopback flow used by `oauthLogin`. */\n loopbackLogin: (provider: string) => Promise<LoopbackOutcome>;\n /** Override the runtime license check (e.g. cache, custom feature key). */\n checkLicense?: (client: BillingClient) => Promise<boolean>;\n}\n\nexport class AuthController {\n private listeners = new Set<(s: AuthStatusState) => void>();\n private current: AuthStatusState = { state: 'loading' };\n\n constructor(private readonly opts: AuthControllerOptions) {}\n\n subscribe(listener: (s: AuthStatusState) => void): () => void {\n this.listeners.add(listener);\n listener(this.current);\n return () => this.listeners.delete(listener);\n }\n\n snapshot(): AuthStatusState {\n return this.current;\n }\n\n private emit(next: AuthStatusState): void {\n this.current = next;\n this.listeners.forEach((l) => l(next));\n }\n\n async bootstrap(): Promise<void> {\n await this.opts.session.hydrate(this.opts.client);\n await this.refresh();\n }\n\n async refresh(): Promise<void> {\n if (!(await this.opts.session.hasToken())) {\n this.emit({ state: 'guest' });\n return;\n }\n try {\n const customer = await this.opts.client.customerMe();\n const licensed = this.opts.checkLicense\n ? await this.opts.checkLicense(this.opts.client)\n : await this.runDefaultLicenseCheck();\n this.emit({ state: 'authenticated', customer, licensed });\n } catch (e) {\n if ((e as { status?: number }).status === 401) {\n await this.opts.session.clear(this.opts.client);\n this.emit({ state: 'guest' });\n return;\n }\n throw e;\n }\n }\n\n private async runDefaultLicenseCheck(): Promise<boolean> {\n try {\n const resp = await this.opts.client.licenseCheck({\n product: this.opts.product,\n feature: this.opts.feature ?? 'general',\n });\n return resp.allowed;\n } catch {\n return false;\n }\n }\n\n async requestOtp(email: string, deviceFp?: string): Promise<void> {\n await this.opts.client.requestOtp({ email, device_fp: deviceFp ?? '' });\n }\n\n async verifyOtp(email: string, code: string, deviceFp?: string): Promise<Customer> {\n const resp = await this.opts.client.verifyOtp({\n email,\n code,\n device_fp: deviceFp ?? '',\n });\n await this.opts.session.persist(this.opts.client, resp.access_token);\n await this.refresh();\n return (await this.opts.client.customerMe());\n }\n\n async oauthLogin(provider: string): Promise<OauthExchangeResponse> {\n const outcome = await this.opts.loopbackLogin(provider);\n await this.opts.session.persist(this.opts.client, outcome.exchange.access_token);\n await this.refresh();\n return outcome.exchange;\n }\n\n async logout(): Promise<void> {\n await this.opts.session.clear(this.opts.client);\n this.emit({ state: 'guest' });\n }\n}\n"]}
package/dist/index.cjs CHANGED
@@ -86,6 +86,12 @@ var BillingClient = class {
86
86
  setCustomerToken(token) {
87
87
  this.customerToken = token;
88
88
  }
89
+ getBaseUrl() {
90
+ return this.baseUrl;
91
+ }
92
+ getProductSlug() {
93
+ return this.productSlug;
94
+ }
89
95
  async requestOtp(payload) {
90
96
  await this.signed("POST", "/api/auth/customer/otp/request", payload);
91
97
  }
@@ -536,17 +542,248 @@ function buildOauthInitUrl(opts) {
536
542
  return `${base}/auth/${opts.provider}?${params.toString()}`;
537
543
  }
538
544
 
545
+ // src/lifecycle.ts
546
+ function computeState(payload, graceWindowMs, now = /* @__PURE__ */ new Date()) {
547
+ if (!payload) return "none";
548
+ if (!payload.valid_until) return "invalid";
549
+ const expiry = Date.parse(payload.valid_until);
550
+ if (Number.isNaN(expiry)) return "invalid";
551
+ const nowMs = now.getTime();
552
+ if (nowMs <= expiry) {
553
+ return isTrialPayload(payload) ? "trialing" : "active";
554
+ }
555
+ const cutoff = expiry + graceWindowMs;
556
+ if (nowMs <= cutoff) return "grace";
557
+ return "expired";
558
+ }
559
+ function trialDaysLeft(payload, now = /* @__PURE__ */ new Date()) {
560
+ if (!payload || !isTrialPayload(payload)) return 0;
561
+ const expiry = Date.parse(payload.valid_until);
562
+ if (Number.isNaN(expiry)) return 0;
563
+ const delta = expiry - now.getTime();
564
+ if (delta <= 0) return 0;
565
+ const dayMs = 24 * 60 * 60 * 1e3;
566
+ return Math.ceil(delta / dayMs);
567
+ }
568
+ function isTrialPayload(payload) {
569
+ if (payload.features?.__trial === true) return true;
570
+ if (payload.plan_key && payload.plan_key.endsWith(":trial")) return true;
571
+ return false;
572
+ }
573
+
574
+ // src/gate.ts
575
+ var GateDeniedError = class extends Error {
576
+ access;
577
+ constructor(access) {
578
+ super(`billing: feature "${access.feature}" denied (${access.reason})`);
579
+ this.name = "GateDeniedError";
580
+ this.access = access;
581
+ }
582
+ };
583
+ function isGateDenied(err) {
584
+ return err instanceof GateDeniedError;
585
+ }
586
+ var Gate = class {
587
+ loader;
588
+ localConsumption;
589
+ graceWindowMs;
590
+ now;
591
+ inflight = null;
592
+ constructor(opts) {
593
+ if (!opts.loader) throw new Error("billing: Gate requires loader");
594
+ this.loader = opts.loader;
595
+ this.localConsumption = opts.localConsumption ?? (() => 0);
596
+ this.graceWindowMs = opts.graceWindowMs ?? 0;
597
+ this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
598
+ }
599
+ async check(feature) {
600
+ const prev = this.inflight;
601
+ const run = (async () => {
602
+ if (prev) {
603
+ try {
604
+ await prev;
605
+ } catch {
606
+ }
607
+ }
608
+ return this.evaluate(feature);
609
+ })();
610
+ this.inflight = run.catch(() => ({}));
611
+ return run;
612
+ }
613
+ async require(feature) {
614
+ const access = await this.check(feature);
615
+ if (!access.allowed) throw new GateDeniedError(access);
616
+ return access;
617
+ }
618
+ async evaluate(feature) {
619
+ const access = {
620
+ feature,
621
+ allowed: false,
622
+ hasFeature: false,
623
+ unlimited: false,
624
+ remaining: 0,
625
+ reason: "",
626
+ plan: "",
627
+ state: "none"
628
+ };
629
+ let loaded;
630
+ try {
631
+ loaded = await this.loader();
632
+ } catch (err) {
633
+ access.reason = "verify_failed";
634
+ throw Object.assign(err instanceof Error ? err : new Error(String(err)), { access });
635
+ }
636
+ if (!loaded) {
637
+ access.reason = "no_license";
638
+ return access;
639
+ }
640
+ const { payload } = loaded;
641
+ access.plan = payload.plan_key;
642
+ access.state = computeState(payload, this.graceWindowMs, this.now());
643
+ if (access.state === "expired" || access.state === "invalid") {
644
+ access.reason = `license_${access.state}`;
645
+ return access;
646
+ }
647
+ const featureFlag = payload.features?.[feature];
648
+ if (featureFlag !== void 0) {
649
+ access.hasFeature = featureFlag;
650
+ if (!featureFlag) {
651
+ access.reason = "feature_disabled";
652
+ return access;
653
+ }
654
+ }
655
+ let consumed;
656
+ try {
657
+ consumed = await this.localConsumption(feature);
658
+ } catch (err) {
659
+ access.reason = "local_consumption_failed";
660
+ throw Object.assign(err instanceof Error ? err : new Error(String(err)), { access });
661
+ }
662
+ const remaining = computeRemaining(payload, feature, consumed);
663
+ if (remaining === null) {
664
+ if (access.hasFeature) {
665
+ access.allowed = true;
666
+ access.unlimited = true;
667
+ return access;
668
+ }
669
+ access.reason = "feature_missing";
670
+ return access;
671
+ }
672
+ if (remaining === Number.POSITIVE_INFINITY) {
673
+ access.unlimited = true;
674
+ access.hasFeature = true;
675
+ access.allowed = true;
676
+ return access;
677
+ }
678
+ access.remaining = remaining;
679
+ if (remaining > 0) {
680
+ access.allowed = true;
681
+ access.hasFeature = true;
682
+ return access;
683
+ }
684
+ access.reason = "limit_reached";
685
+ return access;
686
+ }
687
+ };
688
+
689
+ // src/usage.ts
690
+ var MemoryBuffer = class {
691
+ state = {};
692
+ add(feature, delta) {
693
+ if (delta === 0) return;
694
+ this.state[feature] = (this.state[feature] ?? 0) + delta;
695
+ }
696
+ drain() {
697
+ const out = this.state;
698
+ this.state = {};
699
+ return out;
700
+ }
701
+ restore(deltas) {
702
+ for (const [k, v] of Object.entries(deltas)) {
703
+ this.state[k] = (this.state[k] ?? 0) + v;
704
+ }
705
+ }
706
+ };
707
+ var DEFAULT_FLUSH_INTERVAL_MS = 5 * 60 * 1e3;
708
+ var UsageTracker = class {
709
+ buffer;
710
+ sync;
711
+ serial;
712
+ onRefresh;
713
+ flushIntervalMs;
714
+ timer = null;
715
+ running = false;
716
+ constructor(opts) {
717
+ if (!opts.buffer) throw new Error("billing: tracker requires buffer");
718
+ if (!opts.sync) throw new Error("billing: tracker requires sync");
719
+ this.buffer = opts.buffer;
720
+ this.sync = opts.sync;
721
+ if (opts.serial !== void 0) this.serial = opts.serial;
722
+ if (opts.onRefresh !== void 0) this.onRefresh = opts.onRefresh;
723
+ this.flushIntervalMs = opts.flushIntervalMs && opts.flushIntervalMs > 0 ? opts.flushIntervalMs : DEFAULT_FLUSH_INTERVAL_MS;
724
+ }
725
+ async track(feature, delta) {
726
+ if (delta === 0) return;
727
+ await this.buffer.add(feature, delta);
728
+ }
729
+ async flush() {
730
+ const deltas = await this.buffer.drain();
731
+ if (!deltas || Object.keys(deltas).length === 0) return;
732
+ let serial = 0;
733
+ if (this.serial) {
734
+ try {
735
+ serial = await this.serial();
736
+ } catch (err) {
737
+ await this.buffer.restore(deltas);
738
+ throw err;
739
+ }
740
+ }
741
+ let resp;
742
+ try {
743
+ resp = await this.sync(deltas, serial);
744
+ } catch (err) {
745
+ await this.buffer.restore(deltas);
746
+ throw err;
747
+ }
748
+ if (this.onRefresh && resp) {
749
+ await this.onRefresh(resp);
750
+ }
751
+ }
752
+ start() {
753
+ if (this.running) return;
754
+ this.running = true;
755
+ this.timer = setInterval(() => {
756
+ void this.flush().catch(() => {
757
+ });
758
+ }, this.flushIntervalMs);
759
+ }
760
+ async stop() {
761
+ if (!this.running) return;
762
+ this.running = false;
763
+ if (this.timer) {
764
+ clearInterval(this.timer);
765
+ this.timer = null;
766
+ }
767
+ await this.flush();
768
+ }
769
+ };
770
+
539
771
  exports.BillingApiError = BillingApiError;
540
772
  exports.BillingClient = BillingClient;
773
+ exports.Gate = Gate;
774
+ exports.GateDeniedError = GateDeniedError;
541
775
  exports.HEADER_NONCE = HEADER_NONCE;
542
776
  exports.HEADER_PRODUCT = HEADER_PRODUCT;
543
777
  exports.HEADER_SIGNATURE = HEADER_SIGNATURE;
544
778
  exports.HEADER_TIMESTAMP = HEADER_TIMESTAMP;
779
+ exports.MemoryBuffer = MemoryBuffer;
780
+ exports.UsageTracker = UsageTracker;
545
781
  exports.buildOauthInitUrl = buildOauthInitUrl;
546
782
  exports.canUseUpdate = canUseUpdate;
547
783
  exports.canonical = canonical;
548
784
  exports.checkoutUrl = checkoutUrl;
549
785
  exports.computeRemaining = computeRemaining;
786
+ exports.computeState = computeState;
550
787
  exports.decodeLicense = decodeLicense;
551
788
  exports.defaultInterval = defaultInterval;
552
789
  exports.downloadUrl = downloadUrl;
@@ -559,6 +796,7 @@ exports.getCtaProps = getCtaProps;
559
796
  exports.hasYearly = hasYearly;
560
797
  exports.isExpired = isExpired;
561
798
  exports.isFreeTier = isFreeTier;
799
+ exports.isGateDenied = isGateDenied;
562
800
  exports.isInGrace = isInGrace;
563
801
  exports.isOneTimeTier = isOneTimeTier;
564
802
  exports.isUnlimited = isUnlimited;
@@ -567,6 +805,7 @@ exports.newNonce = newNonce;
567
805
  exports.periodResetAt = periodResetAt;
568
806
  exports.sendCompletionBeacon = sendCompletionBeacon;
569
807
  exports.sign = sign;
808
+ exports.trialDaysLeft = trialDaysLeft;
570
809
  exports.triggerDownload = triggerDownload;
571
810
  exports.verifyLicense = verifyLicense;
572
811
  //# sourceMappingURL=index.cjs.map