@every-app/sdk 0.1.4 → 0.1.6
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 +63 -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/shared/demoModeLocalOnly.d.ts +6 -6
- package/dist/shared/demoModeLocalOnly.d.ts.map +1 -1
- package/dist/shared/demoModeLocalOnly.js +12 -11
- package/dist/tanstack/EmbeddedAppProvider.js +2 -2
- package/dist/tanstack/_internal/useEveryAppSession.d.ts.map +1 -1
- package/dist/tanstack/_internal/useEveryAppSession.js +2 -2
- package/dist/tanstack/server/authConfig.d.ts.map +1 -1
- package/dist/tanstack/server/authConfig.js +1 -5
- package/dist/tanstack/server/authenticateRequest.d.ts.map +1 -1
- package/dist/tanstack/server/authenticateRequest.js +0 -10
- package/dist/tanstack/useSessionTokenClientMiddleware.d.ts +5 -1
- package/dist/tanstack/useSessionTokenClientMiddleware.d.ts.map +1 -1
- package/dist/tanstack/useSessionTokenClientMiddleware.js +6 -9
- package/package.json +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/sessionManager.ts +8 -53
- package/src/env.d.ts +2 -0
- package/src/tanstack/EmbeddedAppProvider.tsx +2 -2
- package/src/tanstack/_internal/useEveryAppSession.tsx +2 -3
- package/src/tanstack/server/authConfig.ts +1 -8
- package/src/tanstack/server/authenticateRequest.ts +0 -19
- package/src/tanstack/useSessionTokenClientMiddleware.ts +8 -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,CAgBzC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
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 when available for zero-latency internal routing
|
|
23
|
+
// (available in production Workers, not in local dev)
|
|
24
|
+
if (env.EVERY_APP_GATEWAY) {
|
|
25
|
+
const url = new URL(authenticatedRequest.url);
|
|
26
|
+
const bindingUrl = `${SERVICE_BINDING_ORIGIN}${url.pathname}${url.search}`;
|
|
27
|
+
const bindingRequest = new Request(bindingUrl, authenticatedRequest);
|
|
28
|
+
return env.EVERY_APP_GATEWAY.fetch(bindingRequest);
|
|
29
|
+
}
|
|
30
|
+
// Fall back to HTTP fetch for local dev
|
|
31
|
+
return fetch(authenticatedRequest);
|
|
32
|
+
}
|
|
33
|
+
function applyAppTokenAuth(request, env) {
|
|
34
|
+
const appToken = getGatewayAppApiToken(env);
|
|
35
|
+
if (!appToken) {
|
|
36
|
+
throw new Error("GATEWAY_APP_API_TOKEN is required. Run `npx everyapp app deploy` to provision one.");
|
|
37
|
+
}
|
|
38
|
+
const headers = new Headers(request.headers);
|
|
39
|
+
headers.delete("authorization");
|
|
40
|
+
headers.set(APP_TOKEN_HEADER, appToken);
|
|
41
|
+
return new Request(request, { headers });
|
|
42
|
+
}
|
|
43
|
+
function getGatewayAppApiToken(env) {
|
|
44
|
+
const configuredToken = env.GATEWAY_APP_API_TOKEN?.trim();
|
|
45
|
+
if (configuredToken) {
|
|
46
|
+
return configuredToken;
|
|
47
|
+
}
|
|
48
|
+
const legacyToken = env.APP_TOKEN?.trim();
|
|
49
|
+
return legacyToken || null;
|
|
50
|
+
}
|
|
51
|
+
function toRequest(input, init, baseUrl) {
|
|
52
|
+
if (input instanceof Request) {
|
|
53
|
+
return init ? new Request(input, init) : input;
|
|
54
|
+
}
|
|
55
|
+
if (input instanceof URL) {
|
|
56
|
+
return new Request(input.toString(), init);
|
|
57
|
+
}
|
|
58
|
+
if (typeof input === "string" && baseUrl && !/^https?:\/\//i.test(input)) {
|
|
59
|
+
const normalizedPath = input.startsWith("/") ? input : `/${input}`;
|
|
60
|
+
return new Request(new URL(normalizedPath, baseUrl).toString(), init);
|
|
61
|
+
}
|
|
62
|
+
return new Request(input, init);
|
|
63
|
+
}
|
|
@@ -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 { LOCAL_ONLY_TOKEN, isLocalOnlyClient, } 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 (isLocalOnlyClient()) {
|
|
7
|
-
return 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 { LOCAL_ONLY_EMAIL, LOCAL_ONLY_TOKEN, LOCAL_ONLY_USER_ID, isLocalOnlyClient, } 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 = isLocalOnlyClient();
|
|
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: 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: 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: LOCAL_ONLY_USER_ID,
|
|
166
|
-
email: LOCAL_ONLY_EMAIL,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
137
|
if (!this.token) {
|
|
170
138
|
return null;
|
|
171
139
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
1
|
+
export declare const DEMO_MODE_LOCAL_ONLY_TOKEN = "DEMO_MODE_LOCAL_ONLY";
|
|
2
|
+
export declare const DEMO_MODE_LOCAL_ONLY_USER_ID = "demo-local-user";
|
|
3
|
+
export declare const DEMO_MODE_LOCAL_ONLY_EMAIL = "demo-local-user@local";
|
|
4
|
+
export declare function isDemoModeLocalOnlyClient(): boolean;
|
|
5
|
+
export declare function isDemoModeLocalOnlyServer(): boolean;
|
|
6
|
+
export declare function createDemoModeLocalOnlySessionPayload(audience: string): {
|
|
7
7
|
sub: string;
|
|
8
8
|
email: string;
|
|
9
9
|
iss: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demoModeLocalOnly.d.ts","sourceRoot":"","sources":["../../src/shared/demoModeLocalOnly.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"demoModeLocalOnly.d.ts","sourceRoot":"","sources":["../../src/shared/demoModeLocalOnly.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AACjE,eAAO,MAAM,4BAA4B,oBAAoB,CAAC;AAC9D,eAAO,MAAM,0BAA0B,0BAA0B,CAAC;AAElE,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,wBAAgB,yBAAyB,IAAI,OAAO,CAiBnD;AAED,wBAAgB,qCAAqC,CAAC,QAAQ,EAAE,MAAM;;;;;;;EAYrE"}
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
4
|
-
export function
|
|
5
|
-
return import.meta.env.
|
|
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
|
+
export function isDemoModeLocalOnlyClient() {
|
|
5
|
+
return import.meta.env.VITE_DEMO_MODE_LOCAL_ONLY === "true";
|
|
6
6
|
}
|
|
7
|
-
export function
|
|
7
|
+
export function isDemoModeLocalOnlyServer() {
|
|
8
8
|
const metaEnv = import.meta
|
|
9
9
|
.env;
|
|
10
|
-
const metaValue = metaEnv?.
|
|
10
|
+
const metaValue = metaEnv?.DEMO_MODE_LOCAL_ONLY ?? metaEnv?.VITE_DEMO_MODE_LOCAL_ONLY;
|
|
11
11
|
if (metaValue === "true") {
|
|
12
12
|
return true;
|
|
13
13
|
}
|
|
14
|
-
if (typeof process !== "undefined" &&
|
|
14
|
+
if (typeof process !== "undefined" &&
|
|
15
|
+
process.env?.DEMO_MODE_LOCAL_ONLY === "true") {
|
|
15
16
|
return true;
|
|
16
17
|
}
|
|
17
18
|
return false;
|
|
18
19
|
}
|
|
19
|
-
export function
|
|
20
|
+
export function createDemoModeLocalOnlySessionPayload(audience) {
|
|
20
21
|
const issuedAt = Math.floor(Date.now() / 1000);
|
|
21
22
|
const expiresAt = issuedAt + 60 * 60;
|
|
22
23
|
return {
|
|
23
|
-
sub:
|
|
24
|
-
email:
|
|
24
|
+
sub: DEMO_MODE_LOCAL_ONLY_USER_ID,
|
|
25
|
+
email: DEMO_MODE_LOCAL_ONLY_EMAIL,
|
|
25
26
|
iss: "local",
|
|
26
27
|
aud: audience,
|
|
27
28
|
iat: issuedAt,
|
|
@@ -11,8 +11,8 @@ export function EmbeddedAppProvider({ children, ...config }) {
|
|
|
11
11
|
useEveryAppRouter({ sessionManager });
|
|
12
12
|
if (!sessionManager)
|
|
13
13
|
return null;
|
|
14
|
-
// Check if the app is running outside of the Gateway iframe
|
|
15
|
-
if (!sessionManager.isInIframe
|
|
14
|
+
// Check if the app is running outside of the Gateway iframe
|
|
15
|
+
if (!sessionManager.isInIframe) {
|
|
16
16
|
return (_jsx(GatewayRequiredError, { gatewayOrigin: sessionManager.parentOrigin, appId: config.appId }));
|
|
17
17
|
}
|
|
18
18
|
const value = {
|
|
@@ -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());
|
|
@@ -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,11 +1,7 @@
|
|
|
1
1
|
import { env } from "cloudflare:workers";
|
|
2
|
-
import { isLocalOnlyServer } from "../../shared/demoModeLocalOnly";
|
|
3
2
|
export function getAuthConfig() {
|
|
4
|
-
const demoModeLocalOnlyEnv = env.LOCAL_ONLY;
|
|
5
|
-
const isDemoModeLocalOnly = demoModeLocalOnlyEnv === "true" || isLocalOnlyServer() === true;
|
|
6
|
-
const issuer = env.GATEWAY_URL || (isDemoModeLocalOnly ? "local" : "");
|
|
7
3
|
return {
|
|
8
|
-
issuer,
|
|
4
|
+
issuer: env.GATEWAY_URL,
|
|
9
5
|
audience: import.meta.env.VITE_APP_ID,
|
|
10
6
|
};
|
|
11
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,26 +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 { LOCAL_ONLY_TOKEN, createLocalOnlySessionPayload, isLocalOnlyServer, } 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.LOCAL_ONLY;
|
|
9
|
-
const isDemoModeLocalOnly = demoModeLocalOnlyEnv === "true" || isLocalOnlyServer() === true;
|
|
10
7
|
if (!authHeader) {
|
|
11
|
-
console.log("No auth header found");
|
|
12
8
|
return null;
|
|
13
9
|
}
|
|
14
10
|
const token = extractBearerToken(authHeader);
|
|
15
11
|
if (!token) {
|
|
16
12
|
return null;
|
|
17
13
|
}
|
|
18
|
-
if (isDemoModeLocalOnly) {
|
|
19
|
-
if (token !== LOCAL_ONLY_TOKEN) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
return createLocalOnlySessionPayload(authConfig.audience);
|
|
23
|
-
}
|
|
24
14
|
try {
|
|
25
15
|
const session = await verifySessionToken(token, authConfig);
|
|
26
16
|
return session;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Exported as `any` to avoid cross-package type identity issues when apps and
|
|
3
|
+
* the SDK resolve different `@tanstack/react-start` instances in a monorepo.
|
|
4
|
+
*/
|
|
5
|
+
export declare const useSessionTokenClientMiddleware: any;
|
|
2
6
|
//# sourceMappingURL=useSessionTokenClientMiddleware.d.ts.map
|
|
@@ -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":"AAgCA;;;GAGG;AACH,eAAO,MAAM,+BAA+B,EAAE,GACZ,CAAC"}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
-
|
|
3
|
-
export const useSessionTokenClientMiddleware = createMiddleware({
|
|
2
|
+
const _useSessionTokenClientMiddleware = createMiddleware({
|
|
4
3
|
type: "function",
|
|
5
4
|
}).client(async ({ next }) => {
|
|
6
|
-
if (isLocalOnlyClient()) {
|
|
7
|
-
return next({
|
|
8
|
-
headers: {
|
|
9
|
-
Authorization: `Bearer ${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;
|
|
@@ -27,3 +19,8 @@ export const useSessionTokenClientMiddleware = createMiddleware({
|
|
|
27
19
|
},
|
|
28
20
|
});
|
|
29
21
|
});
|
|
22
|
+
/**
|
|
23
|
+
* Exported as `any` to avoid cross-package type identity issues when apps and
|
|
24
|
+
* the SDK resolve different `@tanstack/react-start` instances in a monorepo.
|
|
25
|
+
*/
|
|
26
|
+
export const useSessionTokenClientMiddleware = _useSessionTokenClientMiddleware;
|
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -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
|
@@ -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}
|
|
@@ -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());
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import type { AuthConfig } from "./types";
|
|
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
|
}
|
|
@@ -8,11 +8,6 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import type { AuthConfig } from "./types";
|
|
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,21 +1,9 @@
|
|
|
1
1
|
import { createMiddleware } from "@tanstack/react-start";
|
|
2
2
|
import type { SessionManager } from "../core/sessionManager";
|
|
3
|
-
import {
|
|
4
|
-
DEMO_MODE_LOCAL_ONLY_TOKEN,
|
|
5
|
-
isDemoModeLocalOnlyClient,
|
|
6
|
-
} from "../shared/demoModeLocalOnly";
|
|
7
3
|
|
|
8
|
-
|
|
4
|
+
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;
|
|
@@ -41,3 +29,10 @@ export const useSessionTokenClientMiddleware = createMiddleware({
|
|
|
41
29
|
},
|
|
42
30
|
});
|
|
43
31
|
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Exported as `any` to avoid cross-package type identity issues when apps and
|
|
35
|
+
* the SDK resolve different `@tanstack/react-start` instances in a monorepo.
|
|
36
|
+
*/
|
|
37
|
+
export const useSessionTokenClientMiddleware: any =
|
|
38
|
+
_useSessionTokenClientMiddleware;
|
|
@@ -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
|
-
}
|