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