@omg-dev/sdk 0.4.24
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/OmgBadge-LAcimQp0.mjs +345 -0
- package/dist/VibesFeedback-BF2Vf6FK.mjs +808 -0
- package/dist/brand/auto.mjs +18 -0
- package/dist/feedback/auto.mjs +26 -0
- package/dist/index.mjs +1611 -0
- package/package.json +43 -0
- package/src/auth/auto-prompt.tsx +50 -0
- package/src/auth/bridge.ts +63 -0
- package/src/auth/client.ts +222 -0
- package/src/auth/fetch.ts +23 -0
- package/src/auth/guard.tsx +24 -0
- package/src/auth/index.ts +23 -0
- package/src/auth/login.tsx +267 -0
- package/src/auth/mail-apps.ts +52 -0
- package/src/auth/react.tsx +248 -0
- package/src/brand/OmgBadge.tsx +366 -0
- package/src/brand/auto.tsx +35 -0
- package/src/brand/index.ts +1 -0
- package/src/feedback/VibesFeedback.tsx +360 -0
- package/src/feedback/auto.tsx +47 -0
- package/src/feedback/gestures.ts +296 -0
- package/src/feedback/index.ts +18 -0
- package/src/feedback/screenshot.ts +37 -0
- package/src/feedback/trace.ts +166 -0
- package/src/index.ts +1042 -0
- package/src/notifications/index.tsx +179 -0
- package/src/sandbox.test.ts +61 -0
- package/src/sandbox.ts +106 -0
- package/src/storage/VibesUpload.tsx +140 -0
- package/src/storage/index.ts +12 -0
- package/src/storage/useUpload.ts +167 -0
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omg-dev/sdk",
|
|
3
|
+
"version": "0.4.24",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"default": "./dist/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"./feedback/auto": {
|
|
11
|
+
"types": "./src/feedback/auto.tsx",
|
|
12
|
+
"default": "./dist/feedback/auto.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./brand/auto": {
|
|
15
|
+
"types": "./src/brand/auto.tsx",
|
|
16
|
+
"default": "./dist/brand/auto.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"better-auth": "^1.2.0",
|
|
21
|
+
"@better-auth/passkey": "^1.2.0",
|
|
22
|
+
"@microsoft/fetch-event-source": "^2.0.1",
|
|
23
|
+
"modern-screenshot": "^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=18",
|
|
27
|
+
"react-dom": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/BennyKok/vibes.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://docs.omg.dev",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"registry": "https://registry.npmjs.org/"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @omg-dev/sdk/auth — Auto-prompt overlay.
|
|
2
|
+
//
|
|
3
|
+
// Listens for the bridge's auth-required signal (fired by any SDK request that
|
|
4
|
+
// comes back 401 / WS `auth_required`) and surfaces <VibesLogin /> in a
|
|
5
|
+
// lightweight modal overlay. Mounted by <VibesAuthProvider> unless the app
|
|
6
|
+
// opts out with `autoPrompt={false}`. Lives in its own file (not react.tsx) to
|
|
7
|
+
// avoid a react.tsx ↔ login.tsx import cycle.
|
|
8
|
+
|
|
9
|
+
import { useEffect, useState } from "react"
|
|
10
|
+
import { subscribeAuthRequired } from "./bridge"
|
|
11
|
+
import { useUser } from "./react"
|
|
12
|
+
import { VibesLogin } from "./login"
|
|
13
|
+
|
|
14
|
+
export function VibesAuthAutoPrompt() {
|
|
15
|
+
const user = useUser()
|
|
16
|
+
const [open, setOpen] = useState(false)
|
|
17
|
+
|
|
18
|
+
// Open on any auth-required signal.
|
|
19
|
+
useEffect(() => subscribeAuthRequired(() => setOpen(true)), [])
|
|
20
|
+
|
|
21
|
+
// Close automatically once the user is signed in.
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (user) setOpen(false)
|
|
24
|
+
}, [user])
|
|
25
|
+
|
|
26
|
+
if (!open || user) return null
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
role="dialog"
|
|
31
|
+
aria-modal="true"
|
|
32
|
+
onClick={(e) => {
|
|
33
|
+
// Click on the backdrop (not the card) dismisses.
|
|
34
|
+
if (e.target === e.currentTarget) setOpen(false)
|
|
35
|
+
}}
|
|
36
|
+
style={{
|
|
37
|
+
position: "fixed",
|
|
38
|
+
inset: 0,
|
|
39
|
+
zIndex: 2147483000,
|
|
40
|
+
display: "flex",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "center",
|
|
43
|
+
background: "rgba(0,0,0,0.5)",
|
|
44
|
+
backdropFilter: "blur(2px)",
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<VibesLogin subtitle="Sign in to continue." />
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @omg-dev/sdk/auth — Module-scoped bridge so useCollection can read the
|
|
2
|
+
// current auth token without importing React from the main entrypoint.
|
|
3
|
+
// VibesAuthProvider sets this on every token change.
|
|
4
|
+
|
|
5
|
+
import type { VibesUser } from "./client"
|
|
6
|
+
|
|
7
|
+
interface AuthSnapshot {
|
|
8
|
+
user: VibesUser | null
|
|
9
|
+
token: string | null
|
|
10
|
+
/**
|
|
11
|
+
* True once the initial session check AND the first token-fetch attempt have
|
|
12
|
+
* settled. While false, a scoped-collection `auth_required` is treated as a
|
|
13
|
+
* transient "still loading" state — not a real signed-out signal — so apps
|
|
14
|
+
* don't flash a scary error or pop the login dialog during startup.
|
|
15
|
+
*/
|
|
16
|
+
authReady: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let current: AuthSnapshot = { user: null, token: null, authReady: false }
|
|
20
|
+
|
|
21
|
+
type AuthChangeListener = (snap: AuthSnapshot) => void
|
|
22
|
+
const authChangeListeners = new Set<AuthChangeListener>()
|
|
23
|
+
|
|
24
|
+
export function setAuthContext(snap: AuthSnapshot) {
|
|
25
|
+
current = snap
|
|
26
|
+
for (const cb of authChangeListeners) cb(snap)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getAuthContext(): AuthSnapshot {
|
|
30
|
+
return current
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Subscribe to auth snapshot changes (user / token / authReady). Returns an
|
|
35
|
+
* unsubscribe fn. Data hooks use this to re-establish a subscription when the
|
|
36
|
+
* JWT finishes loading (null→token) or on sign-out (token→null) — without it
|
|
37
|
+
* the WS connects once, often before getToken() resolves, and never retries
|
|
38
|
+
* with a bearer.
|
|
39
|
+
*/
|
|
40
|
+
export function subscribeAuthChange(cb: AuthChangeListener): () => void {
|
|
41
|
+
authChangeListeners.add(cb)
|
|
42
|
+
return () => authChangeListeners.delete(cb)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Auth-required signal ───────────────────────────────────────────────────────
|
|
46
|
+
// Fired whenever a request comes back with the server's "auth required" signal
|
|
47
|
+
// (REST 401 or WS `auth_required`). VibesAuthProvider subscribes to this to
|
|
48
|
+
// auto-open the login UI. Lives in the bridge (not React) so the non-React
|
|
49
|
+
// fetch/WS helpers in the main entrypoint can notify without importing React.
|
|
50
|
+
|
|
51
|
+
type AuthRequiredListener = () => void
|
|
52
|
+
const authRequiredListeners = new Set<AuthRequiredListener>()
|
|
53
|
+
|
|
54
|
+
/** Subscribe to auth-required signals. Returns an unsubscribe fn. */
|
|
55
|
+
export function subscribeAuthRequired(cb: AuthRequiredListener): () => void {
|
|
56
|
+
authRequiredListeners.add(cb)
|
|
57
|
+
return () => authRequiredListeners.delete(cb)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Notify all subscribers that a request hit the server's auth-required signal. */
|
|
61
|
+
export function notifyAuthRequired(): void {
|
|
62
|
+
for (const cb of authRequiredListeners) cb()
|
|
63
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// @omg-dev/sdk/auth — Auth client and token manager
|
|
2
|
+
|
|
3
|
+
import { createAuthClient } from "better-auth/react"
|
|
4
|
+
import { passkeyClient } from "@better-auth/passkey/client"
|
|
5
|
+
import { magicLinkClient, emailOTPClient } from "better-auth/client/plugins"
|
|
6
|
+
|
|
7
|
+
export interface VibesUser {
|
|
8
|
+
id: string
|
|
9
|
+
email: string
|
|
10
|
+
name?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface VibesAuthConfig {
|
|
14
|
+
/** App ID registered with the auth service */
|
|
15
|
+
appId: string
|
|
16
|
+
/** Auth service URL. Defaults to https://auth.omg.dev */
|
|
17
|
+
authUrl?: string
|
|
18
|
+
/** JWT mint endpoint. Defaults to `${authUrl}/token`. */
|
|
19
|
+
tokenUrl?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface VibesAuthClient {
|
|
23
|
+
/** The underlying Better Auth client */
|
|
24
|
+
authClient: ReturnType<typeof createAuthClient>
|
|
25
|
+
/** App ID for JWT token requests */
|
|
26
|
+
appId: string
|
|
27
|
+
/** Auth service base URL */
|
|
28
|
+
authUrl: string
|
|
29
|
+
/** JWT mint endpoint */
|
|
30
|
+
tokenUrl: string
|
|
31
|
+
/** Fetch a JWT for this app. Uses the session cookie automatically. */
|
|
32
|
+
getToken: () => Promise<string | null>
|
|
33
|
+
/** Clear the cached token */
|
|
34
|
+
clearToken: () => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_AUTH_URL = "https://auth.omg.dev"
|
|
38
|
+
|
|
39
|
+
// Same-origin Worker bridge prefix for cross-site app hosts. On *.omgs.app the
|
|
40
|
+
// shared `.omg.dev` cookie can't reach auth.omg.dev, so the WHOLE better-auth
|
|
41
|
+
// surface (sign-in, email-OTP send/verify, get-session, sign-out, passkey) and
|
|
42
|
+
// the slug-JWT mint are routed through `${origin}/__vibes/auth/*`, which the
|
|
43
|
+
// Worker forwards server-side and whose Set-Cookie it rewrites host-only. Must
|
|
44
|
+
// match AUTH_BRIDGE_PREFIX in apps/infra/worker/src/auth-bridge.ts.
|
|
45
|
+
const AUTH_BRIDGE_PREFIX = "/__vibes/auth"
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Derive the appId from the current host. The slug is the first DNS label.
|
|
49
|
+
* Canonical deployed apps live at `<slug>.apps.omg.dev`; cross-site app hosts
|
|
50
|
+
* (no shared `.omg.dev` cookie) live at `<slug>.omgs.app`. Returns null when
|
|
51
|
+
* the host doesn't match (local dev, custom domains).
|
|
52
|
+
*/
|
|
53
|
+
export function deriveAppId(host?: string): string | null {
|
|
54
|
+
const h = host ?? (typeof window !== "undefined" ? window.location.host : "")
|
|
55
|
+
if (host === undefined) {
|
|
56
|
+
const injected = readInjectedAppId()
|
|
57
|
+
if (injected) return injected
|
|
58
|
+
}
|
|
59
|
+
const m = /^([a-z0-9-]+)\.apps\.omg\.dev$/.exec(h) ?? /^([a-z0-9-]+)\.omgs\.app$/.exec(h)
|
|
60
|
+
return m ? m[1] : null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A cross-site app host is one served outside the shared `.omg.dev` cookie
|
|
65
|
+
* scope, so the slug JWT must be minted same-origin (the Worker reads the
|
|
66
|
+
* first-party `omg_access` cookie and exchanges it). Today that's `*.omgs.app`.
|
|
67
|
+
* Custom domains are out of scope here — they fall through to the default
|
|
68
|
+
* `${authUrl}/token` for now.
|
|
69
|
+
*/
|
|
70
|
+
function isCrossSiteAppHost(host: string): boolean {
|
|
71
|
+
return host.endsWith(".omgs.app")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function deriveTokenUrl(authUrl = DEFAULT_AUTH_URL): string {
|
|
75
|
+
// Preview injection always wins.
|
|
76
|
+
const injected = readInjectedTokenUrl()
|
|
77
|
+
if (injected) return injected
|
|
78
|
+
// On cross-site app hosts (e.g. *.omgs.app) the shared .omg.dev cookie isn't
|
|
79
|
+
// available cross-origin, so mint the token via the same-origin Worker path.
|
|
80
|
+
if (typeof window !== "undefined" && isCrossSiteAppHost(window.location.host)) {
|
|
81
|
+
return new URL("/__omg/auth/token", window.location.origin).toString()
|
|
82
|
+
}
|
|
83
|
+
// apps.omg.dev / dashboard: unchanged — cross-origin auth.omg.dev with the
|
|
84
|
+
// shared .omg.dev cookie.
|
|
85
|
+
return `${authUrl}/token`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readInjectedAppId(): string | null {
|
|
89
|
+
if (typeof window === "undefined") return null
|
|
90
|
+
const value = (window as unknown as { __VIBES_APP_ID?: unknown }).__VIBES_APP_ID
|
|
91
|
+
return typeof value === "string" && /^[a-z0-9-]+$/.test(value) ? value : null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readInjectedTokenUrl(): string | null {
|
|
95
|
+
if (typeof window === "undefined") return null
|
|
96
|
+
const value = (window as unknown as { __VIBES_AUTH_TOKEN_URL?: unknown }).__VIBES_AUTH_TOKEN_URL
|
|
97
|
+
if (typeof value !== "string" || !value.trim()) return null
|
|
98
|
+
try {
|
|
99
|
+
return new URL(value, window.location.origin).toString()
|
|
100
|
+
} catch {
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Same-origin session-read endpoint, injected by the vite-plugin in preview
|
|
107
|
+
* (`{sandboxId}-5173.preview.omg.dev`). Present only there; null for deployed
|
|
108
|
+
* apps and local dev.
|
|
109
|
+
*/
|
|
110
|
+
function readInjectedSessionUrl(): string | null {
|
|
111
|
+
if (typeof window === "undefined") return null
|
|
112
|
+
const value = (window as unknown as { __VIBES_AUTH_SESSION_URL?: unknown }).__VIBES_AUTH_SESSION_URL
|
|
113
|
+
if (typeof value !== "string" || !value.trim()) return null
|
|
114
|
+
try {
|
|
115
|
+
return new URL(value, window.location.origin).toString()
|
|
116
|
+
} catch {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function createVibesAuth(config: VibesAuthConfig): VibesAuthClient {
|
|
122
|
+
const authUrl = config.authUrl ?? DEFAULT_AUTH_URL
|
|
123
|
+
|
|
124
|
+
// On a cross-site app host (*.omgs.app) the browser can't send the shared
|
|
125
|
+
// `.omg.dev` cookie to auth.omg.dev, so point the better-auth client AND the
|
|
126
|
+
// token mint at the same-origin Worker bridge. The bridge forwards to
|
|
127
|
+
// auth.omg.dev server-side and rewrites the session Set-Cookie host-only, so
|
|
128
|
+
// sign-in / OTP-verify / get-session all land first-party to omgs.app. On
|
|
129
|
+
// apps.omg.dev (same registrable domain) and local dev this is a no-op.
|
|
130
|
+
const crossSite =
|
|
131
|
+
typeof window !== "undefined" && isCrossSiteAppHost(window.location.host)
|
|
132
|
+
const bridgeBase = crossSite ? `${window.location.origin}${AUTH_BRIDGE_PREFIX}` : null
|
|
133
|
+
|
|
134
|
+
// better-auth client appends `/api/auth/<endpoint>` to baseURL, so a bridge
|
|
135
|
+
// base of `${origin}/__vibes/auth` yields `${origin}/__vibes/auth/api/auth/…`.
|
|
136
|
+
const baseURL = bridgeBase ?? authUrl
|
|
137
|
+
const tokenUrl = config.tokenUrl ?? (bridgeBase ? `${bridgeBase}/token` : deriveTokenUrl(authUrl))
|
|
138
|
+
|
|
139
|
+
// In preview, the better-auth client's only cross-origin call is the
|
|
140
|
+
// get-session read to auth.omg.dev. That credentialed cross-origin GET is the
|
|
141
|
+
// one auth request that third-party-cookie blocking / Safari ITP can silently
|
|
142
|
+
// drop — making a signed-in dashboard user look signed-out and popping a
|
|
143
|
+
// needless login. Token minting already dodges this via the same-origin
|
|
144
|
+
// `/__vibes/auth/token` bridge (cookie forwarded server-side); do the same for
|
|
145
|
+
// the session read so preview auto-signs-in from the shared .omg.dev session.
|
|
146
|
+
// Everything else (sign-in, sign-out, OTP verify) is left untouched.
|
|
147
|
+
const sessionUrl = readInjectedSessionUrl()
|
|
148
|
+
const customFetchImpl: typeof fetch = (input, init) => {
|
|
149
|
+
const url =
|
|
150
|
+
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url
|
|
151
|
+
if (sessionUrl && url.includes("/api/auth/get-session")) {
|
|
152
|
+
const qs = url.includes("?") ? url.slice(url.indexOf("?")) : ""
|
|
153
|
+
return fetch(sessionUrl + qs, { ...init, credentials: "include" })
|
|
154
|
+
}
|
|
155
|
+
return fetch(input, init)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const authClient = createAuthClient({
|
|
159
|
+
baseURL,
|
|
160
|
+
...(sessionUrl ? { fetchOptions: { customFetchImpl } } : {}),
|
|
161
|
+
plugins: [
|
|
162
|
+
magicLinkClient(),
|
|
163
|
+
// email-otp registers /sign-in/email-otp on the $sessionSignal atom —
|
|
164
|
+
// useSession refetches right after a successful code verify, so guards
|
|
165
|
+
// and the auto-prompt dialog close without any redirect.
|
|
166
|
+
emailOTPClient(),
|
|
167
|
+
passkeyClient(),
|
|
168
|
+
],
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
let cachedToken: string | null = null
|
|
172
|
+
let tokenExp: number = 0
|
|
173
|
+
|
|
174
|
+
async function getToken(): Promise<string | null> {
|
|
175
|
+
// Return cached token if still valid (with 60s buffer)
|
|
176
|
+
if (cachedToken && Date.now() < tokenExp - 60_000) {
|
|
177
|
+
return cachedToken
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch(tokenUrl, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
headers: { "Content-Type": "application/json" },
|
|
184
|
+
credentials: "include", // send .omg.dev cookie
|
|
185
|
+
body: JSON.stringify({ appId: config.appId }),
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (!res.ok) {
|
|
189
|
+
cachedToken = null
|
|
190
|
+
tokenExp = 0
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const data = await res.json() as { token: string; expiresAt: string }
|
|
195
|
+
cachedToken = data.token
|
|
196
|
+
|
|
197
|
+
// Parse expiry from response or JWT
|
|
198
|
+
if (data.expiresAt) {
|
|
199
|
+
tokenExp = new Date(data.expiresAt).getTime()
|
|
200
|
+
} else {
|
|
201
|
+
// Fallback: decode JWT exp claim
|
|
202
|
+
try {
|
|
203
|
+
const payload = JSON.parse(atob(data.token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")))
|
|
204
|
+
tokenExp = (payload.exp ?? 0) * 1000
|
|
205
|
+
} catch {
|
|
206
|
+
tokenExp = Date.now() + 3600_000 // default 1hr
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return cachedToken
|
|
211
|
+
} catch {
|
|
212
|
+
return null
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function clearToken() {
|
|
217
|
+
cachedToken = null
|
|
218
|
+
tokenExp = 0
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { authClient, appId: config.appId, authUrl, tokenUrl, getToken, clearToken }
|
|
222
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @omg-dev/sdk/auth — Fetch wrapper with JWT Bearer token
|
|
2
|
+
|
|
3
|
+
import type { VibesAuthClient } from "./client"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a fetch wrapper that automatically attaches the JWT Bearer token.
|
|
7
|
+
* If no token is available (user not signed in), falls through without auth header.
|
|
8
|
+
*/
|
|
9
|
+
export function vibesFetch(client: VibesAuthClient) {
|
|
10
|
+
return async function (
|
|
11
|
+
input: RequestInfo | URL,
|
|
12
|
+
init?: RequestInit
|
|
13
|
+
): Promise<Response> {
|
|
14
|
+
const token = await client.getToken()
|
|
15
|
+
|
|
16
|
+
const headers = new Headers(init?.headers)
|
|
17
|
+
if (token) {
|
|
18
|
+
headers.set("Authorization", `Bearer ${token}`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return fetch(input, { ...init, headers })
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @omg-dev/sdk/auth — Gate children behind a logged-in user.
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react"
|
|
4
|
+
import { useUser, useVibesAuth } from "./react"
|
|
5
|
+
import { VibesLogin } from "./login"
|
|
6
|
+
|
|
7
|
+
export function VibesAuthGuard({
|
|
8
|
+
children,
|
|
9
|
+
fallback,
|
|
10
|
+
loadingFallback,
|
|
11
|
+
}: {
|
|
12
|
+
children: ReactNode
|
|
13
|
+
/** Replace the default <VibesLogin/> when the user is signed-out. */
|
|
14
|
+
fallback?: ReactNode
|
|
15
|
+
/** Optional placeholder while the session is still resolving. */
|
|
16
|
+
loadingFallback?: ReactNode
|
|
17
|
+
}) {
|
|
18
|
+
const { loading } = useVibesAuth()
|
|
19
|
+
const user = useUser()
|
|
20
|
+
|
|
21
|
+
if (loading) return <>{loadingFallback ?? null}</>
|
|
22
|
+
if (!user) return <>{fallback ?? <VibesLogin />}</>
|
|
23
|
+
return <>{children}</>
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @omg-dev/sdk/auth — Client-side auth for apps on omg.dev
|
|
2
|
+
//
|
|
3
|
+
// Auto-login via cross-subdomain cookie on .omg.dev,
|
|
4
|
+
// JWT token management via auth.omg.dev/token,
|
|
5
|
+
// and React hooks for auth state.
|
|
6
|
+
|
|
7
|
+
export { createVibesAuth, deriveAppId, deriveTokenUrl } from "./client"
|
|
8
|
+
export {
|
|
9
|
+
VibesAuthProvider,
|
|
10
|
+
useVibesAuth,
|
|
11
|
+
useVibesToken,
|
|
12
|
+
useAuth,
|
|
13
|
+
useUser,
|
|
14
|
+
} from "./react"
|
|
15
|
+
export { vibesFetch } from "./fetch"
|
|
16
|
+
export { VibesLogin } from "./login"
|
|
17
|
+
export { mailAppForEmail } from "./mail-apps"
|
|
18
|
+
export type { MailApp } from "./mail-apps"
|
|
19
|
+
export { VibesAuthGuard } from "./guard"
|
|
20
|
+
export { VibesAuthAutoPrompt } from "./auto-prompt"
|
|
21
|
+
export { getAuthContext, subscribeAuthRequired, notifyAuthRequired } from "./bridge"
|
|
22
|
+
export type { VibesAuthConfig, VibesAuthClient, VibesUser } from "./client"
|
|
23
|
+
export type { VibesAuthContextValue } from "./react"
|