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