@helloleo/plugins 0.2.8 → 0.2.9
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/index.js +42 -18
- package/package.json +1 -1
- package/src/tools/setup-netsuite.ts +42 -18
package/dist/index.js
CHANGED
|
@@ -25568,28 +25568,51 @@ export function signInWithNetSuite(returnTo: string = window.location.href) {
|
|
|
25568
25568
|
'VITE_WORKER_URL is not set. HelloLeo Cloud must be provisioned before sign-in works.',
|
|
25569
25569
|
);
|
|
25570
25570
|
}
|
|
25571
|
+
const workerOrigin = new URL(WORKER_URL).origin;
|
|
25571
25572
|
const url = new URL(\`\${WORKER_URL.replace(/\\/$/, '')}/auth/netsuite/start\`);
|
|
25572
25573
|
url.searchParams.set('return_to', returnTo);
|
|
25573
|
-
//
|
|
25574
|
-
//
|
|
25575
|
-
|
|
25576
|
-
|
|
25577
|
-
//
|
|
25578
|
-
//
|
|
25579
|
-
//
|
|
25580
|
-
//
|
|
25581
|
-
//
|
|
25582
|
-
//
|
|
25583
|
-
|
|
25584
|
-
|
|
25585
|
-
|
|
25586
|
-
|
|
25587
|
-
|
|
25574
|
+
// popup=1 tells the Worker callback to postMessage the session id back to
|
|
25575
|
+
// THIS window (then close) instead of a #nssid= redirect.
|
|
25576
|
+
url.searchParams.set('popup', '1');
|
|
25577
|
+
|
|
25578
|
+
// Open OAuth in a POPUP -- not the iframe, not the top frame. NetSuite's OAuth
|
|
25579
|
+
// page is X-Frame-Options: DENY (can't load in an iframe), and navigating the
|
|
25580
|
+
// TOP frame replaces the HelloLeo preview and lands the session in a DIFFERENT
|
|
25581
|
+
// browsing context -- so back in the preview iframe (its own partitioned
|
|
25582
|
+
// sessionStorage) you still see the login even after signing in. A popup keeps
|
|
25583
|
+
// the app + preview intact and hands the token back to this exact context.
|
|
25584
|
+
const popup = window.open(
|
|
25585
|
+
url.toString(),
|
|
25586
|
+
'netsuite-oauth',
|
|
25587
|
+
'width=600,height=760,menubar=no,toolbar=no',
|
|
25588
|
+
);
|
|
25589
|
+
|
|
25590
|
+
if (!popup) {
|
|
25591
|
+
// Popup blocked -> fall back to top-frame navigation (works first-party / in
|
|
25592
|
+
// a standalone tab; the callback then uses the #nssid= redirect).
|
|
25593
|
+
try {
|
|
25594
|
+
if (window.top && window.top !== window) {
|
|
25595
|
+
window.top.location.href = url.toString();
|
|
25596
|
+
return;
|
|
25597
|
+
}
|
|
25598
|
+
} catch {
|
|
25599
|
+
/* top window not accessible */
|
|
25588
25600
|
}
|
|
25589
|
-
|
|
25590
|
-
|
|
25601
|
+
window.location.href = url.toString();
|
|
25602
|
+
return;
|
|
25591
25603
|
}
|
|
25592
|
-
|
|
25604
|
+
|
|
25605
|
+
const onMessage = (event: MessageEvent) => {
|
|
25606
|
+
if (event.origin !== workerOrigin) return;
|
|
25607
|
+
const data = event.data as { type?: string; nssid?: string } | null;
|
|
25608
|
+
if (!data || data.type !== 'netsuite-auth' || !data.nssid) return;
|
|
25609
|
+
try { sessionStorage.setItem(TOKEN_KEY, data.nssid); } catch {}
|
|
25610
|
+
window.removeEventListener('message', onMessage);
|
|
25611
|
+
try { popup.close(); } catch {}
|
|
25612
|
+
// Re-render as signed-in -- the auth provider re-checks /api/me on load.
|
|
25613
|
+
window.location.reload();
|
|
25614
|
+
};
|
|
25615
|
+
window.addEventListener('message', onMessage);
|
|
25593
25616
|
}
|
|
25594
25617
|
|
|
25595
25618
|
export interface NetSuiteSession {
|
|
@@ -25868,6 +25891,7 @@ var setupNetsuite = tool({
|
|
|
25868
25891
|
"- Read the session anywhere with useNetSuiteAuth() from src/lib/auth.tsx.",
|
|
25869
25892
|
"- Never replace AuthLayout with a password gate. Real OAuth is the only supported login.",
|
|
25870
25893
|
"- The auth worker (deployed via the cloudflare-backend MCP) serves /auth/netsuite/start, /auth/netsuite/callback, /api/me and /auth/netsuite/logout. It reads env.NETSUITE_ACCOUNT_ID, _CLIENT_ID and _CLIENT_SECRET. VITE_WORKER_URL must be set (HelloLeo Cloud seeds it).",
|
|
25894
|
+
"- POPUP FLOW (required so login works inside the HelloLeo preview iframe): the client opens /auth/netsuite/start in a popup with ?popup=1. The worker MUST carry that flag through OAuth (persist it on the flow row in /start, read it back in /callback via state). In /auth/netsuite/callback, when the flow was popup, DO NOT redirect to return_to#nssid \u2014 instead return an HTML page whose script does: window.opener.postMessage({ type: 'netsuite-auth', nssid: SID }, '*'); window.close(); (fall back to the return_to#nssid redirect only when there is no opener / not a popup). Without this, the preview iframe (partitioned sessionStorage) keeps showing the login after sign-in.",
|
|
25871
25895
|
"",
|
|
25872
25896
|
"Next: in src/routes/__root.tsx, import AuthLayout and wrap <Outlet /> with it. Keep any existing providers.",
|
|
25873
25897
|
"",
|
package/package.json
CHANGED
|
@@ -74,28 +74,51 @@ export function signInWithNetSuite(returnTo: string = window.location.href) {
|
|
|
74
74
|
'VITE_WORKER_URL is not set. HelloLeo Cloud must be provisioned before sign-in works.',
|
|
75
75
|
);
|
|
76
76
|
}
|
|
77
|
+
const workerOrigin = new URL(WORKER_URL).origin;
|
|
77
78
|
const url = new URL(\`\${WORKER_URL.replace(/\\/$/, '')}/auth/netsuite/start\`);
|
|
78
79
|
url.searchParams.set('return_to', returnTo);
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
// popup=1 tells the Worker callback to postMessage the session id back to
|
|
81
|
+
// THIS window (then close) instead of a #nssid= redirect.
|
|
82
|
+
url.searchParams.set('popup', '1');
|
|
83
|
+
|
|
84
|
+
// Open OAuth in a POPUP -- not the iframe, not the top frame. NetSuite's OAuth
|
|
85
|
+
// page is X-Frame-Options: DENY (can't load in an iframe), and navigating the
|
|
86
|
+
// TOP frame replaces the HelloLeo preview and lands the session in a DIFFERENT
|
|
87
|
+
// browsing context -- so back in the preview iframe (its own partitioned
|
|
88
|
+
// sessionStorage) you still see the login even after signing in. A popup keeps
|
|
89
|
+
// the app + preview intact and hands the token back to this exact context.
|
|
90
|
+
const popup = window.open(
|
|
91
|
+
url.toString(),
|
|
92
|
+
'netsuite-oauth',
|
|
93
|
+
'width=600,height=760,menubar=no,toolbar=no',
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!popup) {
|
|
97
|
+
// Popup blocked -> fall back to top-frame navigation (works first-party / in
|
|
98
|
+
// a standalone tab; the callback then uses the #nssid= redirect).
|
|
99
|
+
try {
|
|
100
|
+
if (window.top && window.top !== window) {
|
|
101
|
+
window.top.location.href = url.toString();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
/* top window not accessible */
|
|
94
106
|
}
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
window.location.href = url.toString();
|
|
108
|
+
return;
|
|
97
109
|
}
|
|
98
|
-
|
|
110
|
+
|
|
111
|
+
const onMessage = (event: MessageEvent) => {
|
|
112
|
+
if (event.origin !== workerOrigin) return;
|
|
113
|
+
const data = event.data as { type?: string; nssid?: string } | null;
|
|
114
|
+
if (!data || data.type !== 'netsuite-auth' || !data.nssid) return;
|
|
115
|
+
try { sessionStorage.setItem(TOKEN_KEY, data.nssid); } catch {}
|
|
116
|
+
window.removeEventListener('message', onMessage);
|
|
117
|
+
try { popup.close(); } catch {}
|
|
118
|
+
// Re-render as signed-in -- the auth provider re-checks /api/me on load.
|
|
119
|
+
window.location.reload();
|
|
120
|
+
};
|
|
121
|
+
window.addEventListener('message', onMessage);
|
|
99
122
|
}
|
|
100
123
|
|
|
101
124
|
export interface NetSuiteSession {
|
|
@@ -388,6 +411,7 @@ export const setupNetsuite = tool({
|
|
|
388
411
|
"- Read the session anywhere with useNetSuiteAuth() from src/lib/auth.tsx.",
|
|
389
412
|
"- Never replace AuthLayout with a password gate. Real OAuth is the only supported login.",
|
|
390
413
|
"- The auth worker (deployed via the cloudflare-backend MCP) serves /auth/netsuite/start, /auth/netsuite/callback, /api/me and /auth/netsuite/logout. It reads env.NETSUITE_ACCOUNT_ID, _CLIENT_ID and _CLIENT_SECRET. VITE_WORKER_URL must be set (HelloLeo Cloud seeds it).",
|
|
414
|
+
"- POPUP FLOW (required so login works inside the HelloLeo preview iframe): the client opens /auth/netsuite/start in a popup with ?popup=1. The worker MUST carry that flag through OAuth (persist it on the flow row in /start, read it back in /callback via state). In /auth/netsuite/callback, when the flow was popup, DO NOT redirect to return_to#nssid — instead return an HTML page whose script does: window.opener.postMessage({ type: 'netsuite-auth', nssid: SID }, '*'); window.close(); (fall back to the return_to#nssid redirect only when there is no opener / not a popup). Without this, the preview iframe (partitioned sessionStorage) keeps showing the login after sign-in.",
|
|
391
415
|
"",
|
|
392
416
|
"Next: in src/routes/__root.tsx, import AuthLayout and wrap <Outlet /> with it. Keep any existing providers.",
|
|
393
417
|
"",
|