@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.
Files changed (37) hide show
  1. package/dist/client/EmbeddedAppProvider.d.ts +5 -8
  2. package/dist/client/EmbeddedAppProvider.d.ts.map +1 -1
  3. package/dist/client/EmbeddedAppProvider.js +28 -27
  4. package/dist/client/GatewayRequiredError.d.ts +20 -0
  5. package/dist/client/GatewayRequiredError.d.ts.map +1 -0
  6. package/dist/client/GatewayRequiredError.js +92 -0
  7. package/dist/client/_internal/useEveryAppRouter.d.ts +3 -5
  8. package/dist/client/_internal/useEveryAppRouter.js +53 -56
  9. package/dist/client/_internal/useEveryAppSession.d.ts +8 -10
  10. package/dist/client/_internal/useEveryAppSession.d.ts.map +1 -1
  11. package/dist/client/_internal/useEveryAppSession.js +32 -27
  12. package/dist/client/authenticatedFetch.d.ts +2 -5
  13. package/dist/client/authenticatedFetch.js +17 -17
  14. package/dist/client/index.d.ts +3 -2
  15. package/dist/client/index.d.ts.map +1 -1
  16. package/dist/client/index.js +2 -1
  17. package/dist/client/lazyInitForWorkers.d.ts +2 -4
  18. package/dist/client/lazyInitForWorkers.js +42 -45
  19. package/dist/client/session-manager.d.ts +29 -23
  20. package/dist/client/session-manager.d.ts.map +1 -1
  21. package/dist/client/session-manager.js +143 -126
  22. package/dist/client/useSessionTokenClientMiddleware.d.ts +2 -8
  23. package/dist/client/useSessionTokenClientMiddleware.js +17 -20
  24. package/dist/server/auth-config.d.ts +1 -1
  25. package/dist/server/auth-config.js +4 -4
  26. package/dist/server/authenticateRequest.d.ts +11 -16
  27. package/dist/server/authenticateRequest.js +53 -57
  28. package/dist/server/getLocalD1Url.d.ts +1 -1
  29. package/dist/server/getLocalD1Url.js +29 -42
  30. package/dist/server/index.d.ts +1 -1
  31. package/dist/server/types.d.ts +3 -3
  32. package/package.json +2 -2
  33. package/src/client/EmbeddedAppProvider.tsx +11 -0
  34. package/src/client/GatewayRequiredError.tsx +162 -0
  35. package/src/client/_internal/useEveryAppSession.tsx +3 -0
  36. package/src/client/index.ts +2 -1
  37. 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
- return fs
7
- .readdirSync(basePath, { encoding: "utf-8", recursive: true })
8
- .find((f) => f.endsWith(".sqlite"));
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
- const basePath = path.resolve(".wrangler");
12
- // Check if .wrangler directory exists
13
- if (!fs.existsSync(basePath)) {
14
- console.error(
15
- "================================================================================",
16
- );
17
- console.error("WARNING: .wrangler directory not found");
18
- console.error("This is expected in CI/non-development environments.");
19
- console.error(
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
- // Execute the command to initialize the local database
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
- throw new Error(
48
- `Failed to initialize local D1 database. The sqlite file was not created.`,
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
  }
@@ -1,4 +1,4 @@
1
1
  export { authenticateRequest } from "./authenticateRequest";
2
2
  export type { AuthConfig } from "./types";
3
3
  export { getAuthConfig } from "./auth-config";
4
- //# sourceMappingURL=index.d.ts.map
4
+ //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  export interface AuthConfig {
2
- issuer: string;
3
- audience: string;
2
+ issuer: string;
3
+ audience: string;
4
4
  }
5
- //# sourceMappingURL=types.d.ts.map
5
+ //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@every-app/sdk",
3
- "version": "0.0.6",
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);
@@ -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(