@every-app/sdk 0.1.14 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/identity.d.ts +73 -0
- package/dist/internal/identity.d.ts.map +1 -0
- package/dist/internal/identity.js +192 -0
- package/dist/internal/index.d.ts +3 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +2 -0
- package/dist/internal/protocol.d.ts +111 -0
- package/dist/internal/protocol.d.ts.map +1 -0
- package/dist/internal/protocol.js +139 -0
- package/dist/server/everyApp.d.ts +55 -0
- package/dist/server/everyApp.d.ts.map +1 -0
- package/dist/server/everyApp.js +121 -0
- package/dist/server/gatewayFetch.d.ts +21 -0
- package/dist/server/gatewayFetch.d.ts.map +1 -0
- package/dist/server/gatewayFetch.js +70 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +8 -0
- package/dist/server/mcp.d.ts +66 -0
- package/dist/server/mcp.d.ts.map +1 -0
- package/dist/server/mcp.js +147 -0
- package/package.json +20 -43
- package/dist/cloudflare/getLocalD1Url.d.ts +0 -2
- package/dist/cloudflare/getLocalD1Url.d.ts.map +0 -1
- package/dist/cloudflare/getLocalD1Url.js +0 -40
- package/dist/cloudflare/index.d.ts +0 -2
- package/dist/cloudflare/index.d.ts.map +0 -1
- package/dist/cloudflare/index.js +0 -1
- package/dist/cloudflare/lazyInit.d.ts +0 -22
- package/dist/cloudflare/lazyInit.d.ts.map +0 -1
- package/dist/cloudflare/lazyInit.js +0 -65
- package/dist/cloudflare/server/gateway.d.ts +0 -29
- package/dist/cloudflare/server/gateway.d.ts.map +0 -1
- package/dist/cloudflare/server/gateway.js +0 -69
- package/dist/cloudflare/server/index.d.ts +0 -3
- package/dist/cloudflare/server/index.d.ts.map +0 -1
- package/dist/cloudflare/server/index.js +0 -2
- package/dist/core/authenticatedFetch.d.ts +0 -9
- package/dist/core/authenticatedFetch.d.ts.map +0 -1
- package/dist/core/authenticatedFetch.js +0 -31
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -2
- package/dist/core/sessionManager.d.ts +0 -58
- package/dist/core/sessionManager.d.ts.map +0 -1
- package/dist/core/sessionManager.js +0 -357
- package/dist/shared/bypassGatewayLocalOnly.d.ts +0 -15
- package/dist/shared/bypassGatewayLocalOnly.d.ts.map +0 -1
- package/dist/shared/bypassGatewayLocalOnly.js +0 -45
- package/dist/shared/parseMessagePayload.d.ts +0 -3
- package/dist/shared/parseMessagePayload.d.ts.map +0 -1
- package/dist/shared/parseMessagePayload.js +0 -18
- package/dist/tanstack/EmbeddedAppProvider.d.ts +0 -30
- package/dist/tanstack/EmbeddedAppProvider.d.ts.map +0 -1
- package/dist/tanstack/EmbeddedAppProvider.js +0 -57
- package/dist/tanstack/GatewayRequiredError.d.ts +0 -20
- package/dist/tanstack/GatewayRequiredError.d.ts.map +0 -1
- package/dist/tanstack/GatewayRequiredError.js +0 -91
- package/dist/tanstack/_internal/useEveryAppSession.d.ts +0 -18
- package/dist/tanstack/_internal/useEveryAppSession.d.ts.map +0 -1
- package/dist/tanstack/_internal/useEveryAppSession.js +0 -47
- package/dist/tanstack/index.d.ts +0 -4
- package/dist/tanstack/index.d.ts.map +0 -1
- package/dist/tanstack/index.js +0 -3
- package/dist/tanstack/server/authConfig.d.ts +0 -3
- package/dist/tanstack/server/authConfig.d.ts.map +0 -1
- package/dist/tanstack/server/authConfig.js +0 -13
- package/dist/tanstack/server/authenticateRequest.d.ts +0 -31
- package/dist/tanstack/server/authenticateRequest.d.ts.map +0 -1
- package/dist/tanstack/server/authenticateRequest.js +0 -154
- package/dist/tanstack/server/index.d.ts +0 -4
- package/dist/tanstack/server/index.d.ts.map +0 -1
- package/dist/tanstack/server/index.js +0 -2
- package/dist/tanstack/server/types.d.ts +0 -5
- package/dist/tanstack/server/types.d.ts.map +0 -1
- package/dist/tanstack/server/types.js +0 -1
- package/dist/tanstack/useEveryAppRouter.d.ts +0 -7
- package/dist/tanstack/useEveryAppRouter.d.ts.map +0 -1
- package/dist/tanstack/useEveryAppRouter.js +0 -66
- package/dist/tanstack/useSessionTokenClientMiddleware.d.ts +0 -2
- package/dist/tanstack/useSessionTokenClientMiddleware.d.ts.map +0 -1
- package/dist/tanstack/useSessionTokenClientMiddleware.js +0 -29
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wraps a factory function in a Proxy to defer initialization until first access.
|
|
3
|
-
* This prevents async operations (Like creating Tanstack DB Collections) from running in Cloudflare Workers' global scope.
|
|
4
|
-
*
|
|
5
|
-
* @param factory - A function that creates and returns the resource.
|
|
6
|
-
* Must be a callback to defer execution; passing the value directly
|
|
7
|
-
* would evaluate it at module load time, triggering the Cloudflare error.
|
|
8
|
-
* @returns A Proxy that lazily initializes the resource on first property access
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* export const myCollection = lazyInitForWorkers(() =>
|
|
13
|
-
* createCollection(queryCollectionOptions({
|
|
14
|
-
* queryKey: ["myData"],
|
|
15
|
-
* queryFn: async () => fetchData(),
|
|
16
|
-
* // ... other options
|
|
17
|
-
* }))
|
|
18
|
-
* );
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function lazyInitForWorkers(factory) {
|
|
22
|
-
// Closure: This variable is captured by getInstance() and the Proxy traps below.
|
|
23
|
-
// It remains in memory as long as the returned Proxy is referenced, enabling singleton behavior.
|
|
24
|
-
let instance = null;
|
|
25
|
-
function getInstance() {
|
|
26
|
-
if (!instance) {
|
|
27
|
-
instance = factory();
|
|
28
|
-
}
|
|
29
|
-
return instance;
|
|
30
|
-
}
|
|
31
|
-
return new Proxy({}, {
|
|
32
|
-
get(_, prop) {
|
|
33
|
-
const inst = getInstance();
|
|
34
|
-
const value = inst[prop];
|
|
35
|
-
// Bind methods to the instance to preserve `this` context
|
|
36
|
-
return typeof value === "function" ? value.bind(inst) : value;
|
|
37
|
-
},
|
|
38
|
-
set(_, prop, value) {
|
|
39
|
-
const inst = getInstance();
|
|
40
|
-
inst[prop] = value;
|
|
41
|
-
return true;
|
|
42
|
-
},
|
|
43
|
-
deleteProperty(_, prop) {
|
|
44
|
-
const inst = getInstance();
|
|
45
|
-
delete inst[prop];
|
|
46
|
-
return true;
|
|
47
|
-
},
|
|
48
|
-
has(_, prop) {
|
|
49
|
-
const inst = getInstance();
|
|
50
|
-
return prop in inst;
|
|
51
|
-
},
|
|
52
|
-
ownKeys(_) {
|
|
53
|
-
const inst = getInstance();
|
|
54
|
-
return Reflect.ownKeys(inst);
|
|
55
|
-
},
|
|
56
|
-
getOwnPropertyDescriptor(_, prop) {
|
|
57
|
-
const inst = getInstance();
|
|
58
|
-
return Reflect.getOwnPropertyDescriptor(inst, prop);
|
|
59
|
-
},
|
|
60
|
-
getPrototypeOf(_) {
|
|
61
|
-
const inst = getInstance();
|
|
62
|
-
return Reflect.getPrototypeOf(inst);
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,69 +0,0 @@
|
|
|
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 in production for zero-latency internal routing.
|
|
23
|
-
// In local dev wrangler still exposes the binding object, but the target
|
|
24
|
-
// service usually isn't running locally, so we skip it and use HTTP fetch.
|
|
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
|
-
// HTTP fetch – used in local dev, or as a fallback when no binding exists
|
|
32
|
-
return fetch(authenticatedRequest);
|
|
33
|
-
}
|
|
34
|
-
function applyAppTokenAuth(request, env) {
|
|
35
|
-
const gatewayOrigin = new URL(getGatewayUrl(env)).origin;
|
|
36
|
-
const requestOrigin = new URL(request.url).origin;
|
|
37
|
-
if (requestOrigin !== gatewayOrigin) {
|
|
38
|
-
throw new Error(`Refusing to send gateway token to non-gateway origin: ${requestOrigin}`);
|
|
39
|
-
}
|
|
40
|
-
const appToken = getGatewayAppApiToken(env);
|
|
41
|
-
if (!appToken) {
|
|
42
|
-
throw new Error("GATEWAY_APP_API_TOKEN is required. Run `npx everyapp app deploy` to provision one.");
|
|
43
|
-
}
|
|
44
|
-
const headers = new Headers(request.headers);
|
|
45
|
-
headers.delete("authorization");
|
|
46
|
-
headers.set(APP_TOKEN_HEADER, appToken);
|
|
47
|
-
return new Request(request, { headers });
|
|
48
|
-
}
|
|
49
|
-
function getGatewayAppApiToken(env) {
|
|
50
|
-
const configuredToken = env.GATEWAY_APP_API_TOKEN?.trim();
|
|
51
|
-
if (configuredToken) {
|
|
52
|
-
return configuredToken;
|
|
53
|
-
}
|
|
54
|
-
const legacyToken = env.APP_TOKEN?.trim();
|
|
55
|
-
return legacyToken || null;
|
|
56
|
-
}
|
|
57
|
-
function toRequest(input, init, baseUrl) {
|
|
58
|
-
if (input instanceof Request) {
|
|
59
|
-
return init ? new Request(input, init) : input;
|
|
60
|
-
}
|
|
61
|
-
if (input instanceof URL) {
|
|
62
|
-
return new Request(input.toString(), init);
|
|
63
|
-
}
|
|
64
|
-
if (typeof input === "string" && baseUrl && !/^https?:\/\//i.test(input)) {
|
|
65
|
-
const normalizedPath = input.startsWith("/") ? input : `/${input}`;
|
|
66
|
-
return new Request(new URL(normalizedPath, baseUrl).toString(), init);
|
|
67
|
-
}
|
|
68
|
-
return new Request(input, init);
|
|
69
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
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,cAAc,CAAC"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gets the current session token from the embedded session manager
|
|
3
|
-
*/
|
|
4
|
-
export declare function getSessionToken(): Promise<string>;
|
|
5
|
-
/**
|
|
6
|
-
* Performs a fetch request with the authorization header automatically added
|
|
7
|
-
*/
|
|
8
|
-
export declare function authenticatedFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
9
|
-
//# sourceMappingURL=authenticatedFetch.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authenticatedFetch.d.ts","sourceRoot":"","sources":["../../src/core/authenticatedFetch.ts"],"names":[],"mappings":"AAaA;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAmBvD;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { BYPASS_GATEWAY_LOCAL_ONLY_TOKEN, isBypassGatewayLocalOnlyClient, } from "../shared/bypassGatewayLocalOnly.js";
|
|
2
|
-
/**
|
|
3
|
-
* Gets the current session token from the embedded session manager
|
|
4
|
-
*/
|
|
5
|
-
export async function getSessionToken() {
|
|
6
|
-
if (isBypassGatewayLocalOnlyClient()) {
|
|
7
|
-
return BYPASS_GATEWAY_LOCAL_ONLY_TOKEN;
|
|
8
|
-
}
|
|
9
|
-
const windowWithSession = window;
|
|
10
|
-
const sessionManager = windowWithSession.__embeddedSessionManager;
|
|
11
|
-
if (!sessionManager) {
|
|
12
|
-
throw new Error("Session manager not available");
|
|
13
|
-
}
|
|
14
|
-
const token = await sessionManager.getToken();
|
|
15
|
-
if (!token) {
|
|
16
|
-
throw new Error("No token available");
|
|
17
|
-
}
|
|
18
|
-
return token;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Performs a fetch request with the authorization header automatically added
|
|
22
|
-
*/
|
|
23
|
-
export async function authenticatedFetch(input, init) {
|
|
24
|
-
const token = await getSessionToken();
|
|
25
|
-
const headers = new Headers(init?.headers);
|
|
26
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
27
|
-
return fetch(input, {
|
|
28
|
-
...init,
|
|
29
|
-
headers,
|
|
30
|
-
});
|
|
31
|
-
}
|
package/dist/core/index.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { SessionManager, isRunningInIframe, isRunningInReactNativeWebView, detectEnvironment, } from "./sessionManager.js";
|
|
2
|
-
export type { SessionManagerConfig, EmbeddedEnvironment, } from "./sessionManager.js";
|
|
3
|
-
export { authenticatedFetch, getSessionToken } from "./authenticatedFetch.js";
|
|
4
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,6BAA6B,EAC7B,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/core/index.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
export interface SessionManagerConfig {
|
|
2
|
-
appId: string;
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* Environment detection types
|
|
6
|
-
*/
|
|
7
|
-
export type EmbeddedEnvironment = "iframe" | "react-native-webview" | "standalone";
|
|
8
|
-
/**
|
|
9
|
-
* Detects whether the current window is running inside an iframe.
|
|
10
|
-
* Returns true if in an iframe, false if running as top-level window.
|
|
11
|
-
*/
|
|
12
|
-
export declare function isRunningInIframe(): boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Detects whether the current window is running inside a React Native WebView.
|
|
15
|
-
* Returns true if window.ReactNativeWebView is available.
|
|
16
|
-
*/
|
|
17
|
-
export declare function isRunningInReactNativeWebView(): boolean;
|
|
18
|
-
/**
|
|
19
|
-
* Detects the current embedded environment.
|
|
20
|
-
* Priority: React Native WebView > iframe > standalone
|
|
21
|
-
*/
|
|
22
|
-
export declare function detectEnvironment(): EmbeddedEnvironment;
|
|
23
|
-
export declare class SessionManager {
|
|
24
|
-
readonly parentOrigin: string;
|
|
25
|
-
readonly appId: string;
|
|
26
|
-
readonly isInIframe: boolean;
|
|
27
|
-
readonly environment: EmbeddedEnvironment;
|
|
28
|
-
readonly isBypassGatewayLocalOnly: boolean;
|
|
29
|
-
private token;
|
|
30
|
-
private refreshPromise;
|
|
31
|
-
private tokenWaiters;
|
|
32
|
-
constructor(config: SessionManagerConfig);
|
|
33
|
-
/**
|
|
34
|
-
* Check if running in an embedded environment (iframe or React Native WebView)
|
|
35
|
-
*/
|
|
36
|
-
isEmbedded(): boolean;
|
|
37
|
-
isTrustedHostMessage(event: MessageEvent): boolean;
|
|
38
|
-
postToHost(message: object): void;
|
|
39
|
-
private isTokenExpiringSoon;
|
|
40
|
-
private postMessageWithResponse;
|
|
41
|
-
private setupReactNativeTokenListener;
|
|
42
|
-
private waitForReactNativeTokenPush;
|
|
43
|
-
requestNewToken(): Promise<string>;
|
|
44
|
-
getToken(): Promise<string>;
|
|
45
|
-
getTokenState(): {
|
|
46
|
-
status: "NO_TOKEN" | "VALID" | "EXPIRED" | "REFRESHING";
|
|
47
|
-
token: string | null;
|
|
48
|
-
};
|
|
49
|
-
/**
|
|
50
|
-
* Extracts user information from the current JWT token.
|
|
51
|
-
* Returns null if no valid token is available.
|
|
52
|
-
*/
|
|
53
|
-
getUser(): {
|
|
54
|
-
userId: string;
|
|
55
|
-
email: string;
|
|
56
|
-
} | null;
|
|
57
|
-
}
|
|
58
|
-
//# sourceMappingURL=sessionManager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sessionManager.d.ts","sourceRoot":"","sources":["../../src/core/sessionManager.ts"],"names":[],"mappings":"AAiEA,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAOD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAC3B,QAAQ,GACR,sBAAsB,GACtB,YAAY,CAAC;AAEjB;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAY3C;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CASvD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,mBAAmB,CAWvD;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,wBAAwB,EAAE,OAAO,CAAC;IAE3C,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,cAAc,CAAgC;IACtD,OAAO,CAAC,YAAY,CAIZ;gBAEI,MAAM,EAAE,oBAAoB;IAyCxC;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO;IAelD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAiBjC,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,uBAAuB;IA+C/B,OAAO,CAAC,6BAA6B;IAgErC,OAAO,CAAC,2BAA2B;IAmB7B,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAgElC,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAcjC,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;CA0BpD"}
|
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
import { BYPASS_GATEWAY_LOCAL_ONLY_EMAIL, BYPASS_GATEWAY_LOCAL_ONLY_TOKEN, BYPASS_GATEWAY_LOCAL_ONLY_USER_ID, isBypassGatewayLocalOnlyClient, } from "../shared/bypassGatewayLocalOnly.js";
|
|
2
|
-
import { parseMessagePayload } from "../shared/parseMessagePayload.js";
|
|
3
|
-
function isTokenUpdateMessage(data) {
|
|
4
|
-
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
5
|
-
return false;
|
|
6
|
-
}
|
|
7
|
-
return data.type === "SESSION_TOKEN_UPDATE";
|
|
8
|
-
}
|
|
9
|
-
function decodeJwtPayload(token) {
|
|
10
|
-
try {
|
|
11
|
-
const parts = token.split(".");
|
|
12
|
-
if (parts.length !== 3) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
16
|
-
const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
|
|
17
|
-
return JSON.parse(atob(padded));
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function tokenAudienceMatchesApp(payload, appId) {
|
|
24
|
-
const aud = payload.aud;
|
|
25
|
-
if (typeof aud === "string") {
|
|
26
|
-
return aud === appId;
|
|
27
|
-
}
|
|
28
|
-
if (Array.isArray(aud)) {
|
|
29
|
-
return aud.some((value) => value === appId);
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
const MESSAGE_TIMEOUT_MS = 5000;
|
|
34
|
-
const TOKEN_EXPIRY_BUFFER_MS = 10000;
|
|
35
|
-
const DEFAULT_TOKEN_LIFETIME_MS = 60000;
|
|
36
|
-
const REACT_NATIVE_TOKEN_WAIT_TIMEOUT_MS = 10000;
|
|
37
|
-
/**
|
|
38
|
-
* Detects whether the current window is running inside an iframe.
|
|
39
|
-
* Returns true if in an iframe, false if running as top-level window.
|
|
40
|
-
*/
|
|
41
|
-
export function isRunningInIframe() {
|
|
42
|
-
if (typeof window === "undefined") {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
return window.self !== window.top;
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
// If we can't access window.top due to cross-origin restrictions,
|
|
50
|
-
// we're definitely in an iframe
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Detects whether the current window is running inside a React Native WebView.
|
|
56
|
-
* Returns true if window.ReactNativeWebView is available.
|
|
57
|
-
*/
|
|
58
|
-
export function isRunningInReactNativeWebView() {
|
|
59
|
-
if (typeof window === "undefined") {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
return (typeof window.ReactNativeWebView?.postMessage === "function" ||
|
|
63
|
-
window.isReactNativeWebView === true);
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Detects the current embedded environment.
|
|
67
|
-
* Priority: React Native WebView > iframe > standalone
|
|
68
|
-
*/
|
|
69
|
-
export function detectEnvironment() {
|
|
70
|
-
const isRNWebView = isRunningInReactNativeWebView();
|
|
71
|
-
const isIframe = isRunningInIframe();
|
|
72
|
-
if (isRNWebView) {
|
|
73
|
-
return "react-native-webview";
|
|
74
|
-
}
|
|
75
|
-
if (isIframe) {
|
|
76
|
-
return "iframe";
|
|
77
|
-
}
|
|
78
|
-
return "standalone";
|
|
79
|
-
}
|
|
80
|
-
export class SessionManager {
|
|
81
|
-
parentOrigin;
|
|
82
|
-
appId;
|
|
83
|
-
isInIframe;
|
|
84
|
-
environment;
|
|
85
|
-
isBypassGatewayLocalOnly;
|
|
86
|
-
token = null;
|
|
87
|
-
refreshPromise = null;
|
|
88
|
-
tokenWaiters = [];
|
|
89
|
-
constructor(config) {
|
|
90
|
-
if (!config.appId) {
|
|
91
|
-
throw new Error("[SessionManager] appId is required.");
|
|
92
|
-
}
|
|
93
|
-
this.isBypassGatewayLocalOnly = isBypassGatewayLocalOnlyClient();
|
|
94
|
-
const gatewayUrl = import.meta.env.VITE_GATEWAY_URL;
|
|
95
|
-
if (!this.isBypassGatewayLocalOnly) {
|
|
96
|
-
if (!gatewayUrl) {
|
|
97
|
-
throw new Error("[SessionManager] VITE_GATEWAY_URL env var is required.");
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
new URL(gatewayUrl);
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
throw new Error(`[SessionManager] Invalid gateway URL: ${gatewayUrl}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
this.appId = config.appId;
|
|
107
|
-
this.parentOrigin = this.isBypassGatewayLocalOnly
|
|
108
|
-
? window.location.origin
|
|
109
|
-
: gatewayUrl;
|
|
110
|
-
this.environment = detectEnvironment();
|
|
111
|
-
this.isInIframe = isRunningInIframe();
|
|
112
|
-
if (this.environment === "react-native-webview") {
|
|
113
|
-
this.setupReactNativeTokenListener();
|
|
114
|
-
}
|
|
115
|
-
if (this.isBypassGatewayLocalOnly) {
|
|
116
|
-
this.token = {
|
|
117
|
-
token: BYPASS_GATEWAY_LOCAL_ONLY_TOKEN,
|
|
118
|
-
expiresAt: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Check if running in an embedded environment (iframe or React Native WebView)
|
|
124
|
-
*/
|
|
125
|
-
isEmbedded() {
|
|
126
|
-
return this.environment !== "standalone";
|
|
127
|
-
}
|
|
128
|
-
isTrustedHostMessage(event) {
|
|
129
|
-
if (this.environment === "react-native-webview") {
|
|
130
|
-
const origin = event
|
|
131
|
-
.origin;
|
|
132
|
-
return (origin === "react-native" ||
|
|
133
|
-
origin === "null" ||
|
|
134
|
-
origin === "" ||
|
|
135
|
-
origin == null);
|
|
136
|
-
}
|
|
137
|
-
return event.origin === this.parentOrigin;
|
|
138
|
-
}
|
|
139
|
-
postToHost(message) {
|
|
140
|
-
if (this.environment === "react-native-webview") {
|
|
141
|
-
const postMessage = window.ReactNativeWebView?.postMessage;
|
|
142
|
-
if (typeof postMessage !== "function") {
|
|
143
|
-
throw new Error("React Native WebView bridge is unavailable");
|
|
144
|
-
}
|
|
145
|
-
postMessage.call(window.ReactNativeWebView, JSON.stringify(message));
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
window.parent.postMessage(message, this.parentOrigin);
|
|
149
|
-
}
|
|
150
|
-
isTokenExpiringSoon(bufferMs = TOKEN_EXPIRY_BUFFER_MS) {
|
|
151
|
-
if (!this.token)
|
|
152
|
-
return true;
|
|
153
|
-
return Date.now() >= this.token.expiresAt - bufferMs;
|
|
154
|
-
}
|
|
155
|
-
postMessageWithResponse(request, responseType, requestId) {
|
|
156
|
-
return new Promise((resolve, reject) => {
|
|
157
|
-
const cleanup = () => {
|
|
158
|
-
clearTimeout(timeout);
|
|
159
|
-
window.removeEventListener("message", handler);
|
|
160
|
-
};
|
|
161
|
-
const handler = (event) => {
|
|
162
|
-
// Security: validate message origin based on environment
|
|
163
|
-
if (!this.isTrustedHostMessage(event))
|
|
164
|
-
return;
|
|
165
|
-
const data = parseMessagePayload(event.data);
|
|
166
|
-
if (!data)
|
|
167
|
-
return;
|
|
168
|
-
if (data.type === responseType && data.requestId === requestId) {
|
|
169
|
-
cleanup();
|
|
170
|
-
if (typeof data.error === "string" && data.error) {
|
|
171
|
-
reject(new Error(data.error));
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
resolve(data);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
const timeout = setTimeout(() => {
|
|
179
|
-
cleanup();
|
|
180
|
-
reject(new Error("Token request timeout - parent did not respond"));
|
|
181
|
-
}, MESSAGE_TIMEOUT_MS);
|
|
182
|
-
window.addEventListener("message", handler);
|
|
183
|
-
try {
|
|
184
|
-
this.postToHost(request);
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
cleanup();
|
|
188
|
-
reject(error instanceof Error
|
|
189
|
-
? error
|
|
190
|
-
: new Error("Failed to post message to host"));
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
setupReactNativeTokenListener() {
|
|
195
|
-
window.addEventListener("message", (event) => {
|
|
196
|
-
if (!this.isTrustedHostMessage(event)) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const data = parseMessagePayload(event.data);
|
|
200
|
-
if (!data) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (!isTokenUpdateMessage(data)) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const message = data;
|
|
207
|
-
if (!message.token || typeof message.token !== "string") {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (typeof message.appId !== "string" || message.appId !== this.appId) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
if (message.expiresAt !== undefined &&
|
|
214
|
-
typeof message.expiresAt !== "string") {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const payload = decodeJwtPayload(message.token);
|
|
218
|
-
if (!payload || !tokenAudienceMatchesApp(payload, this.appId)) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
// Native controls token minting and pushes updates into the embedded app.
|
|
222
|
-
// We only consume pushed tokens in React Native WebView mode.
|
|
223
|
-
let expiresAt = Date.now() + DEFAULT_TOKEN_LIFETIME_MS;
|
|
224
|
-
if (message.expiresAt) {
|
|
225
|
-
const parsed = new Date(message.expiresAt).getTime();
|
|
226
|
-
if (!Number.isNaN(parsed)) {
|
|
227
|
-
expiresAt = parsed;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
this.token = {
|
|
231
|
-
token: message.token,
|
|
232
|
-
expiresAt,
|
|
233
|
-
};
|
|
234
|
-
if (this.tokenWaiters.length > 0) {
|
|
235
|
-
const waiters = this.tokenWaiters;
|
|
236
|
-
this.tokenWaiters = [];
|
|
237
|
-
for (const waiter of waiters) {
|
|
238
|
-
clearTimeout(waiter.timeout);
|
|
239
|
-
waiter.resolve(message.token);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
waitForReactNativeTokenPush() {
|
|
245
|
-
if (this.token && !this.isTokenExpiringSoon()) {
|
|
246
|
-
return Promise.resolve(this.token.token);
|
|
247
|
-
}
|
|
248
|
-
return new Promise((resolve, reject) => {
|
|
249
|
-
const timeout = setTimeout(() => {
|
|
250
|
-
this.tokenWaiters = this.tokenWaiters.filter((w) => w.timeout !== timeout);
|
|
251
|
-
reject(new Error("Timed out waiting for token from React Native bridge"));
|
|
252
|
-
}, REACT_NATIVE_TOKEN_WAIT_TIMEOUT_MS);
|
|
253
|
-
this.tokenWaiters.push({ resolve, reject, timeout });
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
async requestNewToken() {
|
|
257
|
-
if (this.isBypassGatewayLocalOnly) {
|
|
258
|
-
this.token = {
|
|
259
|
-
token: BYPASS_GATEWAY_LOCAL_ONLY_TOKEN,
|
|
260
|
-
expiresAt: Date.now() + DEFAULT_TOKEN_LIFETIME_MS,
|
|
261
|
-
};
|
|
262
|
-
return this.token.token;
|
|
263
|
-
}
|
|
264
|
-
if (this.refreshPromise) {
|
|
265
|
-
return this.refreshPromise;
|
|
266
|
-
}
|
|
267
|
-
if (this.environment === "react-native-webview") {
|
|
268
|
-
this.refreshPromise = this.waitForReactNativeTokenPush();
|
|
269
|
-
try {
|
|
270
|
-
return await this.refreshPromise;
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
this.refreshPromise = null;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
this.refreshPromise = (async () => {
|
|
277
|
-
const requestId = crypto.randomUUID();
|
|
278
|
-
const response = await this.postMessageWithResponse({
|
|
279
|
-
type: "SESSION_TOKEN_REQUEST",
|
|
280
|
-
requestId,
|
|
281
|
-
appId: this.appId,
|
|
282
|
-
}, "SESSION_TOKEN_RESPONSE", requestId);
|
|
283
|
-
if (!response.token) {
|
|
284
|
-
throw new Error("No token in response");
|
|
285
|
-
}
|
|
286
|
-
// Parse expiresAt, falling back to default lifetime if invalid
|
|
287
|
-
let expiresAt = Date.now() + DEFAULT_TOKEN_LIFETIME_MS;
|
|
288
|
-
if (response.expiresAt) {
|
|
289
|
-
const parsed = new Date(response.expiresAt).getTime();
|
|
290
|
-
if (!Number.isNaN(parsed)) {
|
|
291
|
-
expiresAt = parsed;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
this.token = {
|
|
295
|
-
token: response.token,
|
|
296
|
-
expiresAt,
|
|
297
|
-
};
|
|
298
|
-
return this.token.token;
|
|
299
|
-
})();
|
|
300
|
-
try {
|
|
301
|
-
return await this.refreshPromise;
|
|
302
|
-
}
|
|
303
|
-
finally {
|
|
304
|
-
this.refreshPromise = null;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
async getToken() {
|
|
308
|
-
if (this.isBypassGatewayLocalOnly) {
|
|
309
|
-
if (!this.token || this.isTokenExpiringSoon()) {
|
|
310
|
-
return this.requestNewToken();
|
|
311
|
-
}
|
|
312
|
-
return this.token.token;
|
|
313
|
-
}
|
|
314
|
-
if (this.isTokenExpiringSoon()) {
|
|
315
|
-
return this.requestNewToken();
|
|
316
|
-
}
|
|
317
|
-
return this.token.token;
|
|
318
|
-
}
|
|
319
|
-
getTokenState() {
|
|
320
|
-
if (this.refreshPromise) {
|
|
321
|
-
return { status: "REFRESHING", token: null };
|
|
322
|
-
}
|
|
323
|
-
if (!this.token) {
|
|
324
|
-
return { status: "NO_TOKEN", token: null };
|
|
325
|
-
}
|
|
326
|
-
if (this.isTokenExpiringSoon(0)) {
|
|
327
|
-
return { status: "EXPIRED", token: this.token.token };
|
|
328
|
-
}
|
|
329
|
-
return { status: "VALID", token: this.token.token };
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Extracts user information from the current JWT token.
|
|
333
|
-
* Returns null if no valid token is available.
|
|
334
|
-
*/
|
|
335
|
-
getUser() {
|
|
336
|
-
if (this.isBypassGatewayLocalOnly) {
|
|
337
|
-
return {
|
|
338
|
-
userId: BYPASS_GATEWAY_LOCAL_ONLY_USER_ID,
|
|
339
|
-
email: BYPASS_GATEWAY_LOCAL_ONLY_EMAIL,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
if (!this.token) {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
const payload = decodeJwtPayload(this.token.token);
|
|
346
|
-
if (!payload) {
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
if (typeof payload.sub !== "string") {
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
return {
|
|
353
|
-
userId: payload.sub,
|
|
354
|
-
email: typeof payload.email === "string" ? payload.email : "",
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export declare const BYPASS_GATEWAY_LOCAL_ONLY_TOKEN = "BYPASS_GATEWAY_LOCAL_ONLY";
|
|
2
|
-
export declare const BYPASS_GATEWAY_LOCAL_ONLY_USER_ID = "demo-local-user";
|
|
3
|
-
export declare const BYPASS_GATEWAY_LOCAL_ONLY_EMAIL = "demo-local-user@local";
|
|
4
|
-
export declare function isBypassGatewayLocalOnlyClient(): boolean;
|
|
5
|
-
export declare function isBypassGatewayLocalOnlyServer(): boolean;
|
|
6
|
-
export declare function createBypassGatewayLocalOnlySessionPayload(audience: string, orgId: string): {
|
|
7
|
-
orgId: string;
|
|
8
|
-
sub: string;
|
|
9
|
-
email: string;
|
|
10
|
-
iss: string;
|
|
11
|
-
aud: string;
|
|
12
|
-
iat: number;
|
|
13
|
-
exp: number;
|
|
14
|
-
};
|
|
15
|
-
//# sourceMappingURL=bypassGatewayLocalOnly.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bypassGatewayLocalOnly.d.ts","sourceRoot":"","sources":["../../src/shared/bypassGatewayLocalOnly.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,+BAA+B,8BAA8B,CAAC;AAC3E,eAAO,MAAM,iCAAiC,oBAAoB,CAAC;AACnE,eAAO,MAAM,+BAA+B,0BAA0B,CAAC;AAEvE,wBAAgB,8BAA8B,IAAI,OAAO,CAYxD;AAED,wBAAgB,8BAA8B,IAAI,OAAO,CAsBxD;AAED,wBAAgB,0CAA0C,CACxD,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM;;;;;;;;EAkBd"}
|