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