@agent-native/core 0.11.0 → 0.11.2
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/agent/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +1 -0
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +8 -45
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +76 -16
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/builder-frame.d.ts.map +1 -1
- package/dist/client/builder-frame.js +10 -3
- package/dist/client/builder-frame.js.map +1 -1
- package/dist/client/components/ui/dropdown-menu.js +2 -2
- package/dist/client/components/ui/dropdown-menu.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +6 -6
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +6 -2
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +2 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +84 -24
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +1 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +1 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +14 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +77 -12
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/builder-browser.d.ts +8 -6
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +54 -32
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +7 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +100 -74
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { createPollHandler } from "./poll.js";
|
|
|
6
6
|
import { createSSEHandler } from "./sse.js";
|
|
7
7
|
import { upsertEnvFile } from "./create-server.js";
|
|
8
8
|
import { readBody } from "./h3-helpers.js";
|
|
9
|
-
import { BUILDER_ENV_KEYS, buildBuilderCliAuthUrl, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, getBuilderBranchProjectId, getBuilderBrowserStatusForEvent, isBuilderBranchingEnabled, resolveSafePreviewUrl, runBuilderAgent, } from "./builder-browser.js";
|
|
9
|
+
import { BUILDER_CONNECT_PARAM, BUILDER_ENV_KEYS, appendBuilderConnectToken, buildBuilderCliAuthUrl, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, getBuilderBranchProjectId, getBuilderBrowserStatusForEvent, isBuilderBranchingEnabled, resolveSafePreviewUrl, runBuilderAgent, verifyBuilderConnectToken, } from "./builder-browser.js";
|
|
10
10
|
import { getState, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "../application-state/handlers.js";
|
|
11
11
|
import { getSetting, putSetting, deleteSetting } from "../settings/store.js";
|
|
12
12
|
import { getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/user-settings.js";
|
|
@@ -288,16 +288,35 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
288
288
|
message: process.env.PING_MESSAGE ?? "pong",
|
|
289
289
|
})));
|
|
290
290
|
}
|
|
291
|
+
const resolveBuilderOwnerContext = async (event) => {
|
|
292
|
+
const session = await getSession(event).catch(() => null);
|
|
293
|
+
if (session?.email) {
|
|
294
|
+
return { email: session.email, session, anonymous: false };
|
|
295
|
+
}
|
|
296
|
+
const anonymousOwner = await options.anonymousOwner?.(event);
|
|
297
|
+
if (anonymousOwner) {
|
|
298
|
+
return { email: anonymousOwner, session: null, anonymous: true };
|
|
299
|
+
}
|
|
300
|
+
return { email: undefined, session: null, anonymous: false };
|
|
301
|
+
};
|
|
291
302
|
getH3App(nitroApp).use(`${P}/builder/status`, defineEventHandler(async (event) => {
|
|
292
303
|
const envStatus = getBuilderBrowserStatusForEvent(event);
|
|
293
|
-
const
|
|
294
|
-
const userEmail =
|
|
304
|
+
const ownerContext = await resolveBuilderOwnerContext(event);
|
|
305
|
+
const userEmail = ownerContext.email;
|
|
306
|
+
const withConnectToken = (status) => {
|
|
307
|
+
if (!userEmail)
|
|
308
|
+
return status;
|
|
309
|
+
return {
|
|
310
|
+
...status,
|
|
311
|
+
connectUrl: appendBuilderConnectToken(status.connectUrl, userEmail),
|
|
312
|
+
};
|
|
313
|
+
};
|
|
295
314
|
// Env-managed mode: BUILDER_PRIVATE_KEY is set at the deployment
|
|
296
315
|
// level, so every user shares the operator's Builder identity.
|
|
297
316
|
// Skip per-user lookups entirely — the env key is authoritative
|
|
298
317
|
// and the UI must hide the connect/disconnect controls.
|
|
299
318
|
if (envStatus.envManaged) {
|
|
300
|
-
return envStatus;
|
|
319
|
+
return withConnectToken(envStatus);
|
|
301
320
|
}
|
|
302
321
|
// Pass the user's active orgId so the status read can fall back
|
|
303
322
|
// to org-scoped credentials. Without it, an admin's org-scope
|
|
@@ -305,13 +324,15 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
305
324
|
// poller and the UI would show "not connected" forever even
|
|
306
325
|
// though the chat actually resolves the org-shared credential.
|
|
307
326
|
let orgId = null;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
327
|
+
if (!ownerContext.anonymous) {
|
|
328
|
+
try {
|
|
329
|
+
const { getOrgContext } = await import("../org/context.js");
|
|
330
|
+
const orgCtx = await getOrgContext(event);
|
|
331
|
+
orgId = orgCtx.orgId ?? null;
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
/* org module not present in this template — keep userEmail-only */
|
|
335
|
+
}
|
|
315
336
|
}
|
|
316
337
|
return runWithRequestContext({ userEmail, orgId }, async () => {
|
|
317
338
|
// Per-user OAuth mode: read the user's app_secrets-stored creds.
|
|
@@ -319,7 +340,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
319
340
|
const { resolveBuilderCredentials } = await import("./credential-provider.js");
|
|
320
341
|
const creds = await resolveBuilderCredentials();
|
|
321
342
|
if (creds.privateKey) {
|
|
322
|
-
return {
|
|
343
|
+
return withConnectToken({
|
|
323
344
|
...envStatus,
|
|
324
345
|
configured: true,
|
|
325
346
|
privateKeyConfigured: true,
|
|
@@ -327,7 +348,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
327
348
|
userId: creds.userId || envStatus.userId,
|
|
328
349
|
orgName: creds.orgName || envStatus.orgName,
|
|
329
350
|
orgKind: creds.orgKind || envStatus.orgKind,
|
|
330
|
-
};
|
|
351
|
+
});
|
|
331
352
|
}
|
|
332
353
|
}
|
|
333
354
|
catch {
|
|
@@ -344,7 +365,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
344
365
|
const errRow = await getSetting(errKey);
|
|
345
366
|
if (errRow && typeof errRow.message === "string") {
|
|
346
367
|
await deleteSetting(errKey).catch(() => { });
|
|
347
|
-
return {
|
|
368
|
+
return withConnectToken({
|
|
348
369
|
...envStatus,
|
|
349
370
|
configured: false,
|
|
350
371
|
privateKeyConfigured: false,
|
|
@@ -358,7 +379,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
358
379
|
? errRow.at
|
|
359
380
|
: Date.now(),
|
|
360
381
|
},
|
|
361
|
-
};
|
|
382
|
+
});
|
|
362
383
|
}
|
|
363
384
|
}
|
|
364
385
|
}
|
|
@@ -369,7 +390,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
369
390
|
try {
|
|
370
391
|
const disconnected = await getSetting("builder-disconnected");
|
|
371
392
|
if (disconnected) {
|
|
372
|
-
return {
|
|
393
|
+
return withConnectToken({
|
|
373
394
|
...envStatus,
|
|
374
395
|
configured: false,
|
|
375
396
|
privateKeyConfigured: false,
|
|
@@ -377,7 +398,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
377
398
|
userId: undefined,
|
|
378
399
|
orgName: undefined,
|
|
379
400
|
orgKind: undefined,
|
|
380
|
-
};
|
|
401
|
+
});
|
|
381
402
|
}
|
|
382
403
|
}
|
|
383
404
|
catch {
|
|
@@ -386,7 +407,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
386
407
|
// No env, no per-user creds → not configured. Both authenticated
|
|
387
408
|
// and unauthenticated callers see "not connected" so they can
|
|
388
409
|
// run through the OAuth flow.
|
|
389
|
-
return {
|
|
410
|
+
return withConnectToken({
|
|
390
411
|
...envStatus,
|
|
391
412
|
configured: false,
|
|
392
413
|
privateKeyConfigured: false,
|
|
@@ -394,7 +415,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
394
415
|
userId: undefined,
|
|
395
416
|
orgName: undefined,
|
|
396
417
|
orgKind: undefined,
|
|
397
|
-
};
|
|
418
|
+
});
|
|
398
419
|
});
|
|
399
420
|
}));
|
|
400
421
|
// How long a pending-connect row is valid. Must be long enough for
|
|
@@ -440,26 +461,29 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
440
461
|
// inside a click handler, avoiding the popup-blocker downgrade that
|
|
441
462
|
// happens when an await sits before window.open.
|
|
442
463
|
//
|
|
443
|
-
// CSRF protection here is
|
|
464
|
+
// CSRF protection here is layered because session cookies are
|
|
444
465
|
// SameSite=None;Secure (so the editor iframe can ride along) — that
|
|
445
466
|
// means a session cookie alone does NOT prevent cross-origin
|
|
446
467
|
// window.open from initiating a connect flow on the victim's behalf:
|
|
447
|
-
// 1.
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
468
|
+
// 1. Signed connect token from /builder/status — proves the opener
|
|
469
|
+
// could read same-origin JSON, which cross-site attackers cannot.
|
|
470
|
+
// This covers local/embedded browsers that conservatively label a
|
|
471
|
+
// legitimate popup navigation as same-site/cross-site.
|
|
472
|
+
// 2. Sec-Fetch-Site header fallback — modern browsers stamp every
|
|
473
|
+
// request with the navigation context. We allow `same-origin` or
|
|
474
|
+
// `none` (typed/bookmark/extension); cross-site / same-site without
|
|
475
|
+
// a valid connect token are rejected.
|
|
476
|
+
// 3. Pending row keyed by session email + bound nonce — the callback
|
|
454
477
|
// requires both a valid session and a one-time row that this
|
|
455
478
|
// handler wrote during the same flow. Without the same-origin
|
|
456
|
-
//
|
|
457
|
-
// and then trick the victim into hitting a callback URL
|
|
458
|
-
// attacker-controlled p-key/api-key, hijacking the victim's
|
|
459
|
-
// account.
|
|
479
|
+
// gate or connect token above, an attacker could prime the row from
|
|
480
|
+
// cross-site and then trick the victim into hitting a callback URL
|
|
481
|
+
// with attacker-controlled p-key/api-key, hijacking the victim's
|
|
482
|
+
// account.
|
|
460
483
|
getH3App(nitroApp).use(`${P}/builder/connect`, defineEventHandler(async (event) => {
|
|
461
|
-
const
|
|
462
|
-
|
|
484
|
+
const ownerContext = await resolveBuilderOwnerContext(event);
|
|
485
|
+
const ownerEmail = ownerContext.email;
|
|
486
|
+
if (!ownerEmail) {
|
|
463
487
|
setResponseStatus(event, 401);
|
|
464
488
|
return { error: "Authentication required" };
|
|
465
489
|
}
|
|
@@ -476,15 +500,17 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
476
500
|
envManaged: true,
|
|
477
501
|
};
|
|
478
502
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
|
|
485
|
-
|
|
503
|
+
const requestUrl = new URL(`${event.url?.pathname || "/"}${event.url?.search || ""}`, getOrigin(event));
|
|
504
|
+
const connectToken = requestUrl.searchParams.get(BUILDER_CONNECT_PARAM);
|
|
505
|
+
const hasValidConnectToken = verifyBuilderConnectToken(connectToken, ownerEmail);
|
|
506
|
+
// Same-origin gate. Sec-Fetch-Site remains the fast path; the signed
|
|
507
|
+
// connect token is the compatibility path for legitimate embedded or
|
|
508
|
+
// local desktop popups stamped as same-site/cross-site by the browser.
|
|
509
|
+
if (!isSameOriginConnect(event) && !hasValidConnectToken) {
|
|
510
|
+
trackBuilderLifecycle("builder connect failed", ownerEmail, {
|
|
486
511
|
reason: "cross_origin",
|
|
487
512
|
stage: "connect",
|
|
513
|
+
has_connect_token: Boolean(connectToken),
|
|
488
514
|
});
|
|
489
515
|
setResponseStatus(event, 403);
|
|
490
516
|
return { error: "Cross-origin connect requests are not allowed" };
|
|
@@ -493,7 +519,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
493
519
|
// useBuilderStatus polling sees the stale error and aborts the
|
|
494
520
|
// new attempt before it can complete.
|
|
495
521
|
try {
|
|
496
|
-
await deleteSetting(`builder-connect-error:${
|
|
522
|
+
await deleteSetting(`builder-connect-error:${ownerEmail}`);
|
|
497
523
|
}
|
|
498
524
|
catch {
|
|
499
525
|
// No prior error row — fine
|
|
@@ -503,12 +529,12 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
503
529
|
// via BroadcastChannel, rather than letting the popup show raw
|
|
504
530
|
// JSON and the parent poll for 5 minutes.
|
|
505
531
|
try {
|
|
506
|
-
await putSetting(`builder-pending-connect:${
|
|
532
|
+
await putSetting(`builder-pending-connect:${ownerEmail}`, {
|
|
507
533
|
expiresAt: Date.now() + BUILDER_CONNECT_PENDING_TTL_MS,
|
|
508
534
|
});
|
|
509
535
|
}
|
|
510
536
|
catch (err) {
|
|
511
|
-
trackBuilderLifecycle("builder connect failed",
|
|
537
|
+
trackBuilderLifecycle("builder connect failed", ownerEmail, {
|
|
512
538
|
reason: "pending_storage_unavailable",
|
|
513
539
|
stage: "connect",
|
|
514
540
|
});
|
|
@@ -516,7 +542,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
516
542
|
console.error("[builder] Could not store pending-connect state:", err?.message ?? err);
|
|
517
543
|
// Best-effort: also write the error row so the parent's
|
|
518
544
|
// /builder/status poll picks it up if BroadcastChannel doesn't.
|
|
519
|
-
await putSetting(`builder-connect-error:${
|
|
545
|
+
await putSetting(`builder-connect-error:${ownerEmail}`, {
|
|
520
546
|
message: msg,
|
|
521
547
|
at: Date.now(),
|
|
522
548
|
}).catch(() => { });
|
|
@@ -524,7 +550,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
524
550
|
setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
525
551
|
return createBuilderBrowserCallbackErrorPage(msg);
|
|
526
552
|
}
|
|
527
|
-
trackBuilderLifecycle("builder connect started",
|
|
553
|
+
trackBuilderLifecycle("builder connect started", ownerEmail, {
|
|
528
554
|
stage: "connect",
|
|
529
555
|
});
|
|
530
556
|
// Build the cli-auth URL without embedding state in redirect_url:
|
|
@@ -617,14 +643,12 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
617
643
|
setResponseStatus(event, 405);
|
|
618
644
|
return { error: "Method not allowed" };
|
|
619
645
|
}
|
|
620
|
-
//
|
|
621
|
-
// (combined with the same-origin gate on
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const session = await getSession(event).catch(() => null);
|
|
627
|
-
if (!session?.email) {
|
|
646
|
+
// A real session or a template-approved anonymous owner is required;
|
|
647
|
+
// the pending-row check below (combined with the same-origin gate on
|
|
648
|
+
// /builder/connect) blocks CSRF and callback replay.
|
|
649
|
+
const ownerContext = await resolveBuilderOwnerContext(event);
|
|
650
|
+
const ownerEmail = ownerContext.email;
|
|
651
|
+
if (!ownerEmail) {
|
|
628
652
|
setResponseStatus(event, 401);
|
|
629
653
|
return { error: "Authentication required" };
|
|
630
654
|
}
|
|
@@ -643,12 +667,12 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
643
667
|
let pendingValid = false;
|
|
644
668
|
let pendingError = null;
|
|
645
669
|
try {
|
|
646
|
-
const pending = (await getSetting(`builder-pending-connect:${
|
|
670
|
+
const pending = (await getSetting(`builder-pending-connect:${ownerEmail}`));
|
|
647
671
|
if (pending &&
|
|
648
672
|
typeof pending.expiresAt === "number" &&
|
|
649
673
|
Date.now() < pending.expiresAt) {
|
|
650
674
|
try {
|
|
651
|
-
await deleteSetting(`builder-pending-connect:${
|
|
675
|
+
await deleteSetting(`builder-pending-connect:${ownerEmail}`);
|
|
652
676
|
pendingValid = true;
|
|
653
677
|
}
|
|
654
678
|
catch (err) {
|
|
@@ -662,13 +686,13 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
662
686
|
// DB temporarily unavailable — treat as missing.
|
|
663
687
|
}
|
|
664
688
|
if (pendingError) {
|
|
665
|
-
trackBuilderLifecycle("builder connect failed",
|
|
689
|
+
trackBuilderLifecycle("builder connect failed", ownerEmail, {
|
|
666
690
|
reason: "pending_consume_storage_error",
|
|
667
691
|
stage: "callback",
|
|
668
692
|
});
|
|
669
693
|
// Best-effort signal to the parent's poll loop, then render the
|
|
670
694
|
// popup-friendly error page so the BroadcastChannel notify fires.
|
|
671
|
-
await putSetting(`builder-connect-error:${
|
|
695
|
+
await putSetting(`builder-connect-error:${ownerEmail}`, {
|
|
672
696
|
message: pendingError,
|
|
673
697
|
at: Date.now(),
|
|
674
698
|
}).catch(() => { });
|
|
@@ -677,7 +701,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
677
701
|
return createBuilderBrowserCallbackErrorPage(pendingError);
|
|
678
702
|
}
|
|
679
703
|
if (!pendingValid) {
|
|
680
|
-
trackBuilderLifecycle("builder connect failed",
|
|
704
|
+
trackBuilderLifecycle("builder connect failed", ownerEmail, {
|
|
681
705
|
reason: "missing_pending_connect",
|
|
682
706
|
stage: "callback",
|
|
683
707
|
});
|
|
@@ -685,7 +709,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
685
709
|
// Write an error signal so the polling loop in the parent tab
|
|
686
710
|
// terminates quickly instead of waiting 5 minutes for the timeout.
|
|
687
711
|
try {
|
|
688
|
-
await putSetting(`builder-connect-error:${
|
|
712
|
+
await putSetting(`builder-connect-error:${ownerEmail}`, {
|
|
689
713
|
message: msg,
|
|
690
714
|
at: Date.now(),
|
|
691
715
|
});
|
|
@@ -700,7 +724,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
700
724
|
const privateKey = requestUrl.searchParams.get("p-key");
|
|
701
725
|
const publicKey = requestUrl.searchParams.get("api-key");
|
|
702
726
|
if (!privateKey || !publicKey) {
|
|
703
|
-
trackBuilderLifecycle("builder connect failed",
|
|
727
|
+
trackBuilderLifecycle("builder connect failed", ownerEmail, {
|
|
704
728
|
reason: "missing_credentials",
|
|
705
729
|
stage: "callback",
|
|
706
730
|
});
|
|
@@ -709,7 +733,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
709
733
|
// immediately via BroadcastChannel rather than hanging until the
|
|
710
734
|
// 5-minute timeout.
|
|
711
735
|
const msg = "Builder didn't return credentials. Restart the connect flow from settings.";
|
|
712
|
-
await putSetting(`builder-connect-error:${
|
|
736
|
+
await putSetting(`builder-connect-error:${ownerEmail}`, {
|
|
713
737
|
message: msg,
|
|
714
738
|
at: Date.now(),
|
|
715
739
|
}).catch(() => { });
|
|
@@ -742,23 +766,25 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
742
766
|
// legacy per-user behaviour for that connection.
|
|
743
767
|
let orgId = null;
|
|
744
768
|
let role = null;
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
769
|
+
if (!ownerContext.anonymous) {
|
|
770
|
+
try {
|
|
771
|
+
const { getOrgContext } = await import("../org/context.js");
|
|
772
|
+
const orgCtx = await getOrgContext(event);
|
|
773
|
+
orgId = orgCtx.orgId ?? null;
|
|
774
|
+
role = orgCtx.role ?? null;
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
/* org module not present in this template — keep user scope */
|
|
778
|
+
}
|
|
753
779
|
}
|
|
754
|
-
await writeBuilderCredentials(
|
|
780
|
+
await writeBuilderCredentials(ownerEmail, { privateKey, publicKey, userId, orgName, orgKind }, { orgId, role });
|
|
755
781
|
}
|
|
756
782
|
catch (err) {
|
|
757
783
|
writeError = err?.message ?? String(err);
|
|
758
784
|
console.error("[builder] Failed to persist Builder credentials:", writeError);
|
|
759
785
|
}
|
|
760
786
|
if (writeError) {
|
|
761
|
-
trackBuilderLifecycle("builder connect failed",
|
|
787
|
+
trackBuilderLifecycle("builder connect failed", ownerEmail, {
|
|
762
788
|
reason: "credential_write_failed",
|
|
763
789
|
stage: "callback",
|
|
764
790
|
});
|
|
@@ -766,7 +792,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
766
792
|
// (entire DB unreachable) the popup's postMessage still notifies
|
|
767
793
|
// the parent. If both fail the parent times out at 5min as today.
|
|
768
794
|
try {
|
|
769
|
-
await putSetting(`builder-connect-error:${
|
|
795
|
+
await putSetting(`builder-connect-error:${ownerEmail}`, {
|
|
770
796
|
message: writeError,
|
|
771
797
|
at: Date.now(),
|
|
772
798
|
});
|
|
@@ -787,13 +813,13 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
787
813
|
// DB not ready — proceed
|
|
788
814
|
}
|
|
789
815
|
try {
|
|
790
|
-
await deleteSetting(`builder-connect-error:${
|
|
816
|
+
await deleteSetting(`builder-connect-error:${ownerEmail}`);
|
|
791
817
|
}
|
|
792
818
|
catch {
|
|
793
819
|
// No prior error row — fine
|
|
794
820
|
}
|
|
795
821
|
const previewUrl = resolveSafePreviewUrl(requestUrl.searchParams.get("preview-url"), event);
|
|
796
|
-
trackBuilderLifecycle("builder connect succeeded",
|
|
822
|
+
trackBuilderLifecycle("builder connect succeeded", ownerEmail, {
|
|
797
823
|
stage: "callback",
|
|
798
824
|
has_preview_url: Boolean(previewUrl),
|
|
799
825
|
org_kind: orgKind || undefined,
|