@every-app/sdk 0.0.6 → 0.0.8
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/client/EmbeddedAppProvider.d.ts +5 -8
- package/dist/client/EmbeddedAppProvider.d.ts.map +1 -1
- package/dist/client/EmbeddedAppProvider.js +28 -27
- package/dist/client/GatewayRequiredError.d.ts +20 -0
- package/dist/client/GatewayRequiredError.d.ts.map +1 -0
- package/dist/client/GatewayRequiredError.js +92 -0
- package/dist/client/_internal/useEveryAppRouter.d.ts +3 -5
- package/dist/client/_internal/useEveryAppRouter.js +53 -56
- package/dist/client/_internal/useEveryAppSession.d.ts +8 -10
- package/dist/client/_internal/useEveryAppSession.d.ts.map +1 -1
- package/dist/client/_internal/useEveryAppSession.js +32 -27
- package/dist/client/authenticatedFetch.d.ts +2 -5
- package/dist/client/authenticatedFetch.js +17 -17
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/lazyInitForWorkers.d.ts +2 -4
- package/dist/client/lazyInitForWorkers.js +42 -45
- package/dist/client/session-manager.d.ts +29 -23
- package/dist/client/session-manager.d.ts.map +1 -1
- package/dist/client/session-manager.js +143 -126
- package/dist/client/useSessionTokenClientMiddleware.d.ts +2 -8
- package/dist/client/useSessionTokenClientMiddleware.js +17 -20
- package/dist/server/auth-config.d.ts +1 -1
- package/dist/server/auth-config.js +4 -4
- package/dist/server/authenticateRequest.d.ts +11 -16
- package/dist/server/authenticateRequest.js +53 -57
- package/dist/server/getLocalD1Url.d.ts +1 -1
- package/dist/server/getLocalD1Url.js +29 -42
- package/dist/server/index.d.ts +1 -1
- package/dist/server/types.d.ts +3 -3
- package/package.json +2 -2
- package/src/client/EmbeddedAppProvider.tsx +11 -0
- package/src/client/GatewayRequiredError.tsx +162 -0
- package/src/client/_internal/useEveryAppSession.tsx +3 -0
- package/src/client/index.ts +2 -1
- package/src/client/session-manager.ts +16 -0
|
@@ -3,51 +3,38 @@ import path from "path";
|
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import { parse } from "jsonc-parser";
|
|
5
5
|
function findSqliteFile(basePath) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
return fs
|
|
7
|
+
.readdirSync(basePath, { encoding: "utf-8", recursive: true })
|
|
8
|
+
.find((f) => f.endsWith(".sqlite"));
|
|
9
9
|
}
|
|
10
10
|
export function getLocalD1Url() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"The local D1 database is only available after running 'wrangler dev' which you can trigger by running 'npm run dev'.",
|
|
21
|
-
);
|
|
22
|
-
console.error(
|
|
23
|
-
"================================================================================",
|
|
24
|
-
);
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
let dbFile = findSqliteFile(basePath);
|
|
28
|
-
if (!dbFile) {
|
|
29
|
-
// Read wrangler.jsonc to get the database name
|
|
30
|
-
const wranglerConfigPath = path.resolve("wrangler.jsonc");
|
|
31
|
-
const wranglerConfig = parse(fs.readFileSync(wranglerConfigPath, "utf-8"));
|
|
32
|
-
const databaseName = wranglerConfig.d1_databases?.[0]?.database_name;
|
|
33
|
-
if (!databaseName) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
"Could not find database_name in wrangler.jsonc d1_databases configuration",
|
|
36
|
-
);
|
|
11
|
+
const basePath = path.resolve(".wrangler");
|
|
12
|
+
// Check if .wrangler directory exists
|
|
13
|
+
if (!fs.existsSync(basePath)) {
|
|
14
|
+
console.error("================================================================================");
|
|
15
|
+
console.error("WARNING: .wrangler directory not found");
|
|
16
|
+
console.error("This is expected in CI/non-development environments.");
|
|
17
|
+
console.error("The local D1 database is only available after running 'wrangler dev' which you can trigger by running 'npm run dev'.");
|
|
18
|
+
console.error("================================================================================");
|
|
19
|
+
return null;
|
|
37
20
|
}
|
|
38
|
-
|
|
39
|
-
console.log(`Initializing local D1 database: ${databaseName}...`);
|
|
40
|
-
execSync(
|
|
41
|
-
`npx wrangler d1 execute ${databaseName} --local --command "SELECT 1;"`,
|
|
42
|
-
{ stdio: "pipe" },
|
|
43
|
-
);
|
|
44
|
-
// Try to find the db file again after initialization
|
|
45
|
-
dbFile = findSqliteFile(basePath);
|
|
21
|
+
let dbFile = findSqliteFile(basePath);
|
|
46
22
|
if (!dbFile) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
23
|
+
// Read wrangler.jsonc to get the database name
|
|
24
|
+
const wranglerConfigPath = path.resolve("wrangler.jsonc");
|
|
25
|
+
const wranglerConfig = parse(fs.readFileSync(wranglerConfigPath, "utf-8"));
|
|
26
|
+
const databaseName = wranglerConfig.d1_databases?.[0]?.database_name;
|
|
27
|
+
if (!databaseName) {
|
|
28
|
+
throw new Error("Could not find database_name in wrangler.jsonc d1_databases configuration");
|
|
29
|
+
}
|
|
30
|
+
// Execute the command to initialize the local database
|
|
31
|
+
console.log(`Initializing local D1 database: ${databaseName}...`);
|
|
32
|
+
execSync(`npx wrangler d1 execute ${databaseName} --local --command "SELECT 1;"`, { stdio: "pipe" });
|
|
33
|
+
// Try to find the db file again after initialization
|
|
34
|
+
dbFile = findSqliteFile(basePath);
|
|
35
|
+
if (!dbFile) {
|
|
36
|
+
throw new Error(`Failed to initialize local D1 database. The sqlite file was not created.`);
|
|
37
|
+
}
|
|
50
38
|
}
|
|
51
|
-
|
|
52
|
-
return path.resolve(basePath, dbFile);
|
|
39
|
+
return path.resolve(basePath, dbFile);
|
|
53
40
|
}
|
package/dist/server/index.d.ts
CHANGED
package/dist/server/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@every-app/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/client/index.js",
|
|
6
6
|
"types": "./dist/client/index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"types:check": "tsc --noEmit",
|
|
28
28
|
"format:check": "prettier --check .",
|
|
29
29
|
"format:write": "prettier . --write",
|
|
30
|
-
"test": "vitest run",
|
|
30
|
+
"test": "vitest run --silent",
|
|
31
31
|
"test:watch": "vitest"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
@@ -2,6 +2,7 @@ import React, { createContext, useContext, useMemo } from "react";
|
|
|
2
2
|
import { SessionManager, SessionManagerConfig } from "./session-manager";
|
|
3
3
|
import { useEveryAppSession } from "./_internal/useEveryAppSession";
|
|
4
4
|
import { useEveryAppRouter } from "./_internal/useEveryAppRouter";
|
|
5
|
+
import { GatewayRequiredError } from "./GatewayRequiredError";
|
|
5
6
|
|
|
6
7
|
interface EmbeddedProviderConfig extends SessionManagerConfig {
|
|
7
8
|
children: React.ReactNode;
|
|
@@ -26,6 +27,16 @@ export function EmbeddedAppProvider({
|
|
|
26
27
|
|
|
27
28
|
if (!sessionManager) return null;
|
|
28
29
|
|
|
30
|
+
// Check if the app is running outside of the Gateway iframe
|
|
31
|
+
if (!sessionManager.isInIframe) {
|
|
32
|
+
return (
|
|
33
|
+
<GatewayRequiredError
|
|
34
|
+
gatewayOrigin={sessionManager.parentOrigin}
|
|
35
|
+
appId={config.appId}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
const value: EmbeddedAppContextValue = {
|
|
30
41
|
sessionManager,
|
|
31
42
|
isAuthenticated: sessionTokenState.status === "VALID",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
interface GatewayRequiredErrorProps {
|
|
4
|
+
/**
|
|
5
|
+
* The origin of the Gateway (e.g., "https://gateway.example.com").
|
|
6
|
+
*/
|
|
7
|
+
gatewayOrigin: string;
|
|
8
|
+
/**
|
|
9
|
+
* The app ID used in the Gateway URL path.
|
|
10
|
+
*/
|
|
11
|
+
appId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// CSS custom properties for theming
|
|
15
|
+
const CSS_VARIABLES = `
|
|
16
|
+
@media (prefers-color-scheme: light) {
|
|
17
|
+
:root {
|
|
18
|
+
--gateway-bg: oklch(100% 0 0);
|
|
19
|
+
--gateway-text: oklch(0% 0 0);
|
|
20
|
+
--gateway-text-muted: oklch(40% 0 0);
|
|
21
|
+
--gateway-icon-bg: oklch(94% 0 0);
|
|
22
|
+
--gateway-icon-stroke: oklch(80% 0.18 90);
|
|
23
|
+
--gateway-border: oklch(94% 0 0);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
@media (prefers-color-scheme: dark) {
|
|
27
|
+
:root {
|
|
28
|
+
--gateway-bg: #0a0f0d;
|
|
29
|
+
--gateway-text: oklch(92% 0 0);
|
|
30
|
+
--gateway-text-muted: oklch(60% 0 0);
|
|
31
|
+
--gateway-icon-bg: oklch(22% 0 0);
|
|
32
|
+
--gateway-icon-stroke: oklch(85% 0.18 90);
|
|
33
|
+
--gateway-border: oklch(30% 0 0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const styles = {
|
|
39
|
+
container: {
|
|
40
|
+
display: "flex",
|
|
41
|
+
flexDirection: "column",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
justifyContent: "center",
|
|
44
|
+
minHeight: "100vh",
|
|
45
|
+
padding: "24px",
|
|
46
|
+
fontFamily:
|
|
47
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
|
|
48
|
+
backgroundColor: "var(--gateway-bg, oklch(100% 0 0))",
|
|
49
|
+
color: "var(--gateway-text, oklch(0% 0 0))",
|
|
50
|
+
colorScheme: "light dark",
|
|
51
|
+
} satisfies CSSProperties,
|
|
52
|
+
content: {
|
|
53
|
+
maxWidth: "380px",
|
|
54
|
+
width: "100%",
|
|
55
|
+
textAlign: "left",
|
|
56
|
+
} satisfies CSSProperties,
|
|
57
|
+
iconContainer: {
|
|
58
|
+
width: "44px",
|
|
59
|
+
height: "44px",
|
|
60
|
+
marginBottom: "16px",
|
|
61
|
+
borderRadius: "0.25rem",
|
|
62
|
+
backgroundColor: "var(--gateway-icon-bg, oklch(94% 0 0))",
|
|
63
|
+
display: "flex",
|
|
64
|
+
alignItems: "center",
|
|
65
|
+
justifyContent: "center",
|
|
66
|
+
border: "1px solid var(--gateway-border, oklch(94% 0 0))",
|
|
67
|
+
} satisfies CSSProperties,
|
|
68
|
+
title: {
|
|
69
|
+
fontSize: "18px",
|
|
70
|
+
fontWeight: 600,
|
|
71
|
+
marginBottom: "8px",
|
|
72
|
+
color: "var(--gateway-text, oklch(0% 0 0))",
|
|
73
|
+
letterSpacing: "-0.01em",
|
|
74
|
+
} satisfies CSSProperties,
|
|
75
|
+
description: {
|
|
76
|
+
fontSize: "14px",
|
|
77
|
+
lineHeight: 1.5,
|
|
78
|
+
color: "var(--gateway-text-muted, oklch(40% 0 0))",
|
|
79
|
+
marginBottom: "20px",
|
|
80
|
+
} satisfies CSSProperties,
|
|
81
|
+
link: {
|
|
82
|
+
display: "inline-flex",
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
gap: "8px",
|
|
85
|
+
fontSize: "14px",
|
|
86
|
+
fontWeight: 500,
|
|
87
|
+
lineHeight: "24px",
|
|
88
|
+
color: "rgb(168, 162, 158)",
|
|
89
|
+
textDecoration: "underline",
|
|
90
|
+
textDecorationColor: "rgb(87, 83, 78)",
|
|
91
|
+
textUnderlineOffset: "4px",
|
|
92
|
+
cursor: "pointer",
|
|
93
|
+
transition:
|
|
94
|
+
"color, background-color, border-color, text-decoration-color, fill, stroke 0.15s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
95
|
+
} satisfies CSSProperties,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Error component displayed when an embedded app is accessed directly
|
|
100
|
+
* instead of through the Every App Gateway.
|
|
101
|
+
*
|
|
102
|
+
* This component informs users that authentication requires accessing
|
|
103
|
+
* the app through the Gateway and provides a link to do so.
|
|
104
|
+
*/
|
|
105
|
+
export function GatewayRequiredError({
|
|
106
|
+
gatewayOrigin,
|
|
107
|
+
appId,
|
|
108
|
+
}: GatewayRequiredErrorProps) {
|
|
109
|
+
const gatewayUrl = `${gatewayOrigin}/apps/${appId}${window.location.pathname}`;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div style={styles.container}>
|
|
113
|
+
<style>{CSS_VARIABLES}</style>
|
|
114
|
+
<div style={styles.content}>
|
|
115
|
+
{/* Warning Icon */}
|
|
116
|
+
<div style={styles.iconContainer}>
|
|
117
|
+
<svg
|
|
118
|
+
width="22"
|
|
119
|
+
height="22"
|
|
120
|
+
viewBox="0 0 24 24"
|
|
121
|
+
fill="none"
|
|
122
|
+
stroke="var(--gateway-icon-stroke, oklch(55% 0.22 25))"
|
|
123
|
+
strokeWidth="2"
|
|
124
|
+
strokeLinecap="round"
|
|
125
|
+
strokeLinejoin="round"
|
|
126
|
+
>
|
|
127
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
128
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
129
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
130
|
+
</svg>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Title */}
|
|
134
|
+
<h1 style={styles.title}>Open in Gateway</h1>
|
|
135
|
+
|
|
136
|
+
{/* Description */}
|
|
137
|
+
<p style={styles.description}>
|
|
138
|
+
The Gateway handles sign in and user auth. Access your app from there
|
|
139
|
+
so this works properly.
|
|
140
|
+
</p>
|
|
141
|
+
|
|
142
|
+
{/* Redirect Link */}
|
|
143
|
+
<a href={gatewayUrl} style={styles.link}>
|
|
144
|
+
Go to Gateway
|
|
145
|
+
<svg
|
|
146
|
+
width="16"
|
|
147
|
+
height="16"
|
|
148
|
+
viewBox="0 0 24 24"
|
|
149
|
+
fill="none"
|
|
150
|
+
stroke="currentColor"
|
|
151
|
+
strokeWidth="2"
|
|
152
|
+
strokeLinecap="round"
|
|
153
|
+
strokeLinejoin="round"
|
|
154
|
+
>
|
|
155
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
156
|
+
<polyline points="12 5 19 12 12 19" />
|
|
157
|
+
</svg>
|
|
158
|
+
</a>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -24,6 +24,9 @@ export function useEveryAppSession({
|
|
|
24
24
|
|
|
25
25
|
useEffect(() => {
|
|
26
26
|
if (!sessionManager) return;
|
|
27
|
+
// Skip token requests when not in iframe - the app will show GatewayRequiredError instead
|
|
28
|
+
if (!sessionManager.isInIframe) return;
|
|
29
|
+
|
|
27
30
|
const interval = setInterval(() => {
|
|
28
31
|
setSessionTokenState(sessionManager.getTokenState());
|
|
29
32
|
}, 5000);
|
package/src/client/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export { SessionManager } from "./session-manager";
|
|
1
|
+
export { SessionManager, isRunningInIframe } from "./session-manager";
|
|
2
2
|
export type { SessionManagerConfig } from "./session-manager";
|
|
3
3
|
export { useSessionTokenClientMiddleware } from "./useSessionTokenClientMiddleware";
|
|
4
4
|
|
|
5
5
|
export { EmbeddedAppProvider, useCurrentUser } from "./EmbeddedAppProvider";
|
|
6
|
+
export { GatewayRequiredError } from "./GatewayRequiredError";
|
|
6
7
|
|
|
7
8
|
export { lazyInitForWorkers } from "./lazyInitForWorkers";
|
|
8
9
|
|
|
@@ -17,9 +17,24 @@ const MESSAGE_TIMEOUT_MS = 5000;
|
|
|
17
17
|
const TOKEN_EXPIRY_BUFFER_MS = 10000;
|
|
18
18
|
const DEFAULT_TOKEN_LIFETIME_MS = 60000;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Detects whether the current window is running inside an iframe.
|
|
22
|
+
* Returns true if in an iframe, false if running as top-level window.
|
|
23
|
+
*/
|
|
24
|
+
export function isRunningInIframe(): boolean {
|
|
25
|
+
try {
|
|
26
|
+
return window.self !== window.top;
|
|
27
|
+
} catch {
|
|
28
|
+
// If we can't access window.top due to cross-origin restrictions,
|
|
29
|
+
// we're definitely in an iframe
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
export class SessionManager {
|
|
21
35
|
readonly parentOrigin: string;
|
|
22
36
|
readonly appId: string;
|
|
37
|
+
readonly isInIframe: boolean;
|
|
23
38
|
|
|
24
39
|
private token: SessionToken | null = null;
|
|
25
40
|
private refreshPromise: Promise<string> | null = null;
|
|
@@ -42,6 +57,7 @@ export class SessionManager {
|
|
|
42
57
|
|
|
43
58
|
this.appId = config.appId;
|
|
44
59
|
this.parentOrigin = gatewayUrl;
|
|
60
|
+
this.isInIframe = isRunningInIframe();
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
private isTokenExpiringSoon(
|