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 +21 -0
- package/README.md +111 -0
- package/dist/index.cjs +384 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +351 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,kBAAkB;;;ACFxB,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,eAAN,cAA2B,iBAAiB;AAAA,EACjD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAIO,IAAM,aAAN,cAAyB,iBAAiB;AAAA,EAC/C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;ACdO,IAAM,mBAAmB;AAWzB,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EAER,YAAY,UAAkB,kBAAkB,YAAY,KAAQ;AAClE,SAAK,UAAU,QAAQ,QAAQ,QAAQ,EAAE;AACzC,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,SAAS,MAAc,MAAqD;AAChF,WAAO,KAAK,QAAQ,QAAQ,MAAM,IAAI;AAAA,EACxC;AAAA,EAEA,MAAM,QAAQ,MAAoC;AAChD,WAAO,KAAK,QAAQ,OAAO,MAAM,MAAS;AAAA,EAC5C;AAAA,EAEA,MAAc,QAAQ,QAAgB,MAAc,MAAiE;AACnH,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,SAAS;AAEjE,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AAAA,QACrB;AAAA,QACA,SAAS,SAAS,SAAY,EAAE,gBAAgB,mBAAmB,IAAI;AAAA,QACvE,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,QAClD,QAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,IAAI,aAAa,MAAM;AAAA,IAC/B,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,UAAU,GAAG,IAAI,MAAM,IAAI,IAAI,UAAU;AAC7C,UAAI;AACF,cAAM,UAAW,MAAM,IAAI,KAAK;AAChC,YAAI,SAAS,OAAQ,WAAU,QAAQ;AAAA,MACzC,QAAQ;AAAA,MAER;AACA,aAAO,EAAE,OAAO,OAAO,SAAS,OAAO,MAAM,YAAY,MAAM,OAAO,KAAK;AAAA,IAC7E;AAEA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AACF;;;AC/DA,yBAAwD;AAsBxD,SAAS,qBAAqB,UAAkB;AAC9C,aAAO,oCAAgB;AAAA,IACrB,KAAK,EAAE,KAAK,OAAO,KAAK,WAAW,GAAG,SAAS,SAAS,WAAW,EAAE;AAAA,IACrE,QAAQ;AAAA,EACV,CAAC;AACH;AAWO,SAAS,YAAY,OAAe,gBAAwB,MAAwC;AACzG,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,WAAW,2DAA2D;AAAA,EAClF;AACA,QAAM,CAAC,YAAY,MAAM,IAAI;AAE7B,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,mBAAe,OAAO,KAAK,YAAY,WAAW;AAClD,eAAW,OAAO,KAAK,QAAQ,WAAW;AAAA,EAC5C,SAAS,KAAK;AACZ,UAAM,IAAI,WAAW,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EACnG;AAEA,QAAM,YAAY,qBAAqB,cAAc;AAIrD,MAAI;AACJ,MAAI;AACF,yBAAiB,mBAAAA,QAAa,MAAM,cAAc,WAAW,QAAQ;AAAA,EACvE,QAAQ;AACN,qBAAiB;AAAA,EACnB;AACA,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,WAAW,yBAAyB;AAAA,EAChD;AAEA,MAAI;AACJ,MAAI;AACF,cAAU,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AAAA,EACrD,SAAS,KAAK;AACZ,UAAM,IAAI,WAAW,4BAA4B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EACrG;AAEA,MAAI,QAAQ,gBAAgB,KAAK,oBAAoB;AACnD,UAAM,IAAI,WAAW,kCAAkC;AAAA,EACzD;AACA,MAAI,QAAQ,eAAe,KAAK,mBAAmB;AACjD,UAAM,IAAI,WAAW,iCAAiC;AAAA,EACxD;AACA,MAAI,QAAQ,eAAe,KAAK,mBAAmB;AACjD,UAAM,IAAI,WAAW,iCAAiC;AAAA,EACxD;AAEA,QAAM,MAAM,KAAK,OAAO,oBAAI,KAAK;AAEjC,MAAI,CAAC,QAAQ,oBAAoB,OAAO,IAAI,KAAK,QAAQ,gBAAgB,GAAG;AAC1E,UAAM,IAAI,WAAW,+BAA+B;AAAA,EACtD;AAEA,MAAI,QAAQ,sBAAsB,OAAO,IAAI,KAAK,QAAQ,kBAAkB,GAAG;AAC7E,UAAM,IAAI,WAAW,qBAAqB;AAAA,EAC5C;AAEA,SAAO;AACT;;;AC5FA,IAAAC,sBAA2B;AAC3B,IAAAC,kBAAmE;AACnE,IAAAC,oBAAqB;;;ACRrB,IAAAC,sBAA4B;AAC5B,qBAAsG;AACtG,qBAAwB;AACxB,uBAAqB;AAOd,SAAS,gBAAgB,WAA2B;AACzD,aAAO,2BAAK,wBAAQ,GAAG,gBAAgB,SAAS;AAClD;AAEA,SAAS,UAAU,WAAmB,MAAuB;AAC3D,aAAO,uBAAK,QAAQ,gBAAgB,SAAS,GAAG,YAAY;AAC9D;AAEO,SAAS,UAAU,WAAmB,MAAmC;AAC9E,QAAM,OAAO,UAAU,WAAW,IAAI;AACtC,MAAI,KAAC,2BAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,UAAM,6BAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,UAAU,WAAmB,YAAoB,OAAe,MAAqB;AACnG,QAAM,OAAO,UAAU,WAAW,IAAI;AACtC,oCAAU,uBAAK,MAAM,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,QAAM,OAAO,KAAK,UAAU,EAAE,aAAa,YAAY,MAAM,CAAC;AAE9D,QAAM,cAAU,uBAAK,MAAM,MAAM,cAAU,iCAAY,CAAC,EAAE,SAAS,KAAK,CAAC,MAAM;AAC/E,MAAI;AACF,sCAAc,SAAS,MAAM,OAAO;AACpC,mCAAW,SAAS,IAAI;AAAA,EAC1B,SAAS,KAAK;AACZ,QAAI;AACF,qCAAW,OAAO;AAAA,IACpB,QAAQ;AAAA,IAER;AACA,UAAM;AAAA,EACR;AAEA,MAAI;AACF,kCAAU,MAAM,GAAK;AAAA,EACvB,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,WAAW,WAAmB,MAAqB;AACjE,QAAM,OAAO,UAAU,WAAW,IAAI;AACtC,MAAI;AACF,mCAAW,IAAI;AAAA,EACjB,QAAQ;AAAA,EAER;AACF;;;ADjDO,SAAS,qBAAqB,WAAmB,MAAuB;AAC7E,QAAM,MAAM,QAAQ,gBAAgB,SAAS;AAC7C,QAAM,WAAO,wBAAK,KAAK,YAAY;AAEnC,UAAI,4BAAW,IAAI,GAAG;AACpB,UAAM,eAAW,8BAAa,MAAM,OAAO,EAAE,KAAK;AAClD,QAAI,SAAU,QAAO;AAAA,EACvB;AAEA,QAAM,gBAAY,gCAAW;AAC7B,iCAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,qCAAc,MAAM,WAAW,OAAO;AACtC,SAAO;AACT;;;AEkBO,IAAM,oBAAN,MAAwB;AAAA,EACpB;AAAA,EACQ;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,WAAmB,UAAoC,CAAC,GAAG;AACrE,SAAK,YAAY;AACjB,SAAK,OAAO,IAAI,WAAW,QAAQ,WAAW,kBAAkB,QAAQ,aAAa,GAAM;AAC3F,SAAK,YAAY,QAAQ;AACzB,SAAK,iBAAiB,OAAO,KAAK,QAAQ,iBAAiB,iBAAiB,WAAW;AAAA,EACzF;AAAA;AAAA;AAAA,EAIA,eAAuB;AACrB,WAAO,qBAAqB,KAAK,WAAW,KAAK,SAAS;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAS,YAAoB,WAA4C;AAC7E,UAAM,oBAAoB,aAAa,KAAK,aAAa;AACzD,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,KAAK,KAAK,SAAS,aAAa;AAAA,QAC3C,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,eAAe,IAAI,UAAU,OAAO,GAAG;AACrE,aAAO,EAAE,OAAO,OAAO,SAAS,kBAAkB,MAAM,IAAI,QAAQ,SAAS;AAAA,IAC/E;AAEA,UAAM,SAAwB;AAAA,MAC5B,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,MACd,OAAO,KAAK,SAAS;AAAA,MACrB,WAAW,KAAK,cAAc;AAAA,MAC9B,QAAQ;AAAA,IACV;AACA,QAAI,OAAO,SAAS,KAAK,OAAO;AAC9B,gBAAU,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,SAAS;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,YAAoB,WAAoB,UAAyB,CAAC,GAA2B;AACxG,UAAM,oBAAoB,aAAa,KAAK,aAAa;AACzD,UAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAI,cAAc;AAChB,YAAM,SAAS,UAAU,KAAK,WAAW,KAAK,SAAS;AACvD,UAAI,UAAU,OAAO,gBAAgB,YAAY;AAC/C,YAAI;AACF,gBAAM,UAAU,YAAY,OAAO,OAAO,KAAK,gBAAgB;AAAA,YAC7D,oBAAoB;AAAA,YACpB,mBAAmB;AAAA,YACnB,mBAAmB,KAAK;AAAA,YACxB,KAAK,QAAQ;AAAA,UACf,CAAC;AACD,iBAAO;AAAA,YACL,OAAO;AAAA,YACP,SAAS;AAAA,YACT,OAAO,QAAQ,SAAS;AAAA,YACxB,WAAW,QAAQ,sBAAsB;AAAA,YACzC,QAAQ;AAAA,UACV;AAAA,QACF,SAAS,KAAK;AACZ,cAAI,EAAE,eAAe,YAAa,OAAM;AAAA,QAE1C;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,KAAK,KAAK,SAAS,WAAW;AAAA,QACzC,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH,QAAQ;AACN,aAAO,EAAE,OAAO,OAAO,SAAS,qCAAqC,QAAQ,qBAAqB;AAAA,IACpG;AAEA,UAAM,SAAwB;AAAA,MAC5B,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,MACd,OAAO,KAAK,SAAS;AAAA,MACrB,WAAW,KAAK,cAAc;AAAA,MAC9B,QAAQ;AAAA,IACV;AACA,QAAI,OAAO,SAAS,KAAK,OAAO;AAC9B,gBAAU,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,SAAS;AAAA,IAClE,WAAW,CAAC,OAAO,OAAO;AAGxB,iBAAW,KAAK,WAAW,KAAK,SAAS;AAAA,IAC3C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,YAAoB,WAA4C;AAC/E,UAAM,oBAAoB,aAAa,KAAK,aAAa;AACzD,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,KAAK,KAAK,SAAS,eAAe;AAAA,QAC7C,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,eAAe,IAAI,UAAU,OAAO,GAAG;AACrE,aAAO,EAAE,OAAO,OAAO,SAAS,kBAAkB,MAAM,IAAI,QAAQ,SAAS;AAAA,IAC/E;AAEA,eAAW,KAAK,WAAW,KAAK,SAAS;AACzC,WAAO,EAAE,OAAO,KAAK,OAAO,SAAS,KAAK,SAAS,QAAQ,SAAS;AAAA,EACtE;AAAA;AAAA;AAAA,EAIA,MAAM,WAAW,WAA0C;AACzD,WAAO,KAAK,UAAU,gBAAgB,SAAS;AAAA,EACjD;AAAA;AAAA,EAGA,MAAM,YAAY,WAA0C;AAC1D,WAAO,KAAK,UAAU,iBAAiB,SAAS;AAAA,EAClD;AAAA,EAEA,MAAc,UAAU,MAAc,WAA0C;AAC9E,UAAM,oBAAoB,aAAa,KAAK,aAAa;AACzD,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,KAAK,KAAK,SAAS,MAAM;AAAA,QACpC,YAAY;AAAA,QACZ,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,eAAe,IAAI,UAAU,OAAO,GAAG;AACrE,aAAO,EAAE,QAAQ,QAAQ,UAAU,GAAG,SAAS,kBAAkB,MAAM,GAAG;AAAA,IAC5E;AACA,WAAO;AAAA,MACL,QAAQ,KAAK;AAAA,MACb,UAAW,KAAK,aAAwB;AAAA,MACxC,WAAY,KAAK,cAAyB;AAAA,MAC1C,SAAU,KAAK,WAAsB;AAAA,IACvC;AAAA,EACF;AACF;","names":["cryptoVerify","import_node_crypto","import_node_fs","import_node_path","import_node_crypto"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|