@hosty-sdk/app 0.1.2 → 0.2.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/dist/server.d.ts +10 -0
- package/dist/server.js +47 -7
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -22,11 +22,21 @@ export interface HostIdentity {
|
|
|
22
22
|
appRole: string | null;
|
|
23
23
|
expiresAt: string | null;
|
|
24
24
|
}
|
|
25
|
+
/** Structured detail for a failed resolution: the Core HTTP status (null when Core was
|
|
26
|
+
* never reached) plus a machine-readable code and human-readable message. Codes pass
|
|
27
|
+
* through from Core's error body where one exists; classification never branches on them
|
|
28
|
+
* (decision 10) — they exist for diagnostics and API responses. */
|
|
29
|
+
export interface ResolutionError {
|
|
30
|
+
status: number | null;
|
|
31
|
+
code: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
25
34
|
export type AppSessionResolution = {
|
|
26
35
|
status: "active";
|
|
27
36
|
identity: HostIdentity;
|
|
28
37
|
} | {
|
|
29
38
|
status: AppSessionFailureStatus;
|
|
39
|
+
error?: ResolutionError;
|
|
30
40
|
};
|
|
31
41
|
export declare function getAppId(config: HostyAppConfig): string;
|
|
32
42
|
export declare function getCoreOrigin(): string | null;
|
package/dist/server.js
CHANGED
|
@@ -91,7 +91,12 @@ export async function resolveAppSession(token, config) {
|
|
|
91
91
|
const serviceToken = getServiceToken();
|
|
92
92
|
const coreOrigin = getCoreOrigin();
|
|
93
93
|
if (!serviceToken || !coreOrigin) {
|
|
94
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
status: "misconfigured",
|
|
96
|
+
error: serviceToken
|
|
97
|
+
? { status: null, code: "core_origin_missing", message: "HOSTY_CORE_ORIGIN is not configured." }
|
|
98
|
+
: { status: null, code: "app_service_token_missing", message: "HOSTY_APP_SERVICE_TOKEN is not configured." },
|
|
99
|
+
};
|
|
95
100
|
}
|
|
96
101
|
const now = Date.now();
|
|
97
102
|
const cached = revalidationCache.get(token);
|
|
@@ -120,7 +125,10 @@ async function revalidateWithCore(token, coreOrigin, serviceToken, config) {
|
|
|
120
125
|
endpoint = new URL("/api/auth/apps/revalidate", coreOrigin).toString();
|
|
121
126
|
}
|
|
122
127
|
catch {
|
|
123
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
status: "misconfigured",
|
|
130
|
+
error: { status: null, code: "core_origin_invalid", message: "HOSTY_CORE_ORIGIN is not a valid URL." },
|
|
131
|
+
};
|
|
124
132
|
}
|
|
125
133
|
let response;
|
|
126
134
|
try {
|
|
@@ -135,12 +143,28 @@ async function revalidateWithCore(token, coreOrigin, serviceToken, config) {
|
|
|
135
143
|
signal: AbortSignal.timeout(CORE_AUTH_TIMEOUT_MS),
|
|
136
144
|
});
|
|
137
145
|
}
|
|
138
|
-
catch {
|
|
146
|
+
catch (error) {
|
|
139
147
|
// Network failure or timeout — transient either way; never cached.
|
|
140
|
-
|
|
148
|
+
const timeout = error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
149
|
+
return {
|
|
150
|
+
status: "unavailable",
|
|
151
|
+
error: {
|
|
152
|
+
status: null,
|
|
153
|
+
code: timeout ? "core_revalidation_timeout" : "app_session_revalidation_error",
|
|
154
|
+
message: error instanceof Error ? error.message : "Core identity revalidation failed.",
|
|
155
|
+
},
|
|
156
|
+
};
|
|
141
157
|
}
|
|
142
158
|
if (!response.ok) {
|
|
143
|
-
|
|
159
|
+
const body = (await response.json().catch(() => null));
|
|
160
|
+
return {
|
|
161
|
+
status: classifyRevalidationHttpStatus(response.status),
|
|
162
|
+
error: {
|
|
163
|
+
status: response.status,
|
|
164
|
+
code: readErrorField(body, "code") ?? "app_session_revalidation_failed",
|
|
165
|
+
message: readErrorField(body, "message") ?? `Core revalidation returned HTTP ${response.status}.`,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
144
168
|
}
|
|
145
169
|
let payload;
|
|
146
170
|
try {
|
|
@@ -148,12 +172,18 @@ async function revalidateWithCore(token, coreOrigin, serviceToken, config) {
|
|
|
148
172
|
}
|
|
149
173
|
catch {
|
|
150
174
|
// A non-JSON / truncated body means the session is unverifiable, not proof it is invalid.
|
|
151
|
-
return {
|
|
175
|
+
return {
|
|
176
|
+
status: "unavailable",
|
|
177
|
+
error: { status: response.status, code: "core_response_invalid", message: "Core returned an unreadable revalidation body." },
|
|
178
|
+
};
|
|
152
179
|
}
|
|
153
180
|
const appId = readString(payload?.appId);
|
|
154
181
|
if (appId && appId !== getAppId(config)) {
|
|
155
182
|
// A token minted for a different app is Core's token_app_mismatch — terminal, like 403.
|
|
156
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
status: "forbidden",
|
|
185
|
+
error: { status: response.status, code: "app_identity_app_mismatch", message: "Identity token was issued for a different app." },
|
|
186
|
+
};
|
|
157
187
|
}
|
|
158
188
|
const userId = readString(payload?.userId);
|
|
159
189
|
// An "active" grant without a subject is unusable; classify it as recoverable so a probe
|
|
@@ -329,6 +359,16 @@ function readCookie(cookieHeader, name) {
|
|
|
329
359
|
}
|
|
330
360
|
return null;
|
|
331
361
|
}
|
|
362
|
+
function readErrorField(payload, field) {
|
|
363
|
+
if (!payload || typeof payload !== "object") {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const nested = payload.error;
|
|
367
|
+
if (nested && typeof nested === "object") {
|
|
368
|
+
return readString(nested[field]);
|
|
369
|
+
}
|
|
370
|
+
return readString(payload[field]);
|
|
371
|
+
}
|
|
332
372
|
function readString(value) {
|
|
333
373
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
334
374
|
}
|
package/package.json
CHANGED