@enfyra/mcp-server 0.1.78 → 0.1.81
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/lib/auth.d.ts +1 -0
- package/dist/lib/auth.js +8 -0
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/mcp-example-connect.js +214 -386
- package/dist/lib/mcp-example-connect.js.map +1 -1
- package/dist/lib/mcp-instructions.js +1 -1
- package/dist/lib/mcp-instructions.js.map +1 -1
- package/dist/lib/mutation-guards.js +5 -0
- package/dist/lib/mutation-guards.js.map +1 -1
- package/dist/lib/platform-flow-operations.d.ts +54 -4
- package/dist/lib/platform-flow-operations.js +65 -7
- package/dist/lib/platform-flow-operations.js.map +1 -1
- package/dist/lib/platform-flow-tools.js +20 -24
- package/dist/lib/platform-flow-tools.js.map +1 -1
- package/dist/lib/required-knowledge.js +24 -3
- package/dist/lib/required-knowledge.js.map +1 -1
- package/dist/lib/runtime-cache-socket.js +28 -6
- package/dist/lib/runtime-cache-socket.js.map +1 -1
- package/dist/lib/runtime-zone-registry.js +2 -1
- package/dist/lib/runtime-zone-registry.js.map +1 -1
- package/dist/lib/tool-output-contracts.js +27 -20
- package/dist/lib/tool-output-contracts.js.map +1 -1
- package/dist/lib/toolset-filter.js +3 -0
- package/dist/lib/toolset-filter.js.map +1 -1
- package/dist/lib/workflow-definitions.js +22 -8
- package/dist/lib/workflow-definitions.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,440 +1,268 @@
|
|
|
1
1
|
export const connectExamples = {
|
|
2
|
-
title: 'Connect
|
|
3
|
-
useWhen: 'Use when connecting Nuxt, Next, Angular, or
|
|
2
|
+
title: 'Connect apps to Enfyra using official SDK packages',
|
|
3
|
+
useWhen: 'Use when connecting a Nuxt, Next.js, React, Vue, Angular, or other app to Enfyra. Always prefer the official SDK package for the target framework over manual proxy configuration.',
|
|
4
4
|
examples: [
|
|
5
5
|
{
|
|
6
|
-
name: '
|
|
7
|
-
code:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
name: 'SDK package selection by framework',
|
|
7
|
+
code: `# Nuxt 3/4 (SSR + CSR, auto proxy, auto composables)
|
|
8
|
+
yarn add @enfyra/sdk-nuxt @enfyra/sdk-core
|
|
9
|
+
|
|
10
|
+
# Next.js App Router (SSR + CSR, one-line config preset, providerless hooks)
|
|
11
|
+
yarn add @enfyra/sdk-next @enfyra/sdk-core
|
|
12
|
+
|
|
13
|
+
# React SPA (CSR only, Provider + hooks)
|
|
14
|
+
yarn add @enfyra/sdk-react @enfyra/sdk-core zustand
|
|
15
|
+
|
|
16
|
+
# Vue 3 SPA (CSR only, composables)
|
|
17
|
+
yarn add @enfyra/sdk-vue @enfyra/sdk-core
|
|
18
|
+
|
|
19
|
+
# Any other framework / Node.js scripts (core client only)
|
|
20
|
+
yarn add @enfyra/sdk-core`,
|
|
20
21
|
notes: [
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
22
|
+
'Always install the framework-specific SDK package. Do not write manual proxy configs, route handlers, or cookie bridges when an SDK exists for the target framework.',
|
|
23
|
+
'@enfyra/sdk-nuxt and @enfyra/sdk-next handle the same-origin proxy, cookie bridge, OAuth redirect, and SSR request isolation automatically.',
|
|
24
|
+
'@enfyra/sdk-react and @enfyra/sdk-vue are CSR-only. The host app still needs a same-origin reverse proxy (dev server proxy or production nginx/Caddy) pointing /enfyra/** to the Enfyra App /api bridge.',
|
|
25
|
+
'@enfyra/sdk-core is the transport layer. Use it directly only for Node.js scripts, unsupported frameworks, or when the framework SDK does not cover the use case.',
|
|
24
26
|
],
|
|
25
27
|
},
|
|
26
28
|
{
|
|
27
|
-
name: '
|
|
28
|
-
code:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
source: "/enfyra/:path*",
|
|
33
|
-
destination: \`\${process.env.ENFYRA_API_URL}/:path*\`
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
source: "/socket.io/",
|
|
37
|
-
destination: \`\${process.env.ENFYRA_APP_URL}/ws/socket.io/\`
|
|
38
|
-
}
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
}
|
|
29
|
+
name: 'Nuxt setup with @enfyra/sdk-nuxt',
|
|
30
|
+
code: `// nuxt.config.ts
|
|
31
|
+
export default defineNuxtConfig({
|
|
32
|
+
modules: ['@enfyra/sdk-nuxt'],
|
|
33
|
+
})
|
|
42
34
|
|
|
43
|
-
|
|
35
|
+
// .env
|
|
36
|
+
ENFYRA_APP_URL=https://admin.example.com`,
|
|
44
37
|
notes: [
|
|
45
|
-
'
|
|
46
|
-
'The
|
|
47
|
-
'
|
|
38
|
+
'One module entry and one env var. No routeRules, no server middleware, no plugin, no cookie handler.',
|
|
39
|
+
'The module proxies /${routePrefix}/** to ${appUrl}/api/** with manual redirects, creates a request-scoped SSR client, and auto-imports all composables.',
|
|
40
|
+
'Composables: useEnfyra(), useAuth(), useQuery(), useMutation(), useStorage(), useWebSocket().',
|
|
41
|
+
'Optional config: enfyra.appUrl overrides env; enfyra.routePrefix changes the proxy prefix (default /enfyra).',
|
|
48
42
|
],
|
|
49
43
|
},
|
|
50
44
|
{
|
|
51
|
-
name: '
|
|
52
|
-
code: `//
|
|
53
|
-
{
|
|
54
|
-
"/enfyra/**": {
|
|
55
|
-
"target": "https://demo.enfyra.io/api",
|
|
56
|
-
"secure": true,
|
|
57
|
-
"changeOrigin": true,
|
|
58
|
-
"pathRewrite": {
|
|
59
|
-
"^/enfyra": ""
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
"/socket.io/**": {
|
|
63
|
-
"target": "https://demo.enfyra.io/api/ws",
|
|
64
|
-
"secure": true,
|
|
65
|
-
"changeOrigin": true,
|
|
66
|
-
"ws": true
|
|
67
|
-
}
|
|
68
|
-
}
|
|
45
|
+
name: 'Next.js setup with @enfyra/sdk-next',
|
|
46
|
+
code: `// next.config.mjs — quick path (one line)
|
|
47
|
+
export { default } from '@enfyra/sdk-next'
|
|
69
48
|
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
"projects": {
|
|
73
|
-
"app": {
|
|
74
|
-
"architect": {
|
|
75
|
-
"serve": {
|
|
76
|
-
"options": {
|
|
77
|
-
"proxyConfig": "src/proxy.conf.json"
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}`,
|
|
49
|
+
// .env.local
|
|
50
|
+
ENFYRA_APP_URL=http://localhost:3000`,
|
|
84
51
|
notes: [
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'The
|
|
88
|
-
'
|
|
52
|
+
'One re-export and one env var. No generated route handler, no middleware, no Provider in layout.',
|
|
53
|
+
'This quick path is for new apps or apps without an existing next.config. If the app already has a next.config with custom settings, use withEnfyra(existingConfig, options) instead to preserve them.',
|
|
54
|
+
'The preset adds beforeFiles rewrites proxying /api/enfyra/** to ${appUrl}/api/** and injects the browser prefix as a build constant.',
|
|
55
|
+
'Client hooks (providerless): import { useAuth, useEnfyra, useQuery, useMutation, useStorage } from "@enfyra/sdk-next/client".',
|
|
56
|
+
'Server Components: import { createServerEnfyra } from "@enfyra/sdk-next/server" — request-scoped, forwards cookies.',
|
|
57
|
+
'Server Actions: import { createServerActionEnfyra } from "@enfyra/sdk-next/server" — applies upstream Set-Cookie rotation.',
|
|
58
|
+
'Existing config: wrap with withEnfyra(nextConfig, options). Configured preset: enfyra({ appUrl, routePrefix }).',
|
|
59
|
+
'Requires Next.js >=14 <17 App Router. Rejects output:"export" at config load time.',
|
|
89
60
|
],
|
|
90
61
|
},
|
|
91
62
|
{
|
|
92
|
-
name: '
|
|
93
|
-
code: `
|
|
94
|
-
method: "POST",
|
|
95
|
-
credentials: "include",
|
|
96
|
-
headers: { "Content-Type": "application/json" },
|
|
97
|
-
body: JSON.stringify({ email, password, remember: true })
|
|
98
|
-
})
|
|
63
|
+
name: 'Next.js client hooks usage',
|
|
64
|
+
code: `'use client'
|
|
99
65
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
import { useAuth, useEnfyra, useQuery } from '@enfyra/sdk-next/client'
|
|
67
|
+
|
|
68
|
+
function Dashboard() {
|
|
69
|
+
const { user, isAuthenticated, pending, login, logout } = useAuth()
|
|
70
|
+
const client = useEnfyra()
|
|
71
|
+
const { data, pending: loading } = useQuery(() =>
|
|
72
|
+
client.from('articles').select('id,title').limit(10).execute()
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if (pending) return <p>Checking session…</p>
|
|
76
|
+
if (!isAuthenticated) return <button onClick={() => login({ email, password })}>Login</button>
|
|
77
|
+
return <ul>{data?.map(a => <li key={a.id}>{a.title}</li>)}</ul>
|
|
78
|
+
}`,
|
|
103
79
|
notes: [
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
80
|
+
'No Provider wrapper needed. Hooks use a module-level singleton that is SSR-safe (server render is always anonymous/idle).',
|
|
81
|
+
'Auth refresh starts automatically after hydration via useEffect.',
|
|
82
|
+
'useEnfyra() returns the original EnfyraClient from @enfyra/sdk-core.',
|
|
107
83
|
],
|
|
108
84
|
},
|
|
109
85
|
{
|
|
110
|
-
name: '
|
|
111
|
-
code: `//
|
|
112
|
-
import {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
export function useRealtime() {
|
|
119
|
-
function connect() {
|
|
120
|
-
if (import.meta.server) return null
|
|
121
|
-
if (socket.value) return socket.value
|
|
122
|
-
|
|
123
|
-
const nextSocket = io("/chat", {
|
|
124
|
-
path: "/socket.io",
|
|
125
|
-
withCredentials: true,
|
|
126
|
-
reconnection: true,
|
|
127
|
-
reconnectionAttempts: Infinity,
|
|
128
|
-
reconnectionDelay: 2000,
|
|
129
|
-
reconnectionDelayMax: 30000
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
nextSocket.on("connect", () => {
|
|
133
|
-
isConnected.value = true
|
|
134
|
-
})
|
|
135
|
-
nextSocket.on("disconnect", () => {
|
|
136
|
-
isConnected.value = false
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
socket.value = nextSocket
|
|
140
|
-
return nextSocket
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function disconnect() {
|
|
144
|
-
if (!socket.value) return
|
|
145
|
-
socket.value.disconnect()
|
|
146
|
-
socket.value = null
|
|
147
|
-
isConnected.value = false
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function onMessage(handler) {
|
|
151
|
-
const activeSocket = socket.value ?? connect()
|
|
152
|
-
if (!activeSocket) return () => {}
|
|
153
|
-
activeSocket.on("chat:message", handler)
|
|
154
|
-
return () => activeSocket.off("chat:message", handler)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return { socket, isConnected: readonly(isConnected), connect, disconnect, onMessage }
|
|
86
|
+
name: 'Next.js Server Component and Server Action',
|
|
87
|
+
code: `// app/page.tsx — Server Component
|
|
88
|
+
import { createServerEnfyra } from '@enfyra/sdk-next/server'
|
|
89
|
+
|
|
90
|
+
export default async function Page() {
|
|
91
|
+
const client = await createServerEnfyra()
|
|
92
|
+
const { data } = await client.from('posts').select('id,title').limit(5).execute()
|
|
93
|
+
return <ul>{data?.map(p => <li key={p.id}>{p.title}</li>)}</ul>
|
|
158
94
|
}
|
|
159
95
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
export default defineNuxtPlugin(() => {
|
|
164
|
-
const { me } = useAuth()
|
|
165
|
-
const realtime = useRealtime()
|
|
166
|
-
|
|
167
|
-
watch(
|
|
168
|
-
me,
|
|
169
|
-
user => {
|
|
170
|
-
if (user) realtime.connect()
|
|
171
|
-
else realtime.disconnect()
|
|
172
|
-
},
|
|
173
|
-
{ immediate: true }
|
|
174
|
-
)
|
|
175
|
-
})
|
|
96
|
+
// app/actions.ts — Server Action
|
|
97
|
+
'use server'
|
|
98
|
+
import { createServerActionEnfyra } from '@enfyra/sdk-next/server'
|
|
176
99
|
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
stopRealtime = realtime.onMessage(event => {
|
|
183
|
-
// Update local UI state, then debounce REST refresh if full state is needed.
|
|
100
|
+
export async function loginAction(formData: FormData) {
|
|
101
|
+
const { client, applySetCookies } = await createServerActionEnfyra()
|
|
102
|
+
const res = await client.post('/auth/login', {
|
|
103
|
+
email: formData.get('email'),
|
|
104
|
+
password: formData.get('password'),
|
|
184
105
|
})
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
onUnmounted(() => {
|
|
188
|
-
stopRealtime()
|
|
189
|
-
})`,
|
|
106
|
+
applySetCookies(res.headers['set-cookie'] ?? [])
|
|
107
|
+
}`,
|
|
190
108
|
notes: [
|
|
191
|
-
'
|
|
192
|
-
'
|
|
193
|
-
'
|
|
194
|
-
'Route components add event listeners and remove them on unmount; they can optimistically update local state and debounce REST refreshes.',
|
|
195
|
-
'Disconnect the singleton socket when the current user/session clears.',
|
|
109
|
+
'createServerEnfyra() reads await headers(), forwards cookie/authorization, creates a fresh client per call.',
|
|
110
|
+
'createServerActionEnfyra() reads await cookies() and returns applySetCookies to write upstream Set-Cookie back to the browser.',
|
|
111
|
+
'Never cache the server client at module or process scope.',
|
|
196
112
|
],
|
|
197
113
|
},
|
|
198
114
|
{
|
|
199
|
-
name: '
|
|
200
|
-
code:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
import { EnfyraAuthService } from "./enfyra-auth.service"
|
|
208
|
-
|
|
209
|
-
export const enfyraCredentialsInterceptor: HttpInterceptorFn = (req, next) => {
|
|
210
|
-
if (!req.url.startsWith("/enfyra/")) return next(req)
|
|
211
|
-
return next(req.clone({ withCredentials: true }))
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export const requireUserGuard: CanActivateFn = () => {
|
|
215
|
-
const auth = inject(EnfyraAuthService)
|
|
216
|
-
const router = inject(Router)
|
|
217
|
-
|
|
218
|
-
return auth.loadMe().pipe(
|
|
219
|
-
map(user => user ? true : router.createUrlTree(["/login"])),
|
|
220
|
-
catchError(() => of(router.createUrlTree(["/login"])))
|
|
115
|
+
name: 'React SPA setup with @enfyra/sdk-react',
|
|
116
|
+
code: `import { EnfyraProvider, useAuth, useQuery, useMutation } from '@enfyra/sdk-react'
|
|
117
|
+
|
|
118
|
+
function App() {
|
|
119
|
+
return (
|
|
120
|
+
<EnfyraProvider config={{ baseUrl: '/enfyra', auth: { strategy: 'cookie', cookieBridgePrefix: '/enfyra' } }}>
|
|
121
|
+
<Dashboard />
|
|
122
|
+
</EnfyraProvider>
|
|
221
123
|
)
|
|
222
124
|
}
|
|
223
125
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
]
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// enfyra-auth.service.ts
|
|
232
|
-
import { Injectable, signal } from "@angular/core"
|
|
233
|
-
import { HttpClient } from "@angular/common/http"
|
|
234
|
-
import { Observable, tap } from "rxjs"
|
|
235
|
-
|
|
236
|
-
type EnfyraUser = { id: string | number; email?: string }
|
|
237
|
-
|
|
238
|
-
@Injectable({ providedIn: "root" })
|
|
239
|
-
export class EnfyraAuthService {
|
|
240
|
-
readonly user = signal<EnfyraUser | null>(null)
|
|
241
|
-
|
|
242
|
-
constructor(private readonly http: HttpClient) {}
|
|
243
|
-
|
|
244
|
-
login(email: string, password: string): Observable<unknown> {
|
|
245
|
-
return this.http.post("/enfyra/login", { email, password, remember: true }).pipe(
|
|
246
|
-
tap(() => this.loadMe().subscribe())
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
loadMe(): Observable<EnfyraUser | null> {
|
|
251
|
-
return this.http.get<EnfyraUser | null>("/enfyra/me").pipe(
|
|
252
|
-
tap(user => this.user.set(user))
|
|
253
|
-
)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
logout(): Observable<unknown> {
|
|
257
|
-
return this.http.post("/enfyra/logout", {}).pipe(
|
|
258
|
-
tap(() => this.user.set(null))
|
|
259
|
-
)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
startGoogleOAuth(returnPath = "/") {
|
|
263
|
-
const redirect = new URL(returnPath, window.location.origin)
|
|
264
|
-
const url = new URL("/api/auth/google", "https://demo.enfyra.io")
|
|
265
|
-
url.searchParams.set("redirect", redirect.toString())
|
|
266
|
-
url.searchParams.set("cookieBridgePrefix", "/enfyra")
|
|
267
|
-
window.location.href = url.toString()
|
|
268
|
-
}
|
|
126
|
+
function Dashboard() {
|
|
127
|
+
const { user, isAuthenticated, login, logout } = useAuth()
|
|
128
|
+
const { data, pending } = useQuery('articles', { select: ['id', 'title'], limit: 10 })
|
|
129
|
+
const { execute, pending: saving } = useMutation('articles', { operation: 'insert' })
|
|
269
130
|
}`,
|
|
270
131
|
notes: [
|
|
271
|
-
'
|
|
272
|
-
'
|
|
273
|
-
'
|
|
274
|
-
'OAuth starts at the Enfyra app /api/auth/google URL, not the local /enfyra path. It returns through the local cookie bridge before Angular loads /enfyra/me.',
|
|
132
|
+
'React SPA is CSR-only. The host app must provide a same-origin proxy: Vite dev server proxy, CRA proxy, or production reverse proxy mapping /enfyra/** to the Enfyra App /api bridge.',
|
|
133
|
+
'EnfyraProvider wraps the app once at the root. All hooks read from the shared client/store.',
|
|
134
|
+
'Do not use @enfyra/sdk-react inside Next.js — use @enfyra/sdk-next instead.',
|
|
275
135
|
],
|
|
276
136
|
},
|
|
277
137
|
{
|
|
278
|
-
name: '
|
|
279
|
-
code: `
|
|
280
|
-
|
|
281
|
-
// app/realtime-provider.tsx
|
|
282
|
-
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react"
|
|
283
|
-
import { io, type Socket } from "socket.io-client"
|
|
138
|
+
name: 'Vue 3 SPA setup with @enfyra/sdk-vue',
|
|
139
|
+
code: `import { createEnfyraClient, useAuth, useApi, useStorage, useWebSocket } from '@enfyra/sdk-vue'
|
|
284
140
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const RealtimeContext = createContext<RealtimeContextValue>({
|
|
291
|
-
socket: null,
|
|
292
|
-
isConnected: false
|
|
141
|
+
createEnfyraClient({
|
|
142
|
+
baseUrl: '/enfyra',
|
|
143
|
+
auth: { strategy: 'cookie', cookieBridgePrefix: '/enfyra' },
|
|
293
144
|
})
|
|
294
145
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
user: { id: string | number } | null
|
|
300
|
-
children: React.ReactNode
|
|
301
|
-
}) {
|
|
302
|
-
const socketRef = useRef<Socket | null>(null)
|
|
303
|
-
const [isConnected, setConnected] = useState(false)
|
|
304
|
-
|
|
305
|
-
useEffect(() => {
|
|
306
|
-
if (!user) {
|
|
307
|
-
socketRef.current?.disconnect()
|
|
308
|
-
socketRef.current = null
|
|
309
|
-
setConnected(false)
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (socketRef.current) return
|
|
314
|
-
|
|
315
|
-
const socket = io("/chat", {
|
|
316
|
-
path: "/socket.io",
|
|
317
|
-
withCredentials: true,
|
|
318
|
-
reconnection: true,
|
|
319
|
-
reconnectionAttempts: Infinity,
|
|
320
|
-
reconnectionDelay: 2000,
|
|
321
|
-
reconnectionDelayMax: 30000
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
socket.on("connect", () => setConnected(true))
|
|
325
|
-
socket.on("disconnect", () => setConnected(false))
|
|
326
|
-
socketRef.current = socket
|
|
327
|
-
|
|
328
|
-
return () => {
|
|
329
|
-
socket.off("connect")
|
|
330
|
-
socket.off("disconnect")
|
|
331
|
-
socket.disconnect()
|
|
332
|
-
socketRef.current = null
|
|
333
|
-
setConnected(false)
|
|
334
|
-
}
|
|
335
|
-
}, [user])
|
|
336
|
-
|
|
337
|
-
const value = useMemo(
|
|
338
|
-
() => ({ socket: socketRef.current, isConnected }),
|
|
339
|
-
[isConnected]
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
return <RealtimeContext.Provider value={value}>{children}</RealtimeContext.Provider>
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function useRealtime() {
|
|
346
|
-
return useContext(RealtimeContext)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// app/chat/page.tsx
|
|
350
|
-
// const { socket } = useRealtime()
|
|
351
|
-
// useEffect(() => {
|
|
352
|
-
// if (!socket) return
|
|
353
|
-
// const onMessage = event => {
|
|
354
|
-
// // Update local UI state, then debounce REST refresh if full state is needed.
|
|
355
|
-
// }
|
|
356
|
-
// socket.on("chat:message", onMessage)
|
|
357
|
-
// return () => socket.off("chat:message", onMessage)
|
|
358
|
-
// }, [socket])`,
|
|
146
|
+
// In any component setup():
|
|
147
|
+
const { user, login, logout, fetchUser } = useAuth()
|
|
148
|
+
const { data, loading, error, refresh } = useApi().get('/articles', { query: { limit: 20 } })
|
|
149
|
+
const socket = useWebSocket('chat')`,
|
|
359
150
|
notes: [
|
|
360
|
-
'
|
|
361
|
-
'
|
|
362
|
-
'
|
|
363
|
-
'Pages/components should only subscribe/unsubscribe listeners; they should not create independent socket connections.',
|
|
364
|
-
'Disconnect the singleton socket when the current user/session clears.',
|
|
151
|
+
'Vue SPA is CSR-only. The host app must provide a same-origin proxy (Vite proxy, nginx, Caddy) mapping /enfyra/** to the Enfyra App /api bridge.',
|
|
152
|
+
'createEnfyraClient() is called once at app entry. Composables read from the shared instance.',
|
|
153
|
+
'Do not use @enfyra/sdk-vue inside Nuxt — use @enfyra/sdk-nuxt instead.',
|
|
365
154
|
],
|
|
366
155
|
},
|
|
367
156
|
{
|
|
368
|
-
name: '
|
|
369
|
-
code: `//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
connect() {
|
|
390
|
-
if (this.socket) return this.socket
|
|
391
|
-
|
|
392
|
-
this.socket = io("/chat", {
|
|
393
|
-
path: "/socket.io",
|
|
394
|
-
withCredentials: true,
|
|
395
|
-
reconnection: true,
|
|
396
|
-
reconnectionAttempts: Infinity,
|
|
397
|
-
reconnectionDelay: 2000,
|
|
398
|
-
reconnectionDelayMax: 30000
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
this.socket.on("connect", () => this.connected.set(true))
|
|
402
|
-
this.socket.on("disconnect", () => this.connected.set(false))
|
|
403
|
-
return this.socket
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
disconnect() {
|
|
407
|
-
this.socket?.disconnect()
|
|
408
|
-
this.socket = null
|
|
409
|
-
this.connected.set(false)
|
|
410
|
-
}
|
|
157
|
+
name: 'Dev proxy for CSR-only apps (React/Vue/Angular)',
|
|
158
|
+
code: `// vite.config.ts (React or Vue)
|
|
159
|
+
export default defineConfig({
|
|
160
|
+
server: {
|
|
161
|
+
proxy: {
|
|
162
|
+
'/enfyra': {
|
|
163
|
+
target: 'https://admin.example.com/api',
|
|
164
|
+
changeOrigin: true,
|
|
165
|
+
rewrite: (path) => path.replace(/^\\/enfyra/, ''),
|
|
166
|
+
},
|
|
167
|
+
'/socket.io': {
|
|
168
|
+
target: 'https://admin.example.com/api/ws',
|
|
169
|
+
changeOrigin: true,
|
|
170
|
+
ws: true,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
})
|
|
411
175
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
176
|
+
// Production: nginx/Caddy reverse proxy with the same mapping.
|
|
177
|
+
// Angular: use proxy.conf.json with the same target paths.`,
|
|
178
|
+
notes: [
|
|
179
|
+
'CSR-only SDKs (@enfyra/sdk-react, @enfyra/sdk-vue, @enfyra/sdk-core in browser) require a same-origin proxy for HttpOnly cookie auth.',
|
|
180
|
+
'The proxy maps /enfyra/** to the Enfyra App /api bridge. Do not point it at a raw ESV origin.',
|
|
181
|
+
'Socket.IO proxy maps /socket.io/** to the Enfyra App /ws/socket.io/** for same-origin WebSocket cookies.',
|
|
182
|
+
'For Nuxt and Next.js, the SDK handles this proxy automatically — do not add manual proxy config.',
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'OAuth login flow (all frameworks)',
|
|
187
|
+
code: `// Nuxt — composable handles everything:
|
|
188
|
+
const { oauthLogin } = useAuth()
|
|
189
|
+
oauthLogin('google')
|
|
190
|
+
|
|
191
|
+
// Next.js — use the SDK proxy prefix for OAuth start:
|
|
192
|
+
const redirect = new URL('/dashboard', window.location.origin)
|
|
193
|
+
const url = new URL(window.location.origin + '/api/enfyra/auth/google')
|
|
194
|
+
url.searchParams.set('redirect', redirect.toString())
|
|
195
|
+
url.searchParams.set('cookieBridgePrefix', '/api/enfyra')
|
|
196
|
+
window.location.href = url.toString()
|
|
197
|
+
|
|
198
|
+
// React/Vue CSR — same pattern with the app proxy prefix:
|
|
199
|
+
const oauthUrl = new URL(window.location.origin + '/enfyra/auth/google')
|
|
200
|
+
oauthUrl.searchParams.set('redirect', window.location.origin + '/dashboard')
|
|
201
|
+
oauthUrl.searchParams.set('cookieBridgePrefix', '/enfyra')
|
|
202
|
+
window.location.href = oauthUrl.toString()`,
|
|
418
203
|
notes: [
|
|
419
|
-
'
|
|
420
|
-
'
|
|
421
|
-
'
|
|
422
|
-
'Do not
|
|
204
|
+
'OAuth starts through the same-origin SDK proxy prefix (/api/enfyra/auth/<provider> for Next.js, /enfyra/auth/<provider> for Nuxt/React/Vue). No need to expose the Enfyra App URL to the browser.',
|
|
205
|
+
'cookieBridgePrefix must match the SDK proxy prefix: /enfyra for Nuxt/React/Vue, /api/enfyra for Next.js.',
|
|
206
|
+
'After OAuth return, the SDK session check (/me or useAuth refresh) picks up the HttpOnly cookie automatically.',
|
|
207
|
+
'Do not parse tokens from the URL. Do not create custom callback routes.',
|
|
423
208
|
],
|
|
424
209
|
},
|
|
425
210
|
{
|
|
426
|
-
name: '
|
|
427
|
-
code:
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
211
|
+
name: 'Password login and session check (all frameworks)',
|
|
212
|
+
code: `// Nuxt:
|
|
213
|
+
const { login, user, isAuthenticated } = useAuth()
|
|
214
|
+
await login({ email, password, remember: true })
|
|
215
|
+
|
|
216
|
+
// Next.js:
|
|
217
|
+
const { login, user, isAuthenticated } = useAuth()
|
|
218
|
+
await login({ email, password })
|
|
219
|
+
|
|
220
|
+
// React:
|
|
221
|
+
const { login, user, isAuthenticated } = useAuth()
|
|
222
|
+
await login({ email, password, remember: true })
|
|
223
|
+
|
|
224
|
+
// Vue:
|
|
225
|
+
const { login, user, fetchUser } = useAuth()
|
|
226
|
+
await login({ email, password, remember: true })
|
|
227
|
+
await fetchUser()`,
|
|
228
|
+
notes: [
|
|
229
|
+
'All SDK login methods POST to the same-origin proxy prefix. HttpOnly cookies are set by the Enfyra App bridge.',
|
|
230
|
+
'Session check calls /enfyra/me (Nuxt/React/Vue) or /api/enfyra/me (Next.js) through the SDK proxy. The SDK useAuth/fetchUser methods handle this internally.',
|
|
231
|
+
'Do not read or store JWTs in browser JavaScript when using cookie strategy.',
|
|
232
|
+
'The Enfyra App bridge owns token refresh and Bearer forwarding to ESV internally.',
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'Realtime with SDK (Nuxt and Vue)',
|
|
237
|
+
code: `// Nuxt — auto-imported composable:
|
|
238
|
+
const ws = useWebSocket('chat', { immediate: true })
|
|
239
|
+
ws.on('chat:message', (event) => { /* update state */ })
|
|
240
|
+
|
|
241
|
+
// Vue:
|
|
242
|
+
const socket = useWebSocket('chat')
|
|
243
|
+
await socket.connect()
|
|
244
|
+
socket.on('chat:message', handler)`,
|
|
245
|
+
notes: [
|
|
246
|
+
'Nuxt and Vue SDKs include useWebSocket with automatic same-origin Socket.IO path.',
|
|
247
|
+
'Next.js realtime (useWebSocket) is gated pending E2E verification of WebSocket upgrade through Next rewrites.',
|
|
248
|
+
'For Next.js or unsupported frameworks, use socket.io-client directly with path /socket.io and a same-origin proxy to the Enfyra App /ws/socket.io bridge.',
|
|
249
|
+
'Create one connection per app, not per component. Disconnect when the user logs out.',
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'Node.js scripts and unsupported frameworks with @enfyra/sdk-core',
|
|
254
|
+
code: `import { EnfyraClient } from '@enfyra/sdk-core'
|
|
255
|
+
|
|
256
|
+
const client = new EnfyraClient({
|
|
257
|
+
baseUrl: 'https://admin.example.com',
|
|
258
|
+
auth: { strategy: 'token', accessToken: process.env.ENFYRA_API_TOKEN },
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const { data } = await client.from('orders').select('id,total,status').limit(50).execute()`,
|
|
432
262
|
notes: [
|
|
433
|
-
'
|
|
434
|
-
'
|
|
435
|
-
'
|
|
436
|
-
'Enfyra redirects through {redirect.origin}{cookieBridgePrefix}/auth/set-cookies before returning to redirect.',
|
|
437
|
-
'After returning, call /enfyra/me to load the authenticated user; do not parse tokens from the URL in proxy-cookie mode.',
|
|
263
|
+
'Use @enfyra/sdk-core directly for server-side scripts, CLI tools, or frameworks without an SDK adapter.',
|
|
264
|
+
'For server-to-server, use token strategy with an API token from Enfyra admin.',
|
|
265
|
+
'For browser usage without a framework SDK, use cookie strategy with a same-origin proxy.',
|
|
438
266
|
],
|
|
439
267
|
},
|
|
440
268
|
],
|