@agent-native/core 0.15.0 → 0.15.1
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/dev-mode.d.ts +14 -0
- package/dist/client/dev-mode.d.ts.map +1 -0
- package/dist/client/dev-mode.js +14 -0
- package/dist/client/dev-mode.js.map +1 -0
- package/dist/client/extensions/EmbeddedTool.d.ts +20 -0
- package/dist/client/extensions/EmbeddedTool.d.ts.map +1 -0
- package/dist/client/extensions/EmbeddedTool.js +199 -0
- package/dist/client/extensions/EmbeddedTool.js.map +1 -0
- package/dist/client/extensions/ToolEditor.d.ts +5 -0
- package/dist/client/extensions/ToolEditor.d.ts.map +1 -0
- package/dist/client/extensions/ToolEditor.js +129 -0
- package/dist/client/extensions/ToolEditor.js.map +1 -0
- package/dist/client/extensions/ToolViewer.d.ts +5 -0
- package/dist/client/extensions/ToolViewer.d.ts.map +1 -0
- package/dist/client/extensions/ToolViewer.js +400 -0
- package/dist/client/extensions/ToolViewer.js.map +1 -0
- package/dist/client/extensions/ToolViewerPage.d.ts +2 -0
- package/dist/client/extensions/ToolViewerPage.d.ts.map +1 -0
- package/dist/client/extensions/ToolViewerPage.js +24 -0
- package/dist/client/extensions/ToolViewerPage.js.map +1 -0
- package/dist/client/extensions/ToolsListPage.d.ts +2 -0
- package/dist/client/extensions/ToolsListPage.d.ts.map +1 -0
- package/dist/client/extensions/ToolsListPage.js +67 -0
- package/dist/client/extensions/ToolsListPage.js.map +1 -0
- package/dist/client/extensions/ToolsSidebarSection.d.ts +2 -0
- package/dist/client/extensions/ToolsSidebarSection.d.ts.map +1 -0
- package/dist/client/extensions/ToolsSidebarSection.js +236 -0
- package/dist/client/extensions/ToolsSidebarSection.js.map +1 -0
- package/dist/client/extensions/tool-order.d.ts +7 -0
- package/dist/client/extensions/tool-order.d.ts.map +1 -0
- package/dist/client/extensions/tool-order.js +47 -0
- package/dist/client/extensions/tool-order.js.map +1 -0
- package/dist/client/theme.js +1 -1
- package/dist/client/theme.js.map +1 -1
- package/dist/client/tools/EmbeddedTool.d.ts +20 -0
- package/dist/client/tools/EmbeddedTool.d.ts.map +1 -0
- package/dist/client/tools/EmbeddedTool.js +199 -0
- package/dist/client/tools/EmbeddedTool.js.map +1 -0
- package/dist/client/tools/ExtensionSlot.d.ts +27 -0
- package/dist/client/tools/ExtensionSlot.d.ts.map +1 -0
- package/dist/client/tools/ExtensionSlot.js +96 -0
- package/dist/client/tools/ExtensionSlot.js.map +1 -0
- package/dist/client/tools/ToolEditor.d.ts +5 -0
- package/dist/client/tools/ToolEditor.d.ts.map +1 -0
- package/dist/client/tools/ToolEditor.js +129 -0
- package/dist/client/tools/ToolEditor.js.map +1 -0
- package/dist/client/tools/ToolViewer.d.ts +5 -0
- package/dist/client/tools/ToolViewer.d.ts.map +1 -0
- package/dist/client/tools/ToolViewer.js +400 -0
- package/dist/client/tools/ToolViewer.js.map +1 -0
- package/dist/client/tools/ToolViewerPage.d.ts +2 -0
- package/dist/client/tools/ToolViewerPage.d.ts.map +1 -0
- package/dist/client/tools/ToolViewerPage.js +24 -0
- package/dist/client/tools/ToolViewerPage.js.map +1 -0
- package/dist/client/tools/ToolsListPage.d.ts +2 -0
- package/dist/client/tools/ToolsListPage.d.ts.map +1 -0
- package/dist/client/tools/ToolsListPage.js +67 -0
- package/dist/client/tools/ToolsListPage.js.map +1 -0
- package/dist/client/tools/ToolsSidebarSection.d.ts +2 -0
- package/dist/client/tools/ToolsSidebarSection.d.ts.map +1 -0
- package/dist/client/tools/ToolsSidebarSection.js +236 -0
- package/dist/client/tools/ToolsSidebarSection.js.map +1 -0
- package/dist/client/tools/iframe-bridge.d.ts +38 -0
- package/dist/client/tools/iframe-bridge.d.ts.map +1 -0
- package/dist/client/tools/iframe-bridge.js +207 -0
- package/dist/client/tools/iframe-bridge.js.map +1 -0
- package/dist/client/tools/index.d.ts +8 -0
- package/dist/client/tools/index.d.ts.map +1 -0
- package/dist/client/tools/index.js +8 -0
- package/dist/client/tools/index.js.map +1 -0
- package/dist/client/tools/tool-order.d.ts +7 -0
- package/dist/client/tools/tool-order.d.ts.map +1 -0
- package/dist/client/tools/tool-order.js +47 -0
- package/dist/client/tools/tool-order.js.map +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +44 -6
- package/dist/server/auth.js.map +1 -1
- package/dist/server/google-auth-plugin.d.ts.map +1 -1
- package/dist/server/google-auth-plugin.js +15 -7
- package/dist/server/google-auth-plugin.js.map +1 -1
- package/dist/server/local-migration.d.ts +41 -0
- package/dist/server/local-migration.d.ts.map +1 -0
- package/dist/server/local-migration.js +235 -0
- package/dist/server/local-migration.js.map +1 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +15 -7
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/tools/actions.d.ts +3 -0
- package/dist/tools/actions.d.ts.map +1 -0
- package/dist/tools/actions.js +272 -0
- package/dist/tools/actions.js.map +1 -0
- package/dist/tools/fetch-tool.d.ts +23 -0
- package/dist/tools/fetch-tool.d.ts.map +1 -0
- package/dist/tools/fetch-tool.js +178 -0
- package/dist/tools/fetch-tool.js.map +1 -0
- package/dist/tools/html-shell.d.ts +45 -0
- package/dist/tools/html-shell.d.ts.map +1 -0
- package/dist/tools/html-shell.js +514 -0
- package/dist/tools/html-shell.js.map +1 -0
- package/dist/tools/proxy-security.d.ts +12 -0
- package/dist/tools/proxy-security.d.ts.map +1 -0
- package/dist/tools/proxy-security.js +158 -0
- package/dist/tools/proxy-security.js.map +1 -0
- package/dist/tools/routes.d.ts +2 -0
- package/dist/tools/routes.d.ts.map +1 -0
- package/dist/tools/routes.js +627 -0
- package/dist/tools/routes.js.map +1 -0
- package/dist/tools/schema.d.ts +664 -0
- package/dist/tools/schema.d.ts.map +1 -0
- package/dist/tools/schema.js +146 -0
- package/dist/tools/schema.js.map +1 -0
- package/dist/tools/slots/routes.d.ts +15 -0
- package/dist/tools/slots/routes.d.ts.map +1 -0
- package/dist/tools/slots/routes.js +94 -0
- package/dist/tools/slots/routes.js.map +1 -0
- package/dist/tools/slots/schema.d.ts +303 -0
- package/dist/tools/slots/schema.d.ts.map +1 -0
- package/dist/tools/slots/schema.js +76 -0
- package/dist/tools/slots/schema.js.map +1 -0
- package/dist/tools/slots/store.d.ts +66 -0
- package/dist/tools/slots/store.d.ts.map +1 -0
- package/dist/tools/slots/store.js +227 -0
- package/dist/tools/slots/store.js.map +1 -0
- package/dist/tools/store.d.ts +40 -0
- package/dist/tools/store.d.ts.map +1 -0
- package/dist/tools/store.js +193 -0
- package/dist/tools/store.js.map +1 -0
- package/dist/tools/theme.d.ts +2 -0
- package/dist/tools/theme.d.ts.map +1 -0
- package/dist/tools/theme.js +67 -0
- package/dist/tools/theme.js.map +1 -0
- package/dist/tools/url-safety.d.ts +24 -0
- package/dist/tools/url-safety.d.ts.map +1 -0
- package/dist/tools/url-safety.js +224 -0
- package/dist/tools/url-safety.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-auth-plugin.d.ts","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;
|
|
1
|
+
{"version":3,"file":"google-auth-plugin.d.ts","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AA0XD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,CAAC,EAAE,uBAAuB,GAChC,cAAc,CAYhB"}
|
|
@@ -161,6 +161,15 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
161
161
|
var origin = __anWorkspaceGatewayReturnOrigin();
|
|
162
162
|
return origin ? origin + path : path;
|
|
163
163
|
}
|
|
164
|
+
function __anFinishOAuthExchange(ret, flowId) {
|
|
165
|
+
if (__anIsBuilderPreview()) {
|
|
166
|
+
__anSetOAuthDebug('OAuth exchange redeemed; reloading the embedded app', flowId);
|
|
167
|
+
window.location.reload();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
__anSetOAuthDebug('OAuth exchange redeemed; returning to the app', flowId);
|
|
171
|
+
window.location.href = ret || '/';
|
|
172
|
+
}
|
|
164
173
|
function __anIsBuilderPreview() {
|
|
165
174
|
try {
|
|
166
175
|
var params = new URLSearchParams(window.location.search);
|
|
@@ -225,7 +234,7 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
225
234
|
function __anSetOAuthDebug(message, flowId) {
|
|
226
235
|
var text = message + (flowId ? ' (flow ' + __anFlowDebugId(flowId) + ')' : '');
|
|
227
236
|
try {
|
|
228
|
-
console.info('[agent-native][google-oauth]'
|
|
237
|
+
console.info('[agent-native][google-oauth] ' + text);
|
|
229
238
|
} catch(e) {}
|
|
230
239
|
var debug = document.getElementById('debug');
|
|
231
240
|
if (debug) {
|
|
@@ -254,8 +263,7 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
254
263
|
if (data && (data.email || data.token)) {
|
|
255
264
|
if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);
|
|
256
265
|
__anOAuthPollTimer = null;
|
|
257
|
-
|
|
258
|
-
window.location.href = ret || '/';
|
|
266
|
+
__anFinishOAuthExchange(ret, flowId);
|
|
259
267
|
return;
|
|
260
268
|
}
|
|
261
269
|
if (data && data.error) {
|
|
@@ -272,7 +280,7 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
272
280
|
}
|
|
273
281
|
}
|
|
274
282
|
if (Date.now() - started > timeoutMs) {
|
|
275
|
-
__anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never
|
|
283
|
+
__anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never reached this app. Check the Google OAuth redirect URI and server logs for [agent-native][google-oauth].');
|
|
276
284
|
}
|
|
277
285
|
}
|
|
278
286
|
if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);
|
|
@@ -281,9 +289,9 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
281
289
|
}
|
|
282
290
|
function __anStartPopupOAuth(ret, btn, err) {
|
|
283
291
|
var flowId = __anNewOAuthFlowId();
|
|
284
|
-
var
|
|
292
|
+
var oauthReturn = __anIsBuilderPreview() ? __anOAuthReturnTarget(ret) : ret;
|
|
285
293
|
var params = new URLSearchParams();
|
|
286
|
-
if (
|
|
294
|
+
if (oauthReturn) params.set('return', oauthReturn);
|
|
287
295
|
params.set('desktop', '1');
|
|
288
296
|
params.set('flow_id', flowId);
|
|
289
297
|
params.set('redirect', '1');
|
|
@@ -309,7 +317,7 @@ function getGoogleLoginHtml(googleAuthMode) {
|
|
|
309
317
|
__anShowOAuthError(err, btn, 'Could not open Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));
|
|
310
318
|
return;
|
|
311
319
|
}
|
|
312
|
-
__anWaitForOAuthExchange(flowId,
|
|
320
|
+
__anWaitForOAuthExchange(flowId, ret, btn, err);
|
|
313
321
|
}
|
|
314
322
|
function __anStartNativeDesktopOAuth(ret, btn, err) {
|
|
315
323
|
var flowId = __anNewOAuthFlowId();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google-auth-plugin.js","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EACL,qBAAqB,GAEtB,MAAM,uBAAuB,CAAC;AAe/B,SAAS,kBAAkB,CAAC,cAA8B;IACxD,MAAM,iBAAiB,GAAG,oBAAoB,EAAE,CAAC;IACjD,MAAM,4BAA4B,GAAG,+BAA+B,EAAE,CAAC;IACvE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA+E0B,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;+CACrB,IAAI,CAAC,SAAS,CAAC,4BAA4B,CAAC;gCAC3D,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAyRtD,CAAC;AACT,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAiC;IAEjC,OAAO,gBAAgB,CAAC;QACtB,WAAW,EAAE;YACX,gCAAgC;YAChC,gCAAgC;YAChC,wBAAwB;YACxB,GAAG,CAAC,OAAO,EAAE,WAAW,IAAI,EAAE,CAAC;SAChC;QACD,SAAS,EAAE,kBAAkB,CAC3B,qBAAqB,CAAC,OAAO,EAAE,cAAc,CAAC,CAC/C;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { createAuthPlugin } from \"./auth-plugin.js\";\nimport { getPublicOAuthOrigin } from \"./oauth-public-origin.js\";\nimport { getWorkspaceGatewayReturnOrigin } from \"./oauth-return-url.js\";\nimport {\n resolveGoogleAuthMode,\n type GoogleAuthMode,\n} from \"./google-auth-mode.js\";\n\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\n\nexport interface GoogleAuthPluginOptions {\n /** Additional paths accessible without authentication */\n publicPaths?: string[];\n /**\n * Google sign-in flow: `'popup'`, `'redirect'`, or `'auto'` (default).\n * Falls back to `GOOGLE_AUTH_MODE` env var, then `'auto'`. The Builder.io\n * browser iframe always uses popup regardless (Google blocks framing).\n */\n googleAuthMode?: GoogleAuthMode;\n}\n\nfunction getGoogleLoginHtml(googleAuthMode: GoogleAuthMode): string {\n const publicOAuthOrigin = getPublicOAuthOrigin();\n const workspaceGatewayReturnOrigin = getWorkspaceGatewayReturnOrigin();\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n<title>Sign in</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: #0a0a0a;\n color: #e5e5e5;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n width: 100%;\n max-width: 360px;\n padding: 2rem;\n background: #141414;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 12px;\n text-align: center;\n }\n h1 { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n button {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.625rem;\n padding: 0.625rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 8px;\n font-size: 0.9375rem;\n font-weight: 500;\n cursor: pointer;\n }\n button:hover { opacity: 0.85; }\n button:disabled { opacity: 0.5; cursor: wait; }\n .error { margin-top: 0.75rem; font-size: 0.8125rem; color: #f87171; display: none; }\n .error.show { display: block; }\n .debug {\n display: none;\n margin-top: 0.625rem;\n font-size: 0.6875rem;\n line-height: 1.45;\n color: #777;\n word-break: break-word;\n }\n .debug.show { display: block; }\n svg { width: 18px; height: 18px; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <h1>Sign in</h1>\n <p class=\"subtitle\">Continue with your Google account</p>\n <button id=\"btn\" onclick=\"signIn()\">\n <svg viewBox=\"0 0 24 24\"><path fill=\"#4285F4\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\"/><path fill=\"#34A853\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"/><path fill=\"#FBBC05\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"/><path fill=\"#EA4335\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"/></svg>\n Sign in with Google\n </button>\n <p class=\"error\" id=\"err\"></p>\n <p class=\"debug\" id=\"debug\"></p>\n</div>\n<script>\n function __anBasePath() {\n var marker = '/_agent-native';\n var idx = window.location.pathname.indexOf(marker);\n return idx > 0 ? window.location.pathname.slice(0, idx) : '';\n }\n function __anPath(path) {\n return __anBasePath() + path;\n }\n var __AN_PUBLIC_OAUTH_ORIGIN = ${JSON.stringify(publicOAuthOrigin)};\n var __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN = ${JSON.stringify(workspaceGatewayReturnOrigin)};\n var __AN_GOOGLE_AUTH_MODE = ${JSON.stringify(googleAuthMode)};\n function __anConfiguredOAuthOrigin() {\n if (!__AN_PUBLIC_OAUTH_ORIGIN) return '';\n try {\n var origin = new URL(__AN_PUBLIC_OAUTH_ORIGIN).origin;\n return origin && origin !== window.location.origin ? origin : '';\n } catch(e) {\n return '';\n }\n }\n function __anAuthPath(path) {\n var origin = __anIsBuilderPreview() ? __anConfiguredOAuthOrigin() : '';\n return origin ? origin + path : __anPath(path);\n }\n function __anBuilderPreviewReturnOrigin() {\n var candidates = [window.location.href, document.referrer || ''];\n try {\n if (window.location.ancestorOrigins) {\n for (var j = 0; j < window.location.ancestorOrigins.length; j++) {\n candidates.push(window.location.ancestorOrigins[j]);\n }\n }\n } catch(e) {}\n for (var i = 0; i < candidates.length; i++) {\n try {\n var url = new URL(candidates[i]);\n var host = url.hostname.toLowerCase();\n var isPreviewHost =\n host === 'builderio.xyz' || host.slice(-14) === '.builderio.xyz' ||\n host === 'builderio.dev' || host.slice(-14) === '.builderio.dev' ||\n host === 'builder.codes' || host.slice(-14) === '.builder.codes' ||\n host === 'builder.my' || host.slice(-11) === '.builder.my';\n if (url.protocol === 'https:' && isPreviewHost) return url.origin;\n } catch(e) {}\n }\n return '';\n }\n function __anWorkspaceGatewayReturnOrigin() {\n var previewOrigin = __anBuilderPreviewReturnOrigin();\n if (previewOrigin) return previewOrigin;\n if (__AN_WORKSPACE_GATEWAY_RETURN_ORIGIN) return __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN;\n return __anIsBuilderDesktop() ? 'http://127.0.0.1:8080' : '';\n }\n function __anNormalizeWorkspaceReturnPath(ret) {\n try {\n var url = new URL(ret || '/', window.location.origin);\n var path = url.pathname || '/';\n if (path === '/dispatch/dispatch') {\n path = '/dispatch';\n } else if (path.indexOf('/dispatch/') === 0) {\n var rest = path.slice('/dispatch/'.length);\n var first = rest.split('/')[0];\n var dispatchRoutes = {\n overview: true, apps: true, metrics: true, vault: true,\n integrations: true, messaging: true, workspace: true,\n agents: true, destinations: true, identities: true,\n approvals: true, audit: true, team: true, 'thread-debug': true,\n 'new-app': true\n };\n if (first === 'dispatch') {\n path = '/dispatch' + rest.slice(first.length);\n } else if (first && !dispatchRoutes[first]) {\n path = '/' + rest;\n }\n }\n return path + url.search + url.hash;\n } catch(e) {\n return ret || '/';\n }\n }\n function __anOAuthReturnTarget(ret) {\n var path = __anNormalizeWorkspaceReturnPath(ret);\n var origin = __anWorkspaceGatewayReturnOrigin();\n return origin ? origin + path : path;\n }\n function __anIsBuilderPreview() {\n try {\n var params = new URLSearchParams(window.location.search);\n if (params.has('builder.preview') || params.has('builder.frameEditing') || params.has('__builder_editing__')) return true;\n } catch(e) {}\n try {\n var ref = document.referrer || '';\n return ref.indexOf('builder.io') !== -1 || ref.indexOf('builder.my') !== -1 || ref.indexOf('builderio.xyz') !== -1 || ref.indexOf('builderio.dev') !== -1 || ref.indexOf('builder.codes') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsBuilderDesktop() {\n try {\n var ua = navigator.userAgent || '';\n return ua.indexOf('Electron') !== -1 && ua.indexOf('AgentNativeDesktop') === -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsAgentNativeDesktop() {\n try {\n return (navigator.userAgent || '').indexOf('AgentNativeDesktop') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsElectron() {\n try {\n return (navigator.userAgent || '').indexOf('Electron') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anResolveAuthFlow() {\n if (__anIsBuilderPreview()) return 'popup';\n var mode = __AN_GOOGLE_AUTH_MODE || 'auto';\n if (mode === 'popup') return 'popup';\n if (mode === 'redirect') return 'redirect';\n return __anIsElectron() ? 'redirect' : 'popup';\n }\n var __anOAuthPollTimer = null;\n var __anOAuthPollCount = 0;\n function __anNewOAuthFlowId() {\n try {\n if (window.crypto && typeof window.crypto.randomUUID === 'function') {\n return window.crypto.randomUUID();\n }\n } catch(e) {}\n return 'builder-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);\n }\n function __anFlowDebugId(flowId) {\n return flowId ? String(flowId).slice(-10) : '';\n }\n function __anShouldShowOAuthDebug() {\n try {\n var loc = window.location || {};\n return (typeof loc.hash === 'string' && loc.hash.indexOf('oauth-debug') !== -1) ||\n (typeof loc.search === 'string' && loc.search.indexOf('oauth_debug=1') !== -1);\n } catch(e) { return false; }\n }\n function __anSetOAuthDebug(message, flowId) {\n var text = message + (flowId ? ' (flow ' + __anFlowDebugId(flowId) + ')' : '');\n try {\n console.info('[agent-native][google-oauth]', { message: message, flow: __anFlowDebugId(flowId) || undefined });\n } catch(e) {}\n var debug = document.getElementById('debug');\n if (debug) {\n debug.textContent = text;\n if (__anShouldShowOAuthDebug()) debug.classList.add('show');\n }\n }\n function __anShowOAuthError(err, btn, message) {\n if (__anOAuthPollTimer) {\n clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n }\n err.textContent = message;\n err.classList.add('show');\n btn.disabled = false;\n }\n function __anWaitForOAuthExchange(flowId, ret, btn, err) {\n var started = Date.now();\n var timeoutMs = 5 * 60 * 1000;\n __anOAuthPollCount = 0;\n async function check() {\n __anOAuthPollCount++;\n try {\n var res = await fetch(__anPath('/_agent-native/auth/desktop-exchange') + '?flow_id=' + encodeURIComponent(flowId), { credentials: 'include' });\n var data = await res.json().catch(function() { return {}; });\n if (data && (data.email || data.token)) {\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n __anSetOAuthDebug('OAuth exchange redeemed; returning to the app', flowId);\n window.location.href = ret || '/';\n return;\n }\n if (data && data.error) {\n __anSetOAuthDebug('OAuth exchange returned an error: ' + (data.message || data.error), flowId);\n __anShowOAuthError(err, btn, data.message || data.error);\n return;\n }\n if (data && data.pending && (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0)) {\n __anSetOAuthDebug('Waiting for the Google callback; polling attempt ' + __anOAuthPollCount, flowId);\n }\n } catch(e) {\n if (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0) {\n __anSetOAuthDebug('Could not reach the OAuth exchange endpoint: ' + (e && e.message ? e.message : 'network error'), flowId);\n }\n }\n if (Date.now() - started > timeoutMs) {\n __anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never redeemed; check server logs for [agent-native][google-oauth].');\n }\n }\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = setInterval(check, 1000);\n setTimeout(check, 500);\n }\n function __anStartPopupOAuth(ret, btn, err) {\n var flowId = __anNewOAuthFlowId();\n var target = __anIsBuilderPreview() ? __anOAuthReturnTarget(ret) : ret;\n var params = new URLSearchParams();\n if (target) params.set('return', target);\n params.set('desktop', '1');\n params.set('flow_id', flowId);\n params.set('redirect', '1');\n var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n __anSetOAuthDebug('Opening Google sign-in popup', flowId);\n try {\n var popup = window.open('', '_blank', 'width=640,height=760');\n if (!popup) {\n __anShowOAuthError(err, btn, 'Google popup was blocked. Allow popups for this site and try again (flow ' + __anFlowDebugId(flowId) + ').');\n return;\n }\n try { popup.opener = null; } catch(e) {}\n try {\n popup.location.href = url;\n } catch(e) {\n try { popup.close(); } catch(closeErr) {}\n __anShowOAuthError(err, btn, 'Could not navigate Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anSetOAuthDebug('Google popup opened; waiting for callback', flowId);\n } catch(e) {\n __anShowOAuthError(err, btn, 'Could not open Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anWaitForOAuthExchange(flowId, target, btn, err);\n }\n function __anStartNativeDesktopOAuth(ret, btn, err) {\n var flowId = __anNewOAuthFlowId();\n var params = new URLSearchParams();\n if (ret) params.set('return', ret);\n params.set('desktop', '1');\n params.set('flow_id', flowId);\n params.set('redirect', '1');\n var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();\n __anSetOAuthDebug('Opening Google sign-in in system browser', flowId);\n __anOpenOAuthUrl(url);\n __anWaitForOAuthExchange(flowId, ret, btn, err);\n }\n function __anOpenOAuthUrl(url) {\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n window.location.href = url;\n }\n async function signIn() {\n var btn = document.getElementById('btn');\n var err = document.getElementById('err');\n var ret = window.location.pathname + window.location.search;\n btn.disabled = true;\n err.classList.remove('show');\n if (__anResolveAuthFlow() === 'popup') {\n __anStartPopupOAuth(ret, btn, err);\n return;\n }\n if (__anIsAgentNativeDesktop()) {\n __anStartNativeDesktopOAuth(ret, btn, err);\n return;\n }\n if (__anIsBuilderPreview()) {\n var params = new URLSearchParams();\n if (ret) params.set('return', __anOAuthReturnTarget(ret));\n params.set('redirect', '1');\n __anSetOAuthDebug('Opening Google sign-in redirect');\n __anOpenOAuthUrl(__anAuthPath('/_agent-native/google/auth-url') + '?' + params.toString());\n return;\n }\n try {\n var res = await fetch(__anPath('/_agent-native/google/auth-url') + '?return=' + encodeURIComponent(ret));\n var data = await res.json();\n if (data.url) {\n __anOpenOAuthUrl(data.url);\n } else {\n err.textContent = data.message || 'Google OAuth is not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.';\n err.classList.add('show');\n btn.disabled = false;\n }\n } catch (e) {\n err.textContent = 'Failed to connect. Please try again.';\n err.classList.add('show');\n btn.disabled = false;\n }\n }\n</script>\n</body>\n</html>`;\n}\n\n/**\n * Create an auth plugin that uses Google OAuth for authentication.\n *\n * When a user visits the app unauthenticated, they see a \"Sign in with Google\"\n * page. The Google OAuth callback (handled by the template) creates a session\n * tied to the user's Google email. `getSession()` then returns `{ email }` for\n * all subsequent requests.\n *\n * Better Auth handles Google OAuth internally when GOOGLE_CLIENT_ID and\n * GOOGLE_CLIENT_SECRET are set. The template's callback route at\n * /_agent-native/google/callback handles mobile deep linking.\n *\n * Usage in a template's `server/plugins/auth.ts`:\n * ```ts\n * import { createGoogleAuthPlugin } from \"@agent-native/core/server\";\n * export default createGoogleAuthPlugin();\n * ```\n */\nexport function createGoogleAuthPlugin(\n options?: GoogleAuthPluginOptions,\n): NitroPluginDef {\n return createAuthPlugin({\n publicPaths: [\n \"/_agent-native/google/callback\",\n \"/_agent-native/google/auth-url\",\n \"/_agent-native/auth/ba\",\n ...(options?.publicPaths ?? []),\n ],\n loginHtml: getGoogleLoginHtml(\n resolveGoogleAuthMode(options?.googleAuthMode),\n ),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"google-auth-plugin.js","sourceRoot":"","sources":["../../src/server/google-auth-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EACL,qBAAqB,GAEtB,MAAM,uBAAuB,CAAC;AAe/B,SAAS,kBAAkB,CAAC,cAA8B;IACxD,MAAM,iBAAiB,GAAG,oBAAoB,EAAE,CAAC;IACjD,MAAM,4BAA4B,GAAG,+BAA+B,EAAE,CAAC;IACvE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA+E0B,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;+CACrB,IAAI,CAAC,SAAS,CAAC,4BAA4B,CAAC;gCAC3D,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiStD,CAAC;AACT,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAiC;IAEjC,OAAO,gBAAgB,CAAC;QACtB,WAAW,EAAE;YACX,gCAAgC;YAChC,gCAAgC;YAChC,wBAAwB;YACxB,GAAG,CAAC,OAAO,EAAE,WAAW,IAAI,EAAE,CAAC;SAChC;QACD,SAAS,EAAE,kBAAkB,CAC3B,qBAAqB,CAAC,OAAO,EAAE,cAAc,CAAC,CAC/C;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { createAuthPlugin } from \"./auth-plugin.js\";\nimport { getPublicOAuthOrigin } from \"./oauth-public-origin.js\";\nimport { getWorkspaceGatewayReturnOrigin } from \"./oauth-return-url.js\";\nimport {\n resolveGoogleAuthMode,\n type GoogleAuthMode,\n} from \"./google-auth-mode.js\";\n\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\n\nexport interface GoogleAuthPluginOptions {\n /** Additional paths accessible without authentication */\n publicPaths?: string[];\n /**\n * Google sign-in flow: `'popup'`, `'redirect'`, or `'auto'` (default).\n * Falls back to `GOOGLE_AUTH_MODE` env var, then `'auto'`. The Builder.io\n * browser iframe always uses popup regardless (Google blocks framing).\n */\n googleAuthMode?: GoogleAuthMode;\n}\n\nfunction getGoogleLoginHtml(googleAuthMode: GoogleAuthMode): string {\n const publicOAuthOrigin = getPublicOAuthOrigin();\n const workspaceGatewayReturnOrigin = getWorkspaceGatewayReturnOrigin();\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n<title>Sign in</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: #0a0a0a;\n color: #e5e5e5;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .card {\n width: 100%;\n max-width: 360px;\n padding: 2rem;\n background: #141414;\n border: 1px solid rgba(255,255,255,0.08);\n border-radius: 12px;\n text-align: center;\n }\n h1 { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: #fff; }\n .subtitle { font-size: 0.8125rem; color: #888; margin-bottom: 1.5rem; }\n button {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.625rem;\n padding: 0.625rem;\n background: #fff;\n color: #000;\n border: none;\n border-radius: 8px;\n font-size: 0.9375rem;\n font-weight: 500;\n cursor: pointer;\n }\n button:hover { opacity: 0.85; }\n button:disabled { opacity: 0.5; cursor: wait; }\n .error { margin-top: 0.75rem; font-size: 0.8125rem; color: #f87171; display: none; }\n .error.show { display: block; }\n .debug {\n display: none;\n margin-top: 0.625rem;\n font-size: 0.6875rem;\n line-height: 1.45;\n color: #777;\n word-break: break-word;\n }\n .debug.show { display: block; }\n svg { width: 18px; height: 18px; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <h1>Sign in</h1>\n <p class=\"subtitle\">Continue with your Google account</p>\n <button id=\"btn\" onclick=\"signIn()\">\n <svg viewBox=\"0 0 24 24\"><path fill=\"#4285F4\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\"/><path fill=\"#34A853\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"/><path fill=\"#FBBC05\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"/><path fill=\"#EA4335\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"/></svg>\n Sign in with Google\n </button>\n <p class=\"error\" id=\"err\"></p>\n <p class=\"debug\" id=\"debug\"></p>\n</div>\n<script>\n function __anBasePath() {\n var marker = '/_agent-native';\n var idx = window.location.pathname.indexOf(marker);\n return idx > 0 ? window.location.pathname.slice(0, idx) : '';\n }\n function __anPath(path) {\n return __anBasePath() + path;\n }\n var __AN_PUBLIC_OAUTH_ORIGIN = ${JSON.stringify(publicOAuthOrigin)};\n var __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN = ${JSON.stringify(workspaceGatewayReturnOrigin)};\n var __AN_GOOGLE_AUTH_MODE = ${JSON.stringify(googleAuthMode)};\n function __anConfiguredOAuthOrigin() {\n if (!__AN_PUBLIC_OAUTH_ORIGIN) return '';\n try {\n var origin = new URL(__AN_PUBLIC_OAUTH_ORIGIN).origin;\n return origin && origin !== window.location.origin ? origin : '';\n } catch(e) {\n return '';\n }\n }\n function __anAuthPath(path) {\n var origin = __anIsBuilderPreview() ? __anConfiguredOAuthOrigin() : '';\n return origin ? origin + path : __anPath(path);\n }\n function __anBuilderPreviewReturnOrigin() {\n var candidates = [window.location.href, document.referrer || ''];\n try {\n if (window.location.ancestorOrigins) {\n for (var j = 0; j < window.location.ancestorOrigins.length; j++) {\n candidates.push(window.location.ancestorOrigins[j]);\n }\n }\n } catch(e) {}\n for (var i = 0; i < candidates.length; i++) {\n try {\n var url = new URL(candidates[i]);\n var host = url.hostname.toLowerCase();\n var isPreviewHost =\n host === 'builderio.xyz' || host.slice(-14) === '.builderio.xyz' ||\n host === 'builderio.dev' || host.slice(-14) === '.builderio.dev' ||\n host === 'builder.codes' || host.slice(-14) === '.builder.codes' ||\n host === 'builder.my' || host.slice(-11) === '.builder.my';\n if (url.protocol === 'https:' && isPreviewHost) return url.origin;\n } catch(e) {}\n }\n return '';\n }\n function __anWorkspaceGatewayReturnOrigin() {\n var previewOrigin = __anBuilderPreviewReturnOrigin();\n if (previewOrigin) return previewOrigin;\n if (__AN_WORKSPACE_GATEWAY_RETURN_ORIGIN) return __AN_WORKSPACE_GATEWAY_RETURN_ORIGIN;\n return __anIsBuilderDesktop() ? 'http://127.0.0.1:8080' : '';\n }\n function __anNormalizeWorkspaceReturnPath(ret) {\n try {\n var url = new URL(ret || '/', window.location.origin);\n var path = url.pathname || '/';\n if (path === '/dispatch/dispatch') {\n path = '/dispatch';\n } else if (path.indexOf('/dispatch/') === 0) {\n var rest = path.slice('/dispatch/'.length);\n var first = rest.split('/')[0];\n var dispatchRoutes = {\n overview: true, apps: true, metrics: true, vault: true,\n integrations: true, messaging: true, workspace: true,\n agents: true, destinations: true, identities: true,\n approvals: true, audit: true, team: true, 'thread-debug': true,\n 'new-app': true\n };\n if (first === 'dispatch') {\n path = '/dispatch' + rest.slice(first.length);\n } else if (first && !dispatchRoutes[first]) {\n path = '/' + rest;\n }\n }\n return path + url.search + url.hash;\n } catch(e) {\n return ret || '/';\n }\n }\n function __anOAuthReturnTarget(ret) {\n var path = __anNormalizeWorkspaceReturnPath(ret);\n var origin = __anWorkspaceGatewayReturnOrigin();\n return origin ? origin + path : path;\n }\n function __anFinishOAuthExchange(ret, flowId) {\n if (__anIsBuilderPreview()) {\n __anSetOAuthDebug('OAuth exchange redeemed; reloading the embedded app', flowId);\n window.location.reload();\n return;\n }\n __anSetOAuthDebug('OAuth exchange redeemed; returning to the app', flowId);\n window.location.href = ret || '/';\n }\n function __anIsBuilderPreview() {\n try {\n var params = new URLSearchParams(window.location.search);\n if (params.has('builder.preview') || params.has('builder.frameEditing') || params.has('__builder_editing__')) return true;\n } catch(e) {}\n try {\n var ref = document.referrer || '';\n return ref.indexOf('builder.io') !== -1 || ref.indexOf('builder.my') !== -1 || ref.indexOf('builderio.xyz') !== -1 || ref.indexOf('builderio.dev') !== -1 || ref.indexOf('builder.codes') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsBuilderDesktop() {\n try {\n var ua = navigator.userAgent || '';\n return ua.indexOf('Electron') !== -1 && ua.indexOf('AgentNativeDesktop') === -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsAgentNativeDesktop() {\n try {\n return (navigator.userAgent || '').indexOf('AgentNativeDesktop') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anIsElectron() {\n try {\n return (navigator.userAgent || '').indexOf('Electron') !== -1;\n } catch(e) {\n return false;\n }\n }\n function __anResolveAuthFlow() {\n if (__anIsBuilderPreview()) return 'popup';\n var mode = __AN_GOOGLE_AUTH_MODE || 'auto';\n if (mode === 'popup') return 'popup';\n if (mode === 'redirect') return 'redirect';\n return __anIsElectron() ? 'redirect' : 'popup';\n }\n var __anOAuthPollTimer = null;\n var __anOAuthPollCount = 0;\n function __anNewOAuthFlowId() {\n try {\n if (window.crypto && typeof window.crypto.randomUUID === 'function') {\n return window.crypto.randomUUID();\n }\n } catch(e) {}\n return 'builder-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);\n }\n function __anFlowDebugId(flowId) {\n return flowId ? String(flowId).slice(-10) : '';\n }\n function __anShouldShowOAuthDebug() {\n try {\n var loc = window.location || {};\n return (typeof loc.hash === 'string' && loc.hash.indexOf('oauth-debug') !== -1) ||\n (typeof loc.search === 'string' && loc.search.indexOf('oauth_debug=1') !== -1);\n } catch(e) { return false; }\n }\n function __anSetOAuthDebug(message, flowId) {\n var text = message + (flowId ? ' (flow ' + __anFlowDebugId(flowId) + ')' : '');\n try {\n console.info('[agent-native][google-oauth] ' + text);\n } catch(e) {}\n var debug = document.getElementById('debug');\n if (debug) {\n debug.textContent = text;\n if (__anShouldShowOAuthDebug()) debug.classList.add('show');\n }\n }\n function __anShowOAuthError(err, btn, message) {\n if (__anOAuthPollTimer) {\n clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n }\n err.textContent = message;\n err.classList.add('show');\n btn.disabled = false;\n }\n function __anWaitForOAuthExchange(flowId, ret, btn, err) {\n var started = Date.now();\n var timeoutMs = 5 * 60 * 1000;\n __anOAuthPollCount = 0;\n async function check() {\n __anOAuthPollCount++;\n try {\n var res = await fetch(__anPath('/_agent-native/auth/desktop-exchange') + '?flow_id=' + encodeURIComponent(flowId), { credentials: 'include' });\n var data = await res.json().catch(function() { return {}; });\n if (data && (data.email || data.token)) {\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = null;\n __anFinishOAuthExchange(ret, flowId);\n return;\n }\n if (data && data.error) {\n __anSetOAuthDebug('OAuth exchange returned an error: ' + (data.message || data.error), flowId);\n __anShowOAuthError(err, btn, data.message || data.error);\n return;\n }\n if (data && data.pending && (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0)) {\n __anSetOAuthDebug('Waiting for the Google callback; polling attempt ' + __anOAuthPollCount, flowId);\n }\n } catch(e) {\n if (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0) {\n __anSetOAuthDebug('Could not reach the OAuth exchange endpoint: ' + (e && e.message ? e.message : 'network error'), flowId);\n }\n }\n if (Date.now() - started > timeoutMs) {\n __anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never reached this app. Check the Google OAuth redirect URI and server logs for [agent-native][google-oauth].');\n }\n }\n if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);\n __anOAuthPollTimer = setInterval(check, 1000);\n setTimeout(check, 500);\n }\n function __anStartPopupOAuth(ret, btn, err) {\n var flowId = __anNewOAuthFlowId();\n var oauthReturn = __anIsBuilderPreview() ? __anOAuthReturnTarget(ret) : ret;\n var params = new URLSearchParams();\n if (oauthReturn) params.set('return', oauthReturn);\n params.set('desktop', '1');\n params.set('flow_id', flowId);\n params.set('redirect', '1');\n var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n __anSetOAuthDebug('Opening Google sign-in popup', flowId);\n try {\n var popup = window.open('', '_blank', 'width=640,height=760');\n if (!popup) {\n __anShowOAuthError(err, btn, 'Google popup was blocked. Allow popups for this site and try again (flow ' + __anFlowDebugId(flowId) + ').');\n return;\n }\n try { popup.opener = null; } catch(e) {}\n try {\n popup.location.href = url;\n } catch(e) {\n try { popup.close(); } catch(closeErr) {}\n __anShowOAuthError(err, btn, 'Could not navigate Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anSetOAuthDebug('Google popup opened; waiting for callback', flowId);\n } catch(e) {\n __anShowOAuthError(err, btn, 'Could not open Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));\n return;\n }\n __anWaitForOAuthExchange(flowId, ret, btn, err);\n }\n function __anStartNativeDesktopOAuth(ret, btn, err) {\n var flowId = __anNewOAuthFlowId();\n var params = new URLSearchParams();\n if (ret) params.set('return', ret);\n params.set('desktop', '1');\n params.set('flow_id', flowId);\n params.set('redirect', '1');\n var url = __anPath('/_agent-native/google/auth-url') + '?' + params.toString();\n __anSetOAuthDebug('Opening Google sign-in in system browser', flowId);\n __anOpenOAuthUrl(url);\n __anWaitForOAuthExchange(flowId, ret, btn, err);\n }\n function __anOpenOAuthUrl(url) {\n try { sessionStorage.setItem('__an_signin', '1'); } catch(e) {}\n window.location.href = url;\n }\n async function signIn() {\n var btn = document.getElementById('btn');\n var err = document.getElementById('err');\n var ret = window.location.pathname + window.location.search;\n btn.disabled = true;\n err.classList.remove('show');\n if (__anResolveAuthFlow() === 'popup') {\n __anStartPopupOAuth(ret, btn, err);\n return;\n }\n if (__anIsAgentNativeDesktop()) {\n __anStartNativeDesktopOAuth(ret, btn, err);\n return;\n }\n if (__anIsBuilderPreview()) {\n var params = new URLSearchParams();\n if (ret) params.set('return', __anOAuthReturnTarget(ret));\n params.set('redirect', '1');\n __anSetOAuthDebug('Opening Google sign-in redirect');\n __anOpenOAuthUrl(__anAuthPath('/_agent-native/google/auth-url') + '?' + params.toString());\n return;\n }\n try {\n var res = await fetch(__anPath('/_agent-native/google/auth-url') + '?return=' + encodeURIComponent(ret));\n var data = await res.json();\n if (data.url) {\n __anOpenOAuthUrl(data.url);\n } else {\n err.textContent = data.message || 'Google OAuth is not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.';\n err.classList.add('show');\n btn.disabled = false;\n }\n } catch (e) {\n err.textContent = 'Failed to connect. Please try again.';\n err.classList.add('show');\n btn.disabled = false;\n }\n }\n</script>\n</body>\n</html>`;\n}\n\n/**\n * Create an auth plugin that uses Google OAuth for authentication.\n *\n * When a user visits the app unauthenticated, they see a \"Sign in with Google\"\n * page. The Google OAuth callback (handled by the template) creates a session\n * tied to the user's Google email. `getSession()` then returns `{ email }` for\n * all subsequent requests.\n *\n * Better Auth handles Google OAuth internally when GOOGLE_CLIENT_ID and\n * GOOGLE_CLIENT_SECRET are set. The template's callback route at\n * /_agent-native/google/callback handles mobile deep linking.\n *\n * Usage in a template's `server/plugins/auth.ts`:\n * ```ts\n * import { createGoogleAuthPlugin } from \"@agent-native/core/server\";\n * export default createGoogleAuthPlugin();\n * ```\n */\nexport function createGoogleAuthPlugin(\n options?: GoogleAuthPluginOptions,\n): NitroPluginDef {\n return createAuthPlugin({\n publicPaths: [\n \"/_agent-native/google/callback\",\n \"/_agent-native/google/auth-url\",\n \"/_agent-native/auth/ba\",\n ...(options?.publicPaths ?? []),\n ],\n loginHtml: getGoogleLoginHtml(\n resolveGoogleAuthMode(options?.googleAuthMode),\n ),\n });\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrate data owned by `local@localhost` to a real account.
|
|
3
|
+
*
|
|
4
|
+
* When a user starts an app in local mode and later signs in to create a real
|
|
5
|
+
* account, this function moves all of their existing data over to the new
|
|
6
|
+
* account so they don't lose anything.
|
|
7
|
+
*
|
|
8
|
+
* Scope of the migration:
|
|
9
|
+
* - `application_state`: rows with `session_id = 'local'`
|
|
10
|
+
* - `settings`: keys prefixed with `u:local@localhost:`
|
|
11
|
+
* - `oauth_tokens`: rows with `owner = 'local@localhost'`
|
|
12
|
+
* - Any template table that has an `owner_email` column: rows with
|
|
13
|
+
* `owner_email = 'local@localhost'`
|
|
14
|
+
*
|
|
15
|
+
* The operation is a no-op if the target email is itself `local@localhost`,
|
|
16
|
+
* empty, or if there is nothing to migrate.
|
|
17
|
+
*/
|
|
18
|
+
export interface LocalMigrationResult {
|
|
19
|
+
/** Whether anything was actually migrated. */
|
|
20
|
+
migrated: boolean;
|
|
21
|
+
/** Per-table row counts that were updated. Omits tables with zero updates. */
|
|
22
|
+
tables: Record<string, number>;
|
|
23
|
+
/** Target email the data now belongs to. */
|
|
24
|
+
targetEmail: string;
|
|
25
|
+
/**
|
|
26
|
+
* Non-fatal per-step errors encountered during migration. One bad table
|
|
27
|
+
* no longer fails the whole upgrade — we migrate everything we can and
|
|
28
|
+
* report any steps that threw here so the UI can surface them.
|
|
29
|
+
*/
|
|
30
|
+
errors?: Array<{
|
|
31
|
+
step: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Migrate every piece of local-mode data to the given real account email.
|
|
37
|
+
* Safe to call multiple times — it only touches rows that are still attached
|
|
38
|
+
* to `local@localhost`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function migrateLocalUserData(targetEmail: string): Promise<LocalMigrationResult>;
|
|
41
|
+
//# sourceMappingURL=local-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-migration.d.ts","sourceRoot":"","sources":["../../src/server/local-migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,MAAM,WAAW,oBAAoB;IACnC,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AAoLD;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAyD/B"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrate data owned by `local@localhost` to a real account.
|
|
3
|
+
*
|
|
4
|
+
* When a user starts an app in local mode and later signs in to create a real
|
|
5
|
+
* account, this function moves all of their existing data over to the new
|
|
6
|
+
* account so they don't lose anything.
|
|
7
|
+
*
|
|
8
|
+
* Scope of the migration:
|
|
9
|
+
* - `application_state`: rows with `session_id = 'local'`
|
|
10
|
+
* - `settings`: keys prefixed with `u:local@localhost:`
|
|
11
|
+
* - `oauth_tokens`: rows with `owner = 'local@localhost'`
|
|
12
|
+
* - Any template table that has an `owner_email` column: rows with
|
|
13
|
+
* `owner_email = 'local@localhost'`
|
|
14
|
+
*
|
|
15
|
+
* The operation is a no-op if the target email is itself `local@localhost`,
|
|
16
|
+
* empty, or if there is nothing to migrate.
|
|
17
|
+
*/
|
|
18
|
+
import { getDbExec, isPostgres } from "../db/client.js";
|
|
19
|
+
const LOCAL_EMAIL = "local@localhost";
|
|
20
|
+
const LOCAL_SESSION_ID = "local";
|
|
21
|
+
const OWNER_COLUMN = "owner_email";
|
|
22
|
+
/**
|
|
23
|
+
* Error messages that indicate a missing/inaccessible table or column — the
|
|
24
|
+
* migration treats these as "feature not enabled" and skips silently.
|
|
25
|
+
*/
|
|
26
|
+
const SCHEMA_SKIP_ERR = /no such table|no such column|does not exist|undefined table|undefined column|relation .* does not exist|column .* does not exist|permission denied|is not a table|cannot update view|cannot change column in a view/i;
|
|
27
|
+
function shouldSkipSchemaError(err) {
|
|
28
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
29
|
+
return SCHEMA_SKIP_ERR.test(message);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Discover every table (not view) in `public` that has an `owner_email`
|
|
33
|
+
* column. Views and materialized views are excluded — they can't be updated
|
|
34
|
+
* directly and would 500 the migration.
|
|
35
|
+
*/
|
|
36
|
+
async function discoverOwnerEmailTables() {
|
|
37
|
+
const client = getDbExec();
|
|
38
|
+
if (isPostgres()) {
|
|
39
|
+
// Join against information_schema.tables to filter out views and
|
|
40
|
+
// materialized views (anything that isn't a plain BASE TABLE).
|
|
41
|
+
const { rows } = await client.execute({
|
|
42
|
+
sql: `SELECT c.table_name
|
|
43
|
+
FROM information_schema.columns c
|
|
44
|
+
JOIN information_schema.tables t
|
|
45
|
+
ON t.table_schema = c.table_schema
|
|
46
|
+
AND t.table_name = c.table_name
|
|
47
|
+
WHERE c.table_schema = 'public'
|
|
48
|
+
AND c.column_name = $1
|
|
49
|
+
AND t.table_type = 'BASE TABLE'`,
|
|
50
|
+
args: [OWNER_COLUMN],
|
|
51
|
+
});
|
|
52
|
+
return rows.map((r) => r.table_name ?? r[0]).filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
// SQLite path: iterate tables (type='table', not 'view') and inspect columns via PRAGMA
|
|
55
|
+
const tablesRes = await client.execute(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`);
|
|
56
|
+
const tables = tablesRes.rows
|
|
57
|
+
.map((r) => (r.name ?? r[0]))
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
const withOwner = [];
|
|
60
|
+
for (const table of tables) {
|
|
61
|
+
const escaped = table.replace(/"/g, '""');
|
|
62
|
+
const colsRes = await client.execute(`PRAGMA table_info("${escaped}")`);
|
|
63
|
+
const hasOwner = colsRes.rows.some((row) => (row.name ?? row[1]) === OWNER_COLUMN);
|
|
64
|
+
if (hasOwner)
|
|
65
|
+
withOwner.push(table);
|
|
66
|
+
}
|
|
67
|
+
return withOwner;
|
|
68
|
+
}
|
|
69
|
+
/** Replace `?` placeholders with `$1`, `$2`, … for Postgres. */
|
|
70
|
+
function sqlWithParams(sql) {
|
|
71
|
+
if (!isPostgres())
|
|
72
|
+
return sql;
|
|
73
|
+
let i = 0;
|
|
74
|
+
return sql.replace(/\?/g, () => `$${++i}`);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Rename `settings` keys so a user's config carries over. Keys are prefixed
|
|
78
|
+
* with `u:<email>:` — moving from one email to another is a prefix swap.
|
|
79
|
+
*
|
|
80
|
+
* If a destination key already exists (unlikely but possible if the user had
|
|
81
|
+
* previously signed in with the same email) we leave the destination alone
|
|
82
|
+
* and drop the local row, so we never clobber real-account state.
|
|
83
|
+
*/
|
|
84
|
+
async function migrateSettings(targetEmail) {
|
|
85
|
+
const client = getDbExec();
|
|
86
|
+
const oldPrefix = `u:${LOCAL_EMAIL}:`;
|
|
87
|
+
const newPrefix = `u:${targetEmail}:`;
|
|
88
|
+
const { rows } = await client.execute({
|
|
89
|
+
sql: sqlWithParams(`SELECT key FROM settings WHERE key LIKE ? ESCAPE '\\'`),
|
|
90
|
+
args: [oldPrefix.replace(/([\\%_])/g, "\\$1") + "%"],
|
|
91
|
+
});
|
|
92
|
+
let updated = 0;
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
const oldKey = (row.key ?? row[0]);
|
|
95
|
+
if (!oldKey.startsWith(oldPrefix))
|
|
96
|
+
continue;
|
|
97
|
+
const newKey = newPrefix + oldKey.slice(oldPrefix.length);
|
|
98
|
+
// Skip if the destination already exists — don't overwrite real data.
|
|
99
|
+
const existsRes = await client.execute({
|
|
100
|
+
sql: sqlWithParams(`SELECT 1 FROM settings WHERE key = ?`),
|
|
101
|
+
args: [newKey],
|
|
102
|
+
});
|
|
103
|
+
if (existsRes.rows.length > 0) {
|
|
104
|
+
await client.execute({
|
|
105
|
+
sql: sqlWithParams(`DELETE FROM settings WHERE key = ?`),
|
|
106
|
+
args: [oldKey],
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
await client.execute({
|
|
111
|
+
sql: sqlWithParams(`UPDATE settings SET key = ? WHERE key = ?`),
|
|
112
|
+
args: [newKey, oldKey],
|
|
113
|
+
});
|
|
114
|
+
updated++;
|
|
115
|
+
}
|
|
116
|
+
return updated;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Move application_state rows from `session_id='local'` to the target email.
|
|
120
|
+
* Rows that already exist for the destination session are left alone.
|
|
121
|
+
*/
|
|
122
|
+
async function migrateApplicationState(targetEmail) {
|
|
123
|
+
const client = getDbExec();
|
|
124
|
+
// Only migrate keys that don't already exist under the destination session.
|
|
125
|
+
const { rows } = await client.execute({
|
|
126
|
+
sql: sqlWithParams(`SELECT key FROM application_state WHERE session_id = ?`),
|
|
127
|
+
args: [LOCAL_SESSION_ID],
|
|
128
|
+
});
|
|
129
|
+
let updated = 0;
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
const key = (row.key ?? row[0]);
|
|
132
|
+
const existsRes = await client.execute({
|
|
133
|
+
sql: sqlWithParams(`SELECT 1 FROM application_state WHERE session_id = ? AND key = ?`),
|
|
134
|
+
args: [targetEmail, key],
|
|
135
|
+
});
|
|
136
|
+
if (existsRes.rows.length > 0) {
|
|
137
|
+
await client.execute({
|
|
138
|
+
sql: sqlWithParams(`DELETE FROM application_state WHERE session_id = ? AND key = ?`),
|
|
139
|
+
args: [LOCAL_SESSION_ID, key],
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
await client.execute({
|
|
144
|
+
sql: sqlWithParams(`UPDATE application_state SET session_id = ? WHERE session_id = ? AND key = ?`),
|
|
145
|
+
args: [targetEmail, LOCAL_SESSION_ID, key],
|
|
146
|
+
});
|
|
147
|
+
updated++;
|
|
148
|
+
}
|
|
149
|
+
return updated;
|
|
150
|
+
}
|
|
151
|
+
/** Move oauth_tokens rows. `owner` is the user's email in core tables. */
|
|
152
|
+
async function migrateOauthTokens(targetEmail) {
|
|
153
|
+
const client = getDbExec();
|
|
154
|
+
const res = await client.execute({
|
|
155
|
+
sql: sqlWithParams(`UPDATE oauth_tokens SET owner = ? WHERE owner = ?`),
|
|
156
|
+
args: [targetEmail, LOCAL_EMAIL],
|
|
157
|
+
});
|
|
158
|
+
return res.rowsAffected ?? 0;
|
|
159
|
+
}
|
|
160
|
+
/** Move rows in a template table that uses the `owner_email` convention. */
|
|
161
|
+
async function migrateOwnerEmailTable(table, targetEmail) {
|
|
162
|
+
const client = getDbExec();
|
|
163
|
+
const escaped = table.replace(/"/g, '""');
|
|
164
|
+
const res = await client.execute({
|
|
165
|
+
sql: sqlWithParams(`UPDATE "${escaped}" SET owner_email = ? WHERE owner_email = ?`),
|
|
166
|
+
args: [targetEmail, LOCAL_EMAIL],
|
|
167
|
+
});
|
|
168
|
+
return res.rowsAffected ?? 0;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Migrate every piece of local-mode data to the given real account email.
|
|
172
|
+
* Safe to call multiple times — it only touches rows that are still attached
|
|
173
|
+
* to `local@localhost`.
|
|
174
|
+
*/
|
|
175
|
+
export async function migrateLocalUserData(targetEmail) {
|
|
176
|
+
const email = targetEmail?.trim().toLowerCase();
|
|
177
|
+
if (!email || email === LOCAL_EMAIL) {
|
|
178
|
+
return { migrated: false, tables: {}, targetEmail: email || "" };
|
|
179
|
+
}
|
|
180
|
+
const tables = {};
|
|
181
|
+
// Core tables — best-effort. A missing table just means the feature isn't
|
|
182
|
+
// enabled in this app (e.g. an app that doesn't use oauth_tokens).
|
|
183
|
+
const coreSteps = [
|
|
184
|
+
["settings", () => migrateSettings(email)],
|
|
185
|
+
["application_state", () => migrateApplicationState(email)],
|
|
186
|
+
["oauth_tokens", () => migrateOauthTokens(email)],
|
|
187
|
+
];
|
|
188
|
+
const errors = [];
|
|
189
|
+
for (const [name, fn] of coreSteps) {
|
|
190
|
+
try {
|
|
191
|
+
const count = await fn();
|
|
192
|
+
if (count > 0)
|
|
193
|
+
tables[name] = count;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
// Missing table or column — skip silently. Other errors are logged
|
|
197
|
+
// per-step so one bad table doesn't 500 the entire migration.
|
|
198
|
+
if (!shouldSkipSchemaError(err)) {
|
|
199
|
+
const message = err?.message ?? String(err);
|
|
200
|
+
errors.push({ step: name, message });
|
|
201
|
+
console.error(`[local-migration] ${name} failed:`, err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Template tables — discovered dynamically. If discovery itself fails,
|
|
206
|
+
// fall back to an empty list so the migration doesn't 500.
|
|
207
|
+
let templateTables = [];
|
|
208
|
+
try {
|
|
209
|
+
templateTables = await discoverOwnerEmailTables();
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
console.error("[local-migration] owner_email table discovery failed:", err);
|
|
213
|
+
templateTables = [];
|
|
214
|
+
}
|
|
215
|
+
for (const table of templateTables) {
|
|
216
|
+
try {
|
|
217
|
+
const count = await migrateOwnerEmailTable(table, email);
|
|
218
|
+
if (count > 0)
|
|
219
|
+
tables[table] = count;
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
if (!shouldSkipSchemaError(err)) {
|
|
223
|
+
const message = err?.message ?? String(err);
|
|
224
|
+
errors.push({ step: table, message });
|
|
225
|
+
console.error(`[local-migration] ${table} failed:`, err);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const migrated = Object.values(tables).some((n) => n > 0);
|
|
230
|
+
const result = { migrated, tables, targetEmail: email };
|
|
231
|
+
if (errors.length > 0)
|
|
232
|
+
result.errors = errors;
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=local-migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-migration.js","sourceRoot":"","sources":["../../src/server/local-migration.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Migrate data owned by `local@localhost` to a real account.\n *\n * When a user starts an app in local mode and later signs in to create a real\n * account, this function moves all of their existing data over to the new\n * account so they don't lose anything.\n *\n * Scope of the migration:\n * - `application_state`: rows with `session_id = 'local'`\n * - `settings`: keys prefixed with `u:local@localhost:`\n * - `oauth_tokens`: rows with `owner = 'local@localhost'`\n * - Any template table that has an `owner_email` column: rows with\n * `owner_email = 'local@localhost'`\n *\n * The operation is a no-op if the target email is itself `local@localhost`,\n * empty, or if there is nothing to migrate.\n */\n\nimport { getDbExec, isPostgres } from \"../db/client.js\";\n\nconst LOCAL_EMAIL = \"local@localhost\";\nconst LOCAL_SESSION_ID = \"local\";\nconst OWNER_COLUMN = \"owner_email\";\n\nexport interface LocalMigrationResult {\n /** Whether anything was actually migrated. */\n migrated: boolean;\n /** Per-table row counts that were updated. Omits tables with zero updates. */\n tables: Record<string, number>;\n /** Target email the data now belongs to. */\n targetEmail: string;\n /**\n * Non-fatal per-step errors encountered during migration. One bad table\n * no longer fails the whole upgrade — we migrate everything we can and\n * report any steps that threw here so the UI can surface them.\n */\n errors?: Array<{ step: string; message: string }>;\n}\n\n/**\n * Error messages that indicate a missing/inaccessible table or column — the\n * migration treats these as \"feature not enabled\" and skips silently.\n */\nconst SCHEMA_SKIP_ERR =\n /no such table|no such column|does not exist|undefined table|undefined column|relation .* does not exist|column .* does not exist|permission denied|is not a table|cannot update view|cannot change column in a view/i;\n\nfunction shouldSkipSchemaError(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return SCHEMA_SKIP_ERR.test(message);\n}\n\n/**\n * Discover every table (not view) in `public` that has an `owner_email`\n * column. Views and materialized views are excluded — they can't be updated\n * directly and would 500 the migration.\n */\nasync function discoverOwnerEmailTables(): Promise<string[]> {\n const client = getDbExec();\n if (isPostgres()) {\n // Join against information_schema.tables to filter out views and\n // materialized views (anything that isn't a plain BASE TABLE).\n const { rows } = await client.execute({\n sql: `SELECT c.table_name\n FROM information_schema.columns c\n JOIN information_schema.tables t\n ON t.table_schema = c.table_schema\n AND t.table_name = c.table_name\n WHERE c.table_schema = 'public'\n AND c.column_name = $1\n AND t.table_type = 'BASE TABLE'`,\n args: [OWNER_COLUMN],\n });\n return rows.map((r: any) => r.table_name ?? r[0]).filter(Boolean);\n }\n\n // SQLite path: iterate tables (type='table', not 'view') and inspect columns via PRAGMA\n const tablesRes = await client.execute(\n `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`,\n );\n const tables = tablesRes.rows\n .map((r: any) => (r.name ?? r[0]) as string)\n .filter(Boolean);\n\n const withOwner: string[] = [];\n for (const table of tables) {\n const escaped = table.replace(/\"/g, '\"\"');\n const colsRes = await client.execute(`PRAGMA table_info(\"${escaped}\")`);\n const hasOwner = colsRes.rows.some(\n (row: any) => (row.name ?? row[1]) === OWNER_COLUMN,\n );\n if (hasOwner) withOwner.push(table);\n }\n return withOwner;\n}\n\n/** Replace `?` placeholders with `$1`, `$2`, … for Postgres. */\nfunction sqlWithParams(sql: string): string {\n if (!isPostgres()) return sql;\n let i = 0;\n return sql.replace(/\\?/g, () => `$${++i}`);\n}\n\n/**\n * Rename `settings` keys so a user's config carries over. Keys are prefixed\n * with `u:<email>:` — moving from one email to another is a prefix swap.\n *\n * If a destination key already exists (unlikely but possible if the user had\n * previously signed in with the same email) we leave the destination alone\n * and drop the local row, so we never clobber real-account state.\n */\nasync function migrateSettings(targetEmail: string): Promise<number> {\n const client = getDbExec();\n const oldPrefix = `u:${LOCAL_EMAIL}:`;\n const newPrefix = `u:${targetEmail}:`;\n\n const { rows } = await client.execute({\n sql: sqlWithParams(`SELECT key FROM settings WHERE key LIKE ? ESCAPE '\\\\'`),\n args: [oldPrefix.replace(/([\\\\%_])/g, \"\\\\$1\") + \"%\"],\n });\n\n let updated = 0;\n for (const row of rows) {\n const oldKey = (row.key ?? (row as any)[0]) as string;\n if (!oldKey.startsWith(oldPrefix)) continue;\n const newKey = newPrefix + oldKey.slice(oldPrefix.length);\n\n // Skip if the destination already exists — don't overwrite real data.\n const existsRes = await client.execute({\n sql: sqlWithParams(`SELECT 1 FROM settings WHERE key = ?`),\n args: [newKey],\n });\n if (existsRes.rows.length > 0) {\n await client.execute({\n sql: sqlWithParams(`DELETE FROM settings WHERE key = ?`),\n args: [oldKey],\n });\n continue;\n }\n\n await client.execute({\n sql: sqlWithParams(`UPDATE settings SET key = ? WHERE key = ?`),\n args: [newKey, oldKey],\n });\n updated++;\n }\n return updated;\n}\n\n/**\n * Move application_state rows from `session_id='local'` to the target email.\n * Rows that already exist for the destination session are left alone.\n */\nasync function migrateApplicationState(targetEmail: string): Promise<number> {\n const client = getDbExec();\n // Only migrate keys that don't already exist under the destination session.\n const { rows } = await client.execute({\n sql: sqlWithParams(\n `SELECT key FROM application_state WHERE session_id = ?`,\n ),\n args: [LOCAL_SESSION_ID],\n });\n\n let updated = 0;\n for (const row of rows) {\n const key = (row.key ?? (row as any)[0]) as string;\n const existsRes = await client.execute({\n sql: sqlWithParams(\n `SELECT 1 FROM application_state WHERE session_id = ? AND key = ?`,\n ),\n args: [targetEmail, key],\n });\n if (existsRes.rows.length > 0) {\n await client.execute({\n sql: sqlWithParams(\n `DELETE FROM application_state WHERE session_id = ? AND key = ?`,\n ),\n args: [LOCAL_SESSION_ID, key],\n });\n continue;\n }\n await client.execute({\n sql: sqlWithParams(\n `UPDATE application_state SET session_id = ? WHERE session_id = ? AND key = ?`,\n ),\n args: [targetEmail, LOCAL_SESSION_ID, key],\n });\n updated++;\n }\n return updated;\n}\n\n/** Move oauth_tokens rows. `owner` is the user's email in core tables. */\nasync function migrateOauthTokens(targetEmail: string): Promise<number> {\n const client = getDbExec();\n const res = await client.execute({\n sql: sqlWithParams(`UPDATE oauth_tokens SET owner = ? WHERE owner = ?`),\n args: [targetEmail, LOCAL_EMAIL],\n });\n return res.rowsAffected ?? 0;\n}\n\n/** Move rows in a template table that uses the `owner_email` convention. */\nasync function migrateOwnerEmailTable(\n table: string,\n targetEmail: string,\n): Promise<number> {\n const client = getDbExec();\n const escaped = table.replace(/\"/g, '\"\"');\n const res = await client.execute({\n sql: sqlWithParams(\n `UPDATE \"${escaped}\" SET owner_email = ? WHERE owner_email = ?`,\n ),\n args: [targetEmail, LOCAL_EMAIL],\n });\n return res.rowsAffected ?? 0;\n}\n\n/**\n * Migrate every piece of local-mode data to the given real account email.\n * Safe to call multiple times — it only touches rows that are still attached\n * to `local@localhost`.\n */\nexport async function migrateLocalUserData(\n targetEmail: string,\n): Promise<LocalMigrationResult> {\n const email = targetEmail?.trim().toLowerCase();\n if (!email || email === LOCAL_EMAIL) {\n return { migrated: false, tables: {}, targetEmail: email || \"\" };\n }\n\n const tables: Record<string, number> = {};\n\n // Core tables — best-effort. A missing table just means the feature isn't\n // enabled in this app (e.g. an app that doesn't use oauth_tokens).\n const coreSteps: Array<[string, () => Promise<number>]> = [\n [\"settings\", () => migrateSettings(email)],\n [\"application_state\", () => migrateApplicationState(email)],\n [\"oauth_tokens\", () => migrateOauthTokens(email)],\n ];\n const errors: Array<{ step: string; message: string }> = [];\n for (const [name, fn] of coreSteps) {\n try {\n const count = await fn();\n if (count > 0) tables[name] = count;\n } catch (err: any) {\n // Missing table or column — skip silently. Other errors are logged\n // per-step so one bad table doesn't 500 the entire migration.\n if (!shouldSkipSchemaError(err)) {\n const message = err?.message ?? String(err);\n errors.push({ step: name, message });\n console.error(`[local-migration] ${name} failed:`, err);\n }\n }\n }\n\n // Template tables — discovered dynamically. If discovery itself fails,\n // fall back to an empty list so the migration doesn't 500.\n let templateTables: string[] = [];\n try {\n templateTables = await discoverOwnerEmailTables();\n } catch (err) {\n console.error(\"[local-migration] owner_email table discovery failed:\", err);\n templateTables = [];\n }\n for (const table of templateTables) {\n try {\n const count = await migrateOwnerEmailTable(table, email);\n if (count > 0) tables[table] = count;\n } catch (err: any) {\n if (!shouldSkipSchemaError(err)) {\n const message = err?.message ?? String(err);\n errors.push({ step: table, message });\n console.error(`[local-migration] ${table} failed:`, err);\n }\n }\n }\n\n const migrated = Object.values(tables).some((n) => n > 0);\n const result: LocalMigrationResult = { migrated, tables, targetEmail: email };\n if (errors.length > 0) result.errors = errors;\n return result;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboarding-html.d.ts","sourceRoot":"","sources":["../../src/server/onboarding-html.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAmC/B,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF;;;;OAIG;IACH,kBAAkB,CAAC,EAAE;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QACxB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,qBAA0B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"onboarding-html.d.ts","sourceRoot":"","sources":["../../src/server/onboarding-html.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAmC/B,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF;;;;OAIG;IACH,kBAAkB,CAAC,EAAE;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QACxB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,qBAA0B,GAAG,MAAM,CAgoD1E;AAED,kDAAkD;AAClD,eAAO,MAAM,eAAe,QAAsB,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CA0G7C"}
|
|
@@ -917,6 +917,15 @@ ${googleOnly
|
|
|
917
917
|
var origin = __anWorkspaceGatewayReturnOrigin();
|
|
918
918
|
return origin ? origin + path : path;
|
|
919
919
|
}
|
|
920
|
+
function __anFinishOAuthExchange(ret, flowId) {
|
|
921
|
+
if (__anIsBuilderPreview()) {
|
|
922
|
+
__anSetOAuthDebug('OAuth exchange redeemed; reloading the embedded app', flowId);
|
|
923
|
+
window.location.reload();
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
__anSetOAuthDebug('OAuth exchange redeemed; returning to the app', flowId);
|
|
927
|
+
window.location.href = ret || '/';
|
|
928
|
+
}
|
|
920
929
|
function __anGetReturnPath() {
|
|
921
930
|
try {
|
|
922
931
|
var inner = new URLSearchParams(window.location.search).get('return');
|
|
@@ -989,7 +998,7 @@ ${googleOnly
|
|
|
989
998
|
function __anSetOAuthDebug(message, flowId) {
|
|
990
999
|
var text = message + (flowId ? ' (flow ' + __anFlowDebugId(flowId) + ')' : '');
|
|
991
1000
|
try {
|
|
992
|
-
console.info('[agent-native][google-oauth]'
|
|
1001
|
+
console.info('[agent-native][google-oauth] ' + text);
|
|
993
1002
|
} catch(e) {}
|
|
994
1003
|
// Only surface the debug overlay when explicitly opted in via #oauth-debug
|
|
995
1004
|
// hash or ?oauth_debug=1 query — otherwise it leaks raw flow IDs and
|
|
@@ -1028,8 +1037,7 @@ ${googleOnly
|
|
|
1028
1037
|
if (data && (data.email || data.token)) {
|
|
1029
1038
|
if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);
|
|
1030
1039
|
__anOAuthPollTimer = null;
|
|
1031
|
-
|
|
1032
|
-
window.location.href = ret || '/';
|
|
1040
|
+
__anFinishOAuthExchange(ret, flowId);
|
|
1033
1041
|
return;
|
|
1034
1042
|
}
|
|
1035
1043
|
if (data && data.error) {
|
|
@@ -1046,7 +1054,7 @@ ${googleOnly
|
|
|
1046
1054
|
}
|
|
1047
1055
|
}
|
|
1048
1056
|
if (Date.now() - started > timeoutMs) {
|
|
1049
|
-
__anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never
|
|
1057
|
+
__anShowOAuthError(err, btn, 'Google sign-in did not finish. Flow ' + __anFlowDebugId(flowId) + ' never reached this app. Check the Google OAuth redirect URI and server logs for [agent-native][google-oauth].');
|
|
1050
1058
|
}
|
|
1051
1059
|
}
|
|
1052
1060
|
if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);
|
|
@@ -1055,9 +1063,9 @@ ${googleOnly
|
|
|
1055
1063
|
}
|
|
1056
1064
|
function __anStartPopupOAuth(ret, btn, err) {
|
|
1057
1065
|
var flowId = __anNewOAuthFlowId();
|
|
1058
|
-
var
|
|
1066
|
+
var oauthReturn = __anIsBuilderPreview() ? __anOAuthReturnTarget(ret) : ret;
|
|
1059
1067
|
var params = new URLSearchParams();
|
|
1060
|
-
if (
|
|
1068
|
+
if (oauthReturn) params.set('return', oauthReturn);
|
|
1061
1069
|
params.set('desktop', '1');
|
|
1062
1070
|
params.set('flow_id', flowId);
|
|
1063
1071
|
params.set('redirect', '1');
|
|
@@ -1083,7 +1091,7 @@ ${googleOnly
|
|
|
1083
1091
|
__anShowOAuthError(err, btn, 'Could not open Google popup for flow ' + __anFlowDebugId(flowId) + ': ' + (e && e.message ? e.message : 'unknown error'));
|
|
1084
1092
|
return;
|
|
1085
1093
|
}
|
|
1086
|
-
__anWaitForOAuthExchange(flowId,
|
|
1094
|
+
__anWaitForOAuthExchange(flowId, ret, btn, err);
|
|
1087
1095
|
}
|
|
1088
1096
|
function __anStartNativeDesktopOAuth(ret, btn, err) {
|
|
1089
1097
|
var flowId = __anNewOAuthFlowId();
|