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