@everfur/sdk 0.1.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/animations/package.json +8 -0
  5. package/chat/package.json +8 -0
  6. package/client/package.json +8 -0
  7. package/core/package.json +8 -0
  8. package/dist/CameraPort-Cv31Pz7g.d.cts +29 -0
  9. package/dist/CameraPort-Cv31Pz7g.d.ts +29 -0
  10. package/dist/ChatController-CKdBvPj2.d.ts +146 -0
  11. package/dist/ChatController-CpUMvvZf.d.cts +146 -0
  12. package/dist/EverfurResult-D92-uL82.d.cts +240 -0
  13. package/dist/EverfurResult-D92-uL82.d.ts +240 -0
  14. package/dist/FilePort-BabWrv7I.d.cts +22 -0
  15. package/dist/FilePort-BabWrv7I.d.ts +22 -0
  16. package/dist/PhotoController-BItt5M7u.d.cts +43 -0
  17. package/dist/PhotoController-D8zMTdcW.d.ts +43 -0
  18. package/dist/TelemetryPort-BDNr00hu.d.cts +12 -0
  19. package/dist/TelemetryPort-BDNr00hu.d.ts +12 -0
  20. package/dist/animations/index.cjs +1997 -0
  21. package/dist/animations/index.d.cts +517 -0
  22. package/dist/animations/index.d.ts +517 -0
  23. package/dist/animations/index.js +1972 -0
  24. package/dist/cacheEpoch-DknKn3S0.d.cts +36 -0
  25. package/dist/cacheEpoch-DknKn3S0.d.ts +36 -0
  26. package/dist/chat/index.cjs +1170 -0
  27. package/dist/chat/index.d.cts +59 -0
  28. package/dist/chat/index.d.ts +59 -0
  29. package/dist/chat/index.js +1167 -0
  30. package/dist/client/index.cjs +2317 -0
  31. package/dist/client/index.d.cts +65 -0
  32. package/dist/client/index.d.ts +65 -0
  33. package/dist/client/index.js +2217 -0
  34. package/dist/config-BSjBdxrZ.d.cts +501 -0
  35. package/dist/config-CiJ0PVBB.d.ts +501 -0
  36. package/dist/core/index.cjs +3175 -0
  37. package/dist/core/index.d.cts +70 -0
  38. package/dist/core/index.d.ts +70 -0
  39. package/dist/core/index.js +3163 -0
  40. package/dist/identity-Brl-lDd6.d.cts +91 -0
  41. package/dist/identity-DK9zORrG.d.ts +91 -0
  42. package/dist/ids-CJ1S6adf.d.cts +46 -0
  43. package/dist/ids-CJ1S6adf.d.ts +46 -0
  44. package/dist/index.cjs +4488 -0
  45. package/dist/index.d.cts +156 -0
  46. package/dist/index.d.ts +156 -0
  47. package/dist/index.js +4464 -0
  48. package/dist/petsRepository-BEGb97M9.d.cts +326 -0
  49. package/dist/petsRepository-Bu18r2kK.d.ts +326 -0
  50. package/dist/photo/index.cjs +2189 -0
  51. package/dist/photo/index.d.cts +43 -0
  52. package/dist/photo/index.d.ts +43 -0
  53. package/dist/photo/index.js +2186 -0
  54. package/dist/projection-CeIUsbSk.d.cts +8 -0
  55. package/dist/projection-CeIUsbSk.d.ts +8 -0
  56. package/dist/records/index.cjs +2840 -0
  57. package/dist/records/index.d.cts +224 -0
  58. package/dist/records/index.d.ts +224 -0
  59. package/dist/records/index.js +2834 -0
  60. package/dist/requestFunnel-DuUH-kAe.d.cts +28 -0
  61. package/dist/requestFunnel-dio5OmR9.d.ts +28 -0
  62. package/dist/resolve-Dq_4_agU.d.cts +86 -0
  63. package/dist/resolve-Dq_4_agU.d.ts +86 -0
  64. package/dist/runtime-BgQnA594.d.cts +349 -0
  65. package/dist/runtime-CBA-LvdM.d.ts +349 -0
  66. package/dist/server/index.cjs +533 -0
  67. package/dist/server/index.d.cts +48 -0
  68. package/dist/server/index.d.ts +48 -0
  69. package/dist/server/index.js +530 -0
  70. package/dist/testing/index.cjs +825 -0
  71. package/dist/testing/index.d.cts +113 -0
  72. package/dist/testing/index.d.ts +113 -0
  73. package/dist/testing/index.js +822 -0
  74. package/dist/testing/rn/index.cjs +449 -0
  75. package/dist/testing/rn/index.d.cts +49 -0
  76. package/dist/testing/rn/index.d.ts +49 -0
  77. package/dist/testing/rn/index.js +444 -0
  78. package/dist/video/index.cjs +1941 -0
  79. package/dist/video/index.d.cts +66 -0
  80. package/dist/video/index.d.ts +66 -0
  81. package/dist/video/index.js +1938 -0
  82. package/package.json +311 -0
  83. package/photo/package.json +8 -0
  84. package/records/package.json +8 -0
  85. package/server/device-blocked.cjs +15 -0
  86. package/server/package.json +9 -0
  87. package/testing/package.json +8 -0
  88. package/testing/rn/package.json +8 -0
  89. package/video/package.json +8 -0
@@ -0,0 +1,530 @@
1
+ // src/client/errors/EverfurResult.ts
2
+ function ok(value, warning) {
3
+ return Object.freeze(
4
+ { ok: true, value }
5
+ );
6
+ }
7
+ function err(error) {
8
+ return Object.freeze({ ok: false, error });
9
+ }
10
+ var EverfurConfigError = class _EverfurConfigError extends Error {
11
+ constructor(kind, message) {
12
+ super(message);
13
+ this.name = "EverfurConfigError";
14
+ this.kind = kind;
15
+ Object.setPrototypeOf(this, _EverfurConfigError.prototype);
16
+ }
17
+ };
18
+
19
+ // src/client/config.ts
20
+ var SDK_VERSION = "0.1.0";
21
+ var SDK_PLATFORM = "react-native";
22
+ var DEFAULT_CONTRACT_VERSION = "2026-08-01";
23
+ var ENV_API_BASE_URL = "EVERFUR_API_BASE_URL";
24
+ function configFailure(reason, message) {
25
+ return Object.freeze({ ok: false, failure: Object.freeze({ reason, message }) });
26
+ }
27
+ function tryResolveApiBaseUrl(apiBaseUrl) {
28
+ const explicit = apiBaseUrl?.trim();
29
+ const candidate = explicit && explicit.length > 0 ? explicit : readEnv(ENV_API_BASE_URL);
30
+ if (!candidate) {
31
+ return configFailure(
32
+ "missing-api-base-url",
33
+ `apiBaseUrl is required: pass config.apiBaseUrl or set ${ENV_API_BASE_URL}. The SDK never ships a hardcoded host.`
34
+ );
35
+ }
36
+ return normalizeBaseUrl(candidate);
37
+ }
38
+ function resolveApiBaseUrl(apiBaseUrl) {
39
+ const outcome = tryResolveApiBaseUrl(apiBaseUrl);
40
+ if (!outcome.ok) {
41
+ throw new EverfurConfigError("invalid-argument", outcome.failure.message);
42
+ }
43
+ return outcome.value;
44
+ }
45
+ function buildBaseHeaders(cfg) {
46
+ return {
47
+ "X-Everfur-SDK-Version": cfg.sdkVersion,
48
+ "X-Everfur-SDK-Platform": cfg.platform,
49
+ "X-Everfur-Contract": cfg.contractVersion
50
+ };
51
+ }
52
+ var ABSOLUTE_URL = /^([A-Za-z][A-Za-z0-9+.-]*):\/\/([^/?#]*)/;
53
+ function hostOf(authority) {
54
+ const afterUserinfo = authority.slice(authority.lastIndexOf("@") + 1);
55
+ return afterUserinfo.replace(/:\d+$/, "").toLowerCase();
56
+ }
57
+ function isPrintableHost(host) {
58
+ if (host.length === 0) return false;
59
+ if (host.includes("@")) return false;
60
+ if (host.startsWith("[") && host.endsWith("]")) return true;
61
+ return !host.includes(":");
62
+ }
63
+ function canEchoOrigin(rawTrimmed, host) {
64
+ const schemeEnd = rawTrimmed.indexOf("://");
65
+ if (schemeEnd < 0) return false;
66
+ if (rawTrimmed.slice(schemeEnd + 3).includes("@")) return false;
67
+ return isPrintableHost(host);
68
+ }
69
+ function safeOrigin(scheme, host) {
70
+ return `${scheme}://${host}`;
71
+ }
72
+ function normalizeBaseUrl(raw) {
73
+ const trimmed = raw.trim();
74
+ const match = ABSOLUTE_URL.exec(trimmed);
75
+ if (match === null || match[2] === void 0 || match[2].length === 0) {
76
+ return configFailure("malformed-api-base-url", "apiBaseUrl is not a valid absolute URL");
77
+ }
78
+ const scheme = (match[1] ?? "").toLowerCase();
79
+ const host = hostOf(match[2]);
80
+ if (host.length === 0) {
81
+ return configFailure("malformed-api-base-url", "apiBaseUrl is not a valid absolute URL");
82
+ }
83
+ const shown = canEchoOrigin(trimmed, host) ? `: ${safeOrigin(scheme, host)}` : "";
84
+ if (scheme === "https") ; else if (scheme === "http" && isLoopbackHost(host)) ; else if (scheme === "http") {
85
+ return configFailure(
86
+ "insecure-api-base-url",
87
+ `apiBaseUrl must use https (cleartext http is only allowed for localhost)${shown}`
88
+ );
89
+ } else {
90
+ return configFailure("unsupported-api-base-url-scheme", `apiBaseUrl must be http(s)${shown}`);
91
+ }
92
+ const value = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
93
+ return Object.freeze({ ok: true, value });
94
+ }
95
+ function isLoopbackHost(hostname) {
96
+ const host = hostname.toLowerCase();
97
+ return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
98
+ }
99
+ function readEnv(name) {
100
+ const g = globalThis;
101
+ const value = g.process?.env?.[name];
102
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
103
+ }
104
+ var CONTRACT_RETRYABILITY = Object.freeze([
105
+ Object.freeze({ wireCode: "AccessDeniedException", envelope: "http_envelope", httpStatus: 403, retryable: false }),
106
+ Object.freeze({ wireCode: "AuthorizationException", envelope: "http_envelope", httpStatus: 401, retryable: false }),
107
+ Object.freeze({ wireCode: "ConflictException", envelope: "http_envelope", httpStatus: 409, retryable: true }),
108
+ Object.freeze({ wireCode: "DAILY_QUOTA_EXCEEDED", envelope: "http_envelope", httpStatus: 429, retryable: true }),
109
+ Object.freeze({ wireCode: "IDEMPOTENCY_KEY_IN_FLIGHT", envelope: "http_envelope", httpStatus: 409, retryable: true }),
110
+ Object.freeze({ wireCode: "IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST", envelope: "http_envelope", httpStatus: 409, retryable: false }),
111
+ Object.freeze({ wireCode: "InternalFailureException", envelope: "http_envelope", httpStatus: 500, retryable: true }),
112
+ Object.freeze({ wireCode: "NotImplementedException", envelope: "http_envelope", httpStatus: 501, retryable: false }),
113
+ Object.freeze({ wireCode: "PaymentRequiredException", envelope: "http_envelope", httpStatus: 402, retryable: false }),
114
+ Object.freeze({ wireCode: "ResourceNotFoundException", envelope: "http_envelope", httpStatus: 404, retryable: false }),
115
+ Object.freeze({ wireCode: "ServiceUnavailableException", envelope: "http_envelope", httpStatus: 503, retryable: true }),
116
+ Object.freeze({ wireCode: "TIER_UPGRADE_REQUIRED", envelope: "http_envelope", httpStatus: 402, retryable: false }),
117
+ Object.freeze({ wireCode: "ThrottlingException", envelope: "http_envelope", httpStatus: 429, retryable: true }),
118
+ Object.freeze({ wireCode: "ValidationException", envelope: "http_envelope", httpStatus: 400, retryable: false }),
119
+ Object.freeze({ wireCode: "accessDenied", envelope: "http_envelope", httpStatus: 403, retryable: false }),
120
+ Object.freeze({ wireCode: "authExpired", envelope: "http_envelope", httpStatus: 401, retryable: true }),
121
+ Object.freeze({ wireCode: "internal_error", envelope: "sse_frame", httpStatus: 200, retryable: false }),
122
+ Object.freeze({ wireCode: "invalidRequest", envelope: "http_envelope", httpStatus: 400, retryable: false }),
123
+ Object.freeze({ wireCode: "not_found", envelope: "sse_frame", httpStatus: 200, retryable: false }),
124
+ Object.freeze({ wireCode: "session_required", envelope: "http_envelope", httpStatus: 403, retryable: false }),
125
+ Object.freeze({ wireCode: "timeout", envelope: "sse_frame", httpStatus: 200, retryable: true }),
126
+ Object.freeze({ wireCode: "unavailable", envelope: "http_envelope", httpStatus: 503, retryable: true })
127
+ ]);
128
+ Object.freeze(
129
+ CONTRACT_RETRYABILITY.map((row) => row.wireCode)
130
+ );
131
+
132
+ // src/client/errors/contract.ts
133
+ var NORMALIZED_FOLD = Object.freeze({
134
+ // the default status-mapped envelope
135
+ ValidationException: "validationFailed",
136
+ AuthorizationException: "authRejected",
137
+ PaymentRequiredException: "paymentRequired",
138
+ AccessDeniedException: "accessDenied",
139
+ ResourceNotFoundException: "resourceNotFound",
140
+ ConflictException: "conflict",
141
+ ThrottlingException: "rateLimited",
142
+ InternalFailureException: "internalError",
143
+ NotImplementedException: "notImplemented",
144
+ ServiceUnavailableException: "serviceUnavailable",
145
+ // dict-detail codes on the widget surface
146
+ authExpired: "authExpired",
147
+ unavailable: "serviceUnavailable",
148
+ session_required: "sessionRequired",
149
+ accessDenied: "accessDenied",
150
+ invalidRequest: "sdkUpdateRequired",
151
+ // typed codes
152
+ IDEMPOTENCY_KEY_IN_FLIGHT: "idempotencyInFlight",
153
+ IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST: "idempotencyConflict",
154
+ DAILY_QUOTA_EXCEEDED: "quotaExceeded",
155
+ TIER_UPGRADE_REQUIRED: "paymentRequired",
156
+ // SSE error frames
157
+ not_found: "conversationNotFound",
158
+ timeout: "streamTimeout",
159
+ internal_error: "internalError"
160
+ });
161
+ var CONTRACT_ERROR_ROWS = Object.freeze(
162
+ CONTRACT_RETRYABILITY.map(
163
+ (generated) => Object.freeze({
164
+ wireCode: generated.wireCode,
165
+ envelope: generated.envelope,
166
+ retryable: generated.retryable,
167
+ normalized: NORMALIZED_FOLD[generated.wireCode]
168
+ })
169
+ )
170
+ );
171
+ var BY_UPPER = new Map(
172
+ CONTRACT_ERROR_ROWS.map((r) => [r.wireCode.toUpperCase(), r])
173
+ );
174
+ function contractErrorFor(wireCode) {
175
+ if (typeof wireCode !== "string") return void 0;
176
+ const key = wireCode.trim().toUpperCase();
177
+ return key.length > 0 ? BY_UPPER.get(key) : void 0;
178
+ }
179
+ var CATEGORY_OF = Object.freeze({
180
+ // the caller's sign-in state is wrong and a credential change is the remedy
181
+ authExpired: "authentication",
182
+ authRejected: "authentication",
183
+ sessionRequired: "authentication",
184
+ // signed in, correctly identified, and still not allowed
185
+ accessDenied: "authorization",
186
+ // a plan gate, not a permission failure: the remedy is commercial
187
+ paymentRequired: "entitlement",
188
+ // slow down / come back later
189
+ quotaExceeded: "rateLimit",
190
+ rateLimited: "rateLimit",
191
+ // the request itself was wrong; retrying it unchanged can never succeed
192
+ validationFailed: "invalidRequest",
193
+ sdkUpdateRequired: "invalidRequest",
194
+ // the target is gone, hidden, or was never visible to this caller
195
+ conversationNotFound: "notFound",
196
+ petNotFound: "notFound",
197
+ resourceNotFound: "notFound",
198
+ // something else already holds the thing
199
+ conflict: "conflict",
200
+ idempotencyConflict: "conflict",
201
+ idempotencyInFlight: "conflict",
202
+ // the pipe, not the request: timeouts, drops, a briefly unreachable dependency
203
+ streamTimeout: "connection",
204
+ streamIncomplete: "connection",
205
+ upstreamLlmTimeout: "connection",
206
+ serviceUnavailable: "connection",
207
+ // the server admits fault
208
+ internalError: "server",
209
+ notImplemented: "server",
210
+ mediaUploadFailed: "media",
211
+ moderationBlocked: "policy",
212
+ unknown: "unknown"
213
+ });
214
+ function categoryFor(code) {
215
+ return CATEGORY_OF[code];
216
+ }
217
+ function fromWire(wire) {
218
+ const contractRow = contractErrorFor(wire);
219
+ if (contractRow) return contractRow.normalized;
220
+ const upper = (wire ?? "").trim().toUpperCase();
221
+ const canonical = upper.replace(/[^A-Z0-9]/g, "");
222
+ if (canonical === "AUTHREJECTED") return "authRejected";
223
+ if (upper.startsWith("AUTH")) return "authExpired";
224
+ switch (canonical) {
225
+ case "DAILYQUOTAEXCEEDED":
226
+ case "QUOTAEXCEEDED":
227
+ return "quotaExceeded";
228
+ case "RATELIMITED":
229
+ case "TOOMANYREQUESTS":
230
+ return "rateLimited";
231
+ case "IDEMPOTENCYKEYCONFLICT":
232
+ case "IDEMPOTENCYCONFLICT":
233
+ return "idempotencyConflict";
234
+ case "ACCESSDENIED":
235
+ case "FORBIDDEN":
236
+ return "accessDenied";
237
+ case "NOTFOUND":
238
+ case "CONVERSATIONNOTFOUND":
239
+ return "conversationNotFound";
240
+ case "PETNOTFOUND":
241
+ return "petNotFound";
242
+ case "TIMEOUT":
243
+ case "STREAMTIMEOUT":
244
+ return "streamTimeout";
245
+ // Analysis-domain deterministic 400s (NON-retryable). Additive per SPEC-00 §3.1 / SPEC-11b §2.6:
246
+ // fold to a NON-retried target so the SDK never retries a permanently-failing bad-input request.
247
+ // (Comment above the group so the empty fall-through cases stay lint-clean under no-fallthrough.)
248
+ case "MEDIAUPLOADFAILED":
249
+ case "UNRECOGNIZEDVIDEOFORMAT":
250
+ case "FILETOOSMALL":
251
+ return "mediaUploadFailed";
252
+ case "MODERATIONBLOCKED":
253
+ return "moderationBlocked";
254
+ case "UPSTREAMLLMTIMEOUT":
255
+ return "upstreamLlmTimeout";
256
+ case "INTERNALERROR":
257
+ case "INTERNALSERVERERROR":
258
+ return "internalError";
259
+ case "STREAMINCOMPLETE":
260
+ case "CHATSTREAMINCOMPLETE":
261
+ return "streamIncomplete";
262
+ // Deterministic 400s that can only come from an SDK bug -> unknown (NON-retryable). NEVER internalError
263
+ // (which IS auto-retried): mapping a bad-input 400 to a retryable code retries a request that never succeeds.
264
+ case "INVALIDANALYSISTYPE":
265
+ case "INVALIDUPLOADPURPOSE":
266
+ return "unknown";
267
+ default:
268
+ return "unknown";
269
+ }
270
+ }
271
+ function fromHttpStatus(status) {
272
+ if (status === 401) return "authExpired";
273
+ if (status === 403) return "accessDenied";
274
+ if (status === 404) return "conversationNotFound";
275
+ if (status === 409) return "idempotencyConflict";
276
+ if (status === 421) return "unknown";
277
+ if (status === 429) return "rateLimited";
278
+ if (status === 400 || status === 422) return "unknown";
279
+ if (status >= 500) return "internalError";
280
+ return "unknown";
281
+ }
282
+ var AUTO_RETRYABLE_CODES = /* @__PURE__ */ new Set([
283
+ "streamTimeout",
284
+ "rateLimited",
285
+ "internalError",
286
+ "upstreamLlmTimeout",
287
+ "streamIncomplete"
288
+ ]);
289
+ function isAutoRetryable(code) {
290
+ return AUTO_RETRYABLE_CODES.has(code);
291
+ }
292
+ var RETRYABLE_CONTRACT_CODES = /* @__PURE__ */ new Set([
293
+ "serviceUnavailable",
294
+ "conflict",
295
+ "idempotencyInFlight"
296
+ ]);
297
+ function isRetryableCode(code) {
298
+ return isAutoRetryable(code) || RETRYABLE_CONTRACT_CODES.has(code);
299
+ }
300
+
301
+ // src/client/errors/EverfurError.ts
302
+ var USER_FACING_COPY = Object.freeze({
303
+ authExpired: "Please sign in again to continue.",
304
+ authRejected: "Please sign in again to continue.",
305
+ sessionRequired: "This app needs to be updated to keep using Everfur.",
306
+ accessDenied: "This feature isn't available on your account right now.",
307
+ paymentRequired: "This is part of a paid plan.",
308
+ quotaExceeded: "You have used today's messages. This resets tomorrow.",
309
+ rateLimited: "You are sending messages quickly. Try again in a few seconds.",
310
+ validationFailed: "Something in that request was not right. Check the details and try again.",
311
+ sdkUpdateRequired: "This app needs to be updated to keep using Everfur.",
312
+ conflict: "That is already running. Wait for it to finish, then try again.",
313
+ idempotencyConflict: "That message was already delivered.",
314
+ idempotencyInFlight: "That is still processing. Give it a moment.",
315
+ conversationNotFound: "This conversation is no longer available.",
316
+ petNotFound: "We could not find that pet. Please pick a pet and try again.",
317
+ resourceNotFound: "We could not find that.",
318
+ streamTimeout: "The response timed out. Tap retry to try again.",
319
+ mediaUploadFailed: "We could not upload that photo. Please attach it again.",
320
+ moderationBlocked: "This request cannot be answered here.",
321
+ upstreamLlmTimeout: "The response timed out. Tap retry to try again.",
322
+ internalError: "We hit a snag on our end. Please try again.",
323
+ notImplemented: "That is not available yet.",
324
+ serviceUnavailable: "That service is briefly unavailable. Try again shortly.",
325
+ streamIncomplete: "The connection dropped mid-answer. Tap retry to continue.",
326
+ unknown: "Something went wrong. Please try again."
327
+ });
328
+ function userFacingError(code) {
329
+ return USER_FACING_COPY[code];
330
+ }
331
+ function defineHidden(target, key, value) {
332
+ Object.defineProperty(target, key, {
333
+ value,
334
+ enumerable: false,
335
+ writable: false,
336
+ configurable: false
337
+ });
338
+ }
339
+ function makeEverfurError(code, opts) {
340
+ contractErrorFor(opts?.wireCode);
341
+ const error = {
342
+ code,
343
+ category: categoryFor(code),
344
+ retryable: opts?.retryable ?? isRetryableCode(code),
345
+ displayMessage: userFacingError(code)
346
+ };
347
+ if (opts?.httpStatus !== void 0) error.httpStatus = opts.httpStatus;
348
+ if (opts?.requestId) error.requestId = opts.requestId;
349
+ if (opts?.wireMessage) defineHidden(error, "wireMessage", opts.wireMessage);
350
+ if (opts?.raw !== void 0 && opts.raw !== null) defineHidden(error, "raw", opts.raw);
351
+ return Object.freeze(error);
352
+ }
353
+
354
+ // src/client/transport.ts
355
+ var REQUEST_ID_HEADER = "x-request-id";
356
+ function errorFromResponse(status, body, requestId) {
357
+ const wireCode = extractWireCode(body);
358
+ const code = wireCode ? fromWire(wireCode) : fromHttpStatus(status);
359
+ const wireMessage = extractWireMessage(body);
360
+ return makeEverfurError(code, {
361
+ httpStatus: status,
362
+ ...extractRetryable(body) ? { retryable: true } : {},
363
+ ...requestId ? { requestId } : {},
364
+ ...wireCode ? { wireCode } : {},
365
+ ...wireMessage ? { wireMessage } : {},
366
+ ...body !== null && body !== void 0 ? { raw: body } : {}
367
+ });
368
+ }
369
+ function networkError(cause) {
370
+ return makeEverfurError("internalError", { retryable: true, wireMessage: describeCause(cause) });
371
+ }
372
+ function requestIdOf(res) {
373
+ const value = res.headers.get(REQUEST_ID_HEADER);
374
+ return typeof value === "string" && value.length > 0 ? value : null;
375
+ }
376
+ function getGlobalFetch() {
377
+ const candidate = globalThis.fetch;
378
+ if (typeof candidate !== "function") {
379
+ throw new EverfurConfigError(
380
+ "missing-peer-dependency",
381
+ "global fetch is unavailable; provide config.transport or an injected fetch implementation"
382
+ );
383
+ }
384
+ return candidate;
385
+ }
386
+ function extractWireCode(body) {
387
+ const b = body;
388
+ const code = b?.error?.code ?? b?.code;
389
+ return typeof code === "string" && code.length > 0 ? code : null;
390
+ }
391
+ function extractWireMessage(body) {
392
+ const b = body;
393
+ const message = b?.error?.message ?? b?.message;
394
+ return typeof message === "string" && message.length > 0 ? message : null;
395
+ }
396
+ function extractRetryable(body) {
397
+ const b = body;
398
+ return b?.error?.retryable === true || b?.retryable === true;
399
+ }
400
+ function describeCause(cause) {
401
+ if (cause instanceof Error) return cause.message;
402
+ return typeof cause === "string" ? cause : "network request failed";
403
+ }
404
+
405
+ // src/server/index.ts
406
+ var USER_REF_MAX = 128;
407
+ var TTL_MIN_SECONDS = 60;
408
+ var TTL_MAX_SECONDS = 3600;
409
+ var MINT_TIMEOUT_MS = 3e4;
410
+ async function mintPartnerSession(params) {
411
+ const partnerUserRef = assertValidUserRef(params.userRef);
412
+ const ttlSeconds = clampTtl(params.ttlSeconds);
413
+ const fetchImpl = params.fetchImpl ?? getGlobalFetch();
414
+ const url = `${resolveApiBaseUrl(params.apiBaseUrl)}/widget/v1/sessions`;
415
+ const headers = {
416
+ ...buildBaseHeaders({
417
+ sdkVersion: SDK_VERSION,
418
+ platform: SDK_PLATFORM,
419
+ contractVersion: params.contractVersion ?? DEFAULT_CONTRACT_VERSION
420
+ }),
421
+ accept: "application/json",
422
+ "content-type": "application/json",
423
+ // The ONE legitimate two-credential request (SPEC-00 4.2 carve-out).
424
+ "x-everfur-partner-secret-key": params.partnerSecretKey,
425
+ "x-everfur-partner-key": params.publishableKey
426
+ };
427
+ const body = { user_ref: partnerUserRef };
428
+ if (ttlSeconds !== void 0) body.ttl_seconds = ttlSeconds;
429
+ const deadline = mintTimeoutSignal(MINT_TIMEOUT_MS);
430
+ const signal = mergeMintSignals(deadline.signal, params.signal);
431
+ const init = {
432
+ method: "POST",
433
+ headers,
434
+ body: JSON.stringify(body),
435
+ signal
436
+ };
437
+ let res;
438
+ try {
439
+ res = await fetchImpl(url, init);
440
+ } catch (cause) {
441
+ return err(networkError(cause));
442
+ } finally {
443
+ deadline.cancel();
444
+ }
445
+ const json = await readJsonSafe(res);
446
+ const requestId = requestIdOf(res);
447
+ if (!res.ok) return err(errorFromResponse(res.status, json, requestId));
448
+ const mint = parseMint(json);
449
+ if (!mint) {
450
+ return err(errorFromResponse(502, { error: { code: "INTERNAL_ERROR" } }, requestId));
451
+ }
452
+ return ok(mint);
453
+ }
454
+ function assertValidUserRef(raw) {
455
+ const length = typeof raw === "string" ? [...raw].length : 0;
456
+ if (typeof raw !== "string" || length < 1 || length > USER_REF_MAX) {
457
+ throw new EverfurConfigError("invalid-argument", `user_ref must be 1..${USER_REF_MAX} characters`);
458
+ }
459
+ if (raw.trim().length === 0) {
460
+ throw new EverfurConfigError(
461
+ "invalid-argument",
462
+ "user_ref must not be blank: the server strips surrounding whitespace before validating, so a whitespace-only value arrives empty and is rejected as required."
463
+ );
464
+ }
465
+ if (raw !== raw.trim()) {
466
+ throw new EverfurConfigError(
467
+ "invalid-argument",
468
+ "user_ref must not have leading or trailing whitespace: the server strips it, so padded and unpadded forms of the same id resolve to ONE user. Pass the exact id you want to key on."
469
+ );
470
+ }
471
+ if (raw.includes("@")) {
472
+ throw new EverfurConfigError("invalid-argument", "user_ref must not contain '@'");
473
+ }
474
+ if (!isPrintable(raw)) {
475
+ throw new EverfurConfigError("invalid-argument", "user_ref must be printable (no control characters)");
476
+ }
477
+ return raw;
478
+ }
479
+ function clampTtl(ttl) {
480
+ if (ttl === void 0 || ttl === null) return void 0;
481
+ if (!Number.isInteger(ttl)) {
482
+ throw new EverfurConfigError("invalid-argument", "ttl_seconds must be an integer");
483
+ }
484
+ return Math.min(TTL_MAX_SECONDS, Math.max(TTL_MIN_SECONDS, ttl));
485
+ }
486
+ function isPrintable(value) {
487
+ return !NON_PRINTABLE.test(value);
488
+ }
489
+ var NON_PRINTABLE = /[\p{Cc}\p{Cf}\p{Cs}\p{Co}\p{Cn}\p{Zl}\p{Zp}]|(?!\u0020)[\p{Zs}]/u;
490
+ function parseMint(json) {
491
+ if (typeof json !== "object" || json === null) return null;
492
+ const obj = json;
493
+ if (typeof obj.session_token !== "string" || obj.session_token.length === 0) return null;
494
+ if (typeof obj.expires_at !== "number") return null;
495
+ const capabilities = Array.isArray(obj.capabilities) ? obj.capabilities.filter((c) => typeof c === "string") : [];
496
+ return Object.freeze({
497
+ sessionToken: obj.session_token,
498
+ expiresAt: obj.expires_at,
499
+ capabilities: Object.freeze(capabilities)
500
+ });
501
+ }
502
+ async function readJsonSafe(res) {
503
+ try {
504
+ return await res.json();
505
+ } catch {
506
+ return null;
507
+ }
508
+ }
509
+ function mintTimeoutSignal(ms) {
510
+ const ctrl = new AbortController();
511
+ const handle = setTimeout(() => ctrl.abort(), ms);
512
+ return {
513
+ signal: ctrl.signal,
514
+ cancel: () => clearTimeout(handle)
515
+ };
516
+ }
517
+ function mergeMintSignals(deadline, caller) {
518
+ if (!caller) return deadline;
519
+ const ctrl = new AbortController();
520
+ if (deadline.aborted || caller.aborted) {
521
+ ctrl.abort();
522
+ return ctrl.signal;
523
+ }
524
+ const onAbort = () => ctrl.abort();
525
+ deadline.addEventListener("abort", onAbort, { once: true });
526
+ caller.addEventListener("abort", onAbort, { once: true });
527
+ return ctrl.signal;
528
+ }
529
+
530
+ export { assertValidUserRef, mintPartnerSession };