@moonbase.sh/licensing 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FINGERPRINT_SPEC.md +241 -8
- package/README.md +69 -11
- package/dist/index.cjs +180 -59
- package/dist/index.d.cts +88 -20
- package/dist/index.d.ts +88 -20
- package/dist/index.js +178 -59
- package/fingerprint-vectors.json +204 -0
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ActivationMethod: () => ActivationMethod,
|
|
34
|
+
DEVICE_ID_SOURCE_TAGS: () => DEVICE_ID_SOURCE_TAGS,
|
|
34
35
|
ErrorType: () => ErrorType,
|
|
35
36
|
FINGERPRINT_PREFIX: () => FINGERPRINT_PREFIX,
|
|
36
37
|
FINGERPRINT_VERSION: () => FINGERPRINT_VERSION,
|
|
@@ -52,6 +53,7 @@ __export(index_exports, {
|
|
|
52
53
|
defaultDeviceIdentityReader: () => defaultDeviceIdentityReader,
|
|
53
54
|
fingerprintDeviceId: () => fingerprintDeviceId,
|
|
54
55
|
fingerprintDigest: () => fingerprintDigest,
|
|
56
|
+
identitySource: () => identitySource,
|
|
55
57
|
parseDeviceIdStamp: () => parseDeviceIdStamp,
|
|
56
58
|
parseIoregPlatformUuid: () => parseIoregPlatformUuid,
|
|
57
59
|
parseSmbiosParams: () => parseSmbiosParams,
|
|
@@ -66,6 +68,42 @@ var import_node_process3 = __toESM(require("process"), 1);
|
|
|
66
68
|
// src/client.ts
|
|
67
69
|
var import_cross_fetch = __toESM(require("cross-fetch"), 1);
|
|
68
70
|
|
|
71
|
+
// src/errors.ts
|
|
72
|
+
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
73
|
+
ErrorType2["None"] = "None";
|
|
74
|
+
ErrorType2["ApiError"] = "ApiError";
|
|
75
|
+
ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
|
|
76
|
+
ErrorType2["LicenseInvalid"] = "LicenseInvalid";
|
|
77
|
+
ErrorType2["LicenseRevoked"] = "LicenseRevoked";
|
|
78
|
+
ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
|
|
79
|
+
ErrorType2["LicenseExpired"] = "LicenseExpired";
|
|
80
|
+
ErrorType2["LicenseDeviceMismatch"] = "LicenseDeviceMismatch";
|
|
81
|
+
ErrorType2["DeviceIdentityUnavailable"] = "DeviceIdentityUnavailable";
|
|
82
|
+
return ErrorType2;
|
|
83
|
+
})(ErrorType || {});
|
|
84
|
+
var MoonbaseError = class extends Error {
|
|
85
|
+
constructor(title, detail, type, inner) {
|
|
86
|
+
super();
|
|
87
|
+
this.title = title;
|
|
88
|
+
this.detail = detail;
|
|
89
|
+
this.type = type;
|
|
90
|
+
this.inner = inner;
|
|
91
|
+
this.name = "MoonbaseError";
|
|
92
|
+
this.message = detail != null ? detail : title;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var InsufficientDeviceIdentityError = class extends MoonbaseError {
|
|
96
|
+
constructor(platform, reason = "no identity parameter could be read") {
|
|
97
|
+
super(
|
|
98
|
+
"No device identity",
|
|
99
|
+
`Could not identify this device (platform: ${platform}): ${reason}`,
|
|
100
|
+
"DeviceIdentityUnavailable" /* DeviceIdentityUnavailable */
|
|
101
|
+
);
|
|
102
|
+
this.platform = platform;
|
|
103
|
+
this.name = "InsufficientDeviceIdentityError";
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
69
107
|
// src/schemas.ts
|
|
70
108
|
var import_zod = require("zod");
|
|
71
109
|
|
|
@@ -82,6 +120,11 @@ var activationRequestResponseSchema = import_zod.z.object({
|
|
|
82
120
|
request: import_zod.z.string(),
|
|
83
121
|
browser: import_zod.z.string()
|
|
84
122
|
});
|
|
123
|
+
var problemDetailsSchema = import_zod.z.object({
|
|
124
|
+
title: import_zod.z.string().nullish(),
|
|
125
|
+
detail: import_zod.z.string().nullish(),
|
|
126
|
+
errorType: import_zod.z.string().nullish()
|
|
127
|
+
});
|
|
85
128
|
var productSchema = import_zod.z.object({
|
|
86
129
|
id: import_zod.z.string(),
|
|
87
130
|
name: import_zod.z.string(),
|
|
@@ -136,7 +179,7 @@ var LicenseClient = class {
|
|
|
136
179
|
body: JSON.stringify(content)
|
|
137
180
|
});
|
|
138
181
|
if (response.status >= 400) {
|
|
139
|
-
throw
|
|
182
|
+
throw await errorFromResponse(response);
|
|
140
183
|
}
|
|
141
184
|
try {
|
|
142
185
|
return activationRequestResponseSchema.parse(await response.json());
|
|
@@ -201,12 +244,12 @@ var LicenseClient = class {
|
|
|
201
244
|
body: license.token
|
|
202
245
|
});
|
|
203
246
|
if (response.status >= 400) {
|
|
204
|
-
throw
|
|
247
|
+
throw await errorFromResponse(response);
|
|
205
248
|
}
|
|
206
249
|
}
|
|
207
250
|
async handleLicenseResponse(response) {
|
|
208
251
|
if (response.status >= 400) {
|
|
209
|
-
throw
|
|
252
|
+
throw await errorFromResponse(response);
|
|
210
253
|
}
|
|
211
254
|
return await this.licenseValidator.validateLicense(await response.text());
|
|
212
255
|
}
|
|
@@ -227,6 +270,35 @@ var LicenseClient = class {
|
|
|
227
270
|
return `?${parts.join("&")}`;
|
|
228
271
|
}
|
|
229
272
|
};
|
|
273
|
+
var licenseErrorTypes = /* @__PURE__ */ new Set([
|
|
274
|
+
"NoEligibleLicense" /* NoEligibleLicense */,
|
|
275
|
+
"LicenseInvalid" /* LicenseInvalid */,
|
|
276
|
+
"LicenseRevoked" /* LicenseRevoked */,
|
|
277
|
+
"LicenseActivationRevoked" /* LicenseActivationRevoked */,
|
|
278
|
+
"LicenseExpired" /* LicenseExpired */
|
|
279
|
+
]);
|
|
280
|
+
async function errorFromResponse(response) {
|
|
281
|
+
const fallback = new MoonbaseError(
|
|
282
|
+
"Request not successful",
|
|
283
|
+
`The API responded with a ${response.status} ${response.statusText} response`,
|
|
284
|
+
"ApiError" /* ApiError */
|
|
285
|
+
);
|
|
286
|
+
let body;
|
|
287
|
+
try {
|
|
288
|
+
body = JSON.parse(await response.text());
|
|
289
|
+
} catch (e) {
|
|
290
|
+
return fallback;
|
|
291
|
+
}
|
|
292
|
+
const problem = problemDetailsSchema.safeParse(body);
|
|
293
|
+
if (!problem.success)
|
|
294
|
+
return fallback;
|
|
295
|
+
const { title, detail, errorType } = problem.data;
|
|
296
|
+
return new MoonbaseError(
|
|
297
|
+
title != null ? title : fallback.title,
|
|
298
|
+
detail != null ? detail : fallback.detail,
|
|
299
|
+
errorType && licenseErrorTypes.has(errorType) ? errorType : "ApiError" /* ApiError */
|
|
300
|
+
);
|
|
301
|
+
}
|
|
230
302
|
|
|
231
303
|
// src/deviceIdResolver.ts
|
|
232
304
|
var import_node_child_process2 = __toESM(require("child_process"), 1);
|
|
@@ -235,42 +307,6 @@ var import_node_os2 = __toESM(require("os"), 1);
|
|
|
235
307
|
var import_node_process2 = __toESM(require("process"), 1);
|
|
236
308
|
var import_systeminformation = __toESM(require("systeminformation"), 1);
|
|
237
309
|
|
|
238
|
-
// src/errors.ts
|
|
239
|
-
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
240
|
-
ErrorType2["None"] = "None";
|
|
241
|
-
ErrorType2["ApiError"] = "ApiError";
|
|
242
|
-
ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
|
|
243
|
-
ErrorType2["LicenseInvalid"] = "LicenseInvalid";
|
|
244
|
-
ErrorType2["LicenseRevoked"] = "LicenseRevoked";
|
|
245
|
-
ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
|
|
246
|
-
ErrorType2["LicenseExpired"] = "LicenseExpired";
|
|
247
|
-
ErrorType2["LicenseDeviceMismatch"] = "LicenseDeviceMismatch";
|
|
248
|
-
ErrorType2["DeviceIdentityUnavailable"] = "DeviceIdentityUnavailable";
|
|
249
|
-
return ErrorType2;
|
|
250
|
-
})(ErrorType || {});
|
|
251
|
-
var MoonbaseError = class extends Error {
|
|
252
|
-
constructor(title, detail, type, inner) {
|
|
253
|
-
super();
|
|
254
|
-
this.title = title;
|
|
255
|
-
this.detail = detail;
|
|
256
|
-
this.type = type;
|
|
257
|
-
this.inner = inner;
|
|
258
|
-
this.name = "MoonbaseError";
|
|
259
|
-
this.message = detail != null ? detail : title;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
var InsufficientDeviceIdentityError = class extends MoonbaseError {
|
|
263
|
-
constructor(platform, reason = "no identity parameter could be read") {
|
|
264
|
-
super(
|
|
265
|
-
"No device identity",
|
|
266
|
-
`Could not identify this device (platform: ${platform}): ${reason}`,
|
|
267
|
-
"DeviceIdentityUnavailable" /* DeviceIdentityUnavailable */
|
|
268
|
-
);
|
|
269
|
-
this.platform = platform;
|
|
270
|
-
this.name = "InsufficientDeviceIdentityError";
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
|
|
274
310
|
// src/fingerprint.ts
|
|
275
311
|
var import_node_buffer = require("buffer");
|
|
276
312
|
var import_node_child_process = __toESM(require("child_process"), 1);
|
|
@@ -283,7 +319,20 @@ var FINGERPRINT_VERSION = 2;
|
|
|
283
319
|
var MAX_VALUE_LENGTH = 128;
|
|
284
320
|
var PRINTABLE_ASCII_MIN = 32;
|
|
285
321
|
var PRINTABLE_ASCII_MAX = 126;
|
|
286
|
-
var
|
|
322
|
+
var SOURCE_TAGS = {
|
|
323
|
+
identity: "",
|
|
324
|
+
deviceName: "n",
|
|
325
|
+
scoped: "s"
|
|
326
|
+
};
|
|
327
|
+
var DEVICE_ID_SOURCE_TAGS = Object.freeze({ ...SOURCE_TAGS });
|
|
328
|
+
var SOURCE_BY_TAG = new Map(
|
|
329
|
+
Object.entries(SOURCE_TAGS).map(([source, tag]) => [tag, source])
|
|
330
|
+
);
|
|
331
|
+
var SCOPED_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
|
|
332
|
+
function identitySource(platform) {
|
|
333
|
+
return SCOPED_PLATFORMS.has(platform) ? "scoped" : "identity";
|
|
334
|
+
}
|
|
335
|
+
var STAMP_PATTERN = /^mbd(\d+)([a-z]*)_([0-9a-f]{64})$/;
|
|
287
336
|
function canonicalizeValue(value) {
|
|
288
337
|
var _a;
|
|
289
338
|
let printable = "";
|
|
@@ -294,6 +343,12 @@ function canonicalizeValue(value) {
|
|
|
294
343
|
}
|
|
295
344
|
return printable.slice(0, MAX_VALUE_LENGTH).replace(/^ +| +$/g, "");
|
|
296
345
|
}
|
|
346
|
+
var PLATFORM_TAGS = /* @__PURE__ */ new Set(
|
|
347
|
+
["mac", "ios", "windows", "android", "linux", "bsd", "unknown"]
|
|
348
|
+
);
|
|
349
|
+
function isPlatformTag(value) {
|
|
350
|
+
return PLATFORM_TAGS.has(value);
|
|
351
|
+
}
|
|
297
352
|
function platformTag(platform = import_node_process.default.platform) {
|
|
298
353
|
switch (platform) {
|
|
299
354
|
case "darwin":
|
|
@@ -309,7 +364,7 @@ function platformTag(platform = import_node_process.default.platform) {
|
|
|
309
364
|
case "netbsd":
|
|
310
365
|
return "bsd";
|
|
311
366
|
default:
|
|
312
|
-
return "unknown";
|
|
367
|
+
return isPlatformTag(platform) ? platform : "unknown";
|
|
313
368
|
}
|
|
314
369
|
}
|
|
315
370
|
var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
|
|
@@ -317,6 +372,8 @@ var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
|
|
|
317
372
|
"machineId",
|
|
318
373
|
"systemUuid",
|
|
319
374
|
"baseboardSerialNumber",
|
|
375
|
+
"identifierForVendor",
|
|
376
|
+
"androidId",
|
|
320
377
|
"deviceName"
|
|
321
378
|
]);
|
|
322
379
|
var IDENTIFYING_PARAM_NAMES = Object.freeze([...IDENTIFYING_PARAMS]);
|
|
@@ -341,9 +398,35 @@ var NOT_PROGRAMMED_VALUES = /* @__PURE__ */ new Set([
|
|
|
341
398
|
// deployed from such an image reads it, so it is the opposite of an identifier.
|
|
342
399
|
"uninitialized"
|
|
343
400
|
]);
|
|
401
|
+
var IDENTITY_CONSTRAINTS = {
|
|
402
|
+
androidId: {
|
|
403
|
+
// Reading the *static field* `Settings.Secure.ANDROID_ID` instead of calling
|
|
404
|
+
// `getString` with it yields the key name "android_id", identical on every
|
|
405
|
+
// device, so a single activation would unlock a whole Android install base.
|
|
406
|
+
// That string is not hex, so this stops it reaching the material.
|
|
407
|
+
//
|
|
408
|
+
// The bound is 1,16 and not 16 because AOSP before 8.0 generated the value
|
|
409
|
+
// with `Long.toHexString`, which drops leading zeros — a strict 16 would
|
|
410
|
+
// reject legitimate ids on roughly one in sixteen pre-Oreo devices.
|
|
411
|
+
format: /^[0-9a-f]{1,16}$/,
|
|
412
|
+
// A real ANDROID_ID shared by a large batch of 2010-era devices whose
|
|
413
|
+
// `ro.serialno` was unset, seeding the generator identically on every unit.
|
|
414
|
+
// Valid hex, so the format rule cannot catch it.
|
|
415
|
+
rejected: /* @__PURE__ */ new Set(["9774d56d682e549c"])
|
|
416
|
+
}
|
|
417
|
+
};
|
|
344
418
|
function isNotProgrammed(value) {
|
|
345
419
|
return NOT_PROGRAMMED_VALUES.has(value.toLowerCase()) || /^0+$/.test(value) || /^f+$/i.test(value);
|
|
346
420
|
}
|
|
421
|
+
function isUsableIdentity(name, value) {
|
|
422
|
+
var _a, _b, _c;
|
|
423
|
+
if (isNotProgrammed(value))
|
|
424
|
+
return false;
|
|
425
|
+
const constraint = IDENTITY_CONSTRAINTS[name];
|
|
426
|
+
if (!constraint)
|
|
427
|
+
return true;
|
|
428
|
+
return ((_b = (_a = constraint.format) == null ? void 0 : _a.test(value)) != null ? _b : true) && !((_c = constraint.rejected) == null ? void 0 : _c.has(value.toLowerCase()));
|
|
429
|
+
}
|
|
347
430
|
function canonicalizeParams(params) {
|
|
348
431
|
const kept = [];
|
|
349
432
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -351,7 +434,7 @@ function canonicalizeParams(params) {
|
|
|
351
434
|
const value = canonicalizeValue(rawValue);
|
|
352
435
|
if (value.length === 0)
|
|
353
436
|
continue;
|
|
354
|
-
if (IDENTIFYING_PARAMS.has(name) &&
|
|
437
|
+
if (IDENTIFYING_PARAMS.has(name) && !isUsableIdentity(name, value))
|
|
355
438
|
continue;
|
|
356
439
|
if (seen.has(name))
|
|
357
440
|
throw new Error(`Duplicate fingerprint parameter name: ${name}`);
|
|
@@ -364,6 +447,12 @@ function buildFingerprintMaterial(platform, params) {
|
|
|
364
447
|
const kept = canonicalizeParams(params);
|
|
365
448
|
if (kept.length === 0)
|
|
366
449
|
throw new InsufficientDeviceIdentityError(platform, "no identity parameter could be read");
|
|
450
|
+
if (SCOPED_PLATFORMS.has(platform) && kept.some(([name]) => name === "deviceName")) {
|
|
451
|
+
throw new InsufficientDeviceIdentityError(
|
|
452
|
+
platform,
|
|
453
|
+
"the host-name fallback is not available on this platform, where the host name is the same on every device"
|
|
454
|
+
);
|
|
455
|
+
}
|
|
367
456
|
if (!kept.some(([name]) => IDENTIFYING_PARAMS.has(name))) {
|
|
368
457
|
throw new InsufficientDeviceIdentityError(
|
|
369
458
|
platform,
|
|
@@ -379,18 +468,20 @@ function fingerprintDigest(material) {
|
|
|
379
468
|
return (0, import_node_crypto.createHash)("sha256").update(material, "utf8").digest("hex");
|
|
380
469
|
}
|
|
381
470
|
function stampDeviceId(digest, source = "identity") {
|
|
382
|
-
return `mbd${FINGERPRINT_VERSION}${source
|
|
471
|
+
return `mbd${FINGERPRINT_VERSION}${SOURCE_TAGS[source]}_${digest}`;
|
|
383
472
|
}
|
|
384
473
|
function fingerprintDeviceId(material, source = "identity") {
|
|
385
474
|
return stampDeviceId(fingerprintDigest(material), source);
|
|
386
475
|
}
|
|
387
476
|
function parseDeviceIdStamp(deviceId) {
|
|
477
|
+
var _a;
|
|
388
478
|
const match = STAMP_PATTERN.exec(deviceId);
|
|
389
479
|
if (!match)
|
|
390
480
|
return null;
|
|
391
481
|
return {
|
|
392
482
|
version: Number(match[1]),
|
|
393
|
-
|
|
483
|
+
sourceTag: match[2],
|
|
484
|
+
source: (_a = SOURCE_BY_TAG.get(match[2])) != null ? _a : null,
|
|
394
485
|
digest: match[3]
|
|
395
486
|
};
|
|
396
487
|
}
|
|
@@ -519,18 +610,23 @@ function readWindowsIdentity() {
|
|
|
519
610
|
return { params: parseSmbiosParams(readWindowsSmbios()), deviceName: import_node_os.default.hostname() };
|
|
520
611
|
}
|
|
521
612
|
function defaultDeviceIdentityReader(platform = import_node_process.default.platform) {
|
|
613
|
+
const tag = platformTag(platform);
|
|
522
614
|
return {
|
|
523
615
|
read() {
|
|
524
|
-
switch (
|
|
525
|
-
case "
|
|
616
|
+
switch (tag) {
|
|
617
|
+
case "mac":
|
|
526
618
|
return readMacIdentity();
|
|
527
619
|
case "linux":
|
|
528
620
|
return readLinuxIdentity();
|
|
529
|
-
case "
|
|
621
|
+
case "windows":
|
|
530
622
|
return readWindowsIdentity();
|
|
531
|
-
//
|
|
532
|
-
// has no stable hardware source for them, so they resolve to an
|
|
623
|
+
// Everything else yields no identity params here, so it resolves to an
|
|
533
624
|
// insufficient-identity error unless the deviceName fallback is enabled.
|
|
625
|
+
// For bsd and unknown that matches the spec, which defines none. Android
|
|
626
|
+
// is different: the spec *does* define `androidId`, but it comes from
|
|
627
|
+
// `Settings.Secure.getString`, which is Android-framework API that a
|
|
628
|
+
// Node.js process cannot reach. Collecting it is the C++/.NET SDKs' job.
|
|
629
|
+
// A host that can bridge it should supply its own DeviceIdentityReader.
|
|
534
630
|
default:
|
|
535
631
|
return { params: [], deviceName: import_node_os.default.hostname() };
|
|
536
632
|
}
|
|
@@ -582,7 +678,7 @@ var MoonbaseDeviceIdResolver = class {
|
|
|
582
678
|
computeDescription() {
|
|
583
679
|
const { params, deviceName } = this.readIdentity();
|
|
584
680
|
try {
|
|
585
|
-
return this.describe(params,
|
|
681
|
+
return this.describe(params, identitySource(this.platform));
|
|
586
682
|
} catch (err) {
|
|
587
683
|
if (this.fallback !== "deviceName" || !(err instanceof InsufficientDeviceIdentityError))
|
|
588
684
|
throw err;
|
|
@@ -829,25 +925,48 @@ var LicenseValidator = class {
|
|
|
829
925
|
};
|
|
830
926
|
function deviceMismatchError(expected, bound) {
|
|
831
927
|
const detail = "This license is not for this device";
|
|
832
|
-
const
|
|
928
|
+
const stampNote = describeStampDifference(expected, bound);
|
|
833
929
|
return new MoonbaseError(
|
|
834
930
|
"License is for another device",
|
|
835
|
-
|
|
931
|
+
stampNote ? `${detail}. ${stampNote}` : detail,
|
|
836
932
|
"LicenseDeviceMismatch" /* LicenseDeviceMismatch */
|
|
837
933
|
);
|
|
838
934
|
}
|
|
839
|
-
function
|
|
935
|
+
function describeStampDifference(expected, bound) {
|
|
840
936
|
const expectedStamp = parseDeviceIdStamp(expected);
|
|
841
937
|
if (!expectedStamp)
|
|
842
938
|
return null;
|
|
843
939
|
const boundStamp = parseDeviceIdStamp(bound);
|
|
844
|
-
if (boundStamp
|
|
845
|
-
return
|
|
846
|
-
if (boundStamp
|
|
847
|
-
return
|
|
940
|
+
if (!boundStamp || boundStamp.version !== expectedStamp.version)
|
|
941
|
+
return describeVersionDifference(expectedStamp, boundStamp);
|
|
942
|
+
if (boundStamp.sourceTag !== expectedStamp.sourceTag)
|
|
943
|
+
return describeSourceDifference(expectedStamp, boundStamp);
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
function describeVersionDifference(expected, bound) {
|
|
947
|
+
if (bound && bound.version > expected.version) {
|
|
948
|
+
return `The binding was created by device fingerprint v${bound.version}, which is newer than the v${expected.version} this SDK computes \u2014 update the SDK rather than re-activating, which would rebind the device to the older algorithm.`;
|
|
949
|
+
}
|
|
950
|
+
const boundVersion = bound ? `device fingerprint v${bound.version}` : "an SDK predating versioned device fingerprints";
|
|
951
|
+
return `The binding was created by ${boundVersion}, while this SDK computes v${expected.version}, so this may instead be the same machine bound under the older algorithm \u2014 re-activate to find out, or configure a MigratingDeviceIdResolver to keep accepting the previous id.`;
|
|
952
|
+
}
|
|
953
|
+
function describeSourceDifference(expected, bound) {
|
|
954
|
+
if (bound.source === "scoped") {
|
|
955
|
+
return "The binding uses an app-scoped device identity, which cannot be compared with the id this SDK computes \u2014 not even on the same device. Re-activate here to bind this build.";
|
|
956
|
+
}
|
|
957
|
+
if (expected.source === "scoped") {
|
|
958
|
+
return "This SDK computes an app-scoped device identity, which cannot be compared with the one the binding carries \u2014 not even on the same device. Re-activate here to bind this app.";
|
|
959
|
+
}
|
|
960
|
+
if (bound.source === null) {
|
|
961
|
+
return `The binding carries the device identity tag "${bound.sourceTag}", which this SDK does not recognise \u2014 it was created by a newer Moonbase SDK, so update rather than re-activating.`;
|
|
962
|
+
}
|
|
963
|
+
if (expected.source === null) {
|
|
964
|
+
return `The id this SDK computes carries the device identity tag "${expected.sourceTag}", which the spec does not define \u2014 it came from a custom device id resolver, so check that resolver rather than the binding.`;
|
|
965
|
+
}
|
|
966
|
+
if (bound.source === "deviceName") {
|
|
967
|
+
return "The binding was created from the host-name fallback, while this SDK reads hardware identity, so this may instead be the same machine bound while no hardware identity could be read \u2014 re-activate to find out.";
|
|
848
968
|
}
|
|
849
|
-
|
|
850
|
-
return `The binding was created by ${boundVersion}, while this SDK computes v${expectedStamp.version}, so this may instead be the same machine bound under the older algorithm \u2014 re-activate to find out, or configure a MigratingDeviceIdResolver to keep accepting the previous id.`;
|
|
969
|
+
return "The binding was created from hardware identity, while this SDK has fallen back to the host name \u2014 check why hardware identity cannot be read here rather than re-activating, which would rebind the device to the weaker id.";
|
|
851
970
|
}
|
|
852
971
|
|
|
853
972
|
// src/index.ts
|
|
@@ -895,6 +1014,7 @@ var MoonbaseLicensing = class {
|
|
|
895
1014
|
// Annotate the CommonJS export names for ESM import in node:
|
|
896
1015
|
0 && (module.exports = {
|
|
897
1016
|
ActivationMethod,
|
|
1017
|
+
DEVICE_ID_SOURCE_TAGS,
|
|
898
1018
|
ErrorType,
|
|
899
1019
|
FINGERPRINT_PREFIX,
|
|
900
1020
|
FINGERPRINT_VERSION,
|
|
@@ -916,6 +1036,7 @@ var MoonbaseLicensing = class {
|
|
|
916
1036
|
defaultDeviceIdentityReader,
|
|
917
1037
|
fingerprintDeviceId,
|
|
918
1038
|
fingerprintDigest,
|
|
1039
|
+
identitySource,
|
|
919
1040
|
parseDeviceIdStamp,
|
|
920
1041
|
parseIoregPlatformUuid,
|
|
921
1042
|
parseSmbiosParams,
|
package/dist/index.d.cts
CHANGED
|
@@ -33,11 +33,30 @@ declare const FINGERPRINT_VERSION = 2;
|
|
|
33
33
|
declare const MAX_VALUE_LENGTH = 128;
|
|
34
34
|
/**
|
|
35
35
|
* What the material was built from. `identity` is the real hardware fingerprint;
|
|
36
|
-
* `deviceName` is the opt-in, deliberately weaker host-name fallback
|
|
37
|
-
*
|
|
36
|
+
* `deviceName` is the opt-in, deliberately weaker host-name fallback; `scoped` is
|
|
37
|
+
* an id that is stable only within one app scope (iOS/Android, where the platform
|
|
38
|
+
* exposes nothing an unrelated app can read). Each is stamped distinctly so a
|
|
39
|
+
* server can tell them apart.
|
|
38
40
|
*/
|
|
39
|
-
type DeviceIdSource = 'identity' | 'deviceName';
|
|
40
|
-
|
|
41
|
+
type DeviceIdSource = 'identity' | 'deviceName' | 'scoped';
|
|
42
|
+
/** The tags this version defines, as a detached frozen map (see {@link IDENTIFYING_PARAM_NAMES}). */
|
|
43
|
+
declare const DEVICE_ID_SOURCE_TAGS: Readonly<Record<DeviceIdSource, string>>;
|
|
44
|
+
type PlatformTag = 'mac' | 'ios' | 'windows' | 'android' | 'linux' | 'bsd' | 'unknown';
|
|
45
|
+
/**
|
|
46
|
+
* The source a successful identity read earns on this platform.
|
|
47
|
+
*
|
|
48
|
+
* Scoped platforms expose no identifier an unrelated app can read, so anything
|
|
49
|
+
* built from their parameters is scoped to the app and must be stamped `mbd2s_`.
|
|
50
|
+
* Stamping it `mbd2_` would tell a server the id is a hardware fingerprint
|
|
51
|
+
* comparable across every app on the device, which is exactly what it is not:
|
|
52
|
+
* the server would then be entitled to correlate ids the spec forbids
|
|
53
|
+
* correlating, and a diagnostic would offer remedies that cannot apply.
|
|
54
|
+
*
|
|
55
|
+
* Derived from the platform rather than chosen by the caller, so a host that
|
|
56
|
+
* bridges `identifierForVendor` or `androidId` through a custom
|
|
57
|
+
* {@link DeviceIdentityReader} cannot accidentally mislabel it.
|
|
58
|
+
*/
|
|
59
|
+
declare function identitySource(platform: PlatformTag): DeviceIdSource;
|
|
41
60
|
type FingerprintParam = readonly [name: string, value: string];
|
|
42
61
|
interface DeviceIdentity {
|
|
43
62
|
/** Ordered identity params. Empty values are dropped by the material builder. */
|
|
@@ -53,7 +72,10 @@ interface DeviceIdentityReader {
|
|
|
53
72
|
interface DeviceIdStamp {
|
|
54
73
|
/** Fingerprint spec version that produced the digest. */
|
|
55
74
|
version: number;
|
|
56
|
-
source:
|
|
75
|
+
/** The literal source tag: `''`, `'n'`, `'s'`, or one a newer SDK introduced. */
|
|
76
|
+
sourceTag: string;
|
|
77
|
+
/** What {@link sourceTag} means, or `null` when this SDK does not define that tag. */
|
|
78
|
+
source: DeviceIdSource | null;
|
|
57
79
|
/** The 64-char lowercase-hex SHA-256. */
|
|
58
80
|
digest: string;
|
|
59
81
|
}
|
|
@@ -69,8 +91,23 @@ interface DeviceIdStamp {
|
|
|
69
91
|
* decodings disagree about is discarded either way.
|
|
70
92
|
*/
|
|
71
93
|
declare function canonicalizeValue(value: string): string;
|
|
72
|
-
/**
|
|
73
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Map a Node.js `process.platform` value onto a canonical platform tag, or pass
|
|
96
|
+
* through a value that is already one.
|
|
97
|
+
*
|
|
98
|
+
* The passthrough is what makes the mobile platforms reachable at all. Node does
|
|
99
|
+
* not run on iOS, so `process.platform` is never `'ios'` and no amount of mapping
|
|
100
|
+
* can produce that tag — yet a host embedding Node can bridge
|
|
101
|
+
* `identifierForVendor` from platform API. Without this, such a host has no way
|
|
102
|
+
* to say which platform it is on: the value would fall through to `'unknown'`,
|
|
103
|
+
* and the material would carry `platform=unknown`, so the digest would not match
|
|
104
|
+
* what a conforming iOS SDK computes on the same device, and the id would be
|
|
105
|
+
* stamped `mbd2_` rather than `mbd2s_`. Both wrong, and both silent.
|
|
106
|
+
*
|
|
107
|
+
* `'android'` and `'linux'` belong to both vocabularies and map to themselves, so
|
|
108
|
+
* the two spellings cannot disagree.
|
|
109
|
+
*/
|
|
110
|
+
declare function platformTag(platform?: NodeJS.Platform | PlatformTag): PlatformTag;
|
|
74
111
|
/**
|
|
75
112
|
* The identifying parameter names, as a detached frozen list.
|
|
76
113
|
*
|
|
@@ -107,9 +144,12 @@ declare function stampDeviceId(digest: string, source?: DeviceIdSource): string;
|
|
|
107
144
|
declare function fingerprintDeviceId(material: string, source?: DeviceIdSource): string;
|
|
108
145
|
/**
|
|
109
146
|
* Split a stamped device id into its parts, or `null` if it is not a Moonbase
|
|
110
|
-
* stamp (a legacy id, or one from a custom resolver). Lets a validator tell
|
|
147
|
+
* stamp at all (a legacy id, or one from a custom resolver). Lets a validator tell
|
|
111
148
|
* "this license belongs to another machine" apart from "this license was bound
|
|
112
149
|
* by an older fingerprint version".
|
|
150
|
+
*
|
|
151
|
+
* An id whose *tag* is unrecognised still parses, with `source` null: it came from
|
|
152
|
+
* a newer SDK, and reporting it as unparseable would be worse than saying so.
|
|
113
153
|
*/
|
|
114
154
|
declare function parseDeviceIdStamp(deviceId: string): DeviceIdStamp | null;
|
|
115
155
|
/** Extract `IOPlatformUUID` from `ioreg` output: hyphens stripped, uppercased (spec: macOS `ioPlatformUuid`). */
|
|
@@ -136,7 +176,7 @@ declare function selectMachineId(...sources: string[]): string;
|
|
|
136
176
|
*/
|
|
137
177
|
declare function parseSmbiosParams(smbiosData: Buffer): FingerprintParam[];
|
|
138
178
|
/** The real, platform-dispatching identity reader used by {@link MoonbaseDeviceIdResolver}. */
|
|
139
|
-
declare function defaultDeviceIdentityReader(platform?: NodeJS.Platform): DeviceIdentityReader;
|
|
179
|
+
declare function defaultDeviceIdentityReader(platform?: NodeJS.Platform | PlatformTag): DeviceIdentityReader;
|
|
140
180
|
|
|
141
181
|
interface IDeviceIdResolver {
|
|
142
182
|
resolveDeviceName: () => Promise<string>;
|
|
@@ -181,8 +221,16 @@ interface IMigratingDeviceIdResolver extends IDeviceIdResolver {
|
|
|
181
221
|
interface MoonbaseDeviceIdResolverOptions {
|
|
182
222
|
/** Overrides the identity source. Primarily for testing. */
|
|
183
223
|
reader?: DeviceIdentityReader;
|
|
184
|
-
/**
|
|
185
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Overrides the detected platform. Accepts a Node.js `process.platform` value
|
|
226
|
+
* or a canonical {@link PlatformTag}.
|
|
227
|
+
*
|
|
228
|
+
* The tag spelling exists for hosts that bridge a platform Node does not run
|
|
229
|
+
* on: pass `'ios'` together with a {@link reader} that supplies
|
|
230
|
+
* `identifierForVendor`. Without it the platform would resolve to `'unknown'`,
|
|
231
|
+
* which changes the material and so the device id.
|
|
232
|
+
*/
|
|
233
|
+
platform?: NodeJS.Platform | PlatformTag;
|
|
186
234
|
/**
|
|
187
235
|
* What to do when no hardware identity is readable. `'none'` (the default)
|
|
188
236
|
* throws {@link InsufficientDeviceIdentityError}; `'deviceName'` falls back to
|
|
@@ -190,6 +238,10 @@ interface MoonbaseDeviceIdResolverOptions {
|
|
|
190
238
|
*
|
|
191
239
|
* The fallback is opt-in because a host name is user-renameable, frequently
|
|
192
240
|
* duplicated across imaged machines, and regenerated on every container start.
|
|
241
|
+
*
|
|
242
|
+
* It is **ignored on iOS and Android**, which throw regardless: there the host
|
|
243
|
+
* name is identical on every device, so the fallback would give a whole install
|
|
244
|
+
* base one id rather than merely a weak one.
|
|
193
245
|
*/
|
|
194
246
|
fallback?: 'none' | 'deviceName';
|
|
195
247
|
}
|
|
@@ -545,13 +597,28 @@ declare class LicenseValidator implements ILicenseValidator {
|
|
|
545
597
|
private parseLicenseToken;
|
|
546
598
|
}
|
|
547
599
|
|
|
600
|
+
/**
|
|
601
|
+
* What went wrong, on a {@link MoonbaseError}. The license types say the license
|
|
602
|
+
* is no longer usable, so a stored copy can be deleted. `ApiError` only says no
|
|
603
|
+
* answer was had: keep the stored license and try again later.
|
|
604
|
+
*/
|
|
548
605
|
declare enum ErrorType {
|
|
549
606
|
None = "None",
|
|
607
|
+
/**
|
|
608
|
+
* The request failed without saying anything about the license: an outage, a
|
|
609
|
+
* rate limit, a proxy error page. Transient, so keep any stored license. A
|
|
610
|
+
* network failure is not a `MoonbaseError` at all, and is transient too.
|
|
611
|
+
*/
|
|
550
612
|
ApiError = "ApiError",
|
|
613
|
+
/** The customer has no license for this product to activate. */
|
|
551
614
|
NoEligibleLicense = "NoEligibleLicense",
|
|
615
|
+
/** The license token failed verification (signature, issuer or product), or the stored license could not be read. */
|
|
552
616
|
LicenseInvalid = "LicenseInvalid",
|
|
617
|
+
/** The merchant revoked the license, for example after a refund. */
|
|
553
618
|
LicenseRevoked = "LicenseRevoked",
|
|
619
|
+
/** This device's activation was revoked, freeing its seat. The license itself may still be active. */
|
|
554
620
|
LicenseActivationRevoked = "LicenseActivationRevoked",
|
|
621
|
+
/** The license, or trial, has expired. */
|
|
555
622
|
LicenseExpired = "LicenseExpired",
|
|
556
623
|
/** The license is valid but bound to a different device, or to an older fingerprint version. */
|
|
557
624
|
LicenseDeviceMismatch = "LicenseDeviceMismatch",
|
|
@@ -575,14 +642,15 @@ declare class MoonbaseError extends Error {
|
|
|
575
642
|
* either would hand a whole class of machines the *same* device id, and a license
|
|
576
643
|
* bound to it would validate on all of them.
|
|
577
644
|
*
|
|
578
|
-
* Reachable on platforms with no
|
|
579
|
-
* anything unknown
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
645
|
+
* Reachable on platforms with no identity parameters this package can read (BSD
|
|
646
|
+
* and anything unknown, which the spec leaves undefined, plus Android, whose
|
|
647
|
+
* `androidId` needs Android-framework API a Node.js process cannot call); when
|
|
648
|
+
* every source fails — a sandboxed process that cannot spawn `ioreg`, a container
|
|
649
|
+
* with no DMI, a blocked PowerShell; and on machines whose per-device identifiers
|
|
650
|
+
* are simply absent, such as a Linux install with no `machine-id` or a VM whose
|
|
651
|
+
* SMBIOS carries an unset UUID and a blank baseboard serial. Enable the host-name
|
|
652
|
+
* fallback (`new MoonbaseDeviceIdResolver({ fallback: 'deviceName' })`) to accept
|
|
653
|
+
* a deliberately weaker id on those machines.
|
|
586
654
|
*/
|
|
587
655
|
declare class InsufficientDeviceIdentityError extends MoonbaseError {
|
|
588
656
|
readonly platform: string;
|
|
@@ -631,4 +699,4 @@ declare class MoonbaseLicensing {
|
|
|
631
699
|
readRawLicense(license: Buffer): Promise<License>;
|
|
632
700
|
}
|
|
633
701
|
|
|
634
|
-
export { ActivationMethod, type ActivationRequestResponse, type DeviceIdDescription, type DeviceIdSource, type DeviceIdStamp, type DeviceIdentity, type DeviceIdentityReader, type DeviceToken, ErrorType, FINGERPRINT_PREFIX, FINGERPRINT_VERSION, FileLicenseStore, type FingerprintParam, IDENTIFYING_PARAM_NAMES, type IDescribableDeviceIdResolver, type IDeviceIdResolver, type ILicenseClient, type ILicenseStore, type ILicenseValidator, type IMigratingDeviceIdResolver, InMemoryLicenseStore, InsufficientDeviceIdentityError, LegacyDeviceIdResolver, type License, LicenseClient, LicenseValidator, MAX_VALUE_LENGTH, type Metadata, MigratingDeviceIdResolver, type MoonbaseConfiguration, MoonbaseDeviceIdResolver, type MoonbaseDeviceIdResolverOptions, MoonbaseError, MoonbaseLicensing, type Platform, type PlatformTag, type Product, type User, buildFingerprintMaterial, canonicalizeParams, canonicalizeValue, defaultDeviceIdentityReader, fingerprintDeviceId, fingerprintDigest, parseDeviceIdStamp, parseIoregPlatformUuid, parseSmbiosParams, platformTag, selectMachineId, stampDeviceId };
|
|
702
|
+
export { ActivationMethod, type ActivationRequestResponse, DEVICE_ID_SOURCE_TAGS, type DeviceIdDescription, type DeviceIdSource, type DeviceIdStamp, type DeviceIdentity, type DeviceIdentityReader, type DeviceToken, ErrorType, FINGERPRINT_PREFIX, FINGERPRINT_VERSION, FileLicenseStore, type FingerprintParam, IDENTIFYING_PARAM_NAMES, type IDescribableDeviceIdResolver, type IDeviceIdResolver, type ILicenseClient, type ILicenseStore, type ILicenseValidator, type IMigratingDeviceIdResolver, InMemoryLicenseStore, InsufficientDeviceIdentityError, LegacyDeviceIdResolver, type License, LicenseClient, LicenseValidator, MAX_VALUE_LENGTH, type Metadata, MigratingDeviceIdResolver, type MoonbaseConfiguration, MoonbaseDeviceIdResolver, type MoonbaseDeviceIdResolverOptions, MoonbaseError, MoonbaseLicensing, type Platform, type PlatformTag, type Product, type User, buildFingerprintMaterial, canonicalizeParams, canonicalizeValue, defaultDeviceIdentityReader, fingerprintDeviceId, fingerprintDigest, identitySource, parseDeviceIdStamp, parseIoregPlatformUuid, parseSmbiosParams, platformTag, selectMachineId, stampDeviceId };
|