optropic 1.0.0 → 2.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/dist/index.cjs +379 -13
- package/dist/index.d.cts +411 -41
- package/dist/index.d.ts +411 -41
- package/dist/index.js +363 -12
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,19 +17,30 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
AssetsResource: () => AssetsResource,
|
|
34
|
+
AuditResource: () => AuditResource,
|
|
24
35
|
AuthenticationError: () => AuthenticationError,
|
|
25
36
|
BatchNotFoundError: () => BatchNotFoundError,
|
|
26
37
|
CodeNotFoundError: () => CodeNotFoundError,
|
|
38
|
+
ComplianceResource: () => ComplianceResource,
|
|
27
39
|
InvalidCodeError: () => InvalidCodeError,
|
|
28
40
|
InvalidGTINError: () => InvalidGTINError,
|
|
29
41
|
InvalidSerialError: () => InvalidSerialError,
|
|
30
42
|
KeysResource: () => KeysResource,
|
|
43
|
+
KeysetsResource: () => KeysetsResource,
|
|
31
44
|
NetworkError: () => NetworkError,
|
|
32
45
|
OptropicClient: () => OptropicClient,
|
|
33
46
|
OptropicError: () => OptropicError,
|
|
@@ -35,9 +48,11 @@ __export(index_exports, {
|
|
|
35
48
|
RateLimitedError: () => RateLimitedError,
|
|
36
49
|
RevokedCodeError: () => RevokedCodeError,
|
|
37
50
|
SDK_VERSION: () => SDK_VERSION2,
|
|
51
|
+
SchemasResource: () => SchemasResource,
|
|
38
52
|
ServiceUnavailableError: () => ServiceUnavailableError,
|
|
39
53
|
TimeoutError: () => TimeoutError,
|
|
40
|
-
createClient: () => createClient
|
|
54
|
+
createClient: () => createClient,
|
|
55
|
+
verifyWebhookSignature: () => verifyWebhookSignature
|
|
41
56
|
});
|
|
42
57
|
module.exports = __toCommonJS(index_exports);
|
|
43
58
|
|
|
@@ -425,14 +440,26 @@ function createErrorFromResponse(statusCode, body) {
|
|
|
425
440
|
|
|
426
441
|
// src/resources/assets.ts
|
|
427
442
|
var AssetsResource = class {
|
|
428
|
-
constructor(request) {
|
|
443
|
+
constructor(request, client) {
|
|
429
444
|
this.request = request;
|
|
445
|
+
this.client = client;
|
|
430
446
|
}
|
|
431
447
|
async create(params) {
|
|
432
448
|
return this.request({ method: "POST", path: "/v1/assets", body: params });
|
|
433
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* List assets with optional filtering and pagination.
|
|
452
|
+
*
|
|
453
|
+
* When the client uses a sandbox/test API key, `is_sandbox` is
|
|
454
|
+
* automatically set to `true` so sandbox clients only see sandbox
|
|
455
|
+
* assets. Pass an explicit `is_sandbox` value to override.
|
|
456
|
+
*/
|
|
434
457
|
async list(params) {
|
|
435
|
-
|
|
458
|
+
let effectiveParams = params;
|
|
459
|
+
if (this.client.isSandbox && (!params || params.is_sandbox === void 0)) {
|
|
460
|
+
effectiveParams = { ...params, is_sandbox: true };
|
|
461
|
+
}
|
|
462
|
+
const query = effectiveParams ? this.buildQuery(effectiveParams) : "";
|
|
436
463
|
return this.request({ method: "GET", path: `/v1/assets${query}` });
|
|
437
464
|
}
|
|
438
465
|
async get(assetId) {
|
|
@@ -458,6 +485,117 @@ var AssetsResource = class {
|
|
|
458
485
|
}
|
|
459
486
|
};
|
|
460
487
|
|
|
488
|
+
// src/resources/audit.ts
|
|
489
|
+
var AuditResource = class {
|
|
490
|
+
constructor(request) {
|
|
491
|
+
this.request = request;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* List audit events with optional filtering and pagination.
|
|
495
|
+
*/
|
|
496
|
+
async list(params) {
|
|
497
|
+
const query = params ? this.buildQuery(params) : "";
|
|
498
|
+
return this.request({ method: "GET", path: `/v1/audit${query}` });
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Retrieve a single audit event by ID.
|
|
502
|
+
*/
|
|
503
|
+
async get(eventId) {
|
|
504
|
+
return this.request({
|
|
505
|
+
method: "GET",
|
|
506
|
+
path: `/v1/audit/${encodeURIComponent(eventId)}`
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Record a custom audit event.
|
|
511
|
+
*/
|
|
512
|
+
async create(params) {
|
|
513
|
+
return this.request({
|
|
514
|
+
method: "POST",
|
|
515
|
+
path: "/v1/audit",
|
|
516
|
+
body: {
|
|
517
|
+
event_type: params.eventType,
|
|
518
|
+
...params.resourceId !== void 0 && { resource_id: params.resourceId },
|
|
519
|
+
...params.resourceType !== void 0 && { resource_type: params.resourceType },
|
|
520
|
+
...params.details !== void 0 && { details: params.details }
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
buildQuery(params) {
|
|
525
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
526
|
+
if (entries.length === 0) return "";
|
|
527
|
+
return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// src/resources/compliance.ts
|
|
532
|
+
var ComplianceResource = class {
|
|
533
|
+
constructor(request) {
|
|
534
|
+
this.request = request;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Verify the integrity of the full audit chain.
|
|
538
|
+
*/
|
|
539
|
+
async verifyChain() {
|
|
540
|
+
return this.request({
|
|
541
|
+
method: "POST",
|
|
542
|
+
path: "/v1/compliance/verify-chain"
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Return all Merkle roots.
|
|
547
|
+
*/
|
|
548
|
+
async listMerkleRoots() {
|
|
549
|
+
return this.request({
|
|
550
|
+
method: "GET",
|
|
551
|
+
path: "/v1/compliance/merkle-roots"
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Return a Merkle inclusion proof for a specific audit event.
|
|
556
|
+
*/
|
|
557
|
+
async getMerkleProof(eventId) {
|
|
558
|
+
return this.request({
|
|
559
|
+
method: "GET",
|
|
560
|
+
path: `/v1/compliance/merkle-proof/${encodeURIComponent(eventId)}`
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Export audit data as a signed CSV.
|
|
565
|
+
*/
|
|
566
|
+
async exportAudit(params) {
|
|
567
|
+
const query = params ? this.buildQuery(params) : "";
|
|
568
|
+
return this.request({
|
|
569
|
+
method: "GET",
|
|
570
|
+
path: `/v1/compliance/export${query}`
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Retrieve the current compliance configuration.
|
|
575
|
+
*/
|
|
576
|
+
async getConfig() {
|
|
577
|
+
return this.request({
|
|
578
|
+
method: "GET",
|
|
579
|
+
path: "/v1/compliance/config"
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Update the compliance mode.
|
|
584
|
+
*/
|
|
585
|
+
async updateConfig(mode) {
|
|
586
|
+
return this.request({
|
|
587
|
+
method: "POST",
|
|
588
|
+
path: "/v1/compliance/config",
|
|
589
|
+
body: { compliance_mode: mode }
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
buildQuery(params) {
|
|
593
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
594
|
+
if (entries.length === 0) return "";
|
|
595
|
+
return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
461
599
|
// src/resources/keys.ts
|
|
462
600
|
var KeysResource = class {
|
|
463
601
|
constructor(request) {
|
|
@@ -467,17 +605,162 @@ var KeysResource = class {
|
|
|
467
605
|
return this.request({ method: "POST", path: "/v1/keys", body: params });
|
|
468
606
|
}
|
|
469
607
|
async list() {
|
|
470
|
-
|
|
608
|
+
const result = await this.request({ method: "GET", path: "/v1/keys" });
|
|
609
|
+
return result.data;
|
|
471
610
|
}
|
|
472
611
|
async revoke(keyId) {
|
|
473
612
|
await this.request({ method: "DELETE", path: `/v1/keys/${encodeURIComponent(keyId)}` });
|
|
474
613
|
}
|
|
475
614
|
};
|
|
476
615
|
|
|
616
|
+
// src/resources/keysets.ts
|
|
617
|
+
var KeysetsResource = class {
|
|
618
|
+
constructor(request) {
|
|
619
|
+
this.request = request;
|
|
620
|
+
}
|
|
621
|
+
async create(params) {
|
|
622
|
+
return this.request({ method: "POST", path: "/v1/keysets", body: params });
|
|
623
|
+
}
|
|
624
|
+
async list(params) {
|
|
625
|
+
const query = params ? this.buildQuery(params) : "";
|
|
626
|
+
return this.request({ method: "GET", path: `/v1/keysets${query}` });
|
|
627
|
+
}
|
|
628
|
+
buildQuery(params) {
|
|
629
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
630
|
+
if (entries.length === 0) return "";
|
|
631
|
+
return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// src/resources/schemas.ts
|
|
636
|
+
function checkType(value, expected) {
|
|
637
|
+
switch (expected) {
|
|
638
|
+
case "string":
|
|
639
|
+
return typeof value === "string";
|
|
640
|
+
case "number":
|
|
641
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
642
|
+
case "boolean":
|
|
643
|
+
return typeof value === "boolean";
|
|
644
|
+
case "date":
|
|
645
|
+
return typeof value === "string";
|
|
646
|
+
// ISO 8601 string
|
|
647
|
+
case "array":
|
|
648
|
+
return Array.isArray(value);
|
|
649
|
+
default:
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
var SchemasResource = class {
|
|
654
|
+
constructor(request) {
|
|
655
|
+
this.request = request;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Register or update a vertical config schema.
|
|
659
|
+
* If a schema already exists for the verticalId, it will be updated.
|
|
660
|
+
*/
|
|
661
|
+
async create(params) {
|
|
662
|
+
const body = this.stripUndefined({
|
|
663
|
+
vertical_id: params.verticalId,
|
|
664
|
+
metadata_schema: params.metadataSchema,
|
|
665
|
+
version: params.version,
|
|
666
|
+
export_formats: params.exportFormats,
|
|
667
|
+
description: params.description
|
|
668
|
+
});
|
|
669
|
+
return this.request({ method: "POST", path: "/v1/schemas", body });
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* List registered vertical schemas with pagination.
|
|
673
|
+
*/
|
|
674
|
+
async list(params) {
|
|
675
|
+
const query = params ? this.buildQuery(params) : "";
|
|
676
|
+
return this.request({ method: "GET", path: `/v1/schemas${query}` });
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Get the active schema for a specific vertical.
|
|
680
|
+
*/
|
|
681
|
+
async get(verticalId) {
|
|
682
|
+
return this.request({
|
|
683
|
+
method: "GET",
|
|
684
|
+
path: `/v1/schemas/${encodeURIComponent(verticalId)}`
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Update an existing vertical schema.
|
|
689
|
+
*/
|
|
690
|
+
async update(verticalId, params) {
|
|
691
|
+
const body = this.stripUndefined({
|
|
692
|
+
version: params.version,
|
|
693
|
+
metadata_schema: params.metadataSchema,
|
|
694
|
+
export_formats: params.exportFormats,
|
|
695
|
+
description: params.description,
|
|
696
|
+
is_active: params.isActive
|
|
697
|
+
});
|
|
698
|
+
return this.request({
|
|
699
|
+
method: "PUT",
|
|
700
|
+
path: `/v1/schemas/${encodeURIComponent(verticalId)}`,
|
|
701
|
+
body
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Deactivate a vertical schema (soft delete).
|
|
706
|
+
*/
|
|
707
|
+
async delete(verticalId) {
|
|
708
|
+
await this.request({
|
|
709
|
+
method: "DELETE",
|
|
710
|
+
path: `/v1/schemas/${encodeURIComponent(verticalId)}`
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Pre-flight validation: check if assetConfig matches the registered schema.
|
|
715
|
+
*
|
|
716
|
+
* This is a client-side convenience that fetches the schema and validates locally.
|
|
717
|
+
* The server also validates on asset creation.
|
|
718
|
+
*/
|
|
719
|
+
async validate(verticalId, assetConfig) {
|
|
720
|
+
let schema;
|
|
721
|
+
try {
|
|
722
|
+
schema = await this.get(verticalId);
|
|
723
|
+
} catch {
|
|
724
|
+
return { valid: true, errors: [] };
|
|
725
|
+
}
|
|
726
|
+
const errors = [];
|
|
727
|
+
const metadataSchema = schema.metadataSchema ?? {};
|
|
728
|
+
for (const [fieldName, fieldDef] of Object.entries(metadataSchema)) {
|
|
729
|
+
if (typeof fieldDef !== "object" || fieldDef === null) continue;
|
|
730
|
+
const def = fieldDef;
|
|
731
|
+
const value = assetConfig[fieldName];
|
|
732
|
+
if (def.required && (value === void 0 || value === null || value === "")) {
|
|
733
|
+
const label = def.label ?? fieldName;
|
|
734
|
+
errors.push({ field: fieldName, message: `Required field "${label}" is missing` });
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (value === void 0 || value === null) continue;
|
|
738
|
+
const expectedType = def.type ?? "string";
|
|
739
|
+
if (!checkType(value, expectedType)) {
|
|
740
|
+
errors.push({
|
|
741
|
+
field: fieldName,
|
|
742
|
+
message: `"${def.label ?? fieldName}" must be a ${expectedType}`,
|
|
743
|
+
received: typeof value
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return { valid: errors.length === 0, errors };
|
|
748
|
+
}
|
|
749
|
+
buildQuery(params) {
|
|
750
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
751
|
+
if (entries.length === 0) return "";
|
|
752
|
+
return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
|
|
753
|
+
}
|
|
754
|
+
stripUndefined(obj) {
|
|
755
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
|
|
477
759
|
// src/client.ts
|
|
478
760
|
var DEFAULT_BASE_URL = "https://api.optropic.com";
|
|
479
761
|
var DEFAULT_TIMEOUT = 3e4;
|
|
480
|
-
var SDK_VERSION = "
|
|
762
|
+
var SDK_VERSION = "2.0.0";
|
|
763
|
+
var SANDBOX_PREFIXES = ["optr_test_"];
|
|
481
764
|
var DEFAULT_RETRY_CONFIG = {
|
|
482
765
|
maxRetries: 3,
|
|
483
766
|
baseDelay: 1e3,
|
|
@@ -487,8 +770,13 @@ var OptropicClient = class {
|
|
|
487
770
|
config;
|
|
488
771
|
baseUrl;
|
|
489
772
|
retryConfig;
|
|
773
|
+
_sandbox;
|
|
490
774
|
assets;
|
|
775
|
+
audit;
|
|
776
|
+
compliance;
|
|
491
777
|
keys;
|
|
778
|
+
keysets;
|
|
779
|
+
schemas;
|
|
492
780
|
constructor(config) {
|
|
493
781
|
if (!config.apiKey || !this.isValidApiKey(config.apiKey)) {
|
|
494
782
|
throw new AuthenticationError(
|
|
@@ -499,6 +787,11 @@ var OptropicClient = class {
|
|
|
499
787
|
...config,
|
|
500
788
|
timeout: config.timeout ?? DEFAULT_TIMEOUT
|
|
501
789
|
};
|
|
790
|
+
if (config.sandbox !== void 0) {
|
|
791
|
+
this._sandbox = config.sandbox;
|
|
792
|
+
} else {
|
|
793
|
+
this._sandbox = SANDBOX_PREFIXES.some((p) => config.apiKey.startsWith(p));
|
|
794
|
+
}
|
|
502
795
|
if (config.baseUrl) {
|
|
503
796
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
504
797
|
} else {
|
|
@@ -509,8 +802,27 @@ var OptropicClient = class {
|
|
|
509
802
|
...config.retry
|
|
510
803
|
};
|
|
511
804
|
const boundRequest = this.request.bind(this);
|
|
512
|
-
this.assets = new AssetsResource(boundRequest);
|
|
805
|
+
this.assets = new AssetsResource(boundRequest, this);
|
|
806
|
+
this.audit = new AuditResource(boundRequest);
|
|
807
|
+
this.compliance = new ComplianceResource(boundRequest);
|
|
513
808
|
this.keys = new KeysResource(boundRequest);
|
|
809
|
+
this.keysets = new KeysetsResource(boundRequest);
|
|
810
|
+
this.schemas = new SchemasResource(boundRequest);
|
|
811
|
+
}
|
|
812
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
813
|
+
// ENVIRONMENT DETECTION
|
|
814
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
815
|
+
/** True when the client is in sandbox mode (test API key or explicit override). */
|
|
816
|
+
get isSandbox() {
|
|
817
|
+
return this._sandbox;
|
|
818
|
+
}
|
|
819
|
+
/** True when the client is in live/production mode. */
|
|
820
|
+
get isLive() {
|
|
821
|
+
return !this._sandbox;
|
|
822
|
+
}
|
|
823
|
+
/** Returns 'sandbox' or 'live'. */
|
|
824
|
+
get environment() {
|
|
825
|
+
return this._sandbox ? "sandbox" : "live";
|
|
514
826
|
}
|
|
515
827
|
// ─────────────────────────────────────────────────────────────────────────
|
|
516
828
|
// PRIVATE METHODS
|
|
@@ -601,18 +913,22 @@ var OptropicClient = class {
|
|
|
601
913
|
requestId
|
|
602
914
|
});
|
|
603
915
|
}
|
|
916
|
+
if (response.status === 204) {
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
604
919
|
const json = await response.json();
|
|
605
|
-
if (json.error) {
|
|
920
|
+
if (json && typeof json === "object" && "error" in json && json.error) {
|
|
921
|
+
const err = json.error;
|
|
606
922
|
throw createErrorFromResponse(response.status, {
|
|
607
923
|
// Justified: Error code string from API may not match SDK's ErrorCode enum exactly
|
|
608
924
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
609
|
-
code:
|
|
610
|
-
message:
|
|
611
|
-
details:
|
|
925
|
+
code: err.code ?? "UNKNOWN_ERROR",
|
|
926
|
+
message: err.message ?? "Unknown error",
|
|
927
|
+
details: err.details,
|
|
612
928
|
requestId: json.requestId
|
|
613
929
|
});
|
|
614
930
|
}
|
|
615
|
-
return json
|
|
931
|
+
return json;
|
|
616
932
|
} catch (error) {
|
|
617
933
|
clearTimeout(timeoutId);
|
|
618
934
|
if (error instanceof OptropicError) {
|
|
@@ -637,18 +953,66 @@ function createClient(config) {
|
|
|
637
953
|
return new OptropicClient(config);
|
|
638
954
|
}
|
|
639
955
|
|
|
956
|
+
// src/webhooks.ts
|
|
957
|
+
async function computeHmacSha256(secret, message) {
|
|
958
|
+
const encoder = new TextEncoder();
|
|
959
|
+
if (typeof globalThis.crypto?.subtle !== "undefined") {
|
|
960
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
961
|
+
"raw",
|
|
962
|
+
encoder.encode(secret),
|
|
963
|
+
// OPSEC: Web Crypto API requires this exact algorithm identifier
|
|
964
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
965
|
+
false,
|
|
966
|
+
["sign"]
|
|
967
|
+
);
|
|
968
|
+
const sig = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(message));
|
|
969
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
970
|
+
}
|
|
971
|
+
const { createHmac } = await import("crypto");
|
|
972
|
+
return createHmac("sha256", secret).update(message).digest("hex");
|
|
973
|
+
}
|
|
974
|
+
function timingSafeEqual(a, b) {
|
|
975
|
+
if (a.length !== b.length) return false;
|
|
976
|
+
let result = 0;
|
|
977
|
+
for (let i = 0; i < a.length; i++) {
|
|
978
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
979
|
+
}
|
|
980
|
+
return result === 0;
|
|
981
|
+
}
|
|
982
|
+
async function verifyWebhookSignature(options) {
|
|
983
|
+
const { payload, signature, timestamp, secret, tolerance = 300 } = options;
|
|
984
|
+
const ts = parseInt(timestamp, 10);
|
|
985
|
+
if (isNaN(ts)) {
|
|
986
|
+
return { valid: false, reason: "Invalid timestamp" };
|
|
987
|
+
}
|
|
988
|
+
const age = Math.abs(Math.floor(Date.now() / 1e3) - ts);
|
|
989
|
+
if (age > tolerance) {
|
|
990
|
+
return { valid: false, reason: `Timestamp too old (${age}s > ${tolerance}s tolerance)` };
|
|
991
|
+
}
|
|
992
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
993
|
+
const expectedHex = await computeHmacSha256(secret, signedPayload);
|
|
994
|
+
const expected = `sha256=${expectedHex}`;
|
|
995
|
+
if (!timingSafeEqual(expected, signature)) {
|
|
996
|
+
return { valid: false, reason: "Signature mismatch" };
|
|
997
|
+
}
|
|
998
|
+
return { valid: true };
|
|
999
|
+
}
|
|
1000
|
+
|
|
640
1001
|
// src/index.ts
|
|
641
|
-
var SDK_VERSION2 = "
|
|
1002
|
+
var SDK_VERSION2 = "2.0.0";
|
|
642
1003
|
// Annotate the CommonJS export names for ESM import in node:
|
|
643
1004
|
0 && (module.exports = {
|
|
644
1005
|
AssetsResource,
|
|
1006
|
+
AuditResource,
|
|
645
1007
|
AuthenticationError,
|
|
646
1008
|
BatchNotFoundError,
|
|
647
1009
|
CodeNotFoundError,
|
|
1010
|
+
ComplianceResource,
|
|
648
1011
|
InvalidCodeError,
|
|
649
1012
|
InvalidGTINError,
|
|
650
1013
|
InvalidSerialError,
|
|
651
1014
|
KeysResource,
|
|
1015
|
+
KeysetsResource,
|
|
652
1016
|
NetworkError,
|
|
653
1017
|
OptropicClient,
|
|
654
1018
|
OptropicError,
|
|
@@ -656,7 +1020,9 @@ var SDK_VERSION2 = "1.0.0";
|
|
|
656
1020
|
RateLimitedError,
|
|
657
1021
|
RevokedCodeError,
|
|
658
1022
|
SDK_VERSION,
|
|
1023
|
+
SchemasResource,
|
|
659
1024
|
ServiceUnavailableError,
|
|
660
1025
|
TimeoutError,
|
|
661
|
-
createClient
|
|
1026
|
+
createClient,
|
|
1027
|
+
verifyWebhookSignature
|
|
662
1028
|
});
|