sublimekeys 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sublimearts.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # sublimekeys
2
+
3
+ Official Node.js/Electron SDK for [SublimeKeys](https://keys.sublimearts.io) —
4
+ license key activation, verification and trials for indie desktop apps, with
5
+ **offline-capable verification**: once a license is activated, your app can
6
+ verify it locally in milliseconds with no network call, for up to 7 days at a
7
+ time, before quietly re-syncing online.
8
+
9
+ Zero runtime dependencies — Ed25519 verification uses Node's built-in
10
+ `node:crypto`, so there's nothing to native-compile and nothing for a bundler
11
+ or `asar` packaging step to trip over.
12
+
13
+ ```bash
14
+ npm install sublimekeys
15
+ ```
16
+
17
+ Requires Node.js 20+ (any current Electron release ships a much newer Node
18
+ than that).
19
+
20
+ ## Quickstart
21
+
22
+ This mirrors the lifecycle described in the [SublimeKeys docs](https://keys.sublimearts.io/docs):
23
+ first run activates, every later run verifies (offline-first), uninstall
24
+ deactivates.
25
+
26
+ ```ts
27
+ import { SublimeKeysClient } from "sublimekeys";
28
+
29
+ const client = new SublimeKeysClient("my-app");
30
+
31
+ // First run — ask the user for their key once.
32
+ const activated = await client.activate(userEnteredKey);
33
+ if (activated.valid) unlockFullVersion();
34
+
35
+ // Every later launch — offline-first, instant, falls back online
36
+ // automatically once the cached lease's 7-day trust window lapses.
37
+ const result = await client.verify(savedKey);
38
+ if (result.valid) unlockFullVersion();
39
+ console.log(result.source); // "offline_cache" most days, "online" roughly weekly
40
+
41
+ // Uninstall / sign out.
42
+ await client.deactivate(savedKey);
43
+ ```
44
+
45
+ `client.getMachineId()` gives you a stable, locally-persisted identifier if
46
+ you need to store it yourself — every method above also generates and caches
47
+ one automatically the first time it's needed, so passing it explicitly is
48
+ optional.
49
+
50
+ ## Trials
51
+
52
+ ```ts
53
+ const trial = await client.startTrial(); // get-or-create a 7-day trial; idempotent —
54
+ // reinstalling never resets the clock
55
+ if (trial.status === "active") console.log(`${trial.daysLeft} days left`);
56
+
57
+ const status = await client.trialStatus(); // read-only check, never starts one
58
+ ```
59
+
60
+ ## How offline verification works
61
+
62
+ When `activate()` or `verify()` succeeds, the server returns a lease — a small
63
+ Ed25519-signed token proving "this license is valid for this machine, as of
64
+ now." The SDK caches it locally and, on every later `verify()` call, checks
65
+ the signature against a **pinned public key built into this package** — no
66
+ network call, no dependency on the server being reachable.
67
+
68
+ The lease itself expires after 7 days (server-controlled). Once it does, the
69
+ next `verify()` call transparently goes online, gets a fresh lease, and the
70
+ cycle repeats. This means a revoked or expired license can take up to 7 days
71
+ to be caught while a machine stays fully offline — an intentional tradeoff for
72
+ instant, network-independent checks the rest of the time, not a bug.
73
+
74
+ ## API
75
+
76
+ | Method | What it does |
77
+ |---|---|
78
+ | `activate(licenseKey, machineId?)` | First run. Always online. |
79
+ | `verify(licenseKey, machineId?, { allowOffline? })` | Every later run. Offline-first by default. |
80
+ | `deactivate(licenseKey, machineId?)` | Uninstall/sign-out. Frees the seat, clears the local cache. |
81
+ | `startTrial(machineId?)` | Get-or-create a trial. |
82
+ | `trialStatus(machineId?)` | Read-only trial check. |
83
+ | `getMachineId()` | Stable per-install identifier (auto-generated, persisted locally). |
84
+
85
+ All methods return a plain object (`LicenseResult` or `TrialResult`) — they
86
+ never throw for a normal "not valid" outcome. Network failures are caught
87
+ internally too; `verify()` falls back to the offline cache or a
88
+ `{ valid: false, source: "offline_cache_miss" }` result rather than throwing.
89
+
90
+ ## Using this in an Electron app
91
+
92
+ This SDK is Node-only (it uses `node:crypto` and `node:fs`), so it needs to
93
+ run in Electron's **main process**, not directly in a renderer with
94
+ `contextIsolation` on (the recommended, secure default). Two common patterns:
95
+
96
+ - Call `SublimeKeysClient` methods in `main.ts`/`main.js` and expose the
97
+ results to the renderer over IPC (`ipcMain.handle` / `contextBridge`).
98
+ - Or run it from a preload script if you've deliberately scoped Node access
99
+ there.
100
+
101
+ See `examples/electron/` for a minimal main-process integration, including
102
+ the IPC wiring.
103
+
104
+ Because verification uses only built-in Node APIs (no native addon, no
105
+ prebuilt binary per-platform), there's nothing extra to configure in
106
+ `electron-builder`/`electron-forge`'s `asar` packaging or native-rebuild step
107
+ — the whole SDK is plain JS/TS.
108
+
109
+ ## License
110
+
111
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ LeaseError: () => LeaseError,
24
+ NetworkError: () => NetworkError,
25
+ PUBLIC_KEY_B64U: () => PUBLIC_KEY_B64U,
26
+ SublimeKeysClient: () => SublimeKeysClient,
27
+ SublimeKeysError: () => SublimeKeysError,
28
+ getOrCreateMachineId: () => getOrCreateMachineId,
29
+ verifyLease: () => verifyLease
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/pubkey.ts
34
+ var PUBLIC_KEY_B64U = "OEK14M1tAFMPuh0RkgdV3Xvgpb0igCfzbct527xoAzE";
35
+
36
+ // src/errors.ts
37
+ var SublimeKeysError = class extends Error {
38
+ constructor(message) {
39
+ super(message);
40
+ this.name = "SublimeKeysError";
41
+ }
42
+ };
43
+ var NetworkError = class extends SublimeKeysError {
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = "NetworkError";
47
+ }
48
+ };
49
+ var LeaseError = class extends SublimeKeysError {
50
+ constructor(message) {
51
+ super(message);
52
+ this.name = "LeaseError";
53
+ }
54
+ };
55
+
56
+ // src/http.ts
57
+ var DEFAULT_BASE_URL = "https://api.sublimearts.io";
58
+ var HttpClient = class {
59
+ baseUrl;
60
+ timeoutMs;
61
+ constructor(baseUrl = DEFAULT_BASE_URL, timeoutMs = 1e4) {
62
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
63
+ this.timeoutMs = timeoutMs;
64
+ }
65
+ async postJson(path, body) {
66
+ return this.request("POST", path, body);
67
+ }
68
+ async getJson(path) {
69
+ return this.request("GET", path, void 0);
70
+ }
71
+ async request(method, path, body) {
72
+ const url = `${this.baseUrl}${path}`;
73
+ const controller = new AbortController();
74
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
75
+ let res;
76
+ try {
77
+ res = await fetch(url, {
78
+ method,
79
+ headers: body !== void 0 ? { "Content-Type": "application/json" } : void 0,
80
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
81
+ signal: controller.signal
82
+ });
83
+ } catch (err) {
84
+ const reason = err instanceof Error ? err.message : String(err);
85
+ throw new NetworkError(reason);
86
+ } finally {
87
+ clearTimeout(timer);
88
+ }
89
+ if (!res.ok) {
90
+ let message = `${res.status} ${res.statusText}`;
91
+ try {
92
+ const payload = await res.json();
93
+ if (payload?.detail) message = payload.detail;
94
+ } catch {
95
+ }
96
+ return { valid: false, message, email: null, expires_at: null, lease: null };
97
+ }
98
+ return await res.json();
99
+ }
100
+ };
101
+
102
+ // src/lease.ts
103
+ var import_node_crypto = require("crypto");
104
+ function loadEd25519PublicKey(rawBytes) {
105
+ return (0, import_node_crypto.createPublicKey)({
106
+ key: { kty: "OKP", crv: "Ed25519", x: rawBytes.toString("base64url") },
107
+ format: "jwk"
108
+ });
109
+ }
110
+ function verifyLease(token, publicKeyBytes, opts) {
111
+ const parts = token.split(".");
112
+ if (parts.length !== 2) {
113
+ throw new LeaseError("malformed lease token: expected exactly one '.' separator");
114
+ }
115
+ const [payloadB64, sigB64] = parts;
116
+ let payloadBytes;
117
+ let sigBytes;
118
+ try {
119
+ payloadBytes = Buffer.from(payloadB64, "base64url");
120
+ sigBytes = Buffer.from(sigB64, "base64url");
121
+ } catch (err) {
122
+ throw new LeaseError(`malformed lease token: ${err instanceof Error ? err.message : String(err)}`);
123
+ }
124
+ const publicKey = loadEd25519PublicKey(publicKeyBytes);
125
+ let signatureValid;
126
+ try {
127
+ signatureValid = (0, import_node_crypto.verify)(null, payloadBytes, publicKey, sigBytes);
128
+ } catch {
129
+ signatureValid = false;
130
+ }
131
+ if (!signatureValid) {
132
+ throw new LeaseError("invalid lease signature");
133
+ }
134
+ let payload;
135
+ try {
136
+ payload = JSON.parse(payloadBytes.toString("utf-8"));
137
+ } catch (err) {
138
+ throw new LeaseError(`malformed lease payload: ${err instanceof Error ? err.message : String(err)}`);
139
+ }
140
+ if (payload.license_key !== opts.expectedLicenseKey) {
141
+ throw new LeaseError("lease license_key does not match");
142
+ }
143
+ if (payload.machine_id !== opts.expectedMachineId) {
144
+ throw new LeaseError("lease machine_id does not match");
145
+ }
146
+ if (payload.product_id !== opts.expectedProductId) {
147
+ throw new LeaseError("lease product_id does not match");
148
+ }
149
+ const now = opts.now ?? /* @__PURE__ */ new Date();
150
+ if (!payload.lease_expires_at || now >= new Date(payload.lease_expires_at)) {
151
+ throw new LeaseError("lease trust window has lapsed");
152
+ }
153
+ if (payload.license_expires_at && now >= new Date(payload.license_expires_at)) {
154
+ throw new LeaseError("license has expired");
155
+ }
156
+ return payload;
157
+ }
158
+
159
+ // src/machine.ts
160
+ var import_node_crypto3 = require("crypto");
161
+ var import_node_fs2 = require("fs");
162
+ var import_node_path2 = require("path");
163
+
164
+ // src/storage.ts
165
+ var import_node_crypto2 = require("crypto");
166
+ var import_node_fs = require("fs");
167
+ var import_node_os = require("os");
168
+ var import_node_path = require("path");
169
+ function defaultCacheDir(productId) {
170
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".sublimekeys", productId);
171
+ }
172
+ function leasePath(productId, base) {
173
+ return (0, import_node_path.join)(base ?? defaultCacheDir(productId), "lease.json");
174
+ }
175
+ function loadLease(productId, base) {
176
+ const path = leasePath(productId, base);
177
+ if (!(0, import_node_fs.existsSync)(path)) return null;
178
+ try {
179
+ return JSON.parse((0, import_node_fs.readFileSync)(path, "utf-8"));
180
+ } catch {
181
+ return null;
182
+ }
183
+ }
184
+ function saveLease(productId, licenseKey, token, base) {
185
+ const path = leasePath(productId, base);
186
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(path, ".."), { recursive: true });
187
+ const data = JSON.stringify({ license_key: licenseKey, token });
188
+ const tmpPath = (0, import_node_path.join)(path, "..", `.lease-${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.tmp`);
189
+ try {
190
+ (0, import_node_fs.writeFileSync)(tmpPath, data, "utf-8");
191
+ (0, import_node_fs.renameSync)(tmpPath, path);
192
+ } catch (err) {
193
+ try {
194
+ (0, import_node_fs.unlinkSync)(tmpPath);
195
+ } catch {
196
+ }
197
+ throw err;
198
+ }
199
+ try {
200
+ (0, import_node_fs.chmodSync)(path, 384);
201
+ } catch {
202
+ }
203
+ }
204
+ function clearLease(productId, base) {
205
+ const path = leasePath(productId, base);
206
+ try {
207
+ (0, import_node_fs.unlinkSync)(path);
208
+ } catch {
209
+ }
210
+ }
211
+
212
+ // src/machine.ts
213
+ function getOrCreateMachineId(productId, base) {
214
+ const dir = base ?? defaultCacheDir(productId);
215
+ const path = (0, import_node_path2.join)(dir, "machine_id");
216
+ if ((0, import_node_fs2.existsSync)(path)) {
217
+ const existing = (0, import_node_fs2.readFileSync)(path, "utf-8").trim();
218
+ if (existing) return existing;
219
+ }
220
+ const machineId = (0, import_node_crypto3.randomUUID)();
221
+ (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
222
+ (0, import_node_fs2.writeFileSync)(path, machineId, "utf-8");
223
+ return machineId;
224
+ }
225
+
226
+ // src/client.ts
227
+ var SublimeKeysClient = class {
228
+ productId;
229
+ http;
230
+ cacheBase;
231
+ publicKeyBytes;
232
+ constructor(productId, options = {}) {
233
+ this.productId = productId;
234
+ this.http = new HttpClient(options.baseUrl ?? DEFAULT_BASE_URL, options.timeoutMs ?? 1e4);
235
+ this.cacheBase = options.cacheDir;
236
+ this.publicKeyBytes = Buffer.from(options.publicKeyB64u ?? PUBLIC_KEY_B64U, "base64url");
237
+ }
238
+ /** A stable, locally-persisted machine identifier — generated once on
239
+ * first call, reused after that. */
240
+ getMachineId() {
241
+ return getOrCreateMachineId(this.productId, this.cacheBase);
242
+ }
243
+ /** First run for this license on this machine. Always goes online —
244
+ * activation is inherently server-side state. Safe to call again on a
245
+ * machine that's already activated (the server treats it as a no-op
246
+ * that doesn't consume another activation slot). */
247
+ async activate(licenseKey, machineId) {
248
+ const resolvedMachineId = machineId ?? this.getMachineId();
249
+ let data;
250
+ try {
251
+ data = await this.http.postJson("/activate", {
252
+ license_key: licenseKey,
253
+ machine_id: resolvedMachineId,
254
+ product_id: this.productId
255
+ });
256
+ } catch (err) {
257
+ const reason = err instanceof NetworkError ? err.message : String(err);
258
+ return { valid: false, message: `Network error: ${reason}`, source: "online" };
259
+ }
260
+ const result = {
261
+ valid: data.valid,
262
+ message: data.message,
263
+ email: data.email ?? null,
264
+ expiresAt: data.expires_at ?? null,
265
+ source: "online"
266
+ };
267
+ if (result.valid && data.lease) {
268
+ saveLease(this.productId, licenseKey, data.lease, this.cacheBase);
269
+ }
270
+ return result;
271
+ }
272
+ /** Every launch after the first. Checks the local cached lease first
273
+ * (instant, no network) if allowOffline is true; falls back to an online
274
+ * /verify call when there's no usable cached lease. Refreshes the cache
275
+ * on every successful online check, and clears it on a revoked/invalid
276
+ * result so a stale cache never outlives the license it was issued for. */
277
+ async verify(licenseKey, machineId, options = {}) {
278
+ const resolvedMachineId = machineId ?? this.getMachineId();
279
+ const allowOffline = options.allowOffline ?? true;
280
+ if (allowOffline) {
281
+ const cached = loadLease(this.productId, this.cacheBase);
282
+ if (cached && cached.license_key === licenseKey) {
283
+ try {
284
+ const payload = verifyLease(cached.token, this.publicKeyBytes, {
285
+ expectedLicenseKey: licenseKey,
286
+ expectedMachineId: resolvedMachineId,
287
+ expectedProductId: this.productId,
288
+ now: options._now
289
+ });
290
+ return {
291
+ valid: true,
292
+ message: "Valid (offline)",
293
+ email: payload.email ?? null,
294
+ expiresAt: payload.license_expires_at ?? null,
295
+ source: "offline_cache"
296
+ };
297
+ } catch (err) {
298
+ if (!(err instanceof LeaseError)) throw err;
299
+ }
300
+ }
301
+ }
302
+ let data;
303
+ try {
304
+ data = await this.http.postJson("/verify", {
305
+ license_key: licenseKey,
306
+ machine_id: resolvedMachineId,
307
+ product_id: this.productId
308
+ });
309
+ } catch {
310
+ return { valid: false, message: "Offline and no valid cached lease", source: "offline_cache_miss" };
311
+ }
312
+ const result = {
313
+ valid: data.valid,
314
+ message: data.message,
315
+ email: data.email ?? null,
316
+ expiresAt: data.expires_at ?? null,
317
+ source: "online"
318
+ };
319
+ if (result.valid && data.lease) {
320
+ saveLease(this.productId, licenseKey, data.lease, this.cacheBase);
321
+ } else if (!result.valid) {
322
+ clearLease(this.productId, this.cacheBase);
323
+ }
324
+ return result;
325
+ }
326
+ /** User signs out / uninstalls. Clears the local cache immediately —
327
+ * otherwise a deliberately-deactivated license would keep
328
+ * offline-verifying as valid until its trust window lapsed. */
329
+ async deactivate(licenseKey, machineId) {
330
+ const resolvedMachineId = machineId ?? this.getMachineId();
331
+ let data;
332
+ try {
333
+ data = await this.http.postJson("/deactivate", {
334
+ license_key: licenseKey,
335
+ machine_id: resolvedMachineId,
336
+ product_id: this.productId
337
+ });
338
+ } catch (err) {
339
+ const reason = err instanceof NetworkError ? err.message : String(err);
340
+ return { valid: false, message: `Network error: ${reason}`, source: "online" };
341
+ }
342
+ clearLease(this.productId, this.cacheBase);
343
+ return { valid: data.valid, message: data.message, source: "online" };
344
+ }
345
+ /** Get-or-create a trial for this machine. Idempotent server-side —
346
+ * reinstalling never resets the clock. */
347
+ async startTrial(machineId) {
348
+ return this.trialCall("/trial/start", machineId);
349
+ }
350
+ /** Read-only trial check — never starts one. */
351
+ async trialStatus(machineId) {
352
+ return this.trialCall("/trial/status", machineId);
353
+ }
354
+ async trialCall(path, machineId) {
355
+ const resolvedMachineId = machineId ?? this.getMachineId();
356
+ let data;
357
+ try {
358
+ data = await this.http.postJson(path, {
359
+ machine_id: resolvedMachineId,
360
+ product_id: this.productId
361
+ });
362
+ } catch (err) {
363
+ const reason = err instanceof NetworkError ? err.message : String(err);
364
+ return { status: "none", daysLeft: 0, message: `Network error: ${reason}` };
365
+ }
366
+ return {
367
+ status: data.status,
368
+ daysLeft: data.days_left ?? 0,
369
+ expiresAt: data.expires_at ?? null,
370
+ message: data.message ?? ""
371
+ };
372
+ }
373
+ };
374
+ // Annotate the CommonJS export names for ESM import in node:
375
+ 0 && (module.exports = {
376
+ LeaseError,
377
+ NetworkError,
378
+ PUBLIC_KEY_B64U,
379
+ SublimeKeysClient,
380
+ SublimeKeysError,
381
+ getOrCreateMachineId,
382
+ verifyLease
383
+ });
384
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/pubkey.ts","../src/errors.ts","../src/http.ts","../src/lease.ts","../src/machine.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export { SublimeKeysClient } from \"./client.js\";\nexport type {\n LicenseResult,\n TrialResult,\n LicenseSource,\n SublimeKeysClientOptions,\n VerifyOptions,\n} from \"./client.js\";\nexport { verifyLease } from \"./lease.js\";\nexport type { LeasePayload, VerifyLeaseOptions } from \"./lease.js\";\nexport { SublimeKeysError, NetworkError, LeaseError } from \"./errors.js\";\nexport { PUBLIC_KEY_B64U } from \"./pubkey.js\";\nexport { getOrCreateMachineId } from \"./machine.js\";\n","/** SublimeKeys production Ed25519 public key (base64url, 32 raw bytes).\n * Pinned here so the offline hot path never fetches /public-key at runtime. */\nexport const PUBLIC_KEY_B64U = \"OEK14M1tAFMPuh0RkgdV3Xvgpb0igCfzbct527xoAzE\";\n","export class SublimeKeysError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SublimeKeysError\";\n }\n}\n\n/** A request to the SublimeKeys API failed to complete (unreachable host, timeout, connection reset, ...). */\nexport class NetworkError extends SublimeKeysError {\n constructor(message: string) {\n super(message);\n this.name = \"NetworkError\";\n }\n}\n\n/** A cached offline lease failed verification — malformed, tampered, expired,\n * or scoped to a different license/machine/product. */\nexport class LeaseError extends SublimeKeysError {\n constructor(message: string) {\n super(message);\n this.name = \"LeaseError\";\n }\n}\n","// Thin fetch-based JSON client — zero HTTP dependencies, matching the\n// license server's own minimal-dependency style. This is also where the\n// server's one REST inconsistency gets papered over: /activate raises an\n// HTTP error (404/403, body {\"detail\": \"...\"}) while /verify always returns\n// HTTP 200 with valid:false. Callers of this module see one shape either way.\n\nimport { NetworkError } from \"./errors.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.sublimearts.io\";\n\nexport interface ApiResponse {\n valid: boolean;\n message: string;\n email?: string | null;\n expires_at?: string | null;\n lease?: string | null;\n [key: string]: unknown;\n}\n\nexport class HttpClient {\n private baseUrl: string;\n private timeoutMs: number;\n\n constructor(baseUrl: string = DEFAULT_BASE_URL, timeoutMs = 10_000) {\n this.baseUrl = baseUrl.replace(/\\/+$/, \"\");\n this.timeoutMs = timeoutMs;\n }\n\n async postJson(path: string, body: Record<string, unknown>): Promise<ApiResponse> {\n return this.request(\"POST\", path, body);\n }\n\n async getJson(path: string): Promise<ApiResponse> {\n return this.request(\"GET\", path, undefined);\n }\n\n private async request(method: string, path: string, body: Record<string, unknown> | undefined): Promise<ApiResponse> {\n const url = `${this.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: body !== undefined ? { \"Content-Type\": \"application/json\" } : undefined,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new NetworkError(reason);\n } finally {\n clearTimeout(timer);\n }\n\n if (!res.ok) {\n let message = `${res.status} ${res.statusText}`;\n try {\n const payload = (await res.json()) as { detail?: string };\n if (payload?.detail) message = payload.detail;\n } catch {\n // non-JSON error body — keep the status line as the message\n }\n return { valid: false, message, email: null, expires_at: null, lease: null };\n }\n\n return (await res.json()) as ApiResponse;\n }\n}\n","// Offline verification of SublimeKeys signed leases.\n//\n// A lease is a compact token: base64url(json_payload) + \".\" + base64url(signature),\n// signed with Ed25519 by the SublimeKeys license server. Verifying it locally\n// requires no network call, which is the entire point of this module.\n\nimport { createPublicKey, verify as cryptoVerify } from \"node:crypto\";\nimport { LeaseError } from \"./errors.js\";\n\nexport interface LeasePayload {\n kid?: string;\n license_key: string;\n machine_id: string;\n product_id: string;\n email?: string | null;\n issued_at?: string;\n lease_expires_at: string;\n license_expires_at?: string | null;\n [key: string]: unknown;\n}\n\nexport interface VerifyLeaseOptions {\n expectedLicenseKey: string;\n expectedMachineId: string;\n expectedProductId: string;\n now?: Date;\n}\n\nfunction loadEd25519PublicKey(rawBytes: Buffer) {\n return createPublicKey({\n key: { kty: \"OKP\", crv: \"Ed25519\", x: rawBytes.toString(\"base64url\") },\n format: \"jwk\",\n });\n}\n\n/**\n * Verify a lease token and return its payload if valid.\n *\n * Throws LeaseError for any failure — malformed token, bad signature,\n * wrong license/machine/product, or an expired trust window/license.\n * A valid signature only proves \"SublimeKeys signed this,\" not \"this is\n * for the product/machine/license you expect\" — the three equality\n * checks below are not optional and cannot be skipped by a caller.\n */\nexport function verifyLease(token: string, publicKeyBytes: Buffer, opts: VerifyLeaseOptions): LeasePayload {\n const parts = token.split(\".\");\n if (parts.length !== 2) {\n throw new LeaseError(\"malformed lease token: expected exactly one '.' separator\");\n }\n const [payloadB64, sigB64] = parts;\n\n let payloadBytes: Buffer;\n let sigBytes: Buffer;\n try {\n payloadBytes = Buffer.from(payloadB64, \"base64url\");\n sigBytes = Buffer.from(sigB64, \"base64url\");\n } catch (err) {\n throw new LeaseError(`malformed lease token: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n const publicKey = loadEd25519PublicKey(publicKeyBytes);\n\n // Verify the signature BEFORE parsing the payload as JSON — never hand\n // unauthenticated bytes to a parser first, even a safe one.\n let signatureValid: boolean;\n try {\n signatureValid = cryptoVerify(null, payloadBytes, publicKey, sigBytes);\n } catch {\n signatureValid = false;\n }\n if (!signatureValid) {\n throw new LeaseError(\"invalid lease signature\");\n }\n\n let payload: LeasePayload;\n try {\n payload = JSON.parse(payloadBytes.toString(\"utf-8\")) as LeasePayload;\n } catch (err) {\n throw new LeaseError(`malformed lease payload: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n if (payload.license_key !== opts.expectedLicenseKey) {\n throw new LeaseError(\"lease license_key does not match\");\n }\n if (payload.machine_id !== opts.expectedMachineId) {\n throw new LeaseError(\"lease machine_id does not match\");\n }\n if (payload.product_id !== opts.expectedProductId) {\n throw new LeaseError(\"lease product_id does not match\");\n }\n\n const now = opts.now ?? new Date();\n\n if (!payload.lease_expires_at || now >= new Date(payload.lease_expires_at)) {\n throw new LeaseError(\"lease trust window has lapsed\");\n }\n\n if (payload.license_expires_at && now >= new Date(payload.license_expires_at)) {\n throw new LeaseError(\"license has expired\");\n }\n\n return payload;\n}\n","// A stable per-install machine identifier.\n//\n// Deliberately a persisted random UUID, not a hardware fingerprint. Real\n// hardware IDs (MAC addresses, disk serials, ...) are spoofable, change on\n// VMs/cloud desktops/Apple Silicon Rosetta in ways that generate false\n// mismatches and support tickets, and buy little real security over a\n// persisted UUID for this use case. Trivially resettable by deleting one\n// file — that's a known, accepted tradeoff (a soft deterrent, not DRM),\n// not an oversight.\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { defaultCacheDir } from \"./storage.js\";\n\nexport function getOrCreateMachineId(productId: string, base?: string): string {\n const dir = base ?? defaultCacheDir(productId);\n const path = join(dir, \"machine_id\");\n\n if (existsSync(path)) {\n const existing = readFileSync(path, \"utf-8\").trim();\n if (existing) return existing;\n }\n\n const machineId = randomUUID();\n mkdirSync(dir, { recursive: true });\n writeFileSync(path, machineId, \"utf-8\");\n return machineId;\n}\n","// Local cache for signed leases — one JSON file per product, written\n// atomically (temp file + rename) so a crash mid-write never leaves a\n// half-written file behind.\n\nimport { randomBytes } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, chmodSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface CachedLease {\n license_key: string;\n token: string;\n}\n\nexport function defaultCacheDir(productId: string): string {\n return join(homedir(), \".sublimekeys\", productId);\n}\n\nfunction leasePath(productId: string, base?: string): string {\n return join(base ?? defaultCacheDir(productId), \"lease.json\");\n}\n\nexport function loadLease(productId: string, base?: string): CachedLease | null {\n const path = leasePath(productId, base);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf-8\")) as CachedLease;\n } catch {\n return null;\n }\n}\n\nexport function saveLease(productId: string, licenseKey: string, token: string, base?: string): void {\n const path = leasePath(productId, base);\n mkdirSync(join(path, \"..\"), { recursive: true });\n const data = JSON.stringify({ license_key: licenseKey, token });\n\n const tmpPath = join(path, \"..\", `.lease-${randomBytes(6).toString(\"hex\")}.tmp`);\n try {\n writeFileSync(tmpPath, data, \"utf-8\");\n renameSync(tmpPath, path);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // best-effort cleanup\n }\n throw err;\n }\n\n try {\n chmodSync(path, 0o600);\n } catch {\n // best-effort — no-op on native Windows ACLs, real on POSIX/WSL\n }\n}\n\nexport function clearLease(productId: string, base?: string): void {\n const path = leasePath(productId, base);\n try {\n unlinkSync(path);\n } catch {\n // already gone — fine\n }\n}\n","// SublimeKeysClient — the main SDK entry point.\n//\n// Cache-first, not online-first-with-fallback: after one successful\n// activate()/verify(), subsequent verify() calls make zero network requests\n// for as long as the cached lease's trust window is valid (currently 7 days,\n// set server-side) — that's the actual point of offline leases. A network\n// call only happens again once the cache is missing, invalid, or its trust\n// window has lapsed.\n\nimport { PUBLIC_KEY_B64U } from \"./pubkey.js\";\nimport { NetworkError, LeaseError } from \"./errors.js\";\nimport { DEFAULT_BASE_URL, HttpClient } from \"./http.js\";\nimport { verifyLease } from \"./lease.js\";\nimport { getOrCreateMachineId } from \"./machine.js\";\nimport { loadLease, saveLease, clearLease } from \"./storage.js\";\n\nexport type LicenseSource = \"online\" | \"offline_cache\" | \"offline_cache_miss\";\n\nexport interface LicenseResult {\n valid: boolean;\n message: string;\n email?: string | null;\n expiresAt?: string | null;\n source: LicenseSource;\n}\n\nexport interface TrialResult {\n status: \"active\" | \"expired\" | \"none\";\n daysLeft: number;\n expiresAt?: string | null;\n message: string;\n}\n\nexport interface SublimeKeysClientOptions {\n baseUrl?: string;\n cacheDir?: string;\n publicKeyB64u?: string;\n timeoutMs?: number;\n}\n\nexport interface VerifyOptions {\n allowOffline?: boolean;\n /** Internal test hook — overrides \"now\" when checking lease expiry. */\n _now?: Date;\n}\n\nexport class SublimeKeysClient {\n readonly productId: string;\n private readonly http: HttpClient;\n private readonly cacheBase?: string;\n private readonly publicKeyBytes: Buffer;\n\n constructor(productId: string, options: SublimeKeysClientOptions = {}) {\n this.productId = productId;\n this.http = new HttpClient(options.baseUrl ?? DEFAULT_BASE_URL, options.timeoutMs ?? 10_000);\n this.cacheBase = options.cacheDir;\n this.publicKeyBytes = Buffer.from(options.publicKeyB64u ?? PUBLIC_KEY_B64U, \"base64url\");\n }\n\n /** A stable, locally-persisted machine identifier — generated once on\n * first call, reused after that. */\n getMachineId(): string {\n return getOrCreateMachineId(this.productId, this.cacheBase);\n }\n\n /** First run for this license on this machine. Always goes online —\n * activation is inherently server-side state. Safe to call again on a\n * machine that's already activated (the server treats it as a no-op\n * that doesn't consume another activation slot). */\n async activate(licenseKey: string, machineId?: string): Promise<LicenseResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n let data;\n try {\n data = await this.http.postJson(\"/activate\", {\n license_key: licenseKey,\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch (err) {\n const reason = err instanceof NetworkError ? err.message : String(err);\n return { valid: false, message: `Network error: ${reason}`, source: \"online\" };\n }\n\n const result: LicenseResult = {\n valid: data.valid,\n message: data.message,\n email: data.email ?? null,\n expiresAt: data.expires_at ?? null,\n source: \"online\",\n };\n if (result.valid && data.lease) {\n saveLease(this.productId, licenseKey, data.lease, this.cacheBase);\n }\n return result;\n }\n\n /** Every launch after the first. Checks the local cached lease first\n * (instant, no network) if allowOffline is true; falls back to an online\n * /verify call when there's no usable cached lease. Refreshes the cache\n * on every successful online check, and clears it on a revoked/invalid\n * result so a stale cache never outlives the license it was issued for. */\n async verify(licenseKey: string, machineId?: string, options: VerifyOptions = {}): Promise<LicenseResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n const allowOffline = options.allowOffline ?? true;\n\n if (allowOffline) {\n const cached = loadLease(this.productId, this.cacheBase);\n if (cached && cached.license_key === licenseKey) {\n try {\n const payload = verifyLease(cached.token, this.publicKeyBytes, {\n expectedLicenseKey: licenseKey,\n expectedMachineId: resolvedMachineId,\n expectedProductId: this.productId,\n now: options._now,\n });\n return {\n valid: true,\n message: \"Valid (offline)\",\n email: payload.email ?? null,\n expiresAt: payload.license_expires_at ?? null,\n source: \"offline_cache\",\n };\n } catch (err) {\n if (!(err instanceof LeaseError)) throw err;\n // cache unusable — fall through to an online check\n }\n }\n }\n\n let data;\n try {\n data = await this.http.postJson(\"/verify\", {\n license_key: licenseKey,\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch {\n return { valid: false, message: \"Offline and no valid cached lease\", source: \"offline_cache_miss\" };\n }\n\n const result: LicenseResult = {\n valid: data.valid,\n message: data.message,\n email: data.email ?? null,\n expiresAt: data.expires_at ?? null,\n source: \"online\",\n };\n if (result.valid && data.lease) {\n saveLease(this.productId, licenseKey, data.lease, this.cacheBase);\n } else if (!result.valid) {\n // Don't leave a stale cache behind — a license revoked server-side\n // must not keep offline-verifying as valid for days afterward.\n clearLease(this.productId, this.cacheBase);\n }\n return result;\n }\n\n /** User signs out / uninstalls. Clears the local cache immediately —\n * otherwise a deliberately-deactivated license would keep\n * offline-verifying as valid until its trust window lapsed. */\n async deactivate(licenseKey: string, machineId?: string): Promise<LicenseResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n let data;\n try {\n data = await this.http.postJson(\"/deactivate\", {\n license_key: licenseKey,\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch (err) {\n const reason = err instanceof NetworkError ? err.message : String(err);\n return { valid: false, message: `Network error: ${reason}`, source: \"online\" };\n }\n\n clearLease(this.productId, this.cacheBase);\n return { valid: data.valid, message: data.message, source: \"online\" };\n }\n\n /** Get-or-create a trial for this machine. Idempotent server-side —\n * reinstalling never resets the clock. */\n async startTrial(machineId?: string): Promise<TrialResult> {\n return this.trialCall(\"/trial/start\", machineId);\n }\n\n /** Read-only trial check — never starts one. */\n async trialStatus(machineId?: string): Promise<TrialResult> {\n return this.trialCall(\"/trial/status\", machineId);\n }\n\n private async trialCall(path: string, machineId?: string): Promise<TrialResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n let data;\n try {\n data = await this.http.postJson(path, {\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch (err) {\n const reason = err instanceof NetworkError ? err.message : String(err);\n return { status: \"none\", daysLeft: 0, message: `Network error: ${reason}` };\n }\n return {\n status: data.status as TrialResult[\"status\"],\n daysLeft: (data.days_left as number) ?? 0,\n expiresAt: (data.expires_at as string) ?? null,\n message: (data.message as string) ?? \"\",\n };\n }\n}\n"],"mappings":"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","names":["cryptoVerify","import_node_crypto","import_node_fs","import_node_path","import_node_crypto"]}
@@ -0,0 +1,105 @@
1
+ type LicenseSource = "online" | "offline_cache" | "offline_cache_miss";
2
+ interface LicenseResult {
3
+ valid: boolean;
4
+ message: string;
5
+ email?: string | null;
6
+ expiresAt?: string | null;
7
+ source: LicenseSource;
8
+ }
9
+ interface TrialResult {
10
+ status: "active" | "expired" | "none";
11
+ daysLeft: number;
12
+ expiresAt?: string | null;
13
+ message: string;
14
+ }
15
+ interface SublimeKeysClientOptions {
16
+ baseUrl?: string;
17
+ cacheDir?: string;
18
+ publicKeyB64u?: string;
19
+ timeoutMs?: number;
20
+ }
21
+ interface VerifyOptions {
22
+ allowOffline?: boolean;
23
+ /** Internal test hook — overrides "now" when checking lease expiry. */
24
+ _now?: Date;
25
+ }
26
+ declare class SublimeKeysClient {
27
+ readonly productId: string;
28
+ private readonly http;
29
+ private readonly cacheBase?;
30
+ private readonly publicKeyBytes;
31
+ constructor(productId: string, options?: SublimeKeysClientOptions);
32
+ /** A stable, locally-persisted machine identifier — generated once on
33
+ * first call, reused after that. */
34
+ getMachineId(): string;
35
+ /** First run for this license on this machine. Always goes online —
36
+ * activation is inherently server-side state. Safe to call again on a
37
+ * machine that's already activated (the server treats it as a no-op
38
+ * that doesn't consume another activation slot). */
39
+ activate(licenseKey: string, machineId?: string): Promise<LicenseResult>;
40
+ /** Every launch after the first. Checks the local cached lease first
41
+ * (instant, no network) if allowOffline is true; falls back to an online
42
+ * /verify call when there's no usable cached lease. Refreshes the cache
43
+ * on every successful online check, and clears it on a revoked/invalid
44
+ * result so a stale cache never outlives the license it was issued for. */
45
+ verify(licenseKey: string, machineId?: string, options?: VerifyOptions): Promise<LicenseResult>;
46
+ /** User signs out / uninstalls. Clears the local cache immediately —
47
+ * otherwise a deliberately-deactivated license would keep
48
+ * offline-verifying as valid until its trust window lapsed. */
49
+ deactivate(licenseKey: string, machineId?: string): Promise<LicenseResult>;
50
+ /** Get-or-create a trial for this machine. Idempotent server-side —
51
+ * reinstalling never resets the clock. */
52
+ startTrial(machineId?: string): Promise<TrialResult>;
53
+ /** Read-only trial check — never starts one. */
54
+ trialStatus(machineId?: string): Promise<TrialResult>;
55
+ private trialCall;
56
+ }
57
+
58
+ interface LeasePayload {
59
+ kid?: string;
60
+ license_key: string;
61
+ machine_id: string;
62
+ product_id: string;
63
+ email?: string | null;
64
+ issued_at?: string;
65
+ lease_expires_at: string;
66
+ license_expires_at?: string | null;
67
+ [key: string]: unknown;
68
+ }
69
+ interface VerifyLeaseOptions {
70
+ expectedLicenseKey: string;
71
+ expectedMachineId: string;
72
+ expectedProductId: string;
73
+ now?: Date;
74
+ }
75
+ /**
76
+ * Verify a lease token and return its payload if valid.
77
+ *
78
+ * Throws LeaseError for any failure — malformed token, bad signature,
79
+ * wrong license/machine/product, or an expired trust window/license.
80
+ * A valid signature only proves "SublimeKeys signed this," not "this is
81
+ * for the product/machine/license you expect" — the three equality
82
+ * checks below are not optional and cannot be skipped by a caller.
83
+ */
84
+ declare function verifyLease(token: string, publicKeyBytes: Buffer, opts: VerifyLeaseOptions): LeasePayload;
85
+
86
+ declare class SublimeKeysError extends Error {
87
+ constructor(message: string);
88
+ }
89
+ /** A request to the SublimeKeys API failed to complete (unreachable host, timeout, connection reset, ...). */
90
+ declare class NetworkError extends SublimeKeysError {
91
+ constructor(message: string);
92
+ }
93
+ /** A cached offline lease failed verification — malformed, tampered, expired,
94
+ * or scoped to a different license/machine/product. */
95
+ declare class LeaseError extends SublimeKeysError {
96
+ constructor(message: string);
97
+ }
98
+
99
+ /** SublimeKeys production Ed25519 public key (base64url, 32 raw bytes).
100
+ * Pinned here so the offline hot path never fetches /public-key at runtime. */
101
+ declare const PUBLIC_KEY_B64U = "OEK14M1tAFMPuh0RkgdV3Xvgpb0igCfzbct527xoAzE";
102
+
103
+ declare function getOrCreateMachineId(productId: string, base?: string): string;
104
+
105
+ export { LeaseError, type LeasePayload, type LicenseResult, type LicenseSource, NetworkError, PUBLIC_KEY_B64U, SublimeKeysClient, type SublimeKeysClientOptions, SublimeKeysError, type TrialResult, type VerifyLeaseOptions, type VerifyOptions, getOrCreateMachineId, verifyLease };
@@ -0,0 +1,105 @@
1
+ type LicenseSource = "online" | "offline_cache" | "offline_cache_miss";
2
+ interface LicenseResult {
3
+ valid: boolean;
4
+ message: string;
5
+ email?: string | null;
6
+ expiresAt?: string | null;
7
+ source: LicenseSource;
8
+ }
9
+ interface TrialResult {
10
+ status: "active" | "expired" | "none";
11
+ daysLeft: number;
12
+ expiresAt?: string | null;
13
+ message: string;
14
+ }
15
+ interface SublimeKeysClientOptions {
16
+ baseUrl?: string;
17
+ cacheDir?: string;
18
+ publicKeyB64u?: string;
19
+ timeoutMs?: number;
20
+ }
21
+ interface VerifyOptions {
22
+ allowOffline?: boolean;
23
+ /** Internal test hook — overrides "now" when checking lease expiry. */
24
+ _now?: Date;
25
+ }
26
+ declare class SublimeKeysClient {
27
+ readonly productId: string;
28
+ private readonly http;
29
+ private readonly cacheBase?;
30
+ private readonly publicKeyBytes;
31
+ constructor(productId: string, options?: SublimeKeysClientOptions);
32
+ /** A stable, locally-persisted machine identifier — generated once on
33
+ * first call, reused after that. */
34
+ getMachineId(): string;
35
+ /** First run for this license on this machine. Always goes online —
36
+ * activation is inherently server-side state. Safe to call again on a
37
+ * machine that's already activated (the server treats it as a no-op
38
+ * that doesn't consume another activation slot). */
39
+ activate(licenseKey: string, machineId?: string): Promise<LicenseResult>;
40
+ /** Every launch after the first. Checks the local cached lease first
41
+ * (instant, no network) if allowOffline is true; falls back to an online
42
+ * /verify call when there's no usable cached lease. Refreshes the cache
43
+ * on every successful online check, and clears it on a revoked/invalid
44
+ * result so a stale cache never outlives the license it was issued for. */
45
+ verify(licenseKey: string, machineId?: string, options?: VerifyOptions): Promise<LicenseResult>;
46
+ /** User signs out / uninstalls. Clears the local cache immediately —
47
+ * otherwise a deliberately-deactivated license would keep
48
+ * offline-verifying as valid until its trust window lapsed. */
49
+ deactivate(licenseKey: string, machineId?: string): Promise<LicenseResult>;
50
+ /** Get-or-create a trial for this machine. Idempotent server-side —
51
+ * reinstalling never resets the clock. */
52
+ startTrial(machineId?: string): Promise<TrialResult>;
53
+ /** Read-only trial check — never starts one. */
54
+ trialStatus(machineId?: string): Promise<TrialResult>;
55
+ private trialCall;
56
+ }
57
+
58
+ interface LeasePayload {
59
+ kid?: string;
60
+ license_key: string;
61
+ machine_id: string;
62
+ product_id: string;
63
+ email?: string | null;
64
+ issued_at?: string;
65
+ lease_expires_at: string;
66
+ license_expires_at?: string | null;
67
+ [key: string]: unknown;
68
+ }
69
+ interface VerifyLeaseOptions {
70
+ expectedLicenseKey: string;
71
+ expectedMachineId: string;
72
+ expectedProductId: string;
73
+ now?: Date;
74
+ }
75
+ /**
76
+ * Verify a lease token and return its payload if valid.
77
+ *
78
+ * Throws LeaseError for any failure — malformed token, bad signature,
79
+ * wrong license/machine/product, or an expired trust window/license.
80
+ * A valid signature only proves "SublimeKeys signed this," not "this is
81
+ * for the product/machine/license you expect" — the three equality
82
+ * checks below are not optional and cannot be skipped by a caller.
83
+ */
84
+ declare function verifyLease(token: string, publicKeyBytes: Buffer, opts: VerifyLeaseOptions): LeasePayload;
85
+
86
+ declare class SublimeKeysError extends Error {
87
+ constructor(message: string);
88
+ }
89
+ /** A request to the SublimeKeys API failed to complete (unreachable host, timeout, connection reset, ...). */
90
+ declare class NetworkError extends SublimeKeysError {
91
+ constructor(message: string);
92
+ }
93
+ /** A cached offline lease failed verification — malformed, tampered, expired,
94
+ * or scoped to a different license/machine/product. */
95
+ declare class LeaseError extends SublimeKeysError {
96
+ constructor(message: string);
97
+ }
98
+
99
+ /** SublimeKeys production Ed25519 public key (base64url, 32 raw bytes).
100
+ * Pinned here so the offline hot path never fetches /public-key at runtime. */
101
+ declare const PUBLIC_KEY_B64U = "OEK14M1tAFMPuh0RkgdV3Xvgpb0igCfzbct527xoAzE";
102
+
103
+ declare function getOrCreateMachineId(productId: string, base?: string): string;
104
+
105
+ export { LeaseError, type LeasePayload, type LicenseResult, type LicenseSource, NetworkError, PUBLIC_KEY_B64U, SublimeKeysClient, type SublimeKeysClientOptions, SublimeKeysError, type TrialResult, type VerifyLeaseOptions, type VerifyOptions, getOrCreateMachineId, verifyLease };
package/dist/index.js ADDED
@@ -0,0 +1,351 @@
1
+ // src/pubkey.ts
2
+ var PUBLIC_KEY_B64U = "OEK14M1tAFMPuh0RkgdV3Xvgpb0igCfzbct527xoAzE";
3
+
4
+ // src/errors.ts
5
+ var SublimeKeysError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "SublimeKeysError";
9
+ }
10
+ };
11
+ var NetworkError = class extends SublimeKeysError {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "NetworkError";
15
+ }
16
+ };
17
+ var LeaseError = class extends SublimeKeysError {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = "LeaseError";
21
+ }
22
+ };
23
+
24
+ // src/http.ts
25
+ var DEFAULT_BASE_URL = "https://api.sublimearts.io";
26
+ var HttpClient = class {
27
+ baseUrl;
28
+ timeoutMs;
29
+ constructor(baseUrl = DEFAULT_BASE_URL, timeoutMs = 1e4) {
30
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
31
+ this.timeoutMs = timeoutMs;
32
+ }
33
+ async postJson(path, body) {
34
+ return this.request("POST", path, body);
35
+ }
36
+ async getJson(path) {
37
+ return this.request("GET", path, void 0);
38
+ }
39
+ async request(method, path, body) {
40
+ const url = `${this.baseUrl}${path}`;
41
+ const controller = new AbortController();
42
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
43
+ let res;
44
+ try {
45
+ res = await fetch(url, {
46
+ method,
47
+ headers: body !== void 0 ? { "Content-Type": "application/json" } : void 0,
48
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
49
+ signal: controller.signal
50
+ });
51
+ } catch (err) {
52
+ const reason = err instanceof Error ? err.message : String(err);
53
+ throw new NetworkError(reason);
54
+ } finally {
55
+ clearTimeout(timer);
56
+ }
57
+ if (!res.ok) {
58
+ let message = `${res.status} ${res.statusText}`;
59
+ try {
60
+ const payload = await res.json();
61
+ if (payload?.detail) message = payload.detail;
62
+ } catch {
63
+ }
64
+ return { valid: false, message, email: null, expires_at: null, lease: null };
65
+ }
66
+ return await res.json();
67
+ }
68
+ };
69
+
70
+ // src/lease.ts
71
+ import { createPublicKey, verify as cryptoVerify } from "crypto";
72
+ function loadEd25519PublicKey(rawBytes) {
73
+ return createPublicKey({
74
+ key: { kty: "OKP", crv: "Ed25519", x: rawBytes.toString("base64url") },
75
+ format: "jwk"
76
+ });
77
+ }
78
+ function verifyLease(token, publicKeyBytes, opts) {
79
+ const parts = token.split(".");
80
+ if (parts.length !== 2) {
81
+ throw new LeaseError("malformed lease token: expected exactly one '.' separator");
82
+ }
83
+ const [payloadB64, sigB64] = parts;
84
+ let payloadBytes;
85
+ let sigBytes;
86
+ try {
87
+ payloadBytes = Buffer.from(payloadB64, "base64url");
88
+ sigBytes = Buffer.from(sigB64, "base64url");
89
+ } catch (err) {
90
+ throw new LeaseError(`malformed lease token: ${err instanceof Error ? err.message : String(err)}`);
91
+ }
92
+ const publicKey = loadEd25519PublicKey(publicKeyBytes);
93
+ let signatureValid;
94
+ try {
95
+ signatureValid = cryptoVerify(null, payloadBytes, publicKey, sigBytes);
96
+ } catch {
97
+ signatureValid = false;
98
+ }
99
+ if (!signatureValid) {
100
+ throw new LeaseError("invalid lease signature");
101
+ }
102
+ let payload;
103
+ try {
104
+ payload = JSON.parse(payloadBytes.toString("utf-8"));
105
+ } catch (err) {
106
+ throw new LeaseError(`malformed lease payload: ${err instanceof Error ? err.message : String(err)}`);
107
+ }
108
+ if (payload.license_key !== opts.expectedLicenseKey) {
109
+ throw new LeaseError("lease license_key does not match");
110
+ }
111
+ if (payload.machine_id !== opts.expectedMachineId) {
112
+ throw new LeaseError("lease machine_id does not match");
113
+ }
114
+ if (payload.product_id !== opts.expectedProductId) {
115
+ throw new LeaseError("lease product_id does not match");
116
+ }
117
+ const now = opts.now ?? /* @__PURE__ */ new Date();
118
+ if (!payload.lease_expires_at || now >= new Date(payload.lease_expires_at)) {
119
+ throw new LeaseError("lease trust window has lapsed");
120
+ }
121
+ if (payload.license_expires_at && now >= new Date(payload.license_expires_at)) {
122
+ throw new LeaseError("license has expired");
123
+ }
124
+ return payload;
125
+ }
126
+
127
+ // src/machine.ts
128
+ import { randomUUID } from "crypto";
129
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
130
+ import { join as join2 } from "path";
131
+
132
+ // src/storage.ts
133
+ import { randomBytes } from "crypto";
134
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, chmodSync } from "fs";
135
+ import { homedir } from "os";
136
+ import { join } from "path";
137
+ function defaultCacheDir(productId) {
138
+ return join(homedir(), ".sublimekeys", productId);
139
+ }
140
+ function leasePath(productId, base) {
141
+ return join(base ?? defaultCacheDir(productId), "lease.json");
142
+ }
143
+ function loadLease(productId, base) {
144
+ const path = leasePath(productId, base);
145
+ if (!existsSync(path)) return null;
146
+ try {
147
+ return JSON.parse(readFileSync(path, "utf-8"));
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ function saveLease(productId, licenseKey, token, base) {
153
+ const path = leasePath(productId, base);
154
+ mkdirSync(join(path, ".."), { recursive: true });
155
+ const data = JSON.stringify({ license_key: licenseKey, token });
156
+ const tmpPath = join(path, "..", `.lease-${randomBytes(6).toString("hex")}.tmp`);
157
+ try {
158
+ writeFileSync(tmpPath, data, "utf-8");
159
+ renameSync(tmpPath, path);
160
+ } catch (err) {
161
+ try {
162
+ unlinkSync(tmpPath);
163
+ } catch {
164
+ }
165
+ throw err;
166
+ }
167
+ try {
168
+ chmodSync(path, 384);
169
+ } catch {
170
+ }
171
+ }
172
+ function clearLease(productId, base) {
173
+ const path = leasePath(productId, base);
174
+ try {
175
+ unlinkSync(path);
176
+ } catch {
177
+ }
178
+ }
179
+
180
+ // src/machine.ts
181
+ function getOrCreateMachineId(productId, base) {
182
+ const dir = base ?? defaultCacheDir(productId);
183
+ const path = join2(dir, "machine_id");
184
+ if (existsSync2(path)) {
185
+ const existing = readFileSync2(path, "utf-8").trim();
186
+ if (existing) return existing;
187
+ }
188
+ const machineId = randomUUID();
189
+ mkdirSync2(dir, { recursive: true });
190
+ writeFileSync2(path, machineId, "utf-8");
191
+ return machineId;
192
+ }
193
+
194
+ // src/client.ts
195
+ var SublimeKeysClient = class {
196
+ productId;
197
+ http;
198
+ cacheBase;
199
+ publicKeyBytes;
200
+ constructor(productId, options = {}) {
201
+ this.productId = productId;
202
+ this.http = new HttpClient(options.baseUrl ?? DEFAULT_BASE_URL, options.timeoutMs ?? 1e4);
203
+ this.cacheBase = options.cacheDir;
204
+ this.publicKeyBytes = Buffer.from(options.publicKeyB64u ?? PUBLIC_KEY_B64U, "base64url");
205
+ }
206
+ /** A stable, locally-persisted machine identifier — generated once on
207
+ * first call, reused after that. */
208
+ getMachineId() {
209
+ return getOrCreateMachineId(this.productId, this.cacheBase);
210
+ }
211
+ /** First run for this license on this machine. Always goes online —
212
+ * activation is inherently server-side state. Safe to call again on a
213
+ * machine that's already activated (the server treats it as a no-op
214
+ * that doesn't consume another activation slot). */
215
+ async activate(licenseKey, machineId) {
216
+ const resolvedMachineId = machineId ?? this.getMachineId();
217
+ let data;
218
+ try {
219
+ data = await this.http.postJson("/activate", {
220
+ license_key: licenseKey,
221
+ machine_id: resolvedMachineId,
222
+ product_id: this.productId
223
+ });
224
+ } catch (err) {
225
+ const reason = err instanceof NetworkError ? err.message : String(err);
226
+ return { valid: false, message: `Network error: ${reason}`, source: "online" };
227
+ }
228
+ const result = {
229
+ valid: data.valid,
230
+ message: data.message,
231
+ email: data.email ?? null,
232
+ expiresAt: data.expires_at ?? null,
233
+ source: "online"
234
+ };
235
+ if (result.valid && data.lease) {
236
+ saveLease(this.productId, licenseKey, data.lease, this.cacheBase);
237
+ }
238
+ return result;
239
+ }
240
+ /** Every launch after the first. Checks the local cached lease first
241
+ * (instant, no network) if allowOffline is true; falls back to an online
242
+ * /verify call when there's no usable cached lease. Refreshes the cache
243
+ * on every successful online check, and clears it on a revoked/invalid
244
+ * result so a stale cache never outlives the license it was issued for. */
245
+ async verify(licenseKey, machineId, options = {}) {
246
+ const resolvedMachineId = machineId ?? this.getMachineId();
247
+ const allowOffline = options.allowOffline ?? true;
248
+ if (allowOffline) {
249
+ const cached = loadLease(this.productId, this.cacheBase);
250
+ if (cached && cached.license_key === licenseKey) {
251
+ try {
252
+ const payload = verifyLease(cached.token, this.publicKeyBytes, {
253
+ expectedLicenseKey: licenseKey,
254
+ expectedMachineId: resolvedMachineId,
255
+ expectedProductId: this.productId,
256
+ now: options._now
257
+ });
258
+ return {
259
+ valid: true,
260
+ message: "Valid (offline)",
261
+ email: payload.email ?? null,
262
+ expiresAt: payload.license_expires_at ?? null,
263
+ source: "offline_cache"
264
+ };
265
+ } catch (err) {
266
+ if (!(err instanceof LeaseError)) throw err;
267
+ }
268
+ }
269
+ }
270
+ let data;
271
+ try {
272
+ data = await this.http.postJson("/verify", {
273
+ license_key: licenseKey,
274
+ machine_id: resolvedMachineId,
275
+ product_id: this.productId
276
+ });
277
+ } catch {
278
+ return { valid: false, message: "Offline and no valid cached lease", source: "offline_cache_miss" };
279
+ }
280
+ const result = {
281
+ valid: data.valid,
282
+ message: data.message,
283
+ email: data.email ?? null,
284
+ expiresAt: data.expires_at ?? null,
285
+ source: "online"
286
+ };
287
+ if (result.valid && data.lease) {
288
+ saveLease(this.productId, licenseKey, data.lease, this.cacheBase);
289
+ } else if (!result.valid) {
290
+ clearLease(this.productId, this.cacheBase);
291
+ }
292
+ return result;
293
+ }
294
+ /** User signs out / uninstalls. Clears the local cache immediately —
295
+ * otherwise a deliberately-deactivated license would keep
296
+ * offline-verifying as valid until its trust window lapsed. */
297
+ async deactivate(licenseKey, machineId) {
298
+ const resolvedMachineId = machineId ?? this.getMachineId();
299
+ let data;
300
+ try {
301
+ data = await this.http.postJson("/deactivate", {
302
+ license_key: licenseKey,
303
+ machine_id: resolvedMachineId,
304
+ product_id: this.productId
305
+ });
306
+ } catch (err) {
307
+ const reason = err instanceof NetworkError ? err.message : String(err);
308
+ return { valid: false, message: `Network error: ${reason}`, source: "online" };
309
+ }
310
+ clearLease(this.productId, this.cacheBase);
311
+ return { valid: data.valid, message: data.message, source: "online" };
312
+ }
313
+ /** Get-or-create a trial for this machine. Idempotent server-side —
314
+ * reinstalling never resets the clock. */
315
+ async startTrial(machineId) {
316
+ return this.trialCall("/trial/start", machineId);
317
+ }
318
+ /** Read-only trial check — never starts one. */
319
+ async trialStatus(machineId) {
320
+ return this.trialCall("/trial/status", machineId);
321
+ }
322
+ async trialCall(path, machineId) {
323
+ const resolvedMachineId = machineId ?? this.getMachineId();
324
+ let data;
325
+ try {
326
+ data = await this.http.postJson(path, {
327
+ machine_id: resolvedMachineId,
328
+ product_id: this.productId
329
+ });
330
+ } catch (err) {
331
+ const reason = err instanceof NetworkError ? err.message : String(err);
332
+ return { status: "none", daysLeft: 0, message: `Network error: ${reason}` };
333
+ }
334
+ return {
335
+ status: data.status,
336
+ daysLeft: data.days_left ?? 0,
337
+ expiresAt: data.expires_at ?? null,
338
+ message: data.message ?? ""
339
+ };
340
+ }
341
+ };
342
+ export {
343
+ LeaseError,
344
+ NetworkError,
345
+ PUBLIC_KEY_B64U,
346
+ SublimeKeysClient,
347
+ SublimeKeysError,
348
+ getOrCreateMachineId,
349
+ verifyLease
350
+ };
351
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pubkey.ts","../src/errors.ts","../src/http.ts","../src/lease.ts","../src/machine.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["/** SublimeKeys production Ed25519 public key (base64url, 32 raw bytes).\n * Pinned here so the offline hot path never fetches /public-key at runtime. */\nexport const PUBLIC_KEY_B64U = \"OEK14M1tAFMPuh0RkgdV3Xvgpb0igCfzbct527xoAzE\";\n","export class SublimeKeysError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SublimeKeysError\";\n }\n}\n\n/** A request to the SublimeKeys API failed to complete (unreachable host, timeout, connection reset, ...). */\nexport class NetworkError extends SublimeKeysError {\n constructor(message: string) {\n super(message);\n this.name = \"NetworkError\";\n }\n}\n\n/** A cached offline lease failed verification — malformed, tampered, expired,\n * or scoped to a different license/machine/product. */\nexport class LeaseError extends SublimeKeysError {\n constructor(message: string) {\n super(message);\n this.name = \"LeaseError\";\n }\n}\n","// Thin fetch-based JSON client — zero HTTP dependencies, matching the\n// license server's own minimal-dependency style. This is also where the\n// server's one REST inconsistency gets papered over: /activate raises an\n// HTTP error (404/403, body {\"detail\": \"...\"}) while /verify always returns\n// HTTP 200 with valid:false. Callers of this module see one shape either way.\n\nimport { NetworkError } from \"./errors.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.sublimearts.io\";\n\nexport interface ApiResponse {\n valid: boolean;\n message: string;\n email?: string | null;\n expires_at?: string | null;\n lease?: string | null;\n [key: string]: unknown;\n}\n\nexport class HttpClient {\n private baseUrl: string;\n private timeoutMs: number;\n\n constructor(baseUrl: string = DEFAULT_BASE_URL, timeoutMs = 10_000) {\n this.baseUrl = baseUrl.replace(/\\/+$/, \"\");\n this.timeoutMs = timeoutMs;\n }\n\n async postJson(path: string, body: Record<string, unknown>): Promise<ApiResponse> {\n return this.request(\"POST\", path, body);\n }\n\n async getJson(path: string): Promise<ApiResponse> {\n return this.request(\"GET\", path, undefined);\n }\n\n private async request(method: string, path: string, body: Record<string, unknown> | undefined): Promise<ApiResponse> {\n const url = `${this.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: body !== undefined ? { \"Content-Type\": \"application/json\" } : undefined,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new NetworkError(reason);\n } finally {\n clearTimeout(timer);\n }\n\n if (!res.ok) {\n let message = `${res.status} ${res.statusText}`;\n try {\n const payload = (await res.json()) as { detail?: string };\n if (payload?.detail) message = payload.detail;\n } catch {\n // non-JSON error body — keep the status line as the message\n }\n return { valid: false, message, email: null, expires_at: null, lease: null };\n }\n\n return (await res.json()) as ApiResponse;\n }\n}\n","// Offline verification of SublimeKeys signed leases.\n//\n// A lease is a compact token: base64url(json_payload) + \".\" + base64url(signature),\n// signed with Ed25519 by the SublimeKeys license server. Verifying it locally\n// requires no network call, which is the entire point of this module.\n\nimport { createPublicKey, verify as cryptoVerify } from \"node:crypto\";\nimport { LeaseError } from \"./errors.js\";\n\nexport interface LeasePayload {\n kid?: string;\n license_key: string;\n machine_id: string;\n product_id: string;\n email?: string | null;\n issued_at?: string;\n lease_expires_at: string;\n license_expires_at?: string | null;\n [key: string]: unknown;\n}\n\nexport interface VerifyLeaseOptions {\n expectedLicenseKey: string;\n expectedMachineId: string;\n expectedProductId: string;\n now?: Date;\n}\n\nfunction loadEd25519PublicKey(rawBytes: Buffer) {\n return createPublicKey({\n key: { kty: \"OKP\", crv: \"Ed25519\", x: rawBytes.toString(\"base64url\") },\n format: \"jwk\",\n });\n}\n\n/**\n * Verify a lease token and return its payload if valid.\n *\n * Throws LeaseError for any failure — malformed token, bad signature,\n * wrong license/machine/product, or an expired trust window/license.\n * A valid signature only proves \"SublimeKeys signed this,\" not \"this is\n * for the product/machine/license you expect\" — the three equality\n * checks below are not optional and cannot be skipped by a caller.\n */\nexport function verifyLease(token: string, publicKeyBytes: Buffer, opts: VerifyLeaseOptions): LeasePayload {\n const parts = token.split(\".\");\n if (parts.length !== 2) {\n throw new LeaseError(\"malformed lease token: expected exactly one '.' separator\");\n }\n const [payloadB64, sigB64] = parts;\n\n let payloadBytes: Buffer;\n let sigBytes: Buffer;\n try {\n payloadBytes = Buffer.from(payloadB64, \"base64url\");\n sigBytes = Buffer.from(sigB64, \"base64url\");\n } catch (err) {\n throw new LeaseError(`malformed lease token: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n const publicKey = loadEd25519PublicKey(publicKeyBytes);\n\n // Verify the signature BEFORE parsing the payload as JSON — never hand\n // unauthenticated bytes to a parser first, even a safe one.\n let signatureValid: boolean;\n try {\n signatureValid = cryptoVerify(null, payloadBytes, publicKey, sigBytes);\n } catch {\n signatureValid = false;\n }\n if (!signatureValid) {\n throw new LeaseError(\"invalid lease signature\");\n }\n\n let payload: LeasePayload;\n try {\n payload = JSON.parse(payloadBytes.toString(\"utf-8\")) as LeasePayload;\n } catch (err) {\n throw new LeaseError(`malformed lease payload: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n if (payload.license_key !== opts.expectedLicenseKey) {\n throw new LeaseError(\"lease license_key does not match\");\n }\n if (payload.machine_id !== opts.expectedMachineId) {\n throw new LeaseError(\"lease machine_id does not match\");\n }\n if (payload.product_id !== opts.expectedProductId) {\n throw new LeaseError(\"lease product_id does not match\");\n }\n\n const now = opts.now ?? new Date();\n\n if (!payload.lease_expires_at || now >= new Date(payload.lease_expires_at)) {\n throw new LeaseError(\"lease trust window has lapsed\");\n }\n\n if (payload.license_expires_at && now >= new Date(payload.license_expires_at)) {\n throw new LeaseError(\"license has expired\");\n }\n\n return payload;\n}\n","// A stable per-install machine identifier.\n//\n// Deliberately a persisted random UUID, not a hardware fingerprint. Real\n// hardware IDs (MAC addresses, disk serials, ...) are spoofable, change on\n// VMs/cloud desktops/Apple Silicon Rosetta in ways that generate false\n// mismatches and support tickets, and buy little real security over a\n// persisted UUID for this use case. Trivially resettable by deleting one\n// file — that's a known, accepted tradeoff (a soft deterrent, not DRM),\n// not an oversight.\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { defaultCacheDir } from \"./storage.js\";\n\nexport function getOrCreateMachineId(productId: string, base?: string): string {\n const dir = base ?? defaultCacheDir(productId);\n const path = join(dir, \"machine_id\");\n\n if (existsSync(path)) {\n const existing = readFileSync(path, \"utf-8\").trim();\n if (existing) return existing;\n }\n\n const machineId = randomUUID();\n mkdirSync(dir, { recursive: true });\n writeFileSync(path, machineId, \"utf-8\");\n return machineId;\n}\n","// Local cache for signed leases — one JSON file per product, written\n// atomically (temp file + rename) so a crash mid-write never leaves a\n// half-written file behind.\n\nimport { randomBytes } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, chmodSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface CachedLease {\n license_key: string;\n token: string;\n}\n\nexport function defaultCacheDir(productId: string): string {\n return join(homedir(), \".sublimekeys\", productId);\n}\n\nfunction leasePath(productId: string, base?: string): string {\n return join(base ?? defaultCacheDir(productId), \"lease.json\");\n}\n\nexport function loadLease(productId: string, base?: string): CachedLease | null {\n const path = leasePath(productId, base);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf-8\")) as CachedLease;\n } catch {\n return null;\n }\n}\n\nexport function saveLease(productId: string, licenseKey: string, token: string, base?: string): void {\n const path = leasePath(productId, base);\n mkdirSync(join(path, \"..\"), { recursive: true });\n const data = JSON.stringify({ license_key: licenseKey, token });\n\n const tmpPath = join(path, \"..\", `.lease-${randomBytes(6).toString(\"hex\")}.tmp`);\n try {\n writeFileSync(tmpPath, data, \"utf-8\");\n renameSync(tmpPath, path);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // best-effort cleanup\n }\n throw err;\n }\n\n try {\n chmodSync(path, 0o600);\n } catch {\n // best-effort — no-op on native Windows ACLs, real on POSIX/WSL\n }\n}\n\nexport function clearLease(productId: string, base?: string): void {\n const path = leasePath(productId, base);\n try {\n unlinkSync(path);\n } catch {\n // already gone — fine\n }\n}\n","// SublimeKeysClient — the main SDK entry point.\n//\n// Cache-first, not online-first-with-fallback: after one successful\n// activate()/verify(), subsequent verify() calls make zero network requests\n// for as long as the cached lease's trust window is valid (currently 7 days,\n// set server-side) — that's the actual point of offline leases. A network\n// call only happens again once the cache is missing, invalid, or its trust\n// window has lapsed.\n\nimport { PUBLIC_KEY_B64U } from \"./pubkey.js\";\nimport { NetworkError, LeaseError } from \"./errors.js\";\nimport { DEFAULT_BASE_URL, HttpClient } from \"./http.js\";\nimport { verifyLease } from \"./lease.js\";\nimport { getOrCreateMachineId } from \"./machine.js\";\nimport { loadLease, saveLease, clearLease } from \"./storage.js\";\n\nexport type LicenseSource = \"online\" | \"offline_cache\" | \"offline_cache_miss\";\n\nexport interface LicenseResult {\n valid: boolean;\n message: string;\n email?: string | null;\n expiresAt?: string | null;\n source: LicenseSource;\n}\n\nexport interface TrialResult {\n status: \"active\" | \"expired\" | \"none\";\n daysLeft: number;\n expiresAt?: string | null;\n message: string;\n}\n\nexport interface SublimeKeysClientOptions {\n baseUrl?: string;\n cacheDir?: string;\n publicKeyB64u?: string;\n timeoutMs?: number;\n}\n\nexport interface VerifyOptions {\n allowOffline?: boolean;\n /** Internal test hook — overrides \"now\" when checking lease expiry. */\n _now?: Date;\n}\n\nexport class SublimeKeysClient {\n readonly productId: string;\n private readonly http: HttpClient;\n private readonly cacheBase?: string;\n private readonly publicKeyBytes: Buffer;\n\n constructor(productId: string, options: SublimeKeysClientOptions = {}) {\n this.productId = productId;\n this.http = new HttpClient(options.baseUrl ?? DEFAULT_BASE_URL, options.timeoutMs ?? 10_000);\n this.cacheBase = options.cacheDir;\n this.publicKeyBytes = Buffer.from(options.publicKeyB64u ?? PUBLIC_KEY_B64U, \"base64url\");\n }\n\n /** A stable, locally-persisted machine identifier — generated once on\n * first call, reused after that. */\n getMachineId(): string {\n return getOrCreateMachineId(this.productId, this.cacheBase);\n }\n\n /** First run for this license on this machine. Always goes online —\n * activation is inherently server-side state. Safe to call again on a\n * machine that's already activated (the server treats it as a no-op\n * that doesn't consume another activation slot). */\n async activate(licenseKey: string, machineId?: string): Promise<LicenseResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n let data;\n try {\n data = await this.http.postJson(\"/activate\", {\n license_key: licenseKey,\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch (err) {\n const reason = err instanceof NetworkError ? err.message : String(err);\n return { valid: false, message: `Network error: ${reason}`, source: \"online\" };\n }\n\n const result: LicenseResult = {\n valid: data.valid,\n message: data.message,\n email: data.email ?? null,\n expiresAt: data.expires_at ?? null,\n source: \"online\",\n };\n if (result.valid && data.lease) {\n saveLease(this.productId, licenseKey, data.lease, this.cacheBase);\n }\n return result;\n }\n\n /** Every launch after the first. Checks the local cached lease first\n * (instant, no network) if allowOffline is true; falls back to an online\n * /verify call when there's no usable cached lease. Refreshes the cache\n * on every successful online check, and clears it on a revoked/invalid\n * result so a stale cache never outlives the license it was issued for. */\n async verify(licenseKey: string, machineId?: string, options: VerifyOptions = {}): Promise<LicenseResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n const allowOffline = options.allowOffline ?? true;\n\n if (allowOffline) {\n const cached = loadLease(this.productId, this.cacheBase);\n if (cached && cached.license_key === licenseKey) {\n try {\n const payload = verifyLease(cached.token, this.publicKeyBytes, {\n expectedLicenseKey: licenseKey,\n expectedMachineId: resolvedMachineId,\n expectedProductId: this.productId,\n now: options._now,\n });\n return {\n valid: true,\n message: \"Valid (offline)\",\n email: payload.email ?? null,\n expiresAt: payload.license_expires_at ?? null,\n source: \"offline_cache\",\n };\n } catch (err) {\n if (!(err instanceof LeaseError)) throw err;\n // cache unusable — fall through to an online check\n }\n }\n }\n\n let data;\n try {\n data = await this.http.postJson(\"/verify\", {\n license_key: licenseKey,\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch {\n return { valid: false, message: \"Offline and no valid cached lease\", source: \"offline_cache_miss\" };\n }\n\n const result: LicenseResult = {\n valid: data.valid,\n message: data.message,\n email: data.email ?? null,\n expiresAt: data.expires_at ?? null,\n source: \"online\",\n };\n if (result.valid && data.lease) {\n saveLease(this.productId, licenseKey, data.lease, this.cacheBase);\n } else if (!result.valid) {\n // Don't leave a stale cache behind — a license revoked server-side\n // must not keep offline-verifying as valid for days afterward.\n clearLease(this.productId, this.cacheBase);\n }\n return result;\n }\n\n /** User signs out / uninstalls. Clears the local cache immediately —\n * otherwise a deliberately-deactivated license would keep\n * offline-verifying as valid until its trust window lapsed. */\n async deactivate(licenseKey: string, machineId?: string): Promise<LicenseResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n let data;\n try {\n data = await this.http.postJson(\"/deactivate\", {\n license_key: licenseKey,\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch (err) {\n const reason = err instanceof NetworkError ? err.message : String(err);\n return { valid: false, message: `Network error: ${reason}`, source: \"online\" };\n }\n\n clearLease(this.productId, this.cacheBase);\n return { valid: data.valid, message: data.message, source: \"online\" };\n }\n\n /** Get-or-create a trial for this machine. Idempotent server-side —\n * reinstalling never resets the clock. */\n async startTrial(machineId?: string): Promise<TrialResult> {\n return this.trialCall(\"/trial/start\", machineId);\n }\n\n /** Read-only trial check — never starts one. */\n async trialStatus(machineId?: string): Promise<TrialResult> {\n return this.trialCall(\"/trial/status\", machineId);\n }\n\n private async trialCall(path: string, machineId?: string): Promise<TrialResult> {\n const resolvedMachineId = machineId ?? this.getMachineId();\n let data;\n try {\n data = await this.http.postJson(path, {\n machine_id: resolvedMachineId,\n product_id: this.productId,\n });\n } catch (err) {\n const reason = err instanceof NetworkError ? err.message : String(err);\n return { status: \"none\", daysLeft: 0, message: `Network error: ${reason}` };\n }\n return {\n status: data.status as TrialResult[\"status\"],\n daysLeft: (data.days_left as number) ?? 0,\n expiresAt: (data.expires_at as string) ?? null,\n message: (data.message as string) ?? \"\",\n };\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readFileSync","writeFileSync","join","join","existsSync","readFileSync","mkdirSync","writeFileSync"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "sublimekeys",
3
+ "version": "0.1.0",
4
+ "description": "Official Node.js/Electron SDK for SublimeKeys — license activation, verification, and offline signed-lease checks with zero runtime dependencies.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20.0.0"
21
+ },
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "test": "vitest run",
25
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json"
26
+ },
27
+ "keywords": [
28
+ "license",
29
+ "licensing",
30
+ "sublimekeys",
31
+ "electron",
32
+ "software-licensing",
33
+ "ed25519",
34
+ "offline-verification"
35
+ ],
36
+ "license": "MIT",
37
+ "author": "Sublimearts.io",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Volume00/sublimekeys-node.git"
41
+ },
42
+ "homepage": "https://keys.sublimearts.io",
43
+ "bugs": {
44
+ "url": "https://github.com/Volume00/sublimekeys-node/issues"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22.10.2",
48
+ "tsup": "^8.3.5",
49
+ "typescript": "^5.7.2",
50
+ "vitest": "^4.1.10"
51
+ }
52
+ }