@every-app/sdk 0.1.5 → 0.1.7
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/cloudflare/server/gateway.d.ts +29 -0
- package/dist/cloudflare/server/gateway.d.ts.map +1 -0
- package/dist/cloudflare/server/gateway.js +64 -0
- package/dist/cloudflare/server/index.d.ts +1 -0
- package/dist/cloudflare/server/index.d.ts.map +1 -1
- package/dist/cloudflare/server/index.js +1 -0
- package/dist/core/authenticatedFetch.d.ts.map +1 -1
- package/dist/core/authenticatedFetch.js +0 -4
- package/dist/core/sessionManager.d.ts +0 -1
- package/dist/core/sessionManager.d.ts.map +1 -1
- package/dist/core/sessionManager.js +9 -41
- package/dist/tanstack/EmbeddedAppProvider.d.ts +12 -0
- package/dist/tanstack/EmbeddedAppProvider.d.ts.map +1 -1
- package/dist/tanstack/EmbeddedAppProvider.js +95 -8
- package/dist/tanstack/_internal/useEveryAppSession.d.ts.map +1 -1
- package/dist/tanstack/_internal/useEveryAppSession.js +2 -2
- package/dist/tanstack/index.d.ts +1 -1
- package/dist/tanstack/index.d.ts.map +1 -1
- package/dist/tanstack/index.js +1 -1
- package/dist/tanstack/server/authConfig.d.ts.map +1 -1
- package/dist/tanstack/server/authConfig.js +1 -6
- package/dist/tanstack/server/authenticateRequest.d.ts.map +1 -1
- package/dist/tanstack/server/authenticateRequest.js +0 -11
- package/dist/tanstack/server/index.d.ts +1 -0
- package/dist/tanstack/server/index.d.ts.map +1 -1
- package/dist/tanstack/server/index.js +1 -0
- package/dist/tanstack/server/withBearerTokenFromQuery.d.ts +2 -0
- package/dist/tanstack/server/withBearerTokenFromQuery.d.ts.map +1 -0
- package/dist/tanstack/server/withBearerTokenFromQuery.js +9 -0
- package/dist/tanstack/useSessionTokenClientMiddleware.d.ts.map +1 -1
- package/dist/tanstack/useSessionTokenClientMiddleware.js +0 -8
- package/package.json +3 -2
- package/src/cloudflare/index.ts +1 -1
- package/src/cloudflare/server/gateway.test.ts +183 -0
- package/src/cloudflare/server/gateway.ts +105 -0
- package/src/cloudflare/server/index.ts +1 -0
- package/src/core/authenticatedFetch.ts +0 -9
- package/src/core/index.ts +3 -3
- package/src/core/sessionManager.ts +8 -53
- package/src/env.d.ts +2 -0
- package/src/tanstack/EmbeddedAppProvider.tsx +6 -6
- package/src/tanstack/_internal/useEveryAppSession.tsx +3 -4
- package/src/tanstack/index.ts +3 -3
- package/src/tanstack/server/authConfig.ts +2 -9
- package/src/tanstack/server/authenticateRequest.ts +1 -20
- package/src/tanstack/server/index.ts +3 -3
- package/src/tanstack/useEveryAppRouter.tsx +1 -1
- package/src/tanstack/useSessionTokenClientMiddleware.ts +1 -13
- package/src/shared/demoModeLocalOnly.ts +0 -40
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface GatewayFetcher {
|
|
2
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
3
|
+
}
|
|
4
|
+
interface GatewayEnv {
|
|
5
|
+
GATEWAY_URL?: string;
|
|
6
|
+
EVERY_APP_GATEWAY?: GatewayFetcher;
|
|
7
|
+
GATEWAY_APP_API_TOKEN?: string;
|
|
8
|
+
APP_TOKEN?: string;
|
|
9
|
+
}
|
|
10
|
+
interface FetchGatewayOptions {
|
|
11
|
+
env: GatewayEnv;
|
|
12
|
+
/** The URL, path, or Request to send to the gateway. Typically the full URL
|
|
13
|
+
* passed by a provider SDK's custom `fetch` override. */
|
|
14
|
+
url: string | URL | Request;
|
|
15
|
+
/** Standard `RequestInit` (method, headers, body, etc.) from the provider SDK. */
|
|
16
|
+
init?: RequestInit;
|
|
17
|
+
}
|
|
18
|
+
export declare function getGatewayUrl(env: GatewayEnv): string;
|
|
19
|
+
/**
|
|
20
|
+
* Fetch from the gateway proxy, authenticating with the app token.
|
|
21
|
+
*
|
|
22
|
+
* - Strips any existing `Authorization` header (the gateway only accepts
|
|
23
|
+
* app token auth via `x-every-app-token`).
|
|
24
|
+
* - Requires `GATEWAY_APP_API_TOKEN` (or legacy `APP_TOKEN`) in the env.
|
|
25
|
+
* - Routes via service binding in production, falls back to HTTP in dev.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fetchGateway({ env, url, init, }: FetchGatewayOptions): Promise<Response>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../src/cloudflare/server/gateway.ts"],"names":[],"mappings":"AAGA,UAAU,cAAc;IACtB,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxE;AAED,UAAU,UAAU;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,cAAc,CAAC;IACnC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,mBAAmB;IAC3B,GAAG,EAAE,UAAU,CAAC;IAChB;8DAC0D;IAC1D,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC;IAC5B,kFAAkF;IAClF,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,EACjC,GAAG,EACH,GAAG,EACH,IAAI,GACL,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiBzC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const SERVICE_BINDING_ORIGIN = "http://localhost";
|
|
2
|
+
const APP_TOKEN_HEADER = "x-every-app-token";
|
|
3
|
+
export function getGatewayUrl(env) {
|
|
4
|
+
const gatewayUrl = env.GATEWAY_URL?.trim();
|
|
5
|
+
if (!gatewayUrl) {
|
|
6
|
+
throw new Error("GATEWAY_URL is required");
|
|
7
|
+
}
|
|
8
|
+
return gatewayUrl;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fetch from the gateway proxy, authenticating with the app token.
|
|
12
|
+
*
|
|
13
|
+
* - Strips any existing `Authorization` header (the gateway only accepts
|
|
14
|
+
* app token auth via `x-every-app-token`).
|
|
15
|
+
* - Requires `GATEWAY_APP_API_TOKEN` (or legacy `APP_TOKEN`) in the env.
|
|
16
|
+
* - Routes via service binding in production, falls back to HTTP in dev.
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchGateway({ env, url, init, }) {
|
|
19
|
+
const gatewayBaseUrl = getGatewayUrl(env);
|
|
20
|
+
const resolvedRequest = toRequest(url, init, gatewayBaseUrl);
|
|
21
|
+
const authenticatedRequest = applyAppTokenAuth(resolvedRequest, env);
|
|
22
|
+
// Use service binding only in production for zero-latency internal routing.
|
|
23
|
+
// In local dev, service bindings can exist in config but still fail with:
|
|
24
|
+
// "Couldn't find a local dev session ... to proxy to".
|
|
25
|
+
if (import.meta.env.PROD && env.EVERY_APP_GATEWAY) {
|
|
26
|
+
const url = new URL(authenticatedRequest.url);
|
|
27
|
+
const bindingUrl = `${SERVICE_BINDING_ORIGIN}${url.pathname}${url.search}`;
|
|
28
|
+
const bindingRequest = new Request(bindingUrl, authenticatedRequest);
|
|
29
|
+
return env.EVERY_APP_GATEWAY.fetch(bindingRequest);
|
|
30
|
+
}
|
|
31
|
+
// Fall back to HTTP fetch for local dev (and when no binding is present).
|
|
32
|
+
return fetch(authenticatedRequest);
|
|
33
|
+
}
|
|
34
|
+
function applyAppTokenAuth(request, env) {
|
|
35
|
+
const appToken = getGatewayAppApiToken(env);
|
|
36
|
+
if (!appToken) {
|
|
37
|
+
throw new Error("GATEWAY_APP_API_TOKEN is required. Run `npx everyapp app deploy` to provision one.");
|
|
38
|
+
}
|
|
39
|
+
const headers = new Headers(request.headers);
|
|
40
|
+
headers.delete("authorization");
|
|
41
|
+
headers.set(APP_TOKEN_HEADER, appToken);
|
|
42
|
+
return new Request(request, { headers });
|
|
43
|
+
}
|
|
44
|
+
function getGatewayAppApiToken(env) {
|
|
45
|
+
const configuredToken = env.GATEWAY_APP_API_TOKEN?.trim();
|
|
46
|
+
if (configuredToken) {
|
|
47
|
+
return configuredToken;
|
|
48
|
+
}
|
|
49
|
+
const legacyToken = env.APP_TOKEN?.trim();
|
|
50
|
+
return legacyToken || null;
|
|
51
|
+
}
|
|
52
|
+
function toRequest(input, init, baseUrl) {
|
|
53
|
+
if (input instanceof Request) {
|
|
54
|
+
return init ? new Request(input, init) : input;
|
|
55
|
+
}
|
|
56
|
+
if (input instanceof URL) {
|
|
57
|
+
return new Request(input.toString(), init);
|
|
58
|
+
}
|
|
59
|
+
if (typeof input === "string" && baseUrl && !/^https?:\/\//i.test(input)) {
|
|
60
|
+
const normalizedPath = input.startsWith("/") ? input : `/${input}`;
|
|
61
|
+
return new Request(new URL(normalizedPath, baseUrl).toString(), init);
|
|
62
|
+
}
|
|
63
|
+
return new Request(input, init);
|
|
64
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cloudflare/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cloudflare/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticatedFetch.d.ts","sourceRoot":"","sources":["../../src/core/authenticatedFetch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"authenticatedFetch.d.ts","sourceRoot":"","sources":["../../src/core/authenticatedFetch.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAevD;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import { DEMO_MODE_LOCAL_ONLY_TOKEN, isDemoModeLocalOnlyClient, } from "../shared/demoModeLocalOnly";
|
|
2
1
|
/**
|
|
3
2
|
* Gets the current session token from the embedded session manager
|
|
4
3
|
*/
|
|
5
4
|
export async function getSessionToken() {
|
|
6
|
-
if (isDemoModeLocalOnlyClient()) {
|
|
7
|
-
return DEMO_MODE_LOCAL_ONLY_TOKEN;
|
|
8
|
-
}
|
|
9
5
|
const windowWithSession = window;
|
|
10
6
|
const sessionManager = windowWithSession.__embeddedSessionManager;
|
|
11
7
|
if (!sessionManager) {
|
|
@@ -10,7 +10,6 @@ export declare class SessionManager {
|
|
|
10
10
|
readonly parentOrigin: string;
|
|
11
11
|
readonly appId: string;
|
|
12
12
|
readonly isInIframe: boolean;
|
|
13
|
-
readonly isDemoModeLocalOnly: boolean;
|
|
14
13
|
private token;
|
|
15
14
|
private refreshPromise;
|
|
16
15
|
constructor(config: SessionManagerConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionManager.d.ts","sourceRoot":"","sources":["../../src/core/sessionManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessionManager.d.ts","sourceRoot":"","sources":["../../src/core/sessionManager.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAQ3C;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,cAAc,CAAgC;gBAE1C,MAAM,EAAE,oBAAoB;IAqBxC,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,uBAAuB;IAuCzB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IA8ClC,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAOjC,aAAa,IAAI;QACf,MAAM,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,CAAC;QACxD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB;IAgBD;;;OAGG;IACH,OAAO,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAwBpD"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DEMO_MODE_LOCAL_ONLY_EMAIL, DEMO_MODE_LOCAL_ONLY_TOKEN, DEMO_MODE_LOCAL_ONLY_USER_ID, isDemoModeLocalOnlyClient, } from "../shared/demoModeLocalOnly";
|
|
2
1
|
const MESSAGE_TIMEOUT_MS = 5000;
|
|
3
2
|
const TOKEN_EXPIRY_BUFFER_MS = 10000;
|
|
4
3
|
const DEFAULT_TOKEN_LIFETIME_MS = 60000;
|
|
@@ -20,37 +19,25 @@ export class SessionManager {
|
|
|
20
19
|
parentOrigin;
|
|
21
20
|
appId;
|
|
22
21
|
isInIframe;
|
|
23
|
-
isDemoModeLocalOnly;
|
|
24
22
|
token = null;
|
|
25
23
|
refreshPromise = null;
|
|
26
24
|
constructor(config) {
|
|
27
25
|
if (!config.appId) {
|
|
28
26
|
throw new Error("[SessionManager] appId is required.");
|
|
29
27
|
}
|
|
30
|
-
this.isDemoModeLocalOnly = isDemoModeLocalOnlyClient();
|
|
31
28
|
const gatewayUrl = import.meta.env.VITE_GATEWAY_URL;
|
|
32
|
-
if (!
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
throw new Error(`[SessionManager] Invalid gateway URL: ${gatewayUrl}`);
|
|
41
|
-
}
|
|
29
|
+
if (!gatewayUrl) {
|
|
30
|
+
throw new Error("[SessionManager] VITE_GATEWAY_URL env var is required.");
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
new URL(gatewayUrl);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error(`[SessionManager] Invalid gateway URL: ${gatewayUrl}`);
|
|
42
37
|
}
|
|
43
38
|
this.appId = config.appId;
|
|
44
|
-
this.parentOrigin =
|
|
45
|
-
? window.location.origin
|
|
46
|
-
: gatewayUrl;
|
|
39
|
+
this.parentOrigin = gatewayUrl;
|
|
47
40
|
this.isInIframe = isRunningInIframe();
|
|
48
|
-
if (this.isDemoModeLocalOnly) {
|
|
49
|
-
this.token = {
|
|
50
|
-
token: DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
51
|
-
expiresAt: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
41
|
}
|
|
55
42
|
isTokenExpiringSoon(bufferMs = TOKEN_EXPIRY_BUFFER_MS) {
|
|
56
43
|
if (!this.token)
|
|
@@ -90,13 +77,6 @@ export class SessionManager {
|
|
|
90
77
|
});
|
|
91
78
|
}
|
|
92
79
|
async requestNewToken() {
|
|
93
|
-
if (this.isDemoModeLocalOnly) {
|
|
94
|
-
this.token = {
|
|
95
|
-
token: DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
96
|
-
expiresAt: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,
|
|
97
|
-
};
|
|
98
|
-
return this.token.token;
|
|
99
|
-
}
|
|
100
80
|
if (this.refreshPromise) {
|
|
101
81
|
return this.refreshPromise;
|
|
102
82
|
}
|
|
@@ -132,12 +112,6 @@ export class SessionManager {
|
|
|
132
112
|
}
|
|
133
113
|
}
|
|
134
114
|
async getToken() {
|
|
135
|
-
if (this.isDemoModeLocalOnly) {
|
|
136
|
-
if (!this.token || this.isTokenExpiringSoon()) {
|
|
137
|
-
return this.requestNewToken();
|
|
138
|
-
}
|
|
139
|
-
return this.token.token;
|
|
140
|
-
}
|
|
141
115
|
if (this.isTokenExpiringSoon()) {
|
|
142
116
|
return this.requestNewToken();
|
|
143
117
|
}
|
|
@@ -160,12 +134,6 @@ export class SessionManager {
|
|
|
160
134
|
* Returns null if no valid token is available.
|
|
161
135
|
*/
|
|
162
136
|
getUser() {
|
|
163
|
-
if (this.isDemoModeLocalOnly) {
|
|
164
|
-
return {
|
|
165
|
-
userId: DEMO_MODE_LOCAL_ONLY_USER_ID,
|
|
166
|
-
email: DEMO_MODE_LOCAL_ONLY_EMAIL,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
137
|
if (!this.token) {
|
|
170
138
|
return null;
|
|
171
139
|
}
|
|
@@ -26,5 +26,17 @@ export declare function useCurrentUser(): {
|
|
|
26
26
|
userId: string;
|
|
27
27
|
email: string;
|
|
28
28
|
} | null;
|
|
29
|
+
interface UseSessionTokenOptions {
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
retryDelayMs?: number;
|
|
32
|
+
}
|
|
33
|
+
interface UseSessionTokenResult {
|
|
34
|
+
token: string | null;
|
|
35
|
+
isReady: boolean;
|
|
36
|
+
isLoading: boolean;
|
|
37
|
+
error: Error | null;
|
|
38
|
+
refresh: () => Promise<string | null>;
|
|
39
|
+
}
|
|
40
|
+
export declare function useSessionToken({ enabled, retryDelayMs, }?: UseSessionTokenOptions): UseSessionTokenResult;
|
|
29
41
|
export {};
|
|
30
42
|
//# sourceMappingURL=EmbeddedAppProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmbeddedAppProvider.d.ts","sourceRoot":"","sources":["../../src/tanstack/EmbeddedAppProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"EmbeddedAppProvider.d.ts","sourceRoot":"","sources":["../../src/tanstack/EmbeddedAppProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,EAAkB,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAK9E,UAAU,sBAAuB,SAAQ,oBAAoB;IAC3D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAsBD,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,GAAG,MAAM,EACV,EAAE,sBAAsB,kDA6BxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWzE;AAED,UAAU,sBAAsB;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,qBAAqB;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAc,EACd,YAAkB,GACnB,GAAE,sBAA2B,GAAG,qBAAqB,CAgGrD"}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
|
|
3
3
|
import { useEveryAppSession } from "./_internal/useEveryAppSession";
|
|
4
4
|
import { useEveryAppRouter } from "./useEveryAppRouter";
|
|
5
5
|
import { GatewayRequiredError } from "./GatewayRequiredError";
|
|
6
6
|
const EmbeddedAppContext = createContext(null);
|
|
7
|
+
function useEmbeddedAppContextValue() {
|
|
8
|
+
const context = useContext(EmbeddedAppContext);
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error("Embedded SDK hooks must be used within an EmbeddedAppProvider");
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
7
14
|
export function EmbeddedAppProvider({ children, ...config }) {
|
|
8
15
|
const { sessionManager, sessionTokenState } = useEveryAppSession({
|
|
9
16
|
sessionManagerConfig: config,
|
|
@@ -11,8 +18,8 @@ export function EmbeddedAppProvider({ children, ...config }) {
|
|
|
11
18
|
useEveryAppRouter({ sessionManager });
|
|
12
19
|
if (!sessionManager)
|
|
13
20
|
return null;
|
|
14
|
-
// Check if the app is running outside of the Gateway iframe
|
|
15
|
-
if (!sessionManager.isInIframe
|
|
21
|
+
// Check if the app is running outside of the Gateway iframe
|
|
22
|
+
if (!sessionManager.isInIframe) {
|
|
16
23
|
return (_jsx(GatewayRequiredError, { gatewayOrigin: sessionManager.parentOrigin, appId: config.appId }));
|
|
17
24
|
}
|
|
18
25
|
const value = {
|
|
@@ -41,11 +48,7 @@ export function EmbeddedAppProvider({ children, ...config }) {
|
|
|
41
48
|
* ```
|
|
42
49
|
*/
|
|
43
50
|
export function useCurrentUser() {
|
|
44
|
-
const
|
|
45
|
-
if (!context) {
|
|
46
|
-
throw new Error("useCurrentUser must be used within an EmbeddedAppProvider");
|
|
47
|
-
}
|
|
48
|
-
const { sessionManager, sessionTokenState } = context;
|
|
51
|
+
const { sessionManager, sessionTokenState } = useEmbeddedAppContextValue();
|
|
49
52
|
return useMemo(() => {
|
|
50
53
|
// Only return user if we have a valid token
|
|
51
54
|
if (sessionTokenState.status !== "VALID") {
|
|
@@ -54,3 +57,87 @@ export function useCurrentUser() {
|
|
|
54
57
|
return sessionManager.getUser();
|
|
55
58
|
}, [sessionManager, sessionTokenState]);
|
|
56
59
|
}
|
|
60
|
+
export function useSessionToken({ enabled = true, retryDelayMs = 300, } = {}) {
|
|
61
|
+
const { sessionManager, sessionTokenState } = useEmbeddedAppContextValue();
|
|
62
|
+
const [token, setToken] = useState(sessionTokenState.status === "VALID" ? sessionTokenState.token : null);
|
|
63
|
+
const [isLoading, setIsLoading] = useState(enabled && !token);
|
|
64
|
+
const [error, setError] = useState(null);
|
|
65
|
+
const refresh = useCallback(async () => {
|
|
66
|
+
if (!enabled) {
|
|
67
|
+
setToken(null);
|
|
68
|
+
setError(null);
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
setIsLoading(true);
|
|
73
|
+
try {
|
|
74
|
+
const nextToken = await sessionManager.getToken();
|
|
75
|
+
setToken(nextToken);
|
|
76
|
+
setError(null);
|
|
77
|
+
return nextToken;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const normalized = err instanceof Error
|
|
81
|
+
? err
|
|
82
|
+
: new Error("Failed to resolve session token");
|
|
83
|
+
setError(normalized);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
}
|
|
89
|
+
}, [enabled, sessionManager]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!enabled) {
|
|
92
|
+
setToken(null);
|
|
93
|
+
setIsLoading(false);
|
|
94
|
+
setError(null);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (sessionTokenState.status === "VALID" && sessionTokenState.token) {
|
|
98
|
+
setToken(sessionTokenState.token);
|
|
99
|
+
setError(null);
|
|
100
|
+
setIsLoading(false);
|
|
101
|
+
}
|
|
102
|
+
}, [enabled, sessionTokenState.status, sessionTokenState.token]);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (sessionTokenState.status === "VALID" && sessionTokenState.token) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let cancelled = false;
|
|
111
|
+
let retryTimeout = null;
|
|
112
|
+
const loadToken = async () => {
|
|
113
|
+
if (cancelled)
|
|
114
|
+
return;
|
|
115
|
+
const resolvedToken = await refresh();
|
|
116
|
+
if (cancelled || resolvedToken)
|
|
117
|
+
return;
|
|
118
|
+
retryTimeout = setTimeout(() => {
|
|
119
|
+
void loadToken();
|
|
120
|
+
}, retryDelayMs);
|
|
121
|
+
};
|
|
122
|
+
void loadToken();
|
|
123
|
+
return () => {
|
|
124
|
+
cancelled = true;
|
|
125
|
+
if (retryTimeout) {
|
|
126
|
+
clearTimeout(retryTimeout);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}, [
|
|
130
|
+
enabled,
|
|
131
|
+
refresh,
|
|
132
|
+
retryDelayMs,
|
|
133
|
+
sessionTokenState.status,
|
|
134
|
+
sessionTokenState.token,
|
|
135
|
+
]);
|
|
136
|
+
return {
|
|
137
|
+
token,
|
|
138
|
+
isReady: !enabled || Boolean(token),
|
|
139
|
+
isLoading,
|
|
140
|
+
error,
|
|
141
|
+
refresh,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEveryAppSession.d.ts","sourceRoot":"","sources":["../../../src/tanstack/_internal/useEveryAppSession.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,wBAAwB;IAChC,oBAAoB,EAAE,oBAAoB,CAAC;CAC5C;AAED,wBAAgB,kBAAkB,CAAC,EACjC,oBAAoB,GACrB,EAAE,wBAAwB;;;;;;
|
|
1
|
+
{"version":3,"file":"useEveryAppSession.d.ts","sourceRoot":"","sources":["../../../src/tanstack/_internal/useEveryAppSession.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,wBAAwB;IAChC,oBAAoB,EAAE,oBAAoB,CAAC;CAC5C;AAED,wBAAgB,kBAAkB,CAAC,EACjC,oBAAoB,GACrB,EAAE,wBAAwB;;;;;;EA8C1B"}
|
|
@@ -13,8 +13,8 @@ export function useEveryAppSession({ sessionManagerConfig, }) {
|
|
|
13
13
|
useEffect(() => {
|
|
14
14
|
if (!sessionManager)
|
|
15
15
|
return;
|
|
16
|
-
// Skip token requests when not in iframe
|
|
17
|
-
if (!sessionManager.isInIframe
|
|
16
|
+
// Skip token requests when not in iframe - the app will show GatewayRequiredError instead
|
|
17
|
+
if (!sessionManager.isInIframe)
|
|
18
18
|
return;
|
|
19
19
|
const interval = setInterval(() => {
|
|
20
20
|
setSessionTokenState(sessionManager.getTokenState());
|
package/dist/tanstack/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { useSessionTokenClientMiddleware } from "./useSessionTokenClientMiddleware";
|
|
2
2
|
export { useEveryAppRouter } from "./useEveryAppRouter";
|
|
3
|
-
export { EmbeddedAppProvider, useCurrentUser } from "./EmbeddedAppProvider";
|
|
3
|
+
export { EmbeddedAppProvider, useCurrentUser, useSessionToken, } from "./EmbeddedAppProvider";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tanstack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tanstack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,eAAe,GAChB,MAAM,uBAAuB,CAAC"}
|
package/dist/tanstack/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { useSessionTokenClientMiddleware } from "./useSessionTokenClientMiddleware";
|
|
2
2
|
export { useEveryAppRouter } from "./useEveryAppRouter";
|
|
3
|
-
export { EmbeddedAppProvider, useCurrentUser } from "./EmbeddedAppProvider";
|
|
3
|
+
export { EmbeddedAppProvider, useCurrentUser, useSessionToken, } from "./EmbeddedAppProvider";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authConfig.d.ts","sourceRoot":"","sources":["../../../src/tanstack/server/authConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"authConfig.d.ts","sourceRoot":"","sources":["../../../src/tanstack/server/authConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C,wBAAgB,aAAa,IAAI,UAAU,CAK1C"}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { env } from "cloudflare:workers";
|
|
2
|
-
import { isDemoModeLocalOnlyServer } from "../../shared/demoModeLocalOnly";
|
|
3
2
|
export function getAuthConfig() {
|
|
4
|
-
const demoModeLocalOnlyEnv = env
|
|
5
|
-
.DEMO_MODE_LOCAL_ONLY;
|
|
6
|
-
const isDemoModeLocalOnly = demoModeLocalOnlyEnv === "true" || isDemoModeLocalOnlyServer() === true;
|
|
7
|
-
const issuer = env.GATEWAY_URL || (isDemoModeLocalOnly ? "local" : "");
|
|
8
3
|
return {
|
|
9
|
-
issuer,
|
|
4
|
+
issuer: env.GATEWAY_URL,
|
|
10
5
|
audience: import.meta.env.VITE_APP_ID,
|
|
11
6
|
};
|
|
12
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticateRequest.d.ts","sourceRoot":"","sources":["../../../src/tanstack/server/authenticateRequest.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"authenticateRequest.d.ts","sourceRoot":"","sources":["../../../src/tanstack/server/authenticateRequest.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C;;;GAGG;AACH,UAAU,mBAAmB;IAC3B,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAC;IACZ,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,UAAU,EACtB,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CA8BrC;AAyCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAK3E"}
|
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import { getRequest } from "@tanstack/react-start/server";
|
|
2
2
|
import { createLocalJWKSet, jwtVerify, } from "jose";
|
|
3
3
|
import { env } from "cloudflare:workers";
|
|
4
|
-
import { DEMO_MODE_LOCAL_ONLY_TOKEN, createDemoModeLocalOnlySessionPayload, isDemoModeLocalOnlyServer, } from "../../shared/demoModeLocalOnly";
|
|
5
4
|
export async function authenticateRequest(authConfig, providedRequest) {
|
|
6
5
|
const request = providedRequest || getRequest();
|
|
7
6
|
const authHeader = request.headers.get("authorization");
|
|
8
|
-
const demoModeLocalOnlyEnv = env
|
|
9
|
-
.DEMO_MODE_LOCAL_ONLY;
|
|
10
|
-
const isDemoModeLocalOnly = demoModeLocalOnlyEnv === "true" || isDemoModeLocalOnlyServer() === true;
|
|
11
7
|
if (!authHeader) {
|
|
12
|
-
console.log("No auth header found");
|
|
13
8
|
return null;
|
|
14
9
|
}
|
|
15
10
|
const token = extractBearerToken(authHeader);
|
|
16
11
|
if (!token) {
|
|
17
12
|
return null;
|
|
18
13
|
}
|
|
19
|
-
if (isDemoModeLocalOnly) {
|
|
20
|
-
if (token !== DEMO_MODE_LOCAL_ONLY_TOKEN) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
return createDemoModeLocalOnlySessionPayload(authConfig.audience);
|
|
24
|
-
}
|
|
25
14
|
try {
|
|
26
15
|
const session = await verifySessionToken(token, authConfig);
|
|
27
16
|
return session;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tanstack/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tanstack/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withBearerTokenFromQuery.d.ts","sourceRoot":"","sources":["../../../src/tanstack/server/withBearerTokenFromQuery.ts"],"names":[],"mappings":"AAAA,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,UAAU,GAAE,MAAgB,GAC3B,OAAO,CAWT"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function withBearerTokenFromQuery(request, queryParam = "token") {
|
|
2
|
+
const token = new URL(request.url).searchParams.get(queryParam);
|
|
3
|
+
if (!token || request.headers.has("Authorization")) {
|
|
4
|
+
return request;
|
|
5
|
+
}
|
|
6
|
+
const headers = new Headers(request.headers);
|
|
7
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
8
|
+
return new Request(request, { headers });
|
|
9
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSessionTokenClientMiddleware.d.ts","sourceRoot":"","sources":["../../src/tanstack/useSessionTokenClientMiddleware.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useSessionTokenClientMiddleware.d.ts","sourceRoot":"","sources":["../../src/tanstack/useSessionTokenClientMiddleware.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,+BAA+B,6GA2B1C,CAAC"}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
-
import { DEMO_MODE_LOCAL_ONLY_TOKEN, isDemoModeLocalOnlyClient, } from "../shared/demoModeLocalOnly";
|
|
3
2
|
export const useSessionTokenClientMiddleware = createMiddleware({
|
|
4
3
|
type: "function",
|
|
5
4
|
}).client(async ({ next }) => {
|
|
6
|
-
if (isDemoModeLocalOnlyClient()) {
|
|
7
|
-
return next({
|
|
8
|
-
headers: {
|
|
9
|
-
Authorization: `Bearer ${DEMO_MODE_LOCAL_ONLY_TOKEN}`,
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
5
|
// Get the global sessionManager - this MUST be available for embedded apps
|
|
14
6
|
const sessionManager = window
|
|
15
7
|
.__embeddedSessionManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@every-app/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"format:check": "prettier --check .",
|
|
36
36
|
"format:write": "prettier . --write",
|
|
37
37
|
"test": "vitest run --silent",
|
|
38
|
-
"test:watch": "vitest"
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"check:esm-relative-imports": "node ./scripts/check-esm-relative-imports.mjs"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"jsonc-parser": "^3.3.1"
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { lazyInitForWorkers } from "./lazyInit";
|
|
1
|
+
export { lazyInitForWorkers } from "./lazyInit.js";
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { fetchGateway, getGatewayUrl } from "./gateway";
|
|
3
|
+
|
|
4
|
+
type TestFetcher = {
|
|
5
|
+
fetch: ReturnType<typeof vi.fn>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type TestEnv = {
|
|
9
|
+
GATEWAY_URL?: string;
|
|
10
|
+
EVERY_APP_GATEWAY?: TestFetcher;
|
|
11
|
+
GATEWAY_APP_API_TOKEN?: string;
|
|
12
|
+
APP_TOKEN?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("gateway server helpers", () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns gateway URL and throws when missing", () => {
|
|
25
|
+
expect(getGatewayUrl({ GATEWAY_URL: "https://gateway.example.com" })).toBe(
|
|
26
|
+
"https://gateway.example.com",
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(() => getGatewayUrl({} as TestEnv)).toThrow(
|
|
30
|
+
"GATEWAY_URL is required",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("fetches via HTTP when service binding is unavailable", async () => {
|
|
35
|
+
const fetchMock = vi
|
|
36
|
+
.spyOn(globalThis, "fetch")
|
|
37
|
+
.mockResolvedValue(new Response("ok", { status: 200 }));
|
|
38
|
+
|
|
39
|
+
const env: TestEnv = {
|
|
40
|
+
GATEWAY_URL: "https://gateway.example.com",
|
|
41
|
+
GATEWAY_APP_API_TOKEN: "eat_test_token",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
await fetchGateway({
|
|
45
|
+
env,
|
|
46
|
+
url: "/api/ai/openai/v1/responses?stream=true",
|
|
47
|
+
init: { method: "POST" },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
51
|
+
const [requestArg] = fetchMock.mock.calls[0];
|
|
52
|
+
const request = requestArg as Request;
|
|
53
|
+
|
|
54
|
+
expect(request.url).toBe(
|
|
55
|
+
"https://gateway.example.com/api/ai/openai/v1/responses?stream=true",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("fetches via service binding when available", async () => {
|
|
60
|
+
const bindingFetch = vi
|
|
61
|
+
.fn()
|
|
62
|
+
.mockResolvedValue(new Response("ok", { status: 200 }));
|
|
63
|
+
|
|
64
|
+
const env: TestEnv = {
|
|
65
|
+
GATEWAY_URL: "https://gateway.example.com",
|
|
66
|
+
EVERY_APP_GATEWAY: { fetch: bindingFetch },
|
|
67
|
+
GATEWAY_APP_API_TOKEN: "eat_test_token",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
await fetchGateway({
|
|
71
|
+
env,
|
|
72
|
+
url: "/api/ai/openai/v1/chat/completions",
|
|
73
|
+
init: { method: "POST" },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(bindingFetch).toHaveBeenCalledTimes(1);
|
|
77
|
+
const [requestArg] = bindingFetch.mock.calls[0];
|
|
78
|
+
const request = requestArg as Request;
|
|
79
|
+
expect(request.url).toBe(
|
|
80
|
+
"http://localhost/api/ai/openai/v1/chat/completions",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("always injects app token and strips Authorization header", async () => {
|
|
85
|
+
const fetchMock = vi
|
|
86
|
+
.spyOn(globalThis, "fetch")
|
|
87
|
+
.mockResolvedValue(new Response("ok", { status: 200 }));
|
|
88
|
+
|
|
89
|
+
const env: TestEnv = {
|
|
90
|
+
GATEWAY_URL: "https://gateway.example.com",
|
|
91
|
+
GATEWAY_APP_API_TOKEN: "eat_my_token",
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
await fetchGateway({
|
|
95
|
+
env,
|
|
96
|
+
url: "/api/ai/openai/v1/responses",
|
|
97
|
+
init: {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
authorization: "Bearer some-sdk-dummy-value",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
106
|
+
const [requestArg] = fetchMock.mock.calls[0];
|
|
107
|
+
const request = requestArg as Request;
|
|
108
|
+
|
|
109
|
+
expect(request.headers.get("x-every-app-token")).toBe("eat_my_token");
|
|
110
|
+
expect(request.headers.get("authorization")).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("throws when app token is missing", async () => {
|
|
114
|
+
const env: TestEnv = {
|
|
115
|
+
GATEWAY_URL: "https://gateway.example.com",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
await expect(
|
|
119
|
+
fetchGateway({
|
|
120
|
+
env,
|
|
121
|
+
url: "/api/ai/openai/v1/responses",
|
|
122
|
+
}),
|
|
123
|
+
).rejects.toThrow("GATEWAY_APP_API_TOKEN is required");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("supports legacy APP_TOKEN env variable", async () => {
|
|
127
|
+
const fetchMock = vi
|
|
128
|
+
.spyOn(globalThis, "fetch")
|
|
129
|
+
.mockResolvedValue(new Response("ok", { status: 200 }));
|
|
130
|
+
|
|
131
|
+
const env: TestEnv = {
|
|
132
|
+
GATEWAY_URL: "https://gateway.example.com",
|
|
133
|
+
APP_TOKEN: "eat_legacy_token",
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
await fetchGateway({
|
|
137
|
+
env,
|
|
138
|
+
url: "/api/ai/openai/v1/responses",
|
|
139
|
+
init: { method: "POST" },
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
143
|
+
const [requestArg] = fetchMock.mock.calls[0];
|
|
144
|
+
const request = requestArg as Request;
|
|
145
|
+
|
|
146
|
+
expect(request.headers.get("x-every-app-token")).toBe("eat_legacy_token");
|
|
147
|
+
expect(request.headers.get("authorization")).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("supports Request input and preserves method/body", async () => {
|
|
151
|
+
const fetchMock = vi
|
|
152
|
+
.spyOn(globalThis, "fetch")
|
|
153
|
+
.mockResolvedValue(new Response("ok", { status: 200 }));
|
|
154
|
+
|
|
155
|
+
const env: TestEnv = {
|
|
156
|
+
GATEWAY_URL: "https://gateway.example.com",
|
|
157
|
+
GATEWAY_APP_API_TOKEN: "eat_test_token",
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const sourceRequest = new Request(
|
|
161
|
+
"https://gateway.example.com/api/ai/openai/v1/responses",
|
|
162
|
+
{
|
|
163
|
+
method: "POST",
|
|
164
|
+
body: JSON.stringify({ model: "gpt-5.2" }),
|
|
165
|
+
headers: { "content-type": "application/json" },
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
await fetchGateway({
|
|
170
|
+
env,
|
|
171
|
+
url: sourceRequest,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
175
|
+
const [requestArg] = fetchMock.mock.calls[0];
|
|
176
|
+
const request = requestArg as Request;
|
|
177
|
+
expect(request.url).toBe(
|
|
178
|
+
"https://gateway.example.com/api/ai/openai/v1/responses",
|
|
179
|
+
);
|
|
180
|
+
expect(request.method).toBe("POST");
|
|
181
|
+
expect(request.headers.get("content-type")).toBe("application/json");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const SERVICE_BINDING_ORIGIN = "http://localhost";
|
|
2
|
+
const APP_TOKEN_HEADER = "x-every-app-token";
|
|
3
|
+
|
|
4
|
+
interface GatewayFetcher {
|
|
5
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface GatewayEnv {
|
|
9
|
+
GATEWAY_URL?: string;
|
|
10
|
+
EVERY_APP_GATEWAY?: GatewayFetcher;
|
|
11
|
+
GATEWAY_APP_API_TOKEN?: string;
|
|
12
|
+
APP_TOKEN?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface FetchGatewayOptions {
|
|
16
|
+
env: GatewayEnv;
|
|
17
|
+
/** The URL, path, or Request to send to the gateway. Typically the full URL
|
|
18
|
+
* passed by a provider SDK's custom `fetch` override. */
|
|
19
|
+
url: string | URL | Request;
|
|
20
|
+
/** Standard `RequestInit` (method, headers, body, etc.) from the provider SDK. */
|
|
21
|
+
init?: RequestInit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getGatewayUrl(env: GatewayEnv): string {
|
|
25
|
+
const gatewayUrl = env.GATEWAY_URL?.trim();
|
|
26
|
+
if (!gatewayUrl) {
|
|
27
|
+
throw new Error("GATEWAY_URL is required");
|
|
28
|
+
}
|
|
29
|
+
return gatewayUrl;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Fetch from the gateway proxy, authenticating with the app token.
|
|
34
|
+
*
|
|
35
|
+
* - Strips any existing `Authorization` header (the gateway only accepts
|
|
36
|
+
* app token auth via `x-every-app-token`).
|
|
37
|
+
* - Requires `GATEWAY_APP_API_TOKEN` (or legacy `APP_TOKEN`) in the env.
|
|
38
|
+
* - Routes via service binding in production, falls back to HTTP in dev.
|
|
39
|
+
*/
|
|
40
|
+
export async function fetchGateway({
|
|
41
|
+
env,
|
|
42
|
+
url,
|
|
43
|
+
init,
|
|
44
|
+
}: FetchGatewayOptions): Promise<Response> {
|
|
45
|
+
const gatewayBaseUrl = getGatewayUrl(env);
|
|
46
|
+
const resolvedRequest = toRequest(url, init, gatewayBaseUrl);
|
|
47
|
+
const authenticatedRequest = applyAppTokenAuth(resolvedRequest, env);
|
|
48
|
+
|
|
49
|
+
// Use service binding when available for zero-latency internal routing
|
|
50
|
+
// (available in production Workers, not in local dev)
|
|
51
|
+
if (env.EVERY_APP_GATEWAY) {
|
|
52
|
+
const url = new URL(authenticatedRequest.url);
|
|
53
|
+
const bindingUrl = `${SERVICE_BINDING_ORIGIN}${url.pathname}${url.search}`;
|
|
54
|
+
const bindingRequest = new Request(bindingUrl, authenticatedRequest);
|
|
55
|
+
return env.EVERY_APP_GATEWAY.fetch(bindingRequest);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Fall back to HTTP fetch for local dev
|
|
59
|
+
return fetch(authenticatedRequest);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function applyAppTokenAuth(request: Request, env: GatewayEnv): Request {
|
|
63
|
+
const appToken = getGatewayAppApiToken(env);
|
|
64
|
+
if (!appToken) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"GATEWAY_APP_API_TOKEN is required. Run `npx everyapp app deploy` to provision one.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const headers = new Headers(request.headers);
|
|
71
|
+
headers.delete("authorization");
|
|
72
|
+
headers.set(APP_TOKEN_HEADER, appToken);
|
|
73
|
+
return new Request(request, { headers });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getGatewayAppApiToken(env: GatewayEnv): string | null {
|
|
77
|
+
const configuredToken = env.GATEWAY_APP_API_TOKEN?.trim();
|
|
78
|
+
if (configuredToken) {
|
|
79
|
+
return configuredToken;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const legacyToken = env.APP_TOKEN?.trim();
|
|
83
|
+
return legacyToken || null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toRequest(
|
|
87
|
+
input: RequestInfo | URL,
|
|
88
|
+
init?: RequestInit,
|
|
89
|
+
baseUrl?: string,
|
|
90
|
+
): Request {
|
|
91
|
+
if (input instanceof Request) {
|
|
92
|
+
return init ? new Request(input, init) : input;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (input instanceof URL) {
|
|
96
|
+
return new Request(input.toString(), init);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof input === "string" && baseUrl && !/^https?:\/\//i.test(input)) {
|
|
100
|
+
const normalizedPath = input.startsWith("/") ? input : `/${input}`;
|
|
101
|
+
return new Request(new URL(normalizedPath, baseUrl).toString(), init);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Request(input, init);
|
|
105
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
3
|
-
isDemoModeLocalOnlyClient,
|
|
4
|
-
} from "../shared/demoModeLocalOnly";
|
|
5
|
-
|
|
6
1
|
interface SessionManager {
|
|
7
2
|
getToken(): Promise<string>;
|
|
8
3
|
}
|
|
@@ -15,10 +10,6 @@ interface WindowWithSessionManager extends Window {
|
|
|
15
10
|
* Gets the current session token from the embedded session manager
|
|
16
11
|
*/
|
|
17
12
|
export async function getSessionToken(): Promise<string> {
|
|
18
|
-
if (isDemoModeLocalOnlyClient()) {
|
|
19
|
-
return DEMO_MODE_LOCAL_ONLY_TOKEN;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
const windowWithSession = window as WindowWithSessionManager;
|
|
23
14
|
const sessionManager = windowWithSession.__embeddedSessionManager;
|
|
24
15
|
|
package/src/core/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SessionManager, isRunningInIframe } from "./sessionManager";
|
|
2
|
-
export type { SessionManagerConfig } from "./sessionManager";
|
|
1
|
+
export { SessionManager, isRunningInIframe } from "./sessionManager.js";
|
|
2
|
+
export type { SessionManagerConfig } from "./sessionManager.js";
|
|
3
3
|
|
|
4
|
-
export { authenticatedFetch, getSessionToken } from "./authenticatedFetch";
|
|
4
|
+
export { authenticatedFetch, getSessionToken } from "./authenticatedFetch.js";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEMO_MODE_LOCAL_ONLY_EMAIL,
|
|
3
|
-
DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
4
|
-
DEMO_MODE_LOCAL_ONLY_USER_ID,
|
|
5
|
-
isDemoModeLocalOnlyClient,
|
|
6
|
-
} from "../shared/demoModeLocalOnly";
|
|
7
|
-
|
|
8
1
|
interface SessionToken {
|
|
9
2
|
token: string;
|
|
10
3
|
expiresAt: number;
|
|
@@ -42,7 +35,6 @@ export class SessionManager {
|
|
|
42
35
|
readonly parentOrigin: string;
|
|
43
36
|
readonly appId: string;
|
|
44
37
|
readonly isInIframe: boolean;
|
|
45
|
-
readonly isDemoModeLocalOnly: boolean;
|
|
46
38
|
|
|
47
39
|
private token: SessionToken | null = null;
|
|
48
40
|
private refreshPromise: Promise<string> | null = null;
|
|
@@ -52,35 +44,20 @@ export class SessionManager {
|
|
|
52
44
|
throw new Error("[SessionManager] appId is required.");
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
this.isDemoModeLocalOnly = isDemoModeLocalOnlyClient();
|
|
56
|
-
|
|
57
47
|
const gatewayUrl = import.meta.env.VITE_GATEWAY_URL;
|
|
58
|
-
if (!
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"[SessionManager] VITE_GATEWAY_URL env var is required.",
|
|
62
|
-
);
|
|
63
|
-
}
|
|
48
|
+
if (!gatewayUrl) {
|
|
49
|
+
throw new Error("[SessionManager] VITE_GATEWAY_URL env var is required.");
|
|
50
|
+
}
|
|
64
51
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
52
|
+
try {
|
|
53
|
+
new URL(gatewayUrl);
|
|
54
|
+
} catch {
|
|
55
|
+
throw new Error(`[SessionManager] Invalid gateway URL: ${gatewayUrl}`);
|
|
70
56
|
}
|
|
71
57
|
|
|
72
58
|
this.appId = config.appId;
|
|
73
|
-
this.parentOrigin =
|
|
74
|
-
? window.location.origin
|
|
75
|
-
: gatewayUrl;
|
|
59
|
+
this.parentOrigin = gatewayUrl;
|
|
76
60
|
this.isInIframe = isRunningInIframe();
|
|
77
|
-
|
|
78
|
-
if (this.isDemoModeLocalOnly) {
|
|
79
|
-
this.token = {
|
|
80
|
-
token: DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
81
|
-
expiresAt: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
61
|
}
|
|
85
62
|
|
|
86
63
|
private isTokenExpiringSoon(
|
|
@@ -130,14 +107,6 @@ export class SessionManager {
|
|
|
130
107
|
}
|
|
131
108
|
|
|
132
109
|
async requestNewToken(): Promise<string> {
|
|
133
|
-
if (this.isDemoModeLocalOnly) {
|
|
134
|
-
this.token = {
|
|
135
|
-
token: DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
136
|
-
expiresAt: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,
|
|
137
|
-
};
|
|
138
|
-
return this.token.token;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
110
|
if (this.refreshPromise) {
|
|
142
111
|
return this.refreshPromise;
|
|
143
112
|
}
|
|
@@ -184,13 +153,6 @@ export class SessionManager {
|
|
|
184
153
|
}
|
|
185
154
|
|
|
186
155
|
async getToken(): Promise<string> {
|
|
187
|
-
if (this.isDemoModeLocalOnly) {
|
|
188
|
-
if (!this.token || this.isTokenExpiringSoon()) {
|
|
189
|
-
return this.requestNewToken();
|
|
190
|
-
}
|
|
191
|
-
return this.token.token;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
156
|
if (this.isTokenExpiringSoon()) {
|
|
195
157
|
return this.requestNewToken();
|
|
196
158
|
}
|
|
@@ -221,13 +183,6 @@ export class SessionManager {
|
|
|
221
183
|
* Returns null if no valid token is available.
|
|
222
184
|
*/
|
|
223
185
|
getUser(): { userId: string; email: string } | null {
|
|
224
|
-
if (this.isDemoModeLocalOnly) {
|
|
225
|
-
return {
|
|
226
|
-
userId: DEMO_MODE_LOCAL_ONLY_USER_ID,
|
|
227
|
-
email: DEMO_MODE_LOCAL_ONLY_EMAIL,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
186
|
if (!this.token) {
|
|
232
187
|
return null;
|
|
233
188
|
}
|
package/src/env.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { createContext, useContext, useMemo } from "react";
|
|
2
|
-
import { SessionManager, SessionManagerConfig } from "../core/sessionManager";
|
|
3
|
-
import { useEveryAppSession } from "./_internal/useEveryAppSession";
|
|
4
|
-
import { useEveryAppRouter } from "./useEveryAppRouter";
|
|
5
|
-
import { GatewayRequiredError } from "./GatewayRequiredError";
|
|
2
|
+
import { SessionManager, SessionManagerConfig } from "../core/sessionManager.js";
|
|
3
|
+
import { useEveryAppSession } from "./_internal/useEveryAppSession.js";
|
|
4
|
+
import { useEveryAppRouter } from "./useEveryAppRouter.js";
|
|
5
|
+
import { GatewayRequiredError } from "./GatewayRequiredError.js";
|
|
6
6
|
|
|
7
7
|
interface EmbeddedProviderConfig extends SessionManagerConfig {
|
|
8
8
|
children: React.ReactNode;
|
|
@@ -27,8 +27,8 @@ export function EmbeddedAppProvider({
|
|
|
27
27
|
|
|
28
28
|
if (!sessionManager) return null;
|
|
29
29
|
|
|
30
|
-
// Check if the app is running outside of the Gateway iframe
|
|
31
|
-
if (!sessionManager.isInIframe
|
|
30
|
+
// Check if the app is running outside of the Gateway iframe
|
|
31
|
+
if (!sessionManager.isInIframe) {
|
|
32
32
|
return (
|
|
33
33
|
<GatewayRequiredError
|
|
34
34
|
gatewayOrigin={sessionManager.parentOrigin}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { SessionManager } from "../../core/sessionManager";
|
|
2
|
+
import { SessionManager } from "../../core/sessionManager.js";
|
|
3
3
|
|
|
4
4
|
interface SessionManagerConfig {
|
|
5
5
|
appId: string;
|
|
@@ -28,9 +28,8 @@ export function useEveryAppSession({
|
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
30
|
if (!sessionManager) return;
|
|
31
|
-
// Skip token requests when not in iframe
|
|
32
|
-
if (!sessionManager.isInIframe
|
|
33
|
-
return;
|
|
31
|
+
// Skip token requests when not in iframe - the app will show GatewayRequiredError instead
|
|
32
|
+
if (!sessionManager.isInIframe) return;
|
|
34
33
|
|
|
35
34
|
const interval = setInterval(() => {
|
|
36
35
|
setSessionTokenState(sessionManager.getTokenState());
|
package/src/tanstack/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { useSessionTokenClientMiddleware } from "./useSessionTokenClientMiddleware";
|
|
2
|
-
export { useEveryAppRouter } from "./useEveryAppRouter";
|
|
3
|
-
export { EmbeddedAppProvider, useCurrentUser } from "./EmbeddedAppProvider";
|
|
1
|
+
export { useSessionTokenClientMiddleware } from "./useSessionTokenClientMiddleware.js";
|
|
2
|
+
export { useEveryAppRouter } from "./useEveryAppRouter.js";
|
|
3
|
+
export { EmbeddedAppProvider, useCurrentUser } from "./EmbeddedAppProvider.js";
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import type { AuthConfig } from "./types";
|
|
1
|
+
import type { AuthConfig } from "./types.js";
|
|
2
2
|
import { env } from "cloudflare:workers";
|
|
3
|
-
import { isDemoModeLocalOnlyServer } from "../../shared/demoModeLocalOnly";
|
|
4
3
|
|
|
5
4
|
export function getAuthConfig(): AuthConfig {
|
|
6
|
-
const demoModeLocalOnlyEnv = (env as { DEMO_MODE_LOCAL_ONLY?: string })
|
|
7
|
-
.DEMO_MODE_LOCAL_ONLY;
|
|
8
|
-
const isDemoModeLocalOnly =
|
|
9
|
-
demoModeLocalOnlyEnv === "true" || isDemoModeLocalOnlyServer() === true;
|
|
10
|
-
const issuer = env.GATEWAY_URL || (isDemoModeLocalOnly ? "local" : "");
|
|
11
|
-
|
|
12
5
|
return {
|
|
13
|
-
issuer,
|
|
6
|
+
issuer: env.GATEWAY_URL,
|
|
14
7
|
audience: import.meta.env.VITE_APP_ID,
|
|
15
8
|
};
|
|
16
9
|
}
|
|
@@ -6,13 +6,8 @@ import {
|
|
|
6
6
|
JSONWebKeySet,
|
|
7
7
|
} from "jose";
|
|
8
8
|
|
|
9
|
-
import type { AuthConfig } from "./types";
|
|
9
|
+
import type { AuthConfig } from "./types.js";
|
|
10
10
|
import { env } from "cloudflare:workers";
|
|
11
|
-
import {
|
|
12
|
-
DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
13
|
-
createDemoModeLocalOnlySessionPayload,
|
|
14
|
-
isDemoModeLocalOnlyServer,
|
|
15
|
-
} from "../../shared/demoModeLocalOnly";
|
|
16
11
|
|
|
17
12
|
/**
|
|
18
13
|
* JWT payload structure for embedded app session tokens.
|
|
@@ -40,13 +35,7 @@ export async function authenticateRequest(
|
|
|
40
35
|
const request = providedRequest || getRequest();
|
|
41
36
|
const authHeader = request.headers.get("authorization");
|
|
42
37
|
|
|
43
|
-
const demoModeLocalOnlyEnv = (env as { DEMO_MODE_LOCAL_ONLY?: string })
|
|
44
|
-
.DEMO_MODE_LOCAL_ONLY;
|
|
45
|
-
const isDemoModeLocalOnly =
|
|
46
|
-
demoModeLocalOnlyEnv === "true" || isDemoModeLocalOnlyServer() === true;
|
|
47
|
-
|
|
48
38
|
if (!authHeader) {
|
|
49
|
-
console.log("No auth header found");
|
|
50
39
|
return null;
|
|
51
40
|
}
|
|
52
41
|
|
|
@@ -56,14 +45,6 @@ export async function authenticateRequest(
|
|
|
56
45
|
return null;
|
|
57
46
|
}
|
|
58
47
|
|
|
59
|
-
if (isDemoModeLocalOnly) {
|
|
60
|
-
if (token !== DEMO_MODE_LOCAL_ONLY_TOKEN) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return createDemoModeLocalOnlySessionPayload(authConfig.audience);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
48
|
try {
|
|
68
49
|
const session = await verifySessionToken(token, authConfig);
|
|
69
50
|
return session;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { authenticateRequest } from "./authenticateRequest";
|
|
2
|
-
export type { AuthConfig } from "./types";
|
|
3
|
-
export { getAuthConfig } from "./authConfig";
|
|
1
|
+
export { authenticateRequest } from "./authenticateRequest.js";
|
|
2
|
+
export type { AuthConfig } from "./types.js";
|
|
3
|
+
export { getAuthConfig } from "./authConfig.js";
|
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
-
import type { SessionManager } from "../core/sessionManager";
|
|
3
|
-
import {
|
|
4
|
-
DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
5
|
-
isDemoModeLocalOnlyClient,
|
|
6
|
-
} from "../shared/demoModeLocalOnly";
|
|
2
|
+
import type { SessionManager } from "../core/sessionManager.js";
|
|
7
3
|
|
|
8
4
|
export const useSessionTokenClientMiddleware = createMiddleware({
|
|
9
5
|
type: "function",
|
|
10
6
|
}).client(async ({ next }) => {
|
|
11
|
-
if (isDemoModeLocalOnlyClient()) {
|
|
12
|
-
return next({
|
|
13
|
-
headers: {
|
|
14
|
-
Authorization: `Bearer ${DEMO_MODE_LOCAL_ONLY_TOKEN}`,
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
7
|
// Get the global sessionManager - this MUST be available for embedded apps
|
|
20
8
|
const sessionManager = (window as any)
|
|
21
9
|
.__embeddedSessionManager as SessionManager;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
export const DEMO_MODE_LOCAL_ONLY_TOKEN = "DEMO_MODE_LOCAL_ONLY";
|
|
2
|
-
export const DEMO_MODE_LOCAL_ONLY_USER_ID = "demo-local-user";
|
|
3
|
-
export const DEMO_MODE_LOCAL_ONLY_EMAIL = "demo-local-user@local";
|
|
4
|
-
|
|
5
|
-
export function isDemoModeLocalOnlyClient(): boolean {
|
|
6
|
-
return import.meta.env.VITE_DEMO_MODE_LOCAL_ONLY === "true";
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function isDemoModeLocalOnlyServer(): boolean {
|
|
10
|
-
const metaEnv = (import.meta as { env?: Record<string, string | undefined> })
|
|
11
|
-
.env;
|
|
12
|
-
const metaValue =
|
|
13
|
-
metaEnv?.DEMO_MODE_LOCAL_ONLY ?? metaEnv?.VITE_DEMO_MODE_LOCAL_ONLY;
|
|
14
|
-
if (metaValue === "true") {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
typeof process !== "undefined" &&
|
|
20
|
-
process.env?.DEMO_MODE_LOCAL_ONLY === "true"
|
|
21
|
-
) {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createDemoModeLocalOnlySessionPayload(audience: string) {
|
|
29
|
-
const issuedAt = Math.floor(Date.now() / 1000);
|
|
30
|
-
const expiresAt = issuedAt + 60 * 60;
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
sub: DEMO_MODE_LOCAL_ONLY_USER_ID,
|
|
34
|
-
email: DEMO_MODE_LOCAL_ONLY_EMAIL,
|
|
35
|
-
iss: "local",
|
|
36
|
-
aud: audience,
|
|
37
|
-
iat: issuedAt,
|
|
38
|
-
exp: expiresAt,
|
|
39
|
-
};
|
|
40
|
-
}
|