@neetru/sdk 2.3.6 → 3.0.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/CHANGELOG.md +172 -0
- package/README.md +59 -22
- package/dist/auth.cjs +360 -140
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +360 -140
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +21 -2
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs +21 -2
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +21 -2
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +21 -2
- package/dist/checkout.mjs.map +1 -1
- package/dist/db.cjs +28 -3
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +28 -3
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs +21 -2
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/entitlements.mjs +21 -2
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +3 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +408 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -19
- package/dist/index.d.ts +31 -19
- package/dist/index.mjs +408 -146
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +33 -33
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +23 -19
- package/dist/mocks.d.ts +23 -19
- package/dist/mocks.mjs +33 -33
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +80 -14
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs +80 -14
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.cjs +72 -12
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs +72 -12
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +23 -4
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs +23 -4
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-sGGN1vkg.d.cts → types-DLOvkeAP.d.cts} +110 -49
- package/dist/{types-CSC-RIdS.d.ts → types-dw19bdID.d.ts} +110 -49
- package/dist/usage.cjs +57 -63
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs +57 -63
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +110 -33
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs +110 -33
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,23 @@ var init_errors = __esm({
|
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
// src/idempotency.ts
|
|
35
|
+
function generateIdempotencyKey() {
|
|
36
|
+
const cryptoObj = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto : void 0;
|
|
37
|
+
if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
|
|
38
|
+
return cryptoObj.randomUUID();
|
|
39
|
+
}
|
|
40
|
+
throw new NeetruError(
|
|
41
|
+
"runtime_unsupported",
|
|
42
|
+
"crypto.randomUUID not available in this runtime. SDK 3.0 requires Node \u226520 or modern browser/Edge."
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
var init_idempotency = __esm({
|
|
46
|
+
"src/idempotency.ts"() {
|
|
47
|
+
init_errors();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
34
51
|
// src/http.ts
|
|
35
52
|
var http_exports = {};
|
|
36
53
|
__export(http_exports, {
|
|
@@ -88,7 +105,8 @@ async function safeJson(res) {
|
|
|
88
105
|
async function httpRequest(config, opts) {
|
|
89
106
|
const method = opts.method ?? "GET";
|
|
90
107
|
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
91
|
-
const
|
|
108
|
+
const defaultRetries = READ_METHODS.has(method) ? DEFAULT_RETRIES_READ : DEFAULT_RETRIES_WRITE;
|
|
109
|
+
const maxRetries = opts.retries ?? defaultRetries;
|
|
92
110
|
const headers = {
|
|
93
111
|
accept: "application/json",
|
|
94
112
|
...opts.headers
|
|
@@ -102,6 +120,10 @@ async function httpRequest(config, opts) {
|
|
|
102
120
|
}
|
|
103
121
|
headers.authorization = `Bearer ${config.apiKey}`;
|
|
104
122
|
}
|
|
123
|
+
if (opts.idempotencyKey) {
|
|
124
|
+
const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : generateIdempotencyKey();
|
|
125
|
+
headers["idempotency-key"] = key;
|
|
126
|
+
}
|
|
105
127
|
const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
|
|
106
128
|
if (bodyString !== void 0) {
|
|
107
129
|
headers["content-type"] = "application/json";
|
|
@@ -153,11 +175,14 @@ async function httpRequest(config, opts) {
|
|
|
153
175
|
}
|
|
154
176
|
throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
|
|
155
177
|
}
|
|
156
|
-
var
|
|
178
|
+
var DEFAULT_RETRIES_READ, DEFAULT_RETRIES_WRITE, READ_METHODS, RETRYABLE_CODES;
|
|
157
179
|
var init_http = __esm({
|
|
158
180
|
"src/http.ts"() {
|
|
159
181
|
init_errors();
|
|
160
|
-
|
|
182
|
+
init_idempotency();
|
|
183
|
+
DEFAULT_RETRIES_READ = 2;
|
|
184
|
+
DEFAULT_RETRIES_WRITE = 0;
|
|
185
|
+
READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
161
186
|
RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
162
187
|
"rate_limited",
|
|
163
188
|
"server_error",
|
|
@@ -574,6 +599,109 @@ var init_sql_client = __esm({
|
|
|
574
599
|
// src/auth.ts
|
|
575
600
|
init_errors();
|
|
576
601
|
|
|
602
|
+
// src/api-key.ts
|
|
603
|
+
init_errors();
|
|
604
|
+
var API_KEY_RE = /^nrt_([A-Za-z0-9]+)_([A-Za-z0-9]+)$/;
|
|
605
|
+
function parseApiKey(apiKey) {
|
|
606
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
607
|
+
throw new NeetruError(
|
|
608
|
+
"invalid_config",
|
|
609
|
+
"apiKey is required (formato nrt_<keyId>_<secret>)"
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const match = API_KEY_RE.exec(apiKey);
|
|
613
|
+
if (!match) {
|
|
614
|
+
throw new NeetruError(
|
|
615
|
+
"invalid_config",
|
|
616
|
+
"apiKey malformado - esperado formato nrt_<keyId>_<secret>"
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return { keyId: match[1], secret: match[2] };
|
|
620
|
+
}
|
|
621
|
+
function tryParseApiKey(apiKey) {
|
|
622
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) return null;
|
|
623
|
+
const match = API_KEY_RE.exec(apiKey);
|
|
624
|
+
if (!match) return null;
|
|
625
|
+
return { keyId: match[1], secret: match[2] };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/base-url-allowlist.ts
|
|
629
|
+
init_errors();
|
|
630
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
631
|
+
var NEETRU_SUFFIX = ".neetru.com";
|
|
632
|
+
var NEETRU_APEX = "neetru.com";
|
|
633
|
+
var SAFE_DEV_HOST_RE = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\.(test|localhost)$/;
|
|
634
|
+
function isSafeDevHost(hostname) {
|
|
635
|
+
return SAFE_DEV_HOST_RE.test(hostname);
|
|
636
|
+
}
|
|
637
|
+
function normalizeHostname(hostname) {
|
|
638
|
+
return hostname.toLowerCase().replace(/\.+$/, "");
|
|
639
|
+
}
|
|
640
|
+
function validateBaseUrl(baseUrl, allowedHosts = []) {
|
|
641
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) {
|
|
642
|
+
throw new NeetruError("invalid_config", "baseUrl is required");
|
|
643
|
+
}
|
|
644
|
+
let parsed;
|
|
645
|
+
try {
|
|
646
|
+
parsed = new URL(baseUrl);
|
|
647
|
+
} catch {
|
|
648
|
+
throw new NeetruError("invalid_config", `baseUrl is not a valid URL: ${baseUrl}`);
|
|
649
|
+
}
|
|
650
|
+
const protocol = parsed.protocol;
|
|
651
|
+
const hostname = normalizeHostname(parsed.hostname);
|
|
652
|
+
if (isInsecureBypassActive()) {
|
|
653
|
+
if (typeof console !== "undefined") {
|
|
654
|
+
console.warn(
|
|
655
|
+
`[neetru-sdk] NEETRU_ALLOW_INSECURE_BASEURL=1 ativo. baseUrl=${baseUrl} aceito sem valida\xE7\xE3o.`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return baseUrl.replace(/\/+$/, "");
|
|
659
|
+
}
|
|
660
|
+
if (protocol !== "https:" && protocol !== "http:") {
|
|
661
|
+
throw new NeetruError(
|
|
662
|
+
"invalid_config",
|
|
663
|
+
`baseUrl protocol must be http: or https:, got ${protocol}`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
if (protocol === "http:") {
|
|
667
|
+
const isLoopback = LOOPBACK_HOSTS.has(hostname) || isSafeDevHost(hostname);
|
|
668
|
+
if (!isLoopback) {
|
|
669
|
+
throw new NeetruError(
|
|
670
|
+
"invalid_config",
|
|
671
|
+
`baseUrl with http: only allowed for localhost/127.0.0.1/*.test/*.localhost. Got: ${hostname}. Use https: or set NEETRU_ALLOW_INSECURE_BASEURL=1 to bypass.`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
return baseUrl.replace(/\/+$/, "");
|
|
675
|
+
}
|
|
676
|
+
const isNeetruDomain = hostname === NEETRU_APEX || hostname.endsWith(NEETRU_SUFFIX);
|
|
677
|
+
const isAllowedHost = allowedHosts.some((allowed) => {
|
|
678
|
+
const norm = allowed.toLowerCase();
|
|
679
|
+
return hostname === norm || hostname.endsWith(`.${norm}`);
|
|
680
|
+
});
|
|
681
|
+
const isLoopbackHttps = LOOPBACK_HOSTS.has(hostname) || isSafeDevHost(hostname);
|
|
682
|
+
if (!isNeetruDomain && !isAllowedHost && !isLoopbackHttps) {
|
|
683
|
+
throw new NeetruError(
|
|
684
|
+
"invalid_config",
|
|
685
|
+
`baseUrl ${hostname} is not in the allowlist. Allowed: *.neetru.com, localhost, or configure { allowedHosts: ['...'] }. Bypass: NEETRU_ALLOW_INSECURE_BASEURL=1.`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
return baseUrl.replace(/\/+$/, "");
|
|
689
|
+
}
|
|
690
|
+
function isInsecureBypassActive() {
|
|
691
|
+
try {
|
|
692
|
+
const proc = globalThis.process;
|
|
693
|
+
const envVal = proc?.env?.NEETRU_ALLOW_INSECURE_BASEURL;
|
|
694
|
+
if (envVal === "1" || envVal === "true") return true;
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
const g = globalThis.NEETRU_ALLOW_INSECURE_BASEURL;
|
|
699
|
+
if (g === "1" || g === "true" || g === true) return true;
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
|
|
577
705
|
// src/types.ts
|
|
578
706
|
var DEFAULT_BASE_URL = "https://api.neetru.com";
|
|
579
707
|
|
|
@@ -775,7 +903,7 @@ function createTelemetryNamespace(config) {
|
|
|
775
903
|
if (ev.timestamp) body.timestamp = ev.timestamp;
|
|
776
904
|
await httpRequest(config, {
|
|
777
905
|
method: "POST",
|
|
778
|
-
path: "/api/v1/
|
|
906
|
+
path: "/api/sdk/v1/telemetry/event",
|
|
779
907
|
body,
|
|
780
908
|
requireAuth: true,
|
|
781
909
|
retries: 1
|
|
@@ -824,7 +952,7 @@ function createTelemetryNamespace(config) {
|
|
|
824
952
|
if (input.timestamp) body.timestamp = input.timestamp;
|
|
825
953
|
const raw = await httpRequest(config, {
|
|
826
954
|
method: "POST",
|
|
827
|
-
path: "/api/v1/
|
|
955
|
+
path: "/api/sdk/v1/telemetry/event",
|
|
828
956
|
body,
|
|
829
957
|
requireAuth: true
|
|
830
958
|
});
|
|
@@ -936,65 +1064,33 @@ function createTelemetryNamespace(config) {
|
|
|
936
1064
|
// src/usage.ts
|
|
937
1065
|
init_errors();
|
|
938
1066
|
init_http();
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
if (typeof
|
|
945
|
-
throw new NeetruError(
|
|
1067
|
+
|
|
1068
|
+
// src/validators.ts
|
|
1069
|
+
init_errors();
|
|
1070
|
+
var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
1071
|
+
function assertValidSdkId(field, value) {
|
|
1072
|
+
if (typeof value !== "string" || !SDK_ID_RE.test(value)) {
|
|
1073
|
+
throw new NeetruError(
|
|
1074
|
+
"validation_failed",
|
|
1075
|
+
`${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`
|
|
1076
|
+
);
|
|
946
1077
|
}
|
|
947
|
-
return {
|
|
948
|
-
metric: typeof r.metric === "string" ? r.metric : metric,
|
|
949
|
-
used: r.used,
|
|
950
|
-
limit: r.limit,
|
|
951
|
-
resetsAt: typeof r.resetsAt === "string" ? r.resetsAt : void 0,
|
|
952
|
-
plan: typeof r.plan === "string" ? r.plan : void 0
|
|
953
|
-
};
|
|
954
1078
|
}
|
|
1079
|
+
|
|
1080
|
+
// src/usage.ts
|
|
955
1081
|
function createUsageNamespace(config) {
|
|
956
1082
|
return {
|
|
957
1083
|
/**
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
const body = { event };
|
|
969
|
-
if (properties && typeof properties === "object") body.properties = properties;
|
|
970
|
-
const raw = await httpRequest(config, {
|
|
971
|
-
method: "POST",
|
|
972
|
-
path: "/sdk/v1/usage/record",
|
|
973
|
-
body,
|
|
974
|
-
requireAuth: true
|
|
975
|
-
});
|
|
976
|
-
if (!raw || raw.ok !== true) {
|
|
977
|
-
throw new NeetruError("invalid_response", "Usage record response missing ok");
|
|
978
|
-
}
|
|
979
|
-
return { ok: true };
|
|
980
|
-
},
|
|
981
|
-
/**
|
|
982
|
-
* Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.
|
|
983
|
-
*/
|
|
984
|
-
async getQuota(metric) {
|
|
985
|
-
if (!metric || typeof metric !== "string") {
|
|
986
|
-
throw new NeetruError("validation_failed", "metric is required");
|
|
987
|
-
}
|
|
988
|
-
const raw = await httpRequest(config, {
|
|
989
|
-
method: "GET",
|
|
990
|
-
path: "/sdk/v1/usage/quota",
|
|
991
|
-
query: { metric },
|
|
992
|
-
requireAuth: true
|
|
993
|
-
});
|
|
994
|
-
return toQuota(metric, raw);
|
|
995
|
-
},
|
|
996
|
-
/**
|
|
997
|
-
* v0.3 — Reporta consumo metered. Hit no endpoint canônico Sprint 7.
|
|
1084
|
+
* Reporta consumo metered de um resource. Hit no endpoint canônico
|
|
1085
|
+
* `POST /api/sdk/v1/usage/record`. Idempotency-Key gerado por chamada;
|
|
1086
|
+
* Core dedup serverside TTL 24h.
|
|
1087
|
+
*
|
|
1088
|
+
* @example
|
|
1089
|
+
* ```ts
|
|
1090
|
+
* await client.usage.report('api_call', 1);
|
|
1091
|
+
* // ou explicit:
|
|
1092
|
+
* await client.usage.report('api_call', 5, { productId: 'gestovendas', tenantId: 't-acme' });
|
|
1093
|
+
* ```
|
|
998
1094
|
*/
|
|
999
1095
|
async report(resource, qty = 1, options) {
|
|
1000
1096
|
if (!resource || typeof resource !== "string") {
|
|
@@ -1017,11 +1113,14 @@ function createUsageNamespace(config) {
|
|
|
1017
1113
|
"tenantId required (pass to options or set on createNeetruClient)"
|
|
1018
1114
|
);
|
|
1019
1115
|
}
|
|
1116
|
+
assertValidSdkId("productId", productId);
|
|
1117
|
+
assertValidSdkId("tenantId", tenantId);
|
|
1020
1118
|
const raw = await httpRequest(config, {
|
|
1021
1119
|
method: "POST",
|
|
1022
|
-
path: "/sdk/v1/usage/record",
|
|
1120
|
+
path: "/api/sdk/v1/usage/record",
|
|
1023
1121
|
body: { productId, tenantId, resource, qty: Math.floor(qty) },
|
|
1024
|
-
requireAuth: true
|
|
1122
|
+
requireAuth: true,
|
|
1123
|
+
idempotencyKey: true
|
|
1025
1124
|
});
|
|
1026
1125
|
if (!raw || raw.ok !== true) {
|
|
1027
1126
|
throw new NeetruError("invalid_response", "usage.report response missing ok");
|
|
@@ -1036,7 +1135,10 @@ function createUsageNamespace(config) {
|
|
|
1036
1135
|
};
|
|
1037
1136
|
},
|
|
1038
1137
|
/**
|
|
1039
|
-
*
|
|
1138
|
+
* Verifica entitlement de um resource via `GET /api/sdk/v1/entitlements`.
|
|
1139
|
+
* Resposta inclui `allowed`, `reason` ('granted' | 'not_subscribed' |
|
|
1140
|
+
* 'subscription_inactive' | 'feature_not_in_plan' | 'limit_exceeded'),
|
|
1141
|
+
* `remaining`, `limit`, `planId`, `planFeatures`.
|
|
1040
1142
|
*/
|
|
1041
1143
|
async check(resource, options) {
|
|
1042
1144
|
if (!resource || typeof resource !== "string") {
|
|
@@ -1047,25 +1149,29 @@ function createUsageNamespace(config) {
|
|
|
1047
1149
|
if (!productId || !tenantId) {
|
|
1048
1150
|
throw new NeetruError(
|
|
1049
1151
|
"validation_failed",
|
|
1050
|
-
"productId and tenantId required"
|
|
1152
|
+
"productId and tenantId required (pass to options or set on createNeetruClient)"
|
|
1051
1153
|
);
|
|
1052
1154
|
}
|
|
1155
|
+
assertValidSdkId("productId", productId);
|
|
1156
|
+
assertValidSdkId("tenantId", tenantId);
|
|
1053
1157
|
const raw = await httpRequest(config, {
|
|
1054
1158
|
method: "GET",
|
|
1055
|
-
path: "/sdk/v1/entitlements",
|
|
1159
|
+
path: "/api/sdk/v1/entitlements",
|
|
1056
1160
|
query: { productId, tenantId, feature: resource },
|
|
1057
1161
|
requireAuth: true
|
|
1058
1162
|
});
|
|
1059
1163
|
if (!raw || typeof raw.allowed !== "boolean") {
|
|
1060
1164
|
throw new NeetruError("invalid_response", "usage.check response missing allowed");
|
|
1061
1165
|
}
|
|
1166
|
+
const behavior = raw.behavior ?? (raw.reason === "limit_exceeded" ? "readonly" : null);
|
|
1062
1167
|
return {
|
|
1063
1168
|
allowed: raw.allowed,
|
|
1064
1169
|
reason: raw.reason,
|
|
1065
1170
|
remaining: raw.remaining,
|
|
1066
1171
|
limit: raw.limit,
|
|
1067
1172
|
planId: raw.planId,
|
|
1068
|
-
planFeatures: raw.planFeatures
|
|
1173
|
+
planFeatures: raw.planFeatures,
|
|
1174
|
+
behavior
|
|
1069
1175
|
};
|
|
1070
1176
|
}
|
|
1071
1177
|
};
|
|
@@ -1092,14 +1198,32 @@ function toTicket(raw) {
|
|
|
1092
1198
|
status: VALID_STATUSES.includes(r.status) ? r.status : "open",
|
|
1093
1199
|
createdAt: typeof r.createdAt === "string" ? r.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1094
1200
|
updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : void 0,
|
|
1095
|
-
productSlug: typeof r.productSlug === "string" ? r.productSlug : void 0
|
|
1201
|
+
productSlug: typeof r.productId === "string" ? r.productId : typeof r.productSlug === "string" ? r.productSlug : void 0
|
|
1096
1202
|
};
|
|
1097
1203
|
}
|
|
1204
|
+
function resolveProductId(config, ...candidates) {
|
|
1205
|
+
for (const c of candidates) {
|
|
1206
|
+
if (typeof c === "string" && c.length > 0) {
|
|
1207
|
+
assertValidSdkId("productSlug", c);
|
|
1208
|
+
return c;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (config.productId) {
|
|
1212
|
+
assertValidSdkId("productId", config.productId);
|
|
1213
|
+
return config.productId;
|
|
1214
|
+
}
|
|
1215
|
+
throw new NeetruError(
|
|
1216
|
+
"validation_failed",
|
|
1217
|
+
"productId required (pass to input.productSlug or set on createNeetruClient)"
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1098
1220
|
function createSupportNamespace(config) {
|
|
1099
1221
|
return {
|
|
1100
1222
|
/**
|
|
1101
|
-
* Cria um ticket de suporte. Requer Bearer auth.
|
|
1102
|
-
*
|
|
1223
|
+
* Cria um ticket de suporte. Requer Bearer auth. `productSlug` no input
|
|
1224
|
+
* (ou `config.productId` default) vai como `productId` no body do Core.
|
|
1225
|
+
*
|
|
1226
|
+
* Endpoint canonical: `POST /api/sdk/v1/support/tickets`.
|
|
1103
1227
|
*/
|
|
1104
1228
|
async createTicket(input) {
|
|
1105
1229
|
if (!input || typeof input !== "object") {
|
|
@@ -1118,30 +1242,42 @@ function createSupportNamespace(config) {
|
|
|
1118
1242
|
throw new NeetruError("validation_failed", "message max 10000 chars");
|
|
1119
1243
|
}
|
|
1120
1244
|
if (input.severity && !VALID_SEVERITIES.includes(input.severity)) {
|
|
1121
|
-
throw new NeetruError(
|
|
1245
|
+
throw new NeetruError(
|
|
1246
|
+
"validation_failed",
|
|
1247
|
+
`severity must be one of ${VALID_SEVERITIES.join(", ")}`
|
|
1248
|
+
);
|
|
1122
1249
|
}
|
|
1123
|
-
const
|
|
1250
|
+
const productId = resolveProductId(config, input.productSlug);
|
|
1124
1251
|
const body = {
|
|
1252
|
+
productId,
|
|
1125
1253
|
subject: input.subject,
|
|
1126
1254
|
message: input.message,
|
|
1127
1255
|
severity: input.severity ?? "normal"
|
|
1128
1256
|
};
|
|
1257
|
+
if (input.metadata && typeof input.metadata === "object") {
|
|
1258
|
+
body.metadata = input.metadata;
|
|
1259
|
+
}
|
|
1129
1260
|
const raw = await httpRequest(config, {
|
|
1130
1261
|
method: "POST",
|
|
1131
|
-
path:
|
|
1262
|
+
path: "/api/sdk/v1/support/tickets",
|
|
1132
1263
|
body,
|
|
1133
|
-
requireAuth: true
|
|
1264
|
+
requireAuth: true,
|
|
1265
|
+
idempotencyKey: true
|
|
1134
1266
|
});
|
|
1135
1267
|
const candidate = raw && typeof raw === "object" && "ticket" in raw ? raw.ticket : raw;
|
|
1136
1268
|
return toTicket(candidate);
|
|
1137
1269
|
},
|
|
1138
1270
|
/**
|
|
1139
|
-
* Lista tickets do customer no produto atual
|
|
1271
|
+
* Lista tickets do customer no produto atual. `productSlug` no contexto.
|
|
1272
|
+
*
|
|
1273
|
+
* Endpoint canonical: `GET /api/sdk/v1/support/tickets?productId=...`.
|
|
1140
1274
|
*/
|
|
1141
|
-
async listMyTickets() {
|
|
1275
|
+
async listMyTickets(options) {
|
|
1276
|
+
const productId = resolveProductId(config, options?.productSlug);
|
|
1142
1277
|
const raw = await httpRequest(config, {
|
|
1143
1278
|
method: "GET",
|
|
1144
|
-
path: "/api/v1/
|
|
1279
|
+
path: "/api/sdk/v1/support/tickets",
|
|
1280
|
+
query: { productId },
|
|
1145
1281
|
requireAuth: true
|
|
1146
1282
|
});
|
|
1147
1283
|
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "tickets" in raw ? raw.tickets ?? [] : [];
|
|
@@ -4719,6 +4855,52 @@ function createCheckoutNamespace(config) {
|
|
|
4719
4855
|
// src/webhooks.ts
|
|
4720
4856
|
init_errors();
|
|
4721
4857
|
init_http();
|
|
4858
|
+
async function verifyWebhookSignature(payload, signature, secret) {
|
|
4859
|
+
if (!signature || !secret) return false;
|
|
4860
|
+
const sigHex = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
4861
|
+
if (!/^[0-9a-f]+$/i.test(sigHex)) return false;
|
|
4862
|
+
const subtle = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto?.subtle : void 0;
|
|
4863
|
+
if (!subtle) {
|
|
4864
|
+
throw new NeetruError(
|
|
4865
|
+
"runtime_unsupported",
|
|
4866
|
+
"verifyWebhookSignature requires globalThis.crypto.subtle (Node \u226520, modern browser, or Edge runtime)."
|
|
4867
|
+
);
|
|
4868
|
+
}
|
|
4869
|
+
const encoder = new TextEncoder();
|
|
4870
|
+
const keyBytes = encoder.encode(secret);
|
|
4871
|
+
const payloadBytes = typeof payload === "string" ? encoder.encode(payload) : payload;
|
|
4872
|
+
const key = await subtle.importKey(
|
|
4873
|
+
"raw",
|
|
4874
|
+
keyBytes,
|
|
4875
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
4876
|
+
false,
|
|
4877
|
+
["sign"]
|
|
4878
|
+
);
|
|
4879
|
+
const macBuffer = await subtle.sign(
|
|
4880
|
+
"HMAC",
|
|
4881
|
+
key,
|
|
4882
|
+
payloadBytes
|
|
4883
|
+
);
|
|
4884
|
+
const macBytes = new Uint8Array(macBuffer);
|
|
4885
|
+
const sigBytes = hexToBytes(sigHex.toLowerCase());
|
|
4886
|
+
if (!sigBytes || sigBytes.length !== macBytes.length) return false;
|
|
4887
|
+
let diff = 0;
|
|
4888
|
+
for (let i = 0; i < macBytes.length; i++) {
|
|
4889
|
+
diff |= macBytes[i] ^ sigBytes[i];
|
|
4890
|
+
}
|
|
4891
|
+
return diff === 0;
|
|
4892
|
+
}
|
|
4893
|
+
function hexToBytes(hex) {
|
|
4894
|
+
if (hex.length % 2 !== 0) return null;
|
|
4895
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
4896
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
4897
|
+
const high = parseInt(hex[i * 2], 16);
|
|
4898
|
+
const low = parseInt(hex[i * 2 + 1], 16);
|
|
4899
|
+
if (Number.isNaN(high) || Number.isNaN(low)) return null;
|
|
4900
|
+
bytes[i] = high << 4 | low;
|
|
4901
|
+
}
|
|
4902
|
+
return bytes;
|
|
4903
|
+
}
|
|
4722
4904
|
var VALID_EVENTS = [
|
|
4723
4905
|
"subscription.activated",
|
|
4724
4906
|
"subscription.cancelled",
|
|
@@ -4774,42 +4956,71 @@ function validateInput(input) {
|
|
|
4774
4956
|
throw new NeetruError("validation_failed", "secret deve ter \u226516 chars (recomendado 32+)");
|
|
4775
4957
|
}
|
|
4776
4958
|
}
|
|
4959
|
+
function resolveProductId2(config, ...candidates) {
|
|
4960
|
+
for (const c of candidates) {
|
|
4961
|
+
if (typeof c === "string" && c.length > 0) {
|
|
4962
|
+
assertValidSdkId("productId", c);
|
|
4963
|
+
return c;
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
if (config.productId) {
|
|
4967
|
+
assertValidSdkId("productId", config.productId);
|
|
4968
|
+
return config.productId;
|
|
4969
|
+
}
|
|
4970
|
+
throw new NeetruError(
|
|
4971
|
+
"validation_failed",
|
|
4972
|
+
"productId required (pass to options/input or set on createNeetruClient)"
|
|
4973
|
+
);
|
|
4974
|
+
}
|
|
4777
4975
|
function createWebhooksNamespace(config) {
|
|
4778
4976
|
return {
|
|
4779
4977
|
async register(input) {
|
|
4780
4978
|
validateInput(input);
|
|
4979
|
+
const productId = resolveProductId2(config, input.productId);
|
|
4781
4980
|
const raw = await httpRequest(config, {
|
|
4782
4981
|
method: "POST",
|
|
4783
4982
|
path: "/api/sdk/v1/webhooks",
|
|
4784
|
-
body:
|
|
4983
|
+
body: {
|
|
4984
|
+
productId,
|
|
4985
|
+
url: input.url,
|
|
4986
|
+
events: input.events,
|
|
4987
|
+
...input.secret ? { secret: input.secret } : {}
|
|
4988
|
+
},
|
|
4785
4989
|
requireAuth: true
|
|
4786
4990
|
});
|
|
4787
4991
|
return toEndpoint(raw);
|
|
4788
4992
|
},
|
|
4789
|
-
async list() {
|
|
4993
|
+
async list(options) {
|
|
4994
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4790
4995
|
const raw = await httpRequest(config, {
|
|
4791
4996
|
method: "GET",
|
|
4792
4997
|
path: "/api/sdk/v1/webhooks",
|
|
4998
|
+
query: { productId },
|
|
4793
4999
|
requireAuth: true
|
|
4794
5000
|
});
|
|
4795
5001
|
const list = Array.isArray(raw?.endpoints) ? raw.endpoints : [];
|
|
4796
5002
|
return list.map(toEndpoint);
|
|
4797
5003
|
},
|
|
4798
|
-
async unregister(id) {
|
|
5004
|
+
async unregister(id, options) {
|
|
4799
5005
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5006
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4800
5007
|
await httpRequest(config, {
|
|
4801
5008
|
method: "DELETE",
|
|
4802
5009
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}`,
|
|
5010
|
+
query: { productId },
|
|
4803
5011
|
requireAuth: true
|
|
4804
5012
|
});
|
|
4805
5013
|
return { ok: true };
|
|
4806
5014
|
},
|
|
4807
|
-
async test(id) {
|
|
5015
|
+
async test(id, options) {
|
|
4808
5016
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5017
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4809
5018
|
const raw = await httpRequest(config, {
|
|
4810
5019
|
method: "POST",
|
|
4811
5020
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}/test`,
|
|
4812
|
-
|
|
5021
|
+
query: { productId },
|
|
5022
|
+
requireAuth: true,
|
|
5023
|
+
idempotencyKey: true
|
|
4813
5024
|
});
|
|
4814
5025
|
if (!raw || typeof raw !== "object") {
|
|
4815
5026
|
return { ok: false, error: "invalid response" };
|
|
@@ -4841,17 +5052,17 @@ var MockWebhooks = class {
|
|
|
4841
5052
|
this.endpoints.set(id, endpoint);
|
|
4842
5053
|
return endpoint;
|
|
4843
5054
|
}
|
|
4844
|
-
async list() {
|
|
5055
|
+
async list(_options) {
|
|
4845
5056
|
return [...this.endpoints.values()];
|
|
4846
5057
|
}
|
|
4847
|
-
async unregister(id) {
|
|
5058
|
+
async unregister(id, _options) {
|
|
4848
5059
|
if (!this.endpoints.has(id)) {
|
|
4849
5060
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4850
5061
|
}
|
|
4851
5062
|
this.endpoints.delete(id);
|
|
4852
5063
|
return { ok: true };
|
|
4853
5064
|
}
|
|
4854
|
-
async test(id) {
|
|
5065
|
+
async test(id, _options) {
|
|
4855
5066
|
if (!this.endpoints.has(id)) {
|
|
4856
5067
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4857
5068
|
}
|
|
@@ -4908,49 +5119,85 @@ function validateInput2(input) {
|
|
|
4908
5119
|
throw new NeetruError("validation_failed", "body m\xE1x 2000 chars");
|
|
4909
5120
|
}
|
|
4910
5121
|
}
|
|
5122
|
+
function resolveProductId3(config, ...candidates) {
|
|
5123
|
+
for (const c of candidates) {
|
|
5124
|
+
if (typeof c === "string" && c.length > 0) {
|
|
5125
|
+
assertValidSdkId("productId", c);
|
|
5126
|
+
return c;
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
if (config.productId) {
|
|
5130
|
+
assertValidSdkId("productId", config.productId);
|
|
5131
|
+
return config.productId;
|
|
5132
|
+
}
|
|
5133
|
+
throw new NeetruError(
|
|
5134
|
+
"validation_failed",
|
|
5135
|
+
"productId required (pass to options/input or set on createNeetruClient)"
|
|
5136
|
+
);
|
|
5137
|
+
}
|
|
4911
5138
|
function createNotificationsNamespace(config) {
|
|
4912
5139
|
return {
|
|
4913
5140
|
async send(input) {
|
|
4914
5141
|
validateInput2(input);
|
|
5142
|
+
const productId = resolveProductId3(config, input.productId);
|
|
5143
|
+
const body = {
|
|
5144
|
+
productId,
|
|
5145
|
+
userId: input.userId,
|
|
5146
|
+
kind: input.kind,
|
|
5147
|
+
title: input.title,
|
|
5148
|
+
severity: input.severity ?? "info"
|
|
5149
|
+
};
|
|
5150
|
+
if (input.body !== void 0) body.body = input.body;
|
|
5151
|
+
if (input.link !== void 0) body.link = input.link;
|
|
5152
|
+
if (input.metadata !== void 0) body.metadata = input.metadata;
|
|
5153
|
+
if (input.fingerprint !== void 0) body.fingerprint = input.fingerprint;
|
|
4915
5154
|
const raw = await httpRequest(config, {
|
|
4916
5155
|
method: "POST",
|
|
4917
5156
|
path: "/api/sdk/v1/notifications",
|
|
4918
|
-
body
|
|
4919
|
-
requireAuth: true
|
|
5157
|
+
body,
|
|
5158
|
+
requireAuth: true,
|
|
5159
|
+
idempotencyKey: true
|
|
4920
5160
|
});
|
|
4921
5161
|
return toNotification(raw);
|
|
4922
5162
|
},
|
|
4923
5163
|
async list(userId, options) {
|
|
4924
5164
|
if (!userId) throw new NeetruError("validation_failed", "userId obrigat\xF3rio");
|
|
4925
|
-
const
|
|
4926
|
-
|
|
4927
|
-
|
|
5165
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
5166
|
+
const query = {
|
|
5167
|
+
productId
|
|
5168
|
+
};
|
|
5169
|
+
if (options?.includeDismissed) query.includeDismissed = true;
|
|
5170
|
+
if (options?.onlyUnread) query.onlyUnread = true;
|
|
4928
5171
|
if (options?.limit) {
|
|
4929
|
-
|
|
5172
|
+
query.limit = Math.min(Math.max(1, options.limit), 200);
|
|
4930
5173
|
}
|
|
4931
|
-
const qs = params.toString();
|
|
4932
5174
|
const raw = await httpRequest(config, {
|
|
4933
5175
|
method: "GET",
|
|
4934
|
-
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}
|
|
5176
|
+
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}`,
|
|
5177
|
+
query,
|
|
4935
5178
|
requireAuth: true
|
|
4936
5179
|
});
|
|
4937
5180
|
const list = Array.isArray(raw?.notifications) ? raw.notifications : [];
|
|
4938
5181
|
return list.map(toNotification);
|
|
4939
5182
|
},
|
|
4940
|
-
async markRead(id) {
|
|
5183
|
+
async markRead(id, options) {
|
|
4941
5184
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5185
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4942
5186
|
await httpRequest(config, {
|
|
4943
5187
|
method: "POST",
|
|
4944
5188
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/read`,
|
|
5189
|
+
query: { productId },
|
|
4945
5190
|
requireAuth: true
|
|
4946
5191
|
});
|
|
4947
5192
|
return { ok: true };
|
|
4948
5193
|
},
|
|
4949
|
-
async dismiss(id) {
|
|
5194
|
+
async dismiss(id, options) {
|
|
4950
5195
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5196
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4951
5197
|
await httpRequest(config, {
|
|
4952
5198
|
method: "POST",
|
|
4953
5199
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/dismiss`,
|
|
5200
|
+
query: { productId },
|
|
4954
5201
|
requireAuth: true
|
|
4955
5202
|
});
|
|
4956
5203
|
return { ok: true };
|
|
@@ -4991,7 +5238,7 @@ var MockNotifications = class {
|
|
|
4991
5238
|
const limit = Math.min(Math.max(1, options?.limit ?? 50), 200);
|
|
4992
5239
|
return [...this.notifications.values()].filter((n) => n.userId === userId).filter((n) => options?.includeDismissed || !n.dismissedAt).filter((n) => !options?.onlyUnread || !n.readAt).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, limit);
|
|
4993
5240
|
}
|
|
4994
|
-
async markRead(id) {
|
|
5241
|
+
async markRead(id, _options) {
|
|
4995
5242
|
const n = this.notifications.get(id);
|
|
4996
5243
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
4997
5244
|
if (!n.readAt) {
|
|
@@ -5000,7 +5247,7 @@ var MockNotifications = class {
|
|
|
5000
5247
|
}
|
|
5001
5248
|
return { ok: true };
|
|
5002
5249
|
}
|
|
5003
|
-
async dismiss(id) {
|
|
5250
|
+
async dismiss(id, _options) {
|
|
5004
5251
|
const n = this.notifications.get(id);
|
|
5005
5252
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
5006
5253
|
if (!n.dismissedAt) {
|
|
@@ -5013,6 +5260,7 @@ var MockNotifications = class {
|
|
|
5013
5260
|
|
|
5014
5261
|
// src/mocks.ts
|
|
5015
5262
|
init_db_errors();
|
|
5263
|
+
init_errors();
|
|
5016
5264
|
var DEV_FIXTURE_USER = Object.freeze({
|
|
5017
5265
|
uid: "dev-fixture-uid-0001",
|
|
5018
5266
|
email: "dev@neetru.local",
|
|
@@ -5120,39 +5368,25 @@ var MockAuth = class {
|
|
|
5120
5368
|
}
|
|
5121
5369
|
};
|
|
5122
5370
|
var MockUsage = class {
|
|
5123
|
-
|
|
5371
|
+
_reports = [];
|
|
5124
5372
|
_quotas;
|
|
5125
|
-
/** v0.3 — counters in-memory pra `report()` / `check()`. */
|
|
5126
5373
|
_counters = /* @__PURE__ */ new Map();
|
|
5127
5374
|
constructor(initialQuotas = {}) {
|
|
5128
5375
|
this._quotas = new Map(Object.entries(initialQuotas));
|
|
5129
5376
|
}
|
|
5130
|
-
async
|
|
5131
|
-
this._records.push({
|
|
5132
|
-
event,
|
|
5133
|
-
properties,
|
|
5134
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5135
|
-
});
|
|
5136
|
-
return { ok: true };
|
|
5137
|
-
}
|
|
5138
|
-
async getQuota(metric) {
|
|
5139
|
-
const existing = this._quotas.get(metric);
|
|
5140
|
-
if (existing) return existing;
|
|
5141
|
-
return {
|
|
5142
|
-
metric,
|
|
5143
|
-
used: this._records.filter((r) => r.event === metric).length,
|
|
5144
|
-
limit: -1,
|
|
5145
|
-
// unlimited em mock por default
|
|
5146
|
-
plan: "mock"
|
|
5147
|
-
};
|
|
5148
|
-
}
|
|
5149
|
-
/** v0.3 — Mock report incrementa o counter local. */
|
|
5150
|
-
async report(resource, qty = 1, _options) {
|
|
5377
|
+
async report(resource, qty = 1, options) {
|
|
5151
5378
|
if (!resource) throw new Error("resource required");
|
|
5152
5379
|
const safeQty = Number.isFinite(qty) && qty > 0 ? Math.floor(qty) : 1;
|
|
5153
5380
|
const existing = this._counters.get(resource) ?? 0;
|
|
5154
5381
|
const next = existing + safeQty;
|
|
5155
5382
|
this._counters.set(resource, next);
|
|
5383
|
+
this._reports.push({
|
|
5384
|
+
resource,
|
|
5385
|
+
qty: safeQty,
|
|
5386
|
+
productId: options?.productId,
|
|
5387
|
+
tenantId: options?.tenantId,
|
|
5388
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5389
|
+
});
|
|
5156
5390
|
const limitFixture = this._quotas.get(resource);
|
|
5157
5391
|
const limit = limitFixture?.limit ?? -1;
|
|
5158
5392
|
return {
|
|
@@ -5164,12 +5398,11 @@ var MockUsage = class {
|
|
|
5164
5398
|
status: limit > 0 && next >= limit ? "read_only_overlimit" : "ok"
|
|
5165
5399
|
};
|
|
5166
5400
|
}
|
|
5167
|
-
/** v0.3 — Mock check usa quotas + counters in-memory. */
|
|
5168
5401
|
async check(resource, _options) {
|
|
5169
5402
|
const quota = this._quotas.get(resource);
|
|
5170
5403
|
const used = this._counters.get(resource) ?? 0;
|
|
5171
5404
|
if (!quota) {
|
|
5172
|
-
return { allowed: true, reason: "granted", limit: -1, remaining: -1 };
|
|
5405
|
+
return { allowed: true, reason: "granted", limit: -1, remaining: -1, behavior: null };
|
|
5173
5406
|
}
|
|
5174
5407
|
const remaining = quota.limit < 0 ? -1 : Math.max(0, quota.limit - used);
|
|
5175
5408
|
if (remaining === 0) {
|
|
@@ -5177,19 +5410,25 @@ var MockUsage = class {
|
|
|
5177
5410
|
allowed: false,
|
|
5178
5411
|
reason: "limit_exceeded",
|
|
5179
5412
|
limit: quota.limit,
|
|
5180
|
-
remaining: 0
|
|
5413
|
+
remaining: 0,
|
|
5414
|
+
behavior: "readonly"
|
|
5181
5415
|
};
|
|
5182
5416
|
}
|
|
5183
5417
|
return {
|
|
5184
5418
|
allowed: true,
|
|
5185
5419
|
reason: "granted",
|
|
5186
5420
|
limit: quota.limit,
|
|
5187
|
-
remaining
|
|
5421
|
+
remaining,
|
|
5422
|
+
behavior: null
|
|
5188
5423
|
};
|
|
5189
5424
|
}
|
|
5190
|
-
/** Test helper. */
|
|
5425
|
+
/** Test helper — retorna histórico de `report()` calls. */
|
|
5426
|
+
__getReports() {
|
|
5427
|
+
return [...this._reports];
|
|
5428
|
+
}
|
|
5429
|
+
/** @deprecated Removido em 3.0 (usage.track foi removido). Use `__getReports`. */
|
|
5191
5430
|
__getRecords() {
|
|
5192
|
-
return
|
|
5431
|
+
return this.__getReports();
|
|
5193
5432
|
}
|
|
5194
5433
|
/** Test helper — substitui quota fixture. */
|
|
5195
5434
|
__setQuota(metric, quota) {
|
|
@@ -5197,7 +5436,7 @@ var MockUsage = class {
|
|
|
5197
5436
|
}
|
|
5198
5437
|
/** Test helper — limpa estado. */
|
|
5199
5438
|
__reset() {
|
|
5200
|
-
this.
|
|
5439
|
+
this._reports = [];
|
|
5201
5440
|
this._quotas.clear();
|
|
5202
5441
|
this._counters.clear();
|
|
5203
5442
|
}
|
|
@@ -5206,8 +5445,14 @@ var MockSupport = class {
|
|
|
5206
5445
|
_tickets = [];
|
|
5207
5446
|
_ticketSeq = 0;
|
|
5208
5447
|
async createTicket(input) {
|
|
5209
|
-
if (!input?.subject) throw new
|
|
5210
|
-
if (!input?.message) throw new
|
|
5448
|
+
if (!input?.subject) throw new NeetruError("validation_failed", "subject required");
|
|
5449
|
+
if (!input?.message) throw new NeetruError("validation_failed", "message required");
|
|
5450
|
+
if (!input?.productSlug) {
|
|
5451
|
+
throw new NeetruError(
|
|
5452
|
+
"validation_failed",
|
|
5453
|
+
"productSlug required (3.0 breaking \u2014 pass to input or set on createNeetruClient)"
|
|
5454
|
+
);
|
|
5455
|
+
}
|
|
5211
5456
|
const ticket = {
|
|
5212
5457
|
id: `mock-ticket-${++this._ticketSeq}`,
|
|
5213
5458
|
subject: input.subject,
|
|
@@ -5220,7 +5465,10 @@ var MockSupport = class {
|
|
|
5220
5465
|
this._tickets.push(ticket);
|
|
5221
5466
|
return ticket;
|
|
5222
5467
|
}
|
|
5223
|
-
async listMyTickets() {
|
|
5468
|
+
async listMyTickets(options) {
|
|
5469
|
+
if (options?.productSlug) {
|
|
5470
|
+
return this._tickets.filter((t) => t.productSlug === options.productSlug);
|
|
5471
|
+
}
|
|
5224
5472
|
return [...this._tickets];
|
|
5225
5473
|
}
|
|
5226
5474
|
/** Test helper. */
|
|
@@ -5689,8 +5937,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5689
5937
|
url.searchParams.set("scope", scope);
|
|
5690
5938
|
url.searchParams.set("state", state);
|
|
5691
5939
|
if (config.apiKey) {
|
|
5692
|
-
const
|
|
5693
|
-
url.searchParams.set("client_id",
|
|
5940
|
+
const { keyId } = parseApiKey(config.apiKey);
|
|
5941
|
+
url.searchParams.set("client_id", keyId);
|
|
5694
5942
|
}
|
|
5695
5943
|
globalThis.location.assign(url.toString());
|
|
5696
5944
|
return;
|
|
@@ -5704,14 +5952,30 @@ function createOidcAuthNamespace(config) {
|
|
|
5704
5952
|
const storage = getStorage();
|
|
5705
5953
|
if (storage) storage.removeItem(STORAGE_KEY);
|
|
5706
5954
|
cachedUser = null;
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5955
|
+
const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
|
|
5956
|
+
const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
|
|
5957
|
+
const revokeUrl = `${idpOrigin}/api/v1/oauth/revoke`;
|
|
5958
|
+
let revokeError = null;
|
|
5959
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5960
|
+
try {
|
|
5961
|
+
await config.fetch(revokeUrl, {
|
|
5962
|
+
method: "POST",
|
|
5963
|
+
headers: { "content-type": "application/json" }
|
|
5964
|
+
});
|
|
5965
|
+
revokeError = null;
|
|
5966
|
+
break;
|
|
5967
|
+
} catch (err) {
|
|
5968
|
+
revokeError = err;
|
|
5969
|
+
if (attempt === 0) {
|
|
5970
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
if (revokeError && typeof console !== "undefined") {
|
|
5975
|
+
console.warn(
|
|
5976
|
+
"[neetru-sdk] signOut: server-side revoke falhou (2 tentativas). Refresh token pode estar stale.",
|
|
5977
|
+
revokeError
|
|
5978
|
+
);
|
|
5715
5979
|
}
|
|
5716
5980
|
notify();
|
|
5717
5981
|
},
|
|
@@ -5737,7 +6001,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5737
6001
|
const issuer = idpOrigin;
|
|
5738
6002
|
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
|
|
5739
6003
|
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
5740
|
-
const
|
|
6004
|
+
const parsedKey = tryParseApiKey(config.apiKey);
|
|
6005
|
+
const audience = parsedKey?.keyId;
|
|
5741
6006
|
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
5742
6007
|
try {
|
|
5743
6008
|
const { payload } = await jwtVerify(token, jwks, {
|
|
@@ -5777,7 +6042,8 @@ function createNeetruClient(config = {}) {
|
|
|
5777
6042
|
"fetch is not available in this runtime. Pass `fetch` explicitly to createNeetruClient."
|
|
5778
6043
|
);
|
|
5779
6044
|
}
|
|
5780
|
-
const
|
|
6045
|
+
const rawBaseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
6046
|
+
const baseUrl = validateBaseUrl(rawBaseUrl, config.allowedHosts ?? []);
|
|
5781
6047
|
const apiKey = config.apiKey ?? readEnvApiKey();
|
|
5782
6048
|
const env = resolveEnv(config.env);
|
|
5783
6049
|
const resolved = Object.freeze({
|
|
@@ -5815,12 +6081,8 @@ function createNeetruClient(config = {}) {
|
|
|
5815
6081
|
// src/index.ts
|
|
5816
6082
|
init_errors();
|
|
5817
6083
|
init_db_errors();
|
|
5818
|
-
var VERSION = "
|
|
5819
|
-
function initNeetru(config) {
|
|
5820
|
-
const { apiUrl: _apiUrl, baseUrl, ...rest } = config;
|
|
5821
|
-
return createNeetruClient({ ...rest, baseUrl });
|
|
5822
|
-
}
|
|
6084
|
+
var VERSION = "3.0.0";
|
|
5823
6085
|
|
|
5824
|
-
export { DEFAULT_BASE_URL, DEV_FIXTURE_USER, MockAuth, MockCheckout, MockDb, MockEntitlements, MockNotifications, MockSupport, MockUsage, MockWebhooks, NeetruDbError, NeetruError, VERSION, createCheckoutNamespace, createNeetruClient, createNeetruDb, createNotificationsNamespace, createWebhooksNamespace,
|
|
6086
|
+
export { DEFAULT_BASE_URL, DEV_FIXTURE_USER, MockAuth, MockCheckout, MockDb, MockEntitlements, MockNotifications, MockSupport, MockUsage, MockWebhooks, NeetruDbError, NeetruError, VERSION, createCheckoutNamespace, createNeetruClient, createNeetruDb, createNotificationsNamespace, createWebhooksNamespace, verifyWebhookSignature };
|
|
5825
6087
|
//# sourceMappingURL=index.mjs.map
|
|
5826
6088
|
//# sourceMappingURL=index.mjs.map
|