@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.
- package/CHANGELOG.md +74 -0
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/animations/package.json +8 -0
- package/chat/package.json +8 -0
- package/client/package.json +8 -0
- package/core/package.json +8 -0
- package/dist/CameraPort-Cv31Pz7g.d.cts +29 -0
- package/dist/CameraPort-Cv31Pz7g.d.ts +29 -0
- package/dist/ChatController-CKdBvPj2.d.ts +146 -0
- package/dist/ChatController-CpUMvvZf.d.cts +146 -0
- package/dist/EverfurResult-D92-uL82.d.cts +240 -0
- package/dist/EverfurResult-D92-uL82.d.ts +240 -0
- package/dist/FilePort-BabWrv7I.d.cts +22 -0
- package/dist/FilePort-BabWrv7I.d.ts +22 -0
- package/dist/PhotoController-BItt5M7u.d.cts +43 -0
- package/dist/PhotoController-D8zMTdcW.d.ts +43 -0
- package/dist/TelemetryPort-BDNr00hu.d.cts +12 -0
- package/dist/TelemetryPort-BDNr00hu.d.ts +12 -0
- package/dist/animations/index.cjs +1997 -0
- package/dist/animations/index.d.cts +517 -0
- package/dist/animations/index.d.ts +517 -0
- package/dist/animations/index.js +1972 -0
- package/dist/cacheEpoch-DknKn3S0.d.cts +36 -0
- package/dist/cacheEpoch-DknKn3S0.d.ts +36 -0
- package/dist/chat/index.cjs +1170 -0
- package/dist/chat/index.d.cts +59 -0
- package/dist/chat/index.d.ts +59 -0
- package/dist/chat/index.js +1167 -0
- package/dist/client/index.cjs +2317 -0
- package/dist/client/index.d.cts +65 -0
- package/dist/client/index.d.ts +65 -0
- package/dist/client/index.js +2217 -0
- package/dist/config-BSjBdxrZ.d.cts +501 -0
- package/dist/config-CiJ0PVBB.d.ts +501 -0
- package/dist/core/index.cjs +3175 -0
- package/dist/core/index.d.cts +70 -0
- package/dist/core/index.d.ts +70 -0
- package/dist/core/index.js +3163 -0
- package/dist/identity-Brl-lDd6.d.cts +91 -0
- package/dist/identity-DK9zORrG.d.ts +91 -0
- package/dist/ids-CJ1S6adf.d.cts +46 -0
- package/dist/ids-CJ1S6adf.d.ts +46 -0
- package/dist/index.cjs +4488 -0
- package/dist/index.d.cts +156 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +4464 -0
- package/dist/petsRepository-BEGb97M9.d.cts +326 -0
- package/dist/petsRepository-Bu18r2kK.d.ts +326 -0
- package/dist/photo/index.cjs +2189 -0
- package/dist/photo/index.d.cts +43 -0
- package/dist/photo/index.d.ts +43 -0
- package/dist/photo/index.js +2186 -0
- package/dist/projection-CeIUsbSk.d.cts +8 -0
- package/dist/projection-CeIUsbSk.d.ts +8 -0
- package/dist/records/index.cjs +2840 -0
- package/dist/records/index.d.cts +224 -0
- package/dist/records/index.d.ts +224 -0
- package/dist/records/index.js +2834 -0
- package/dist/requestFunnel-DuUH-kAe.d.cts +28 -0
- package/dist/requestFunnel-dio5OmR9.d.ts +28 -0
- package/dist/resolve-Dq_4_agU.d.cts +86 -0
- package/dist/resolve-Dq_4_agU.d.ts +86 -0
- package/dist/runtime-BgQnA594.d.cts +349 -0
- package/dist/runtime-CBA-LvdM.d.ts +349 -0
- package/dist/server/index.cjs +533 -0
- package/dist/server/index.d.cts +48 -0
- package/dist/server/index.d.ts +48 -0
- package/dist/server/index.js +530 -0
- package/dist/testing/index.cjs +825 -0
- package/dist/testing/index.d.cts +113 -0
- package/dist/testing/index.d.ts +113 -0
- package/dist/testing/index.js +822 -0
- package/dist/testing/rn/index.cjs +449 -0
- package/dist/testing/rn/index.d.cts +49 -0
- package/dist/testing/rn/index.d.ts +49 -0
- package/dist/testing/rn/index.js +444 -0
- package/dist/video/index.cjs +1941 -0
- package/dist/video/index.d.cts +66 -0
- package/dist/video/index.d.ts +66 -0
- package/dist/video/index.js +1938 -0
- package/package.json +311 -0
- package/photo/package.json +8 -0
- package/records/package.json +8 -0
- package/server/device-blocked.cjs +15 -0
- package/server/package.json +9 -0
- package/testing/package.json +8 -0
- package/testing/rn/package.json +8 -0
- 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 };
|