@neetru/sdk 2.3.5 → 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 +184 -0
- package/README.md +59 -22
- package/dist/auth.cjs +368 -142
- 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 +368 -142
- 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 +36 -5
- 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 +36 -5
- 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 +432 -152
- 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 +432 -152
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +49 -37
- 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 +49 -37
- 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.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",
|
|
@@ -450,7 +475,10 @@ function parseLease(raw) {
|
|
|
450
475
|
};
|
|
451
476
|
}
|
|
452
477
|
async function createPgPool(lease) {
|
|
453
|
-
const { Pool } = await import(
|
|
478
|
+
const { Pool } = await import(
|
|
479
|
+
/* webpackIgnore: true */
|
|
480
|
+
'pg'
|
|
481
|
+
);
|
|
454
482
|
const ssl = lease.sslca ? {
|
|
455
483
|
ca: lease.sslca,
|
|
456
484
|
cert: lease.clientCert ?? void 0,
|
|
@@ -470,7 +498,10 @@ async function createPgPool(lease) {
|
|
|
470
498
|
});
|
|
471
499
|
}
|
|
472
500
|
async function createDrizzleHandle(pool, schema) {
|
|
473
|
-
const { drizzle } = await import(
|
|
501
|
+
const { drizzle } = await import(
|
|
502
|
+
/* webpackIgnore: true */
|
|
503
|
+
'drizzle-orm/node-postgres'
|
|
504
|
+
);
|
|
474
505
|
return drizzle(pool, { schema });
|
|
475
506
|
}
|
|
476
507
|
async function createSqlClientWithDeps(deps, options) {
|
|
@@ -570,6 +601,109 @@ var init_sql_client = __esm({
|
|
|
570
601
|
// src/auth.ts
|
|
571
602
|
init_errors();
|
|
572
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
|
+
|
|
573
707
|
// src/types.ts
|
|
574
708
|
var DEFAULT_BASE_URL = "https://api.neetru.com";
|
|
575
709
|
|
|
@@ -771,7 +905,7 @@ function createTelemetryNamespace(config) {
|
|
|
771
905
|
if (ev.timestamp) body.timestamp = ev.timestamp;
|
|
772
906
|
await httpRequest(config, {
|
|
773
907
|
method: "POST",
|
|
774
|
-
path: "/api/v1/
|
|
908
|
+
path: "/api/sdk/v1/telemetry/event",
|
|
775
909
|
body,
|
|
776
910
|
requireAuth: true,
|
|
777
911
|
retries: 1
|
|
@@ -820,7 +954,7 @@ function createTelemetryNamespace(config) {
|
|
|
820
954
|
if (input.timestamp) body.timestamp = input.timestamp;
|
|
821
955
|
const raw = await httpRequest(config, {
|
|
822
956
|
method: "POST",
|
|
823
|
-
path: "/api/v1/
|
|
957
|
+
path: "/api/sdk/v1/telemetry/event",
|
|
824
958
|
body,
|
|
825
959
|
requireAuth: true
|
|
826
960
|
});
|
|
@@ -932,65 +1066,33 @@ function createTelemetryNamespace(config) {
|
|
|
932
1066
|
// src/usage.ts
|
|
933
1067
|
init_errors();
|
|
934
1068
|
init_http();
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (typeof
|
|
941
|
-
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
|
+
);
|
|
942
1079
|
}
|
|
943
|
-
return {
|
|
944
|
-
metric: typeof r.metric === "string" ? r.metric : metric,
|
|
945
|
-
used: r.used,
|
|
946
|
-
limit: r.limit,
|
|
947
|
-
resetsAt: typeof r.resetsAt === "string" ? r.resetsAt : void 0,
|
|
948
|
-
plan: typeof r.plan === "string" ? r.plan : void 0
|
|
949
|
-
};
|
|
950
1080
|
}
|
|
1081
|
+
|
|
1082
|
+
// src/usage.ts
|
|
951
1083
|
function createUsageNamespace(config) {
|
|
952
1084
|
return {
|
|
953
1085
|
/**
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const body = { event };
|
|
965
|
-
if (properties && typeof properties === "object") body.properties = properties;
|
|
966
|
-
const raw = await httpRequest(config, {
|
|
967
|
-
method: "POST",
|
|
968
|
-
path: "/sdk/v1/usage/record",
|
|
969
|
-
body,
|
|
970
|
-
requireAuth: true
|
|
971
|
-
});
|
|
972
|
-
if (!raw || raw.ok !== true) {
|
|
973
|
-
throw new NeetruError("invalid_response", "Usage record response missing ok");
|
|
974
|
-
}
|
|
975
|
-
return { ok: true };
|
|
976
|
-
},
|
|
977
|
-
/**
|
|
978
|
-
* Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.
|
|
979
|
-
*/
|
|
980
|
-
async getQuota(metric) {
|
|
981
|
-
if (!metric || typeof metric !== "string") {
|
|
982
|
-
throw new NeetruError("validation_failed", "metric is required");
|
|
983
|
-
}
|
|
984
|
-
const raw = await httpRequest(config, {
|
|
985
|
-
method: "GET",
|
|
986
|
-
path: "/sdk/v1/usage/quota",
|
|
987
|
-
query: { metric },
|
|
988
|
-
requireAuth: true
|
|
989
|
-
});
|
|
990
|
-
return toQuota(metric, raw);
|
|
991
|
-
},
|
|
992
|
-
/**
|
|
993
|
-
* 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
|
+
* ```
|
|
994
1096
|
*/
|
|
995
1097
|
async report(resource, qty = 1, options) {
|
|
996
1098
|
if (!resource || typeof resource !== "string") {
|
|
@@ -1013,11 +1115,14 @@ function createUsageNamespace(config) {
|
|
|
1013
1115
|
"tenantId required (pass to options or set on createNeetruClient)"
|
|
1014
1116
|
);
|
|
1015
1117
|
}
|
|
1118
|
+
assertValidSdkId("productId", productId);
|
|
1119
|
+
assertValidSdkId("tenantId", tenantId);
|
|
1016
1120
|
const raw = await httpRequest(config, {
|
|
1017
1121
|
method: "POST",
|
|
1018
|
-
path: "/sdk/v1/usage/record",
|
|
1122
|
+
path: "/api/sdk/v1/usage/record",
|
|
1019
1123
|
body: { productId, tenantId, resource, qty: Math.floor(qty) },
|
|
1020
|
-
requireAuth: true
|
|
1124
|
+
requireAuth: true,
|
|
1125
|
+
idempotencyKey: true
|
|
1021
1126
|
});
|
|
1022
1127
|
if (!raw || raw.ok !== true) {
|
|
1023
1128
|
throw new NeetruError("invalid_response", "usage.report response missing ok");
|
|
@@ -1032,7 +1137,10 @@ function createUsageNamespace(config) {
|
|
|
1032
1137
|
};
|
|
1033
1138
|
},
|
|
1034
1139
|
/**
|
|
1035
|
-
*
|
|
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`.
|
|
1036
1144
|
*/
|
|
1037
1145
|
async check(resource, options) {
|
|
1038
1146
|
if (!resource || typeof resource !== "string") {
|
|
@@ -1043,25 +1151,29 @@ function createUsageNamespace(config) {
|
|
|
1043
1151
|
if (!productId || !tenantId) {
|
|
1044
1152
|
throw new NeetruError(
|
|
1045
1153
|
"validation_failed",
|
|
1046
|
-
"productId and tenantId required"
|
|
1154
|
+
"productId and tenantId required (pass to options or set on createNeetruClient)"
|
|
1047
1155
|
);
|
|
1048
1156
|
}
|
|
1157
|
+
assertValidSdkId("productId", productId);
|
|
1158
|
+
assertValidSdkId("tenantId", tenantId);
|
|
1049
1159
|
const raw = await httpRequest(config, {
|
|
1050
1160
|
method: "GET",
|
|
1051
|
-
path: "/sdk/v1/entitlements",
|
|
1161
|
+
path: "/api/sdk/v1/entitlements",
|
|
1052
1162
|
query: { productId, tenantId, feature: resource },
|
|
1053
1163
|
requireAuth: true
|
|
1054
1164
|
});
|
|
1055
1165
|
if (!raw || typeof raw.allowed !== "boolean") {
|
|
1056
1166
|
throw new NeetruError("invalid_response", "usage.check response missing allowed");
|
|
1057
1167
|
}
|
|
1168
|
+
const behavior = raw.behavior ?? (raw.reason === "limit_exceeded" ? "readonly" : null);
|
|
1058
1169
|
return {
|
|
1059
1170
|
allowed: raw.allowed,
|
|
1060
1171
|
reason: raw.reason,
|
|
1061
1172
|
remaining: raw.remaining,
|
|
1062
1173
|
limit: raw.limit,
|
|
1063
1174
|
planId: raw.planId,
|
|
1064
|
-
planFeatures: raw.planFeatures
|
|
1175
|
+
planFeatures: raw.planFeatures,
|
|
1176
|
+
behavior
|
|
1065
1177
|
};
|
|
1066
1178
|
}
|
|
1067
1179
|
};
|
|
@@ -1088,14 +1200,32 @@ function toTicket(raw) {
|
|
|
1088
1200
|
status: VALID_STATUSES.includes(r.status) ? r.status : "open",
|
|
1089
1201
|
createdAt: typeof r.createdAt === "string" ? r.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1090
1202
|
updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : void 0,
|
|
1091
|
-
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
|
|
1092
1204
|
};
|
|
1093
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
|
+
}
|
|
1094
1222
|
function createSupportNamespace(config) {
|
|
1095
1223
|
return {
|
|
1096
1224
|
/**
|
|
1097
|
-
* Cria um ticket de suporte. Requer Bearer auth.
|
|
1098
|
-
*
|
|
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`.
|
|
1099
1229
|
*/
|
|
1100
1230
|
async createTicket(input) {
|
|
1101
1231
|
if (!input || typeof input !== "object") {
|
|
@@ -1114,30 +1244,42 @@ function createSupportNamespace(config) {
|
|
|
1114
1244
|
throw new NeetruError("validation_failed", "message max 10000 chars");
|
|
1115
1245
|
}
|
|
1116
1246
|
if (input.severity && !VALID_SEVERITIES.includes(input.severity)) {
|
|
1117
|
-
throw new NeetruError(
|
|
1247
|
+
throw new NeetruError(
|
|
1248
|
+
"validation_failed",
|
|
1249
|
+
`severity must be one of ${VALID_SEVERITIES.join(", ")}`
|
|
1250
|
+
);
|
|
1118
1251
|
}
|
|
1119
|
-
const
|
|
1252
|
+
const productId = resolveProductId(config, input.productSlug);
|
|
1120
1253
|
const body = {
|
|
1254
|
+
productId,
|
|
1121
1255
|
subject: input.subject,
|
|
1122
1256
|
message: input.message,
|
|
1123
1257
|
severity: input.severity ?? "normal"
|
|
1124
1258
|
};
|
|
1259
|
+
if (input.metadata && typeof input.metadata === "object") {
|
|
1260
|
+
body.metadata = input.metadata;
|
|
1261
|
+
}
|
|
1125
1262
|
const raw = await httpRequest(config, {
|
|
1126
1263
|
method: "POST",
|
|
1127
|
-
path:
|
|
1264
|
+
path: "/api/sdk/v1/support/tickets",
|
|
1128
1265
|
body,
|
|
1129
|
-
requireAuth: true
|
|
1266
|
+
requireAuth: true,
|
|
1267
|
+
idempotencyKey: true
|
|
1130
1268
|
});
|
|
1131
1269
|
const candidate = raw && typeof raw === "object" && "ticket" in raw ? raw.ticket : raw;
|
|
1132
1270
|
return toTicket(candidate);
|
|
1133
1271
|
},
|
|
1134
1272
|
/**
|
|
1135
|
-
* 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=...`.
|
|
1136
1276
|
*/
|
|
1137
|
-
async listMyTickets() {
|
|
1277
|
+
async listMyTickets(options) {
|
|
1278
|
+
const productId = resolveProductId(config, options?.productSlug);
|
|
1138
1279
|
const raw = await httpRequest(config, {
|
|
1139
1280
|
method: "GET",
|
|
1140
|
-
path: "/api/v1/
|
|
1281
|
+
path: "/api/sdk/v1/support/tickets",
|
|
1282
|
+
query: { productId },
|
|
1141
1283
|
requireAuth: true
|
|
1142
1284
|
});
|
|
1143
1285
|
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "tickets" in raw ? raw.tickets ?? [] : [];
|
|
@@ -4770,42 +4912,71 @@ function validateInput(input) {
|
|
|
4770
4912
|
throw new NeetruError("validation_failed", "secret deve ter \u226516 chars (recomendado 32+)");
|
|
4771
4913
|
}
|
|
4772
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
|
+
}
|
|
4773
4931
|
function createWebhooksNamespace(config) {
|
|
4774
4932
|
return {
|
|
4775
4933
|
async register(input) {
|
|
4776
4934
|
validateInput(input);
|
|
4935
|
+
const productId = resolveProductId2(config, input.productId);
|
|
4777
4936
|
const raw = await httpRequest(config, {
|
|
4778
4937
|
method: "POST",
|
|
4779
4938
|
path: "/api/sdk/v1/webhooks",
|
|
4780
|
-
body:
|
|
4939
|
+
body: {
|
|
4940
|
+
productId,
|
|
4941
|
+
url: input.url,
|
|
4942
|
+
events: input.events,
|
|
4943
|
+
...input.secret ? { secret: input.secret } : {}
|
|
4944
|
+
},
|
|
4781
4945
|
requireAuth: true
|
|
4782
4946
|
});
|
|
4783
4947
|
return toEndpoint(raw);
|
|
4784
4948
|
},
|
|
4785
|
-
async list() {
|
|
4949
|
+
async list(options) {
|
|
4950
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4786
4951
|
const raw = await httpRequest(config, {
|
|
4787
4952
|
method: "GET",
|
|
4788
4953
|
path: "/api/sdk/v1/webhooks",
|
|
4954
|
+
query: { productId },
|
|
4789
4955
|
requireAuth: true
|
|
4790
4956
|
});
|
|
4791
4957
|
const list = Array.isArray(raw?.endpoints) ? raw.endpoints : [];
|
|
4792
4958
|
return list.map(toEndpoint);
|
|
4793
4959
|
},
|
|
4794
|
-
async unregister(id) {
|
|
4960
|
+
async unregister(id, options) {
|
|
4795
4961
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
4962
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4796
4963
|
await httpRequest(config, {
|
|
4797
4964
|
method: "DELETE",
|
|
4798
4965
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}`,
|
|
4966
|
+
query: { productId },
|
|
4799
4967
|
requireAuth: true
|
|
4800
4968
|
});
|
|
4801
4969
|
return { ok: true };
|
|
4802
4970
|
},
|
|
4803
|
-
async test(id) {
|
|
4971
|
+
async test(id, options) {
|
|
4804
4972
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
4973
|
+
const productId = resolveProductId2(config, options?.productId);
|
|
4805
4974
|
const raw = await httpRequest(config, {
|
|
4806
4975
|
method: "POST",
|
|
4807
4976
|
path: `/api/sdk/v1/webhooks/${encodeURIComponent(id)}/test`,
|
|
4808
|
-
|
|
4977
|
+
query: { productId },
|
|
4978
|
+
requireAuth: true,
|
|
4979
|
+
idempotencyKey: true
|
|
4809
4980
|
});
|
|
4810
4981
|
if (!raw || typeof raw !== "object") {
|
|
4811
4982
|
return { ok: false, error: "invalid response" };
|
|
@@ -4837,17 +5008,17 @@ var MockWebhooks = class {
|
|
|
4837
5008
|
this.endpoints.set(id, endpoint);
|
|
4838
5009
|
return endpoint;
|
|
4839
5010
|
}
|
|
4840
|
-
async list() {
|
|
5011
|
+
async list(_options) {
|
|
4841
5012
|
return [...this.endpoints.values()];
|
|
4842
5013
|
}
|
|
4843
|
-
async unregister(id) {
|
|
5014
|
+
async unregister(id, _options) {
|
|
4844
5015
|
if (!this.endpoints.has(id)) {
|
|
4845
5016
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4846
5017
|
}
|
|
4847
5018
|
this.endpoints.delete(id);
|
|
4848
5019
|
return { ok: true };
|
|
4849
5020
|
}
|
|
4850
|
-
async test(id) {
|
|
5021
|
+
async test(id, _options) {
|
|
4851
5022
|
if (!this.endpoints.has(id)) {
|
|
4852
5023
|
throw new NeetruError("not_found", `Webhook ${id} n\xE3o encontrado`);
|
|
4853
5024
|
}
|
|
@@ -4904,49 +5075,85 @@ function validateInput2(input) {
|
|
|
4904
5075
|
throw new NeetruError("validation_failed", "body m\xE1x 2000 chars");
|
|
4905
5076
|
}
|
|
4906
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
|
+
}
|
|
4907
5094
|
function createNotificationsNamespace(config) {
|
|
4908
5095
|
return {
|
|
4909
5096
|
async send(input) {
|
|
4910
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;
|
|
4911
5110
|
const raw = await httpRequest(config, {
|
|
4912
5111
|
method: "POST",
|
|
4913
5112
|
path: "/api/sdk/v1/notifications",
|
|
4914
|
-
body
|
|
4915
|
-
requireAuth: true
|
|
5113
|
+
body,
|
|
5114
|
+
requireAuth: true,
|
|
5115
|
+
idempotencyKey: true
|
|
4916
5116
|
});
|
|
4917
5117
|
return toNotification(raw);
|
|
4918
5118
|
},
|
|
4919
5119
|
async list(userId, options) {
|
|
4920
5120
|
if (!userId) throw new NeetruError("validation_failed", "userId obrigat\xF3rio");
|
|
4921
|
-
const
|
|
4922
|
-
|
|
4923
|
-
|
|
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;
|
|
4924
5127
|
if (options?.limit) {
|
|
4925
|
-
|
|
5128
|
+
query.limit = Math.min(Math.max(1, options.limit), 200);
|
|
4926
5129
|
}
|
|
4927
|
-
const qs = params.toString();
|
|
4928
5130
|
const raw = await httpRequest(config, {
|
|
4929
5131
|
method: "GET",
|
|
4930
|
-
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}
|
|
5132
|
+
path: `/api/sdk/v1/notifications/user/${encodeURIComponent(userId)}`,
|
|
5133
|
+
query,
|
|
4931
5134
|
requireAuth: true
|
|
4932
5135
|
});
|
|
4933
5136
|
const list = Array.isArray(raw?.notifications) ? raw.notifications : [];
|
|
4934
5137
|
return list.map(toNotification);
|
|
4935
5138
|
},
|
|
4936
|
-
async markRead(id) {
|
|
5139
|
+
async markRead(id, options) {
|
|
4937
5140
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5141
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4938
5142
|
await httpRequest(config, {
|
|
4939
5143
|
method: "POST",
|
|
4940
5144
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/read`,
|
|
5145
|
+
query: { productId },
|
|
4941
5146
|
requireAuth: true
|
|
4942
5147
|
});
|
|
4943
5148
|
return { ok: true };
|
|
4944
5149
|
},
|
|
4945
|
-
async dismiss(id) {
|
|
5150
|
+
async dismiss(id, options) {
|
|
4946
5151
|
if (!id) throw new NeetruError("validation_failed", "id obrigat\xF3rio");
|
|
5152
|
+
const productId = resolveProductId3(config, options?.productId);
|
|
4947
5153
|
await httpRequest(config, {
|
|
4948
5154
|
method: "POST",
|
|
4949
5155
|
path: `/api/sdk/v1/notifications/${encodeURIComponent(id)}/dismiss`,
|
|
5156
|
+
query: { productId },
|
|
4950
5157
|
requireAuth: true
|
|
4951
5158
|
});
|
|
4952
5159
|
return { ok: true };
|
|
@@ -4987,7 +5194,7 @@ var MockNotifications = class {
|
|
|
4987
5194
|
const limit = Math.min(Math.max(1, options?.limit ?? 50), 200);
|
|
4988
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);
|
|
4989
5196
|
}
|
|
4990
|
-
async markRead(id) {
|
|
5197
|
+
async markRead(id, _options) {
|
|
4991
5198
|
const n = this.notifications.get(id);
|
|
4992
5199
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
4993
5200
|
if (!n.readAt) {
|
|
@@ -4996,7 +5203,7 @@ var MockNotifications = class {
|
|
|
4996
5203
|
}
|
|
4997
5204
|
return { ok: true };
|
|
4998
5205
|
}
|
|
4999
|
-
async dismiss(id) {
|
|
5206
|
+
async dismiss(id, _options) {
|
|
5000
5207
|
const n = this.notifications.get(id);
|
|
5001
5208
|
if (!n) throw new NeetruError("not_found", `Notification ${id} n\xE3o encontrada`);
|
|
5002
5209
|
if (!n.dismissedAt) {
|
|
@@ -5008,6 +5215,7 @@ var MockNotifications = class {
|
|
|
5008
5215
|
};
|
|
5009
5216
|
|
|
5010
5217
|
// src/mocks.ts
|
|
5218
|
+
init_errors();
|
|
5011
5219
|
var DEV_FIXTURE_USER = Object.freeze({
|
|
5012
5220
|
uid: "dev-fixture-uid-0001",
|
|
5013
5221
|
email: "dev@neetru.local",
|
|
@@ -5115,39 +5323,25 @@ var MockAuth = class {
|
|
|
5115
5323
|
}
|
|
5116
5324
|
};
|
|
5117
5325
|
var MockUsage = class {
|
|
5118
|
-
|
|
5326
|
+
_reports = [];
|
|
5119
5327
|
_quotas;
|
|
5120
|
-
/** v0.3 — counters in-memory pra `report()` / `check()`. */
|
|
5121
5328
|
_counters = /* @__PURE__ */ new Map();
|
|
5122
5329
|
constructor(initialQuotas = {}) {
|
|
5123
5330
|
this._quotas = new Map(Object.entries(initialQuotas));
|
|
5124
5331
|
}
|
|
5125
|
-
async
|
|
5126
|
-
this._records.push({
|
|
5127
|
-
event,
|
|
5128
|
-
properties,
|
|
5129
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5130
|
-
});
|
|
5131
|
-
return { ok: true };
|
|
5132
|
-
}
|
|
5133
|
-
async getQuota(metric) {
|
|
5134
|
-
const existing = this._quotas.get(metric);
|
|
5135
|
-
if (existing) return existing;
|
|
5136
|
-
return {
|
|
5137
|
-
metric,
|
|
5138
|
-
used: this._records.filter((r) => r.event === metric).length,
|
|
5139
|
-
limit: -1,
|
|
5140
|
-
// unlimited em mock por default
|
|
5141
|
-
plan: "mock"
|
|
5142
|
-
};
|
|
5143
|
-
}
|
|
5144
|
-
/** v0.3 — Mock report incrementa o counter local. */
|
|
5145
|
-
async report(resource, qty = 1, _options) {
|
|
5332
|
+
async report(resource, qty = 1, options) {
|
|
5146
5333
|
if (!resource) throw new Error("resource required");
|
|
5147
5334
|
const safeQty = Number.isFinite(qty) && qty > 0 ? Math.floor(qty) : 1;
|
|
5148
5335
|
const existing = this._counters.get(resource) ?? 0;
|
|
5149
5336
|
const next = existing + safeQty;
|
|
5150
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
|
+
});
|
|
5151
5345
|
const limitFixture = this._quotas.get(resource);
|
|
5152
5346
|
const limit = limitFixture?.limit ?? -1;
|
|
5153
5347
|
return {
|
|
@@ -5159,12 +5353,11 @@ var MockUsage = class {
|
|
|
5159
5353
|
status: limit > 0 && next >= limit ? "read_only_overlimit" : "ok"
|
|
5160
5354
|
};
|
|
5161
5355
|
}
|
|
5162
|
-
/** v0.3 — Mock check usa quotas + counters in-memory. */
|
|
5163
5356
|
async check(resource, _options) {
|
|
5164
5357
|
const quota = this._quotas.get(resource);
|
|
5165
5358
|
const used = this._counters.get(resource) ?? 0;
|
|
5166
5359
|
if (!quota) {
|
|
5167
|
-
return { allowed: true, reason: "granted", limit: -1, remaining: -1 };
|
|
5360
|
+
return { allowed: true, reason: "granted", limit: -1, remaining: -1, behavior: null };
|
|
5168
5361
|
}
|
|
5169
5362
|
const remaining = quota.limit < 0 ? -1 : Math.max(0, quota.limit - used);
|
|
5170
5363
|
if (remaining === 0) {
|
|
@@ -5172,19 +5365,25 @@ var MockUsage = class {
|
|
|
5172
5365
|
allowed: false,
|
|
5173
5366
|
reason: "limit_exceeded",
|
|
5174
5367
|
limit: quota.limit,
|
|
5175
|
-
remaining: 0
|
|
5368
|
+
remaining: 0,
|
|
5369
|
+
behavior: "readonly"
|
|
5176
5370
|
};
|
|
5177
5371
|
}
|
|
5178
5372
|
return {
|
|
5179
5373
|
allowed: true,
|
|
5180
5374
|
reason: "granted",
|
|
5181
5375
|
limit: quota.limit,
|
|
5182
|
-
remaining
|
|
5376
|
+
remaining,
|
|
5377
|
+
behavior: null
|
|
5183
5378
|
};
|
|
5184
5379
|
}
|
|
5185
|
-
/** 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`. */
|
|
5186
5385
|
__getRecords() {
|
|
5187
|
-
return
|
|
5386
|
+
return this.__getReports();
|
|
5188
5387
|
}
|
|
5189
5388
|
/** Test helper — substitui quota fixture. */
|
|
5190
5389
|
__setQuota(metric, quota) {
|
|
@@ -5192,7 +5391,7 @@ var MockUsage = class {
|
|
|
5192
5391
|
}
|
|
5193
5392
|
/** Test helper — limpa estado. */
|
|
5194
5393
|
__reset() {
|
|
5195
|
-
this.
|
|
5394
|
+
this._reports = [];
|
|
5196
5395
|
this._quotas.clear();
|
|
5197
5396
|
this._counters.clear();
|
|
5198
5397
|
}
|
|
@@ -5201,8 +5400,14 @@ var MockSupport = class {
|
|
|
5201
5400
|
_tickets = [];
|
|
5202
5401
|
_ticketSeq = 0;
|
|
5203
5402
|
async createTicket(input) {
|
|
5204
|
-
if (!input?.subject) throw new
|
|
5205
|
-
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
|
+
}
|
|
5206
5411
|
const ticket = {
|
|
5207
5412
|
id: `mock-ticket-${++this._ticketSeq}`,
|
|
5208
5413
|
subject: input.subject,
|
|
@@ -5215,7 +5420,10 @@ var MockSupport = class {
|
|
|
5215
5420
|
this._tickets.push(ticket);
|
|
5216
5421
|
return ticket;
|
|
5217
5422
|
}
|
|
5218
|
-
async listMyTickets() {
|
|
5423
|
+
async listMyTickets(options) {
|
|
5424
|
+
if (options?.productSlug) {
|
|
5425
|
+
return this._tickets.filter((t) => t.productSlug === options.productSlug);
|
|
5426
|
+
}
|
|
5219
5427
|
return [...this._tickets];
|
|
5220
5428
|
}
|
|
5221
5429
|
/** Test helper. */
|
|
@@ -5373,8 +5581,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5373
5581
|
url.searchParams.set("scope", scope);
|
|
5374
5582
|
url.searchParams.set("state", state);
|
|
5375
5583
|
if (config.apiKey) {
|
|
5376
|
-
const
|
|
5377
|
-
url.searchParams.set("client_id",
|
|
5584
|
+
const { keyId } = parseApiKey(config.apiKey);
|
|
5585
|
+
url.searchParams.set("client_id", keyId);
|
|
5378
5586
|
}
|
|
5379
5587
|
globalThis.location.assign(url.toString());
|
|
5380
5588
|
return;
|
|
@@ -5388,14 +5596,30 @@ function createOidcAuthNamespace(config) {
|
|
|
5388
5596
|
const storage = getStorage();
|
|
5389
5597
|
if (storage) storage.removeItem(STORAGE_KEY);
|
|
5390
5598
|
cachedUser = null;
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
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
|
+
);
|
|
5399
5623
|
}
|
|
5400
5624
|
notify();
|
|
5401
5625
|
},
|
|
@@ -5421,7 +5645,8 @@ function createOidcAuthNamespace(config) {
|
|
|
5421
5645
|
const issuer = idpOrigin;
|
|
5422
5646
|
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
|
|
5423
5647
|
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
5424
|
-
const
|
|
5648
|
+
const parsedKey = tryParseApiKey(config.apiKey);
|
|
5649
|
+
const audience = parsedKey?.keyId;
|
|
5425
5650
|
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
5426
5651
|
try {
|
|
5427
5652
|
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
@@ -5461,7 +5686,8 @@ function createNeetruClient(config = {}) {
|
|
|
5461
5686
|
"fetch is not available in this runtime. Pass `fetch` explicitly to createNeetruClient."
|
|
5462
5687
|
);
|
|
5463
5688
|
}
|
|
5464
|
-
const
|
|
5689
|
+
const rawBaseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
5690
|
+
const baseUrl = validateBaseUrl(rawBaseUrl, config.allowedHosts ?? []);
|
|
5465
5691
|
const apiKey = config.apiKey ?? readEnvApiKey();
|
|
5466
5692
|
const env = resolveEnv(config.env);
|
|
5467
5693
|
const resolved = Object.freeze({
|