@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/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 new MoonbaseError("Request not successful", `The API responded with a ${response.status} ${response.statusText} response`, "ApiError" /* ApiError */);
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 new MoonbaseError("Request not successful", `The API responded with a ${response.status} ${response.statusText} response`, "ApiError" /* ApiError */);
247
+ throw await errorFromResponse(response);
205
248
  }
206
249
  }
207
250
  async handleLicenseResponse(response) {
208
251
  if (response.status >= 400) {
209
- throw new MoonbaseError("Request not successful", `The API responded with a ${response.status} ${response.statusText} response`, "ApiError" /* ApiError */);
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 STAMP_PATTERN = /^mbd(\d+)(n?)_([0-9a-f]{64})$/;
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) && isNotProgrammed(value))
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 === "deviceName" ? "n" : ""}_${digest}`;
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
- source: match[2] === "n" ? "deviceName" : "identity",
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 (platform) {
525
- case "darwin":
616
+ switch (tag) {
617
+ case "mac":
526
618
  return readMacIdentity();
527
619
  case "linux":
528
620
  return readLinuxIdentity();
529
- case "win32":
621
+ case "windows":
530
622
  return readWindowsIdentity();
531
- // android, bsd and anything else define no identity params: the spec
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, "identity");
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 versionNote = describeVersionDifference(expected, bound);
928
+ const stampNote = describeStampDifference(expected, bound);
833
929
  return new MoonbaseError(
834
930
  "License is for another device",
835
- versionNote ? `${detail}. ${versionNote}` : detail,
931
+ stampNote ? `${detail}. ${stampNote}` : detail,
836
932
  "LicenseDeviceMismatch" /* LicenseDeviceMismatch */
837
933
  );
838
934
  }
839
- function describeVersionDifference(expected, bound) {
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 && boundStamp.version === expectedStamp.version)
845
- return null;
846
- if (boundStamp && boundStamp.version > expectedStamp.version) {
847
- return `The binding was created by device fingerprint v${boundStamp.version}, which is newer than the v${expectedStamp.version} this SDK computes \u2014 update the SDK rather than re-activating, which would rebind the device to the older algorithm.`;
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
- const boundVersion = boundStamp ? `device fingerprint v${boundStamp.version}` : "an SDK predating versioned device fingerprints";
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, stamped
37
- * distinctly so a server can tell the two apart.
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
- type PlatformTag = 'mac' | 'windows' | 'android' | 'linux' | 'bsd' | 'unknown';
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: DeviceIdSource;
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
- /** Map a Node.js `process.platform` value onto a canonical platform tag. */
73
- declare function platformTag(platform?: NodeJS.Platform): PlatformTag;
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
- /** Overrides the detected platform. Primarily for testing. */
185
- platform?: NodeJS.Platform;
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 defined identity parameters (Android, BSD,
579
- * anything unknown); when every source fails — a sandboxed process that cannot
580
- * spawn `ioreg`, a container with no DMI, a blocked PowerShell; and on machines
581
- * whose per-device identifiers are simply absent, such as a Linux install with no
582
- * `machine-id` or a VM whose SMBIOS carries an unset UUID and a blank baseboard
583
- * serial. Enable the host-name fallback
584
- * (`new MoonbaseDeviceIdResolver({ fallback: 'deviceName' })`) to accept a
585
- * deliberately weaker id on those machines.
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 };