@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/auth.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 ?? [] : [];
|
|
@@ -4774,42 +4910,71 @@ function validateInput(input) {
|
|
|
4774
4910
|
throw new NeetruError("validation_failed", "secret deve ter \u226516 chars (recomendado 32+)");
|
|
4775
4911
|
}
|
|
4776
4912
|
}
|
|
4913
|
+
function resolveProductId2(config, ...candidates) {
|
|
4914
|
+
for (const c of candidates) {
|
|
4915
|
+
if (typeof c === "string" && c.length > 0) {
|
|
4916
|
+
assertValidSdkId("productId", c);
|
|
4917
|
+
return c;
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
if (config.productId) {
|
|
4921
|
+
assertValidSdkId("productId", config.productId);
|
|
4922
|
+
return config.productId;
|
|
4923
|
+
}
|
|
4924
|
+
throw new NeetruError(
|
|
4925
|
+
"validation_failed",
|
|
4926
|
+
"productId required (pass to options/input or set on createNeetruClient)"
|
|
4927
|
+
);
|
|
4928
|
+
}
|
|
4777
4929
|
function createWebhooksNamespace(config) {
|
|
4778
4930
|
return {
|
|
4779
4931
|
async register(input) {
|
|
4780
4932
|
validateInput(input);
|
|
4933
|
+
const productId = resolveProductId2(config, input.productId);
|
|
4781
4934
|
const raw = await httpRequest(config, {
|
|
4782
4935
|
method: "POST",
|
|
4783
4936
|
path: "/api/sdk/v1/webhooks",
|
|
4784
|
-
body:
|
|
4937
|
+
body: {
|
|
4938
|
+
productId,
|
|
4939
|
+
url: input.url,
|
|
4940
|
+
events: input.events,
|
|
4941
|
+
...input.secret ? { secret: input.secret } : {}
|
|
4942
|
+
},
|
|
4785
4943
|
requireAuth: true
|
|
4786
4944
|
});
|
|
4787
4945
|
return toEndpoint(raw);
|
|
4788
4946
|
},
|
|
4789
|
-
async list() {
|
|
4947
|
+
async list(options) {
|
|
4948
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4790
4949
|
const raw = await httpRequest(config, {
|
|
4791
4950
|
method: "GET",
|
|
4792
4951
|
path: "/api/sdk/v1/webhooks",
|
|
4952
|
+
query: { productId },
|
|
4793
4953
|
requireAuth: true
|
|
4794
4954
|
});
|
|
4795
4955
|
const list = Array.isArray(raw?.endpoints) ? raw.endpoints : [];
|
|
4796
4956
|
return list.map(toEndpoint);
|
|
4797
4957
|
},
|
|
4798
|
-
async unregister(id) {
|
|
4958
|
+
async unregister(id, options) {
|
|
4799
4959
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
4960
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4800
4961
|
await httpRequest(config, {
|
|
4801
4962
|
method: "DELETE",
|
|
4802
4963
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}`,
|
|
4964
|
+
query: { productId },
|
|
4803
4965
|
requireAuth: true
|
|
4804
4966
|
});
|
|
4805
4967
|
return { ok: true };
|
|
4806
4968
|
},
|
|
4807
|
-
async test(id) {
|
|
4969
|
+
async test(id, options) {
|
|
4808
4970
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
4971
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4809
4972
|
const raw = await httpRequest(config, {
|
|
4810
4973
|
method: "POST",
|
|
4811
4974
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}/test`,
|
|
4812
|
-
|
|
4975
|
+
query: { productId },
|
|
4976
|
+
requireAuth: true,
|
|
4977
|
+
idempotencyKey: true
|
|
4813
4978
|
});
|
|
4814
4979
|
if (!raw || typeof raw !== "object") {
|
|
4815
4980
|
return { ok: false, error: "invalid response" };
|
|
@@ -4841,17 +5006,17 @@ var MockWebhooks = class {
|
|
|
4841
5006
|
this.endpoints.set(id, endpoint);
|
|
4842
5007
|
return endpoint;
|
|
4843
5008
|
}
|
|
4844
|
-
async list() {
|
|
5009
|
+
async list(_options) {
|
|
4845
5010
|
return [...this.endpoints.values()];
|
|
4846
5011
|
}
|
|
4847
|
-
async unregister(id) {
|
|
5012
|
+
async unregister(id, _options) {
|
|
4848
5013
|
if (!this.endpoints.has(id)) {
|
|
4849
5014
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4850
5015
|
}
|
|
4851
5016
|
this.endpoints.delete(id);
|
|
4852
5017
|
return { ok: true };
|
|
4853
5018
|
}
|
|
4854
|
-
async test(id) {
|
|
5019
|
+
async test(id, _options) {
|
|
4855
5020
|
if (!this.endpoints.has(id)) {
|
|
4856
5021
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4857
5022
|
}
|
|
@@ -4908,49 +5073,85 @@ function validateInput2(input) {
|
|
|
4908
5073
|
throw new NeetruError("validation_failed", "body m\xE1x 2000 chars");
|
|
4909
5074
|
}
|
|
4910
5075
|
}
|
|
5076
|
+
function resolveProductId3(config, ...candidates) {
|
|
5077
|
+
for (const c of candidates) {
|
|
5078
|
+
if (typeof c === "string" && c.length > 0) {
|
|
5079
|
+
assertValidSdkId("productId", c);
|
|
5080
|
+
return c;
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
if (config.productId) {
|
|
5084
|
+
assertValidSdkId("productId", config.productId);
|
|
5085
|
+
return config.productId;
|
|
5086
|
+
}
|
|
5087
|
+
throw new NeetruError(
|
|
5088
|
+
"validation_failed",
|
|
5089
|
+
"productId required (pass to options/input or set on createNeetruClient)"
|
|
5090
|
+
);
|
|
5091
|
+
}
|
|
4911
5092
|
function createNotificationsNamespace(config) {
|
|
4912
5093
|
return {
|
|
4913
5094
|
async send(input) {
|
|
4914
5095
|
validateInput2(input);
|
|
5096
|
+
const productId = resolveProductId3(config, input.productId);
|
|
5097
|
+
const body = {
|
|
5098
|
+
productId,
|
|
5099
|
+
userId: input.userId,
|
|
5100
|
+
kind: input.kind,
|
|
5101
|
+
title: input.title,
|
|
5102
|
+
severity: input.severity ?? "info"
|
|
5103
|
+
};
|
|
5104
|
+
if (input.body !== void 0) body.body = input.body;
|
|
5105
|
+
if (input.link !== void 0) body.link = input.link;
|
|
5106
|
+
if (input.metadata !== void 0) body.metadata = input.metadata;
|
|
5107
|
+
if (input.fingerprint !== void 0) body.fingerprint = input.fingerprint;
|
|
4915
5108
|
const raw = await httpRequest(config, {
|
|
4916
5109
|
method: "POST",
|
|
4917
5110
|
path: "/api/sdk/v1/notifications",
|
|
4918
|
-
body
|
|
4919
|
-
requireAuth: true
|
|
5111
|
+
body,
|
|
5112
|
+
requireAuth: true,
|
|
5113
|
+
idempotencyKey: true
|
|
4920
5114
|
});
|
|
4921
5115
|
return toNotification(raw);
|
|
4922
5116
|
},
|
|
4923
5117
|
async list(userId, options) {
|
|
4924
5118
|
if (!userId) throw new NeetruError("validation_failed", "userId obrigat\xF3rio");
|
|
4925
|
-
const
|
|
4926
|
-
|
|
4927
|
-
|
|
5119
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
5120
|
+
const query = {
|
|
5121
|
+
productId
|
|
5122
|
+
};
|
|
5123
|
+
if (options?.includeDismissed) query.includeDismissed = true;
|
|
5124
|
+
if (options?.onlyUnread) query.onlyUnread = true;
|
|
4928
5125
|
if (options?.limit) {
|
|
4929
|
-
|
|
5126
|
+
query.limit = Math.min(Math.max(1, options.limit), 200);
|
|
4930
5127
|
}
|
|
4931
|
-
const qs = params.toString();
|
|
4932
5128
|
const raw = await httpRequest(config, {
|
|
4933
5129
|
method: "GET",
|
|
4934
|
-
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}
|
|
5130
|
+
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}`,
|
|
5131
|
+
query,
|
|
4935
5132
|
requireAuth: true
|
|
4936
5133
|
});
|
|
4937
5134
|
const list = Array.isArray(raw?.notifications) ? raw.notifications : [];
|
|
4938
5135
|
return list.map(toNotification);
|
|
4939
5136
|
},
|
|
4940
|
-
async markRead(id) {
|
|
5137
|
+
async markRead(id, options) {
|
|
4941
5138
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5139
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4942
5140
|
await httpRequest(config, {
|
|
4943
5141
|
method: "POST",
|
|
4944
5142
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/read`,
|
|
5143
|
+
query: { productId },
|
|
4945
5144
|
requireAuth: true
|
|
4946
5145
|
});
|
|
4947
5146
|
return { ok: true };
|
|
4948
5147
|
},
|
|
4949
|
-
async dismiss(id) {
|
|
5148
|
+
async dismiss(id, options) {
|
|
4950
5149
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5150
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4951
5151
|
await httpRequest(config, {
|
|
4952
5152
|
method: "POST",
|
|
4953
5153
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/dismiss`,
|
|
5154
|
+
query: { productId },
|
|
4954
5155
|
requireAuth: true
|
|
4955
5156
|
});
|
|
4956
5157
|
return { ok: true };
|
|
@@ -4991,7 +5192,7 @@ var MockNotifications = class {
|
|
|
4991
5192
|
const limit = Math.min(Math.max(1, options?.limit ?? 50), 200);
|
|
4992
5193
|
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
5194
|
}
|
|
4994
|
-
async markRead(id) {
|
|
5195
|
+
async markRead(id, _options) {
|
|
4995
5196
|
const n = this.notifications.get(id);
|
|
4996
5197
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
4997
5198
|
if (!n.readAt) {
|
|
@@ -5000,7 +5201,7 @@ var MockNotifications = class {
|
|
|
5000
5201
|
}
|
|
5001
5202
|
return { ok: true };
|
|
5002
5203
|
}
|
|
5003
|
-
async dismiss(id) {
|
|
5204
|
+
async dismiss(id, _options) {
|
|
5004
5205
|
const n = this.notifications.get(id);
|
|
5005
5206
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
5006
5207
|
if (!n.dismissedAt) {
|
|
@@ -5012,6 +5213,7 @@ var MockNotifications = class {
|
|
|
5012
5213
|
};
|
|
5013
5214
|
|
|
5014
5215
|
// src/mocks.ts
|
|
5216
|
+
init_errors();
|
|
5015
5217
|
var DEV_FIXTURE_USER = Object.freeze({
|
|
5016
5218
|
uid: "dev-fixture-uid-0001",
|
|
5017
5219
|
email: "dev@neetru.local",
|
|
@@ -5119,39 +5321,25 @@ var MockAuth = class {
|
|
|
5119
5321
|
}
|
|
5120
5322
|
};
|
|
5121
5323
|
var MockUsage = class {
|
|
5122
|
-
|
|
5324
|
+
_reports = [];
|
|
5123
5325
|
_quotas;
|
|
5124
|
-
/** v0.3 — counters in-memory pra `report()` / `check()`. */
|
|
5125
5326
|
_counters = /* @__PURE__ */ new Map();
|
|
5126
5327
|
constructor(initialQuotas = {}) {
|
|
5127
5328
|
this._quotas = new Map(Object.entries(initialQuotas));
|
|
5128
5329
|
}
|
|
5129
|
-
async
|
|
5130
|
-
this._records.push({
|
|
5131
|
-
event,
|
|
5132
|
-
properties,
|
|
5133
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5134
|
-
});
|
|
5135
|
-
return { ok: true };
|
|
5136
|
-
}
|
|
5137
|
-
async getQuota(metric) {
|
|
5138
|
-
const existing = this._quotas.get(metric);
|
|
5139
|
-
if (existing) return existing;
|
|
5140
|
-
return {
|
|
5141
|
-
metric,
|
|
5142
|
-
used: this._records.filter((r) => r.event === metric).length,
|
|
5143
|
-
limit: -1,
|
|
5144
|
-
// unlimited em mock por default
|
|
5145
|
-
plan: "mock"
|
|
5146
|
-
};
|
|
5147
|
-
}
|
|
5148
|
-
/** v0.3 — Mock report incrementa o counter local. */
|
|
5149
|
-
async report(resource, qty = 1, _options) {
|
|
5330
|
+
async report(resource, qty = 1, options) {
|
|
5150
5331
|
if (!resource) throw new Error("resource required");
|
|
5151
5332
|
const safeQty = Number.isFinite(qty) && qty > 0 ? Math.floor(qty) : 1;
|
|
5152
5333
|
const existing = this._counters.get(resource) ?? 0;
|
|
5153
5334
|
const next = existing + safeQty;
|
|
5154
5335
|
this._counters.set(resource, next);
|
|
5336
|
+
this._reports.push({
|
|
5337
|
+
resource,
|
|
5338
|
+
qty: safeQty,
|
|
5339
|
+
productId: options?.productId,
|
|
5340
|
+
tenantId: options?.tenantId,
|
|
5341
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5342
|
+
});
|
|
5155
5343
|
const limitFixture = this._quotas.get(resource);
|
|
5156
5344
|
const limit = limitFixture?.limit ?? -1;
|
|
5157
5345
|
return {
|
|
@@ -5163,12 +5351,11 @@ var MockUsage = class {
|
|
|
5163
5351
|
status: limit > 0 && next >= limit ? "read_only_overlimit" : "ok"
|
|
5164
5352
|
};
|
|
5165
5353
|
}
|
|
5166
|
-
/** v0.3 — Mock check usa quotas + counters in-memory. */
|
|
5167
5354
|
async check(resource, _options) {
|
|
5168
5355
|
const quota = this._quotas.get(resource);
|
|
5169
5356
|
const used = this._counters.get(resource) ?? 0;
|
|
5170
5357
|
if (!quota) {
|
|
5171
|
-
return { allowed: true, reason: "granted", limit: -1, remaining: -1 };
|
|
5358
|
+
return { allowed: true, reason: "granted", limit: -1, remaining: -1, behavior: null };
|
|
5172
5359
|
}
|
|
5173
5360
|
const remaining = quota.limit < 0 ? -1 : Math.max(0, quota.limit - used);
|
|
5174
5361
|
if (remaining === 0) {
|
|
@@ -5176,19 +5363,25 @@ var MockUsage = class {
|
|
|
5176
5363
|
allowed: false,
|
|
5177
5364
|
reason: "limit_exceeded",
|
|
5178
5365
|
limit: quota.limit,
|
|
5179
|
-
remaining: 0
|
|
5366
|
+
remaining: 0,
|
|
5367
|
+
behavior: "readonly"
|
|
5180
5368
|
};
|
|
5181
5369
|
}
|
|
5182
5370
|
return {
|
|
5183
5371
|
allowed: true,
|
|
5184
5372
|
reason: "granted",
|
|
5185
5373
|
limit: quota.limit,
|
|
5186
|
-
remaining
|
|
5374
|
+
remaining,
|
|
5375
|
+
behavior: null
|
|
5187
5376
|
};
|
|
5188
5377
|
}
|
|
5189
|
-
/** Test helper. */
|
|
5378
|
+
/** Test helper — retorna histórico de `report()` calls. */
|
|
5379
|
+
__getReports() {
|
|
5380
|
+
return [...this._reports];
|
|
5381
|
+
}
|
|
5382
|
+
/** @deprecated Removido em 3.0 (usage.track foi removido). Use `__getReports`. */
|
|
5190
5383
|
__getRecords() {
|
|
5191
|
-
return
|
|
5384
|
+
return this.__getReports();
|
|
5192
5385
|
}
|
|
5193
5386
|
/** Test helper — substitui quota fixture. */
|
|
5194
5387
|
__setQuota(metric, quota) {
|
|
@@ -5196,7 +5389,7 @@ var MockUsage = class {
|
|
|
5196
5389
|
}
|
|
5197
5390
|
/** Test helper — limpa estado. */
|
|
5198
5391
|
__reset() {
|
|
5199
|
-
this.
|
|
5392
|
+
this._reports = [];
|
|
5200
5393
|
this._quotas.clear();
|
|
5201
5394
|
this._counters.clear();
|
|
5202
5395
|
}
|
|
@@ -5205,8 +5398,14 @@ var MockSupport = class {
|
|
|
5205
5398
|
_tickets = [];
|
|
5206
5399
|
_ticketSeq = 0;
|
|
5207
5400
|
async createTicket(input) {
|
|
5208
|
-
if (!input?.subject) throw new
|
|
5209
|
-
if (!input?.message) throw new
|
|
5401
|
+
if (!input?.subject) throw new NeetruError("validation_failed", "subject required");
|
|
5402
|
+
if (!input?.message) throw new NeetruError("validation_failed", "message required");
|
|
5403
|
+
if (!input?.productSlug) {
|
|
5404
|
+
throw new NeetruError(
|
|
5405
|
+
"validation_failed",
|
|
5406
|
+
"productSlug required (3.0 breaking \u2014 pass to input or set on createNeetruClient)"
|
|
5407
|
+
);
|
|
5408
|
+
}
|
|
5210
5409
|
const ticket = {
|
|
5211
5410
|
id: `mock-ticket-${++this._ticketSeq}`,
|
|
5212
5411
|
subject: input.subject,
|
|
@@ -5219,7 +5418,10 @@ var MockSupport = class {
|
|
|
5219
5418
|
this._tickets.push(ticket);
|
|
5220
5419
|
return ticket;
|
|
5221
5420
|
}
|
|
5222
|
-
async listMyTickets() {
|
|
5421
|
+
async listMyTickets(options) {
|
|
5422
|
+
if (options?.productSlug) {
|
|
5423
|
+
return this._tickets.filter((t) => t.productSlug === options.productSlug);
|
|
5424
|
+
}
|
|
5223
5425
|
return [...this._tickets];
|
|
5224
5426
|
}
|
|
5225
5427
|
/** Test helper. */
|
|
@@ -5377,8 +5579,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5377
5579
|
url.searchParams.set("scope", scope);
|
|
5378
5580
|
url.searchParams.set("state", state);
|
|
5379
5581
|
if (config.apiKey) {
|
|
5380
|
-
const
|
|
5381
|
-
url.searchParams.set("client_id",
|
|
5582
|
+
const { keyId } = parseApiKey(config.apiKey);
|
|
5583
|
+
url.searchParams.set("client_id", keyId);
|
|
5382
5584
|
}
|
|
5383
5585
|
globalThis.location.assign(url.toString());
|
|
5384
5586
|
return;
|
|
@@ -5392,14 +5594,30 @@ function createOidcAuthNamespace(config) {
|
|
|
5392
5594
|
const storage = getStorage();
|
|
5393
5595
|
if (storage) storage.removeItem(STORAGE_KEY);
|
|
5394
5596
|
cachedUser = null;
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5597
|
+
const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
|
|
5598
|
+
const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
|
|
5599
|
+
const revokeUrl = `${idpOrigin}/api/v1/oauth/revoke`;
|
|
5600
|
+
let revokeError = null;
|
|
5601
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5602
|
+
try {
|
|
5603
|
+
await config.fetch(revokeUrl, {
|
|
5604
|
+
method: "POST",
|
|
5605
|
+
headers: { "content-type": "application/json" }
|
|
5606
|
+
});
|
|
5607
|
+
revokeError = null;
|
|
5608
|
+
break;
|
|
5609
|
+
} catch (err) {
|
|
5610
|
+
revokeError = err;
|
|
5611
|
+
if (attempt === 0) {
|
|
5612
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
if (revokeError && typeof console !== "undefined") {
|
|
5617
|
+
console.warn(
|
|
5618
|
+
"[neetru-sdk] signOut: server-side revoke falhou (2 tentativas). Refresh token pode estar stale.",
|
|
5619
|
+
revokeError
|
|
5620
|
+
);
|
|
5403
5621
|
}
|
|
5404
5622
|
notify();
|
|
5405
5623
|
},
|
|
@@ -5425,7 +5643,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5425
5643
|
const issuer = idpOrigin;
|
|
5426
5644
|
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
|
|
5427
5645
|
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
5428
|
-
const
|
|
5646
|
+
const parsedKey = tryParseApiKey(config.apiKey);
|
|
5647
|
+
const audience = parsedKey?.keyId;
|
|
5429
5648
|
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
5430
5649
|
try {
|
|
5431
5650
|
const { payload } = await jwtVerify(token, jwks, {
|
|
@@ -5465,7 +5684,8 @@ function createNeetruClient(config = {}) {
|
|
|
5465
5684
|
"fetch is not available in this runtime. Pass `fetch` explicitly to createNeetruClient."
|
|
5466
5685
|
);
|
|
5467
5686
|
}
|
|
5468
|
-
const
|
|
5687
|
+
const rawBaseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
5688
|
+
const baseUrl = validateBaseUrl(rawBaseUrl, config.allowedHosts ?? []);
|
|
5469
5689
|
const apiKey = config.apiKey ?? readEnvApiKey();
|
|
5470
5690
|
const env = resolveEnv(config.env);
|
|
5471
5691
|
const resolved = Object.freeze({
|