@helloleo/plugins 0.2.16 → 0.2.19
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 +940 -95
- package/dist/templates/tanstack-router-2026-06-15/src/components/error-component.tsx +18 -15
- package/dist/templates/tanstack-router-2026-06-15/src/lib/proxy.ts +39 -1
- package/dist/templates/tanstack-start-2026-06-18/src/components/error-component.tsx +18 -15
- package/dist/templates/tanstack-start-2026-06-18/src/lib/proxy.ts +38 -1
- package/dist/templates/tanstack-start-2026-07-30/src/components/error-component.tsx +18 -15
- package/dist/templates/tanstack-start-2026-07-30/src/lib/proxy.ts +30 -1
- package/package.json +1 -1
- package/src/templates/tanstack-router-2026-06-15/src/components/error-component.tsx +18 -15
- package/src/templates/tanstack-router-2026-06-15/src/lib/proxy.ts +39 -1
- package/src/templates/tanstack-start-2026-06-18/src/components/error-component.tsx +18 -15
- package/src/templates/tanstack-start-2026-06-18/src/lib/proxy.ts +38 -1
- package/src/templates/tanstack-start-2026-07-30/src/components/error-component.tsx +18 -15
- package/src/templates/tanstack-start-2026-07-30/src/lib/proxy.ts +30 -1
- package/src/tools/scaffold.ts +43 -25
- package/src/tools/setup-netsuite.ts +1007 -98
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
* user already configured in Project Settings -- you NEVER pass an API key
|
|
8
8
|
* from frontend code, and you NEVER call third-party hosts directly.
|
|
9
9
|
*
|
|
10
|
+
* EXCEPTION -- NetSuite: never route it through this proxy. This proxy
|
|
11
|
+
* refreshes tokens per PROJECT (one shared credential for every end user).
|
|
12
|
+
* NetSuite runtime calls must stay per USER: the app signs each visitor in
|
|
13
|
+
* via its own /auth/netsuite/* + /api server routes and calls NetSuite with
|
|
14
|
+
* THAT visitor's session (see setup_netsuite / src/lib/netsuite-server.ts,
|
|
15
|
+
* netsuiteFetch(bearer(request), ...)). The proxy rejects "netsuite" with a
|
|
16
|
+
* 400 -- that's intentional, not a gap to fill in.
|
|
17
|
+
*
|
|
10
18
|
* Two modes:
|
|
11
19
|
* A) Frontend-only project -> import callIntegration() from this file.
|
|
12
20
|
* B) HelloLeo Cloud project -> route via Worker fetch(env.INTEGRATION_PROXY).
|
|
@@ -44,6 +52,29 @@ export const INTEGRATION_PROXY_URL = '{{LEO_INTEGRATION_PROXY_URL}}';
|
|
|
44
52
|
|
|
45
53
|
export const BACKEND_URL = (import.meta.env.VITE_WORKER_URL as string | undefined) ?? '';
|
|
46
54
|
|
|
55
|
+
// Multi-environment integrations: the published HelloLeo Cloud Worker has
|
|
56
|
+
// HELLOLEO_ENV=production injected at publish. Forwarding `?env=production`
|
|
57
|
+
// makes the proxy resolve credentials from the project's PRODUCTION integration
|
|
58
|
+
// connection (e.g. your production Odoo) instead of the development one, so a
|
|
59
|
+
// published app talks to production while the preview keeps talking to staging.
|
|
60
|
+
//
|
|
61
|
+
// Reads the flag from the server runtime (Cloudflare Worker / Node) when
|
|
62
|
+
// available; in the browser it's absent and we send no param — the proxy then
|
|
63
|
+
// derives the environment from the request origin, so client-side calls from
|
|
64
|
+
// the published app still resolve to production. Server-side calls (SSR / Cloud
|
|
65
|
+
// Worker) carry no browser origin, which is exactly why the explicit param
|
|
66
|
+
// matters there.
|
|
67
|
+
function proxyUrl(): string {
|
|
68
|
+
// Read via globalThis only (no bare `process` reference) so this compiles in
|
|
69
|
+
// any scaffolded project regardless of whether node types are present.
|
|
70
|
+
const g = globalThis as {
|
|
71
|
+
HELLOLEO_ENV?: string;
|
|
72
|
+
process?: { env?: Record<string, string | undefined> };
|
|
73
|
+
};
|
|
74
|
+
const env = g.HELLOLEO_ENV ?? g.process?.env?.HELLOLEO_ENV;
|
|
75
|
+
return env === 'production' ? `${INTEGRATION_PROXY_URL}?env=production` : INTEGRATION_PROXY_URL;
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
export interface ProxyRequest {
|
|
48
79
|
integration: string;
|
|
49
80
|
endpoint: string;
|
|
@@ -61,7 +92,14 @@ export interface ProxyResponse<T = unknown> {
|
|
|
61
92
|
export async function callIntegration<T = unknown>(
|
|
62
93
|
req: ProxyRequest,
|
|
63
94
|
): Promise<ProxyResponse<T>> {
|
|
64
|
-
|
|
95
|
+
if (req.integration.toLowerCase() === 'netsuite') {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'NetSuite is not proxied -- use the setup_netsuite auth flow (its ' +
|
|
98
|
+
'/auth/netsuite + /api routes) with the signed-in user\'s session, ' +
|
|
99
|
+
'not callIntegration().',
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const res = await fetch(proxyUrl(), {
|
|
65
103
|
method: 'POST',
|
|
66
104
|
headers: { 'Content-Type': 'application/json' },
|
|
67
105
|
body: JSON.stringify({ method: 'POST', ...req }),
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
* user already configured in Project Settings -- you NEVER pass an API key
|
|
8
8
|
* from frontend code, and you NEVER call third-party hosts directly.
|
|
9
9
|
*
|
|
10
|
+
* EXCEPTION -- NetSuite: never route it through this proxy. This proxy
|
|
11
|
+
* refreshes tokens per PROJECT (one shared credential for every end user).
|
|
12
|
+
* NetSuite runtime calls must stay per USER: the app signs each visitor in
|
|
13
|
+
* via its own /auth/netsuite/* + /api server routes and calls NetSuite with
|
|
14
|
+
* THAT visitor's session (see setup_netsuite / src/lib/netsuite-server.ts,
|
|
15
|
+
* netsuiteFetch(bearer(request), ...)). The proxy rejects "netsuite" with a
|
|
16
|
+
* 400 -- that's intentional, not a gap to fill in.
|
|
17
|
+
*
|
|
10
18
|
* Two modes:
|
|
11
19
|
* A) Frontend-only project -> import callIntegration() from this file.
|
|
12
20
|
* B) HelloLeo Cloud project -> route via Worker fetch(env.INTEGRATION_PROXY).
|
|
@@ -41,6 +49,29 @@
|
|
|
41
49
|
|
|
42
50
|
export const INTEGRATION_PROXY_URL = '{{LEO_INTEGRATION_PROXY_URL}}';
|
|
43
51
|
|
|
52
|
+
// Multi-environment integrations: the published HelloLeo Cloud Worker has
|
|
53
|
+
// HELLOLEO_ENV=production injected at publish. Forwarding `?env=production`
|
|
54
|
+
// makes the proxy resolve credentials from the project's PRODUCTION integration
|
|
55
|
+
// connection (e.g. your production Odoo) instead of the development one, so a
|
|
56
|
+
// published app talks to production while the preview keeps talking to staging.
|
|
57
|
+
//
|
|
58
|
+
// Reads the flag from the server runtime (Cloudflare Worker / Node) when
|
|
59
|
+
// available; in the browser it's absent and we send no param — the proxy then
|
|
60
|
+
// derives the environment from the request origin, so client-side calls from
|
|
61
|
+
// the published app still resolve to production. Server-side calls (SSR / Cloud
|
|
62
|
+
// Worker) carry no browser origin, which is exactly why the explicit param
|
|
63
|
+
// matters there.
|
|
64
|
+
function proxyUrl(): string {
|
|
65
|
+
// Read via globalThis only (no bare `process` reference) so this compiles in
|
|
66
|
+
// any scaffolded project regardless of whether node types are present.
|
|
67
|
+
const g = globalThis as {
|
|
68
|
+
HELLOLEO_ENV?: string;
|
|
69
|
+
process?: { env?: Record<string, string | undefined> };
|
|
70
|
+
};
|
|
71
|
+
const env = g.HELLOLEO_ENV ?? g.process?.env?.HELLOLEO_ENV;
|
|
72
|
+
return env === 'production' ? `${INTEGRATION_PROXY_URL}?env=production` : INTEGRATION_PROXY_URL;
|
|
73
|
+
}
|
|
74
|
+
|
|
44
75
|
export interface ProxyRequest {
|
|
45
76
|
integration: string;
|
|
46
77
|
endpoint: string;
|
|
@@ -58,7 +89,13 @@ export interface ProxyResponse<T = unknown> {
|
|
|
58
89
|
export async function callIntegration<T = unknown>(
|
|
59
90
|
req: ProxyRequest,
|
|
60
91
|
): Promise<ProxyResponse<T>> {
|
|
61
|
-
|
|
92
|
+
if (req.integration.toLowerCase() === 'netsuite') {
|
|
93
|
+
throw new Error(
|
|
94
|
+
'NetSuite is not proxied -- call it from this app\'s own /auth/netsuite + ' +
|
|
95
|
+
'/api server routes with the signed-in user\'s session, not callIntegration().',
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const res = await fetch(proxyUrl(), {
|
|
62
99
|
method: 'POST',
|
|
63
100
|
headers: { 'Content-Type': 'application/json' },
|
|
64
101
|
body: JSON.stringify({ method: 'POST', ...req }),
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -41,6 +41,29 @@
|
|
|
41
41
|
|
|
42
42
|
export const INTEGRATION_PROXY_URL = '{{LEO_INTEGRATION_PROXY_URL}}';
|
|
43
43
|
|
|
44
|
+
// Multi-environment integrations: the published HelloLeo Cloud Worker has
|
|
45
|
+
// HELLOLEO_ENV=production injected at publish. Forwarding `?env=production`
|
|
46
|
+
// makes the proxy resolve credentials from the project's PRODUCTION integration
|
|
47
|
+
// connection (e.g. your production Odoo) instead of the development one, so a
|
|
48
|
+
// published app talks to production while the preview keeps talking to staging.
|
|
49
|
+
//
|
|
50
|
+
// Reads the flag from the server runtime (Cloudflare Worker / Node) when
|
|
51
|
+
// available; in the browser it's absent and we send no param — the proxy then
|
|
52
|
+
// derives the environment from the request origin, so client-side calls from
|
|
53
|
+
// the published app still resolve to production. Server-side calls (SSR / Cloud
|
|
54
|
+
// Worker) carry no browser origin, which is exactly why the explicit param
|
|
55
|
+
// matters there.
|
|
56
|
+
function proxyUrl(): string {
|
|
57
|
+
// Read via globalThis only (no bare `process` reference) so this compiles in
|
|
58
|
+
// any scaffolded project regardless of whether node types are present.
|
|
59
|
+
const g = globalThis as {
|
|
60
|
+
HELLOLEO_ENV?: string;
|
|
61
|
+
process?: { env?: Record<string, string | undefined> };
|
|
62
|
+
};
|
|
63
|
+
const env = g.HELLOLEO_ENV ?? g.process?.env?.HELLOLEO_ENV;
|
|
64
|
+
return env === 'production' ? `${INTEGRATION_PROXY_URL}?env=production` : INTEGRATION_PROXY_URL;
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
export interface ProxyRequest {
|
|
45
68
|
integration: string;
|
|
46
69
|
endpoint: string;
|
|
@@ -58,7 +81,13 @@ export interface ProxyResponse<T = unknown> {
|
|
|
58
81
|
export async function callIntegration<T = unknown>(
|
|
59
82
|
req: ProxyRequest,
|
|
60
83
|
): Promise<ProxyResponse<T>> {
|
|
61
|
-
|
|
84
|
+
if (req.integration.toLowerCase() === 'netsuite') {
|
|
85
|
+
throw new Error(
|
|
86
|
+
'NetSuite is not proxied -- call it from this app\'s own /auth/netsuite + ' +
|
|
87
|
+
'/api server routes with the signed-in user\'s session, not callIntegration().',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const res = await fetch(proxyUrl(), {
|
|
62
91
|
method: 'POST',
|
|
63
92
|
headers: { 'Content-Type': 'application/json' },
|
|
64
93
|
body: JSON.stringify({ method: 'POST', ...req }),
|
package/package.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
* user already configured in Project Settings -- you NEVER pass an API key
|
|
8
8
|
* from frontend code, and you NEVER call third-party hosts directly.
|
|
9
9
|
*
|
|
10
|
+
* EXCEPTION -- NetSuite: never route it through this proxy. This proxy
|
|
11
|
+
* refreshes tokens per PROJECT (one shared credential for every end user).
|
|
12
|
+
* NetSuite runtime calls must stay per USER: the app signs each visitor in
|
|
13
|
+
* via its own /auth/netsuite/* + /api server routes and calls NetSuite with
|
|
14
|
+
* THAT visitor's session (see setup_netsuite / src/lib/netsuite-server.ts,
|
|
15
|
+
* netsuiteFetch(bearer(request), ...)). The proxy rejects "netsuite" with a
|
|
16
|
+
* 400 -- that's intentional, not a gap to fill in.
|
|
17
|
+
*
|
|
10
18
|
* Two modes:
|
|
11
19
|
* A) Frontend-only project -> import callIntegration() from this file.
|
|
12
20
|
* B) HelloLeo Cloud project -> route via Worker fetch(env.INTEGRATION_PROXY).
|
|
@@ -44,6 +52,29 @@ export const INTEGRATION_PROXY_URL = '{{LEO_INTEGRATION_PROXY_URL}}';
|
|
|
44
52
|
|
|
45
53
|
export const BACKEND_URL = (import.meta.env.VITE_WORKER_URL as string | undefined) ?? '';
|
|
46
54
|
|
|
55
|
+
// Multi-environment integrations: the published HelloLeo Cloud Worker has
|
|
56
|
+
// HELLOLEO_ENV=production injected at publish. Forwarding `?env=production`
|
|
57
|
+
// makes the proxy resolve credentials from the project's PRODUCTION integration
|
|
58
|
+
// connection (e.g. your production Odoo) instead of the development one, so a
|
|
59
|
+
// published app talks to production while the preview keeps talking to staging.
|
|
60
|
+
//
|
|
61
|
+
// Reads the flag from the server runtime (Cloudflare Worker / Node) when
|
|
62
|
+
// available; in the browser it's absent and we send no param — the proxy then
|
|
63
|
+
// derives the environment from the request origin, so client-side calls from
|
|
64
|
+
// the published app still resolve to production. Server-side calls (SSR / Cloud
|
|
65
|
+
// Worker) carry no browser origin, which is exactly why the explicit param
|
|
66
|
+
// matters there.
|
|
67
|
+
function proxyUrl(): string {
|
|
68
|
+
// Read via globalThis only (no bare `process` reference) so this compiles in
|
|
69
|
+
// any scaffolded project regardless of whether node types are present.
|
|
70
|
+
const g = globalThis as {
|
|
71
|
+
HELLOLEO_ENV?: string;
|
|
72
|
+
process?: { env?: Record<string, string | undefined> };
|
|
73
|
+
};
|
|
74
|
+
const env = g.HELLOLEO_ENV ?? g.process?.env?.HELLOLEO_ENV;
|
|
75
|
+
return env === 'production' ? `${INTEGRATION_PROXY_URL}?env=production` : INTEGRATION_PROXY_URL;
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
export interface ProxyRequest {
|
|
48
79
|
integration: string;
|
|
49
80
|
endpoint: string;
|
|
@@ -61,7 +92,14 @@ export interface ProxyResponse<T = unknown> {
|
|
|
61
92
|
export async function callIntegration<T = unknown>(
|
|
62
93
|
req: ProxyRequest,
|
|
63
94
|
): Promise<ProxyResponse<T>> {
|
|
64
|
-
|
|
95
|
+
if (req.integration.toLowerCase() === 'netsuite') {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'NetSuite is not proxied -- use the setup_netsuite auth flow (its ' +
|
|
98
|
+
'/auth/netsuite + /api routes) with the signed-in user\'s session, ' +
|
|
99
|
+
'not callIntegration().',
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const res = await fetch(proxyUrl(), {
|
|
65
103
|
method: 'POST',
|
|
66
104
|
headers: { 'Content-Type': 'application/json' },
|
|
67
105
|
body: JSON.stringify({ method: 'POST', ...req }),
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
* user already configured in Project Settings -- you NEVER pass an API key
|
|
8
8
|
* from frontend code, and you NEVER call third-party hosts directly.
|
|
9
9
|
*
|
|
10
|
+
* EXCEPTION -- NetSuite: never route it through this proxy. This proxy
|
|
11
|
+
* refreshes tokens per PROJECT (one shared credential for every end user).
|
|
12
|
+
* NetSuite runtime calls must stay per USER: the app signs each visitor in
|
|
13
|
+
* via its own /auth/netsuite/* + /api server routes and calls NetSuite with
|
|
14
|
+
* THAT visitor's session (see setup_netsuite / src/lib/netsuite-server.ts,
|
|
15
|
+
* netsuiteFetch(bearer(request), ...)). The proxy rejects "netsuite" with a
|
|
16
|
+
* 400 -- that's intentional, not a gap to fill in.
|
|
17
|
+
*
|
|
10
18
|
* Two modes:
|
|
11
19
|
* A) Frontend-only project -> import callIntegration() from this file.
|
|
12
20
|
* B) HelloLeo Cloud project -> route via Worker fetch(env.INTEGRATION_PROXY).
|
|
@@ -41,6 +49,29 @@
|
|
|
41
49
|
|
|
42
50
|
export const INTEGRATION_PROXY_URL = '{{LEO_INTEGRATION_PROXY_URL}}';
|
|
43
51
|
|
|
52
|
+
// Multi-environment integrations: the published HelloLeo Cloud Worker has
|
|
53
|
+
// HELLOLEO_ENV=production injected at publish. Forwarding `?env=production`
|
|
54
|
+
// makes the proxy resolve credentials from the project's PRODUCTION integration
|
|
55
|
+
// connection (e.g. your production Odoo) instead of the development one, so a
|
|
56
|
+
// published app talks to production while the preview keeps talking to staging.
|
|
57
|
+
//
|
|
58
|
+
// Reads the flag from the server runtime (Cloudflare Worker / Node) when
|
|
59
|
+
// available; in the browser it's absent and we send no param — the proxy then
|
|
60
|
+
// derives the environment from the request origin, so client-side calls from
|
|
61
|
+
// the published app still resolve to production. Server-side calls (SSR / Cloud
|
|
62
|
+
// Worker) carry no browser origin, which is exactly why the explicit param
|
|
63
|
+
// matters there.
|
|
64
|
+
function proxyUrl(): string {
|
|
65
|
+
// Read via globalThis only (no bare `process` reference) so this compiles in
|
|
66
|
+
// any scaffolded project regardless of whether node types are present.
|
|
67
|
+
const g = globalThis as {
|
|
68
|
+
HELLOLEO_ENV?: string;
|
|
69
|
+
process?: { env?: Record<string, string | undefined> };
|
|
70
|
+
};
|
|
71
|
+
const env = g.HELLOLEO_ENV ?? g.process?.env?.HELLOLEO_ENV;
|
|
72
|
+
return env === 'production' ? `${INTEGRATION_PROXY_URL}?env=production` : INTEGRATION_PROXY_URL;
|
|
73
|
+
}
|
|
74
|
+
|
|
44
75
|
export interface ProxyRequest {
|
|
45
76
|
integration: string;
|
|
46
77
|
endpoint: string;
|
|
@@ -58,7 +89,13 @@ export interface ProxyResponse<T = unknown> {
|
|
|
58
89
|
export async function callIntegration<T = unknown>(
|
|
59
90
|
req: ProxyRequest,
|
|
60
91
|
): Promise<ProxyResponse<T>> {
|
|
61
|
-
|
|
92
|
+
if (req.integration.toLowerCase() === 'netsuite') {
|
|
93
|
+
throw new Error(
|
|
94
|
+
'NetSuite is not proxied -- call it from this app\'s own /auth/netsuite + ' +
|
|
95
|
+
'/api server routes with the signed-in user\'s session, not callIntegration().',
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const res = await fetch(proxyUrl(), {
|
|
62
99
|
method: 'POST',
|
|
63
100
|
headers: { 'Content-Type': 'application/json' },
|
|
64
101
|
body: JSON.stringify({ method: 'POST', ...req }),
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
import { useRouterState } from '@tanstack/react-router'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
3
|
|
|
6
4
|
// Rendered by the router's defaultErrorComponent for any uncaught render or
|
|
7
5
|
// loader error. Per-route `errorComponent` options override it. Keep this
|
|
8
|
-
// component free of app dependencies (toasts, auth, etc.
|
|
9
|
-
// render when the rest of the app is broken.
|
|
6
|
+
// component free of app dependencies (toasts, auth, ui components, etc).
|
|
10
7
|
export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
11
8
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
|
12
9
|
const [copied, setCopied] = useState(false)
|
|
@@ -27,21 +24,27 @@ export function DefaultErrorComponent({ error }: { error: Error }) {
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
</
|
|
35
|
-
<
|
|
36
|
-
<pre className="text-sm text-
|
|
27
|
+
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|
28
|
+
<div className="w-full max-w-lg rounded-xl border border-neutral-200 bg-white p-6 shadow-sm">
|
|
29
|
+
<h2 className="text-base font-semibold text-neutral-900">
|
|
30
|
+
Something went wrong
|
|
31
|
+
</h2>
|
|
32
|
+
<div className="mt-4 space-y-4">
|
|
33
|
+
<pre className="text-sm text-red-600 whitespace-pre-wrap break-words rounded-md bg-neutral-100 p-3">
|
|
37
34
|
{error.message}
|
|
38
35
|
</pre>
|
|
39
|
-
<
|
|
40
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={copyForLeo}
|
|
39
|
+
className="inline-flex items-center rounded-md bg-neutral-900 px-4 py-2 text-sm font-medium text-neutral-50"
|
|
40
|
+
>
|
|
41
|
+
{copied ? 'Copied!' : 'Copy error'}
|
|
42
|
+
</button>
|
|
43
|
+
<p className="text-sm text-neutral-500">
|
|
41
44
|
Paste the copied error to Leo and it will help you fix it.
|
|
42
45
|
</p>
|
|
43
|
-
</
|
|
44
|
-
</
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
45
48
|
</div>
|
|
46
49
|
)
|
|
47
50
|
}
|