@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.
@@ -1,440 +1,268 @@
1
1
  export const connectExamples = {
2
- title: 'Connect SSR and browser apps to Enfyra',
3
- useWhen: 'Use when connecting Nuxt, Next, Angular, or another browser app to Enfyra for REST, login, OAuth, refresh, files, GraphQL, or Socket.IO; adapt the framework wrapper while preserving the app-origin proxy and cookie boundary.',
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: 'Nuxt routeRules for REST and Socket.IO',
7
- code: `export default defineNuxtConfig({
8
- routeRules: {
9
- "/enfyra/**": {
10
- proxy: {
11
- to: \`\${process.env.ENFYRA_API_URL}/**\`,
12
- fetchOptions: { redirect: "manual" }
13
- }
14
- },
15
- "/socket.io/**": {
16
- proxy: \`\${process.env.ENFYRA_APP_URL}/ws/socket.io/**\`
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
- 'Browser code calls app-origin routes such as /enfyra/login, /enfyra/me, /enfyra/logout, and /enfyra/<table>.',
22
- 'Keep redirects manual so OAuth and the set-cookie bridge return their redirect response to the browser.',
23
- 'Proxy to the Enfyra app /api bridge, not the raw Enfyra server. The Enfyra app bridge reads and refreshes cookies, then injects Authorization for protected ESV requests.',
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: 'Next rewrites for REST and Socket.IO',
28
- code: `const nextConfig = {
29
- async rewrites() {
30
- return [
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
- export default nextConfig`,
35
+ // .env
36
+ ENFYRA_APP_URL=https://admin.example.com`,
44
37
  notes: [
45
- 'Use rewrites for browser traffic, including the OAuth cookie bridge.',
46
- 'The destination is the Enfyra app /api bridge. Do not point the browser rewrite at a raw ESV origin.',
47
- 'For server components, forward the incoming Cookie header when fetching through the third app origin.',
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: 'Angular dev proxy for REST and Socket.IO',
52
- code: `// src/proxy.conf.json
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
- // angular.json
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
- 'Browser code still calls /enfyra/login, /enfyra/me, /enfyra/logout, and /enfyra/<table>.',
86
- 'The /enfyra proxy strips the prefix before forwarding to the Enfyra API origin.',
87
- 'The /socket.io proxy forwards to the Enfyra app bridge /ws/socket.io while keeping the browser transport path as /socket.io.',
88
- 'Restart ng serve after changing proxy.conf.json.',
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: 'Password login and current user fetch',
93
- code: `await fetch("/enfyra/login", {
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
- const me = await fetch("/enfyra/me", {
101
- credentials: "include"
102
- }).then((res) => res.ok ? res.json() : null)`,
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
- 'The third app proxy maps /enfyra/login to the Enfyra app /api/login cookie endpoint; use /login, not raw /auth/login, in browser cookie mode.',
105
- 'The Enfyra app bridge owns refresh and Bearer forwarding to ESV while HttpOnly cookies stay outside browser JavaScript.',
106
- 'Do not read or store JWTs in browser JavaScript in proxy-cookie mode.',
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: 'Nuxt client plugin for authenticated realtime',
111
- code: `// composables/useRealtime.ts
112
- import { io, type Socket } from "socket.io-client"
113
- import { readonly, ref, shallowRef } from "vue"
114
-
115
- const socket = shallowRef<Socket | null>(null)
116
- const isConnected = ref(false)
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
- // plugins/realtime.client.ts
161
- import { watch } from "vue"
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
- // pages/chat.vue
178
- const realtime = useRealtime()
179
- let stopRealtime = () => {}
180
-
181
- onMounted(() => {
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
- 'Create the socket once in a client-only plugin after auth has resolved; pages should not own the initial connection lifecycle.',
192
- 'Use the websocket namespace path from live metadata, such as /chat, and keep the transport path as /socket.io.',
193
- 'Proxy /socket.io/** to the Enfyra app bridge /ws/socket.io/** so cookies are same-origin.',
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: 'Angular HttpClient auth service and route guard',
200
- code: `// app.config.ts
201
- import { ApplicationConfig, inject } from "@angular/core"
202
- import { provideRouter, CanActivateFn, Router } from "@angular/router"
203
- import { HttpInterceptorFn, provideHttpClient, withInterceptors } from "@angular/common/http"
204
- import { catchError, map, of } from "rxjs"
205
-
206
- import { routes } from "./app.routes"
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
- export const appConfig: ApplicationConfig = {
225
- providers: [
226
- provideHttpClient(withInterceptors([enfyraCredentialsInterceptor])),
227
- provideRouter(routes)
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
- 'Use HttpClient with a credentials interceptor for /enfyra/* calls so cookies are sent consistently.',
272
- 'The guard is only for user experience; Enfyra route permissions and server-side owner checks remain authoritative.',
273
- 'Keep the current user in an Angular service or store; do not read JWTs from cookies or URLs.',
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: 'Next client provider for authenticated realtime',
279
- code: `"use client"
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
- type RealtimeContextValue = {
286
- socket: Socket | null
287
- isConnected: boolean
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
- export function RealtimeProvider({
296
- user,
297
- children
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
- 'Create the Socket.IO client once in a top-level client provider after the current user is known.',
361
- 'Use the websocket namespace path from live metadata, such as /chat, and keep the transport path as /socket.io.',
362
- 'Proxy /socket.io through Next rewrites to the Enfyra app bridge /ws/socket.io so cookies remain same-origin.',
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: 'Angular singleton Socket.IO realtime service',
369
- code: `// enfyra-realtime.service.ts
370
- import { Injectable, computed, effect, signal } from "@angular/core"
371
- import { io, Socket } from "socket.io-client"
372
-
373
- import { EnfyraAuthService } from "./enfyra-auth.service"
374
-
375
- @Injectable({ providedIn: "root" })
376
- export class EnfyraRealtimeService {
377
- private socket: Socket | null = null
378
- private readonly connected = signal(false)
379
- readonly isConnected = computed(() => this.connected())
380
-
381
- constructor(private readonly auth: EnfyraAuthService) {
382
- effect(() => {
383
- const user = this.auth.user()
384
- if (user) this.connect()
385
- else this.disconnect()
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
- onMessage(handler: (event: unknown) => void) {
413
- const activeSocket = this.connect()
414
- activeSocket.on("chat:message", handler)
415
- return () => activeSocket.off("chat:message", handler)
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
- 'Create one app-level Socket.IO connection after auth is known.',
420
- 'Use the websocket namespace path from live metadata, such as /chat, and keep the transport path as /socket.io.',
421
- 'Components subscribe with onMessage and call the returned cleanup function in ngOnDestroy.',
422
- 'Do not create a new socket per routed component.',
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: 'Google OAuth button',
427
- code: `const redirect = new URL("/chat", window.location.origin)
428
- const url = new URL("/api/auth/google", "https://demo.enfyra.io")
429
- url.searchParams.set("redirect", redirect.toString())
430
- url.searchParams.set("cookieBridgePrefix", "/enfyra")
431
- window.location.href = url.toString()`,
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
- 'redirect must be absolute and must include the app origin.',
434
- 'Start OAuth on the Enfyra app URL. Do not start it at the third app /enfyra proxy path.',
435
- 'cookieBridgePrefix is the app proxy prefix that forwards to Enfyra API routes.',
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
  ],