index.ts265 lines · main
| 1 | /** |
| 2 | * @briven/auth/svelte — Svelte bindings for `@briven/auth`. |
| 3 | * |
| 4 | * import { createBrivenAuth } from '@briven/auth'; |
| 5 | * import { setBrivenAuthContext, createSessionStore, createUserStore } from '@briven/auth/svelte'; |
| 6 | * |
| 7 | * const auth = createBrivenAuth({ projectId: 'p_abc123', publicKey: '...' }); |
| 8 | * |
| 9 | * <!-- App.svelte --> |
| 10 | * <script> |
| 11 | * import { setBrivenAuthContext } from '@briven/auth/svelte'; |
| 12 | * setBrivenAuthContext(auth); |
| 13 | * </script> |
| 14 | * |
| 15 | * <!-- Child.svelte --> |
| 16 | * <script> |
| 17 | * import { createSessionStore } from '@briven/auth/svelte'; |
| 18 | * const { session, isLoading, refresh } = createSessionStore(); |
| 19 | * </script> |
| 20 | * |
| 21 | * Zero hard dependency on SvelteKit — works in any Svelte 4/5 environment. |
| 22 | */ |
| 23 | |
| 24 | import { getContext, setContext } from 'svelte'; |
| 25 | import { writable, type Readable } from 'svelte/store'; |
| 26 | |
| 27 | import { |
| 28 | type BrivenAuthClient, |
| 29 | type ClientSession, |
| 30 | type Org, |
| 31 | type SessionResponse, |
| 32 | type SsoConnection, |
| 33 | type User, |
| 34 | type UserEmail, |
| 35 | } from '../index.js'; |
| 36 | |
| 37 | const BRIVEN_AUTH_KEY = Symbol('briven-auth'); |
| 38 | |
| 39 | /** Set the auth client in Svelte context (call once in your root layout). */ |
| 40 | export function setBrivenAuthContext(client: BrivenAuthClient): void { |
| 41 | setContext(BRIVEN_AUTH_KEY, client); |
| 42 | } |
| 43 | |
| 44 | /** Get the auth client from Svelte context. Throws if not set. */ |
| 45 | export function getBrivenAuthContext(): BrivenAuthClient { |
| 46 | const client = getContext<BrivenAuthClient | undefined>(BRIVEN_AUTH_KEY); |
| 47 | if (!client) { |
| 48 | throw new Error('getBrivenAuthContext must be called inside a component with setBrivenAuthContext'); |
| 49 | } |
| 50 | return client; |
| 51 | } |
| 52 | |
| 53 | // ─── Stores ──────────────────────────────────────────────────────────────── |
| 54 | |
| 55 | export interface SessionStore { |
| 56 | session: Readable<SessionResponse | null>; |
| 57 | isLoading: Readable<boolean>; |
| 58 | refresh: () => Promise<void>; |
| 59 | } |
| 60 | |
| 61 | export function createSessionStore(client?: BrivenAuthClient): SessionStore { |
| 62 | const auth = client ?? getBrivenAuthContext(); |
| 63 | const session = writable<SessionResponse | null>(null); |
| 64 | const isLoading = writable(true); |
| 65 | |
| 66 | const refresh = async () => { |
| 67 | isLoading.set(true); |
| 68 | const next = await auth.getSession(); |
| 69 | session.set(next); |
| 70 | isLoading.set(false); |
| 71 | }; |
| 72 | |
| 73 | // Auto-fetch on first subscription |
| 74 | const unsub = session.subscribe(() => {}); |
| 75 | refresh().then(() => unsub()); |
| 76 | |
| 77 | return { session, isLoading, refresh }; |
| 78 | } |
| 79 | |
| 80 | export interface UserStore { |
| 81 | user: Readable<User | null>; |
| 82 | isLoading: Readable<boolean>; |
| 83 | refresh: () => Promise<void>; |
| 84 | } |
| 85 | |
| 86 | export function createUserStore(client?: BrivenAuthClient): UserStore { |
| 87 | const auth = client ?? getBrivenAuthContext(); |
| 88 | const user = writable<User | null>(null); |
| 89 | const isLoading = writable(true); |
| 90 | |
| 91 | const refresh = async () => { |
| 92 | isLoading.set(true); |
| 93 | const next = await auth.getUser(); |
| 94 | user.set(next); |
| 95 | isLoading.set(false); |
| 96 | }; |
| 97 | |
| 98 | const unsub = user.subscribe(() => {}); |
| 99 | refresh().then(() => unsub()); |
| 100 | |
| 101 | return { user, isLoading, refresh }; |
| 102 | } |
| 103 | |
| 104 | export interface UserMetadataStore { |
| 105 | metadata: Readable<Record<string, unknown> | null>; |
| 106 | isLoading: Readable<boolean>; |
| 107 | refresh: () => Promise<void>; |
| 108 | set: (patch: Record<string, unknown>) => Promise<void>; |
| 109 | } |
| 110 | |
| 111 | export function createUserMetadataStore(client?: BrivenAuthClient): UserMetadataStore { |
| 112 | const auth = client ?? getBrivenAuthContext(); |
| 113 | const metadata = writable<Record<string, unknown> | null>(null); |
| 114 | const isLoading = writable(true); |
| 115 | |
| 116 | const refresh = async () => { |
| 117 | isLoading.set(true); |
| 118 | const result = await auth.user.getMetadata(); |
| 119 | metadata.set(result.ok ? result.publicMetadata : null); |
| 120 | isLoading.set(false); |
| 121 | }; |
| 122 | |
| 123 | const set = async (patch: Record<string, unknown>) => { |
| 124 | const result = await auth.user.setMetadata(patch); |
| 125 | if (result.ok) metadata.set(result.publicMetadata); |
| 126 | }; |
| 127 | |
| 128 | const unsub = metadata.subscribe(() => {}); |
| 129 | refresh().then(() => unsub()); |
| 130 | |
| 131 | return { metadata, isLoading, refresh, set }; |
| 132 | } |
| 133 | |
| 134 | export interface UserEmailsStore { |
| 135 | emails: Readable<UserEmail[] | null>; |
| 136 | isLoading: Readable<boolean>; |
| 137 | refresh: () => Promise<void>; |
| 138 | add: (email: string) => Promise<void>; |
| 139 | remove: (emailId: string) => Promise<void>; |
| 140 | } |
| 141 | |
| 142 | export function createUserEmailsStore(client?: BrivenAuthClient): UserEmailsStore { |
| 143 | const auth = client ?? getBrivenAuthContext(); |
| 144 | const emails = writable<UserEmail[] | null>(null); |
| 145 | const isLoading = writable(true); |
| 146 | |
| 147 | const refresh = async () => { |
| 148 | isLoading.set(true); |
| 149 | const result = await auth.user.listEmails(); |
| 150 | emails.set(result.ok ? result.emails : null); |
| 151 | isLoading.set(false); |
| 152 | }; |
| 153 | |
| 154 | const add = async (email: string) => { |
| 155 | const result = await auth.user.addEmail(email); |
| 156 | if (result.ok) await refresh(); |
| 157 | }; |
| 158 | |
| 159 | const remove = async (emailId: string) => { |
| 160 | const result = await auth.user.removeEmail(emailId); |
| 161 | if (result.ok) await refresh(); |
| 162 | }; |
| 163 | |
| 164 | const unsub = emails.subscribe(() => {}); |
| 165 | refresh().then(() => unsub()); |
| 166 | |
| 167 | return { emails, isLoading, refresh, add, remove }; |
| 168 | } |
| 169 | |
| 170 | export interface ActiveOrganizationStore { |
| 171 | activeOrg: Readable<Org | null>; |
| 172 | isLoading: Readable<boolean>; |
| 173 | refresh: () => Promise<void>; |
| 174 | setActive: (orgId: string) => Promise<void>; |
| 175 | } |
| 176 | |
| 177 | export function createActiveOrganizationStore(client?: BrivenAuthClient): ActiveOrganizationStore { |
| 178 | const auth = client ?? getBrivenAuthContext(); |
| 179 | const activeOrg = writable<Org | null>(null); |
| 180 | const isLoading = writable(true); |
| 181 | |
| 182 | const refresh = async () => { |
| 183 | isLoading.set(true); |
| 184 | const result = await auth.organization.getActive(); |
| 185 | if (result.ok) activeOrg.set(result.data); |
| 186 | isLoading.set(false); |
| 187 | }; |
| 188 | |
| 189 | const setActive = async (orgId: string) => { |
| 190 | const result = await auth.organization.setActive(orgId); |
| 191 | if (result.ok) await refresh(); |
| 192 | }; |
| 193 | |
| 194 | const unsub = activeOrg.subscribe(() => {}); |
| 195 | refresh().then(() => unsub()); |
| 196 | |
| 197 | return { activeOrg, isLoading, refresh, setActive }; |
| 198 | } |
| 199 | |
| 200 | export interface SessionsStore { |
| 201 | sessions: Readable<ClientSession[]>; |
| 202 | isLoading: Readable<boolean>; |
| 203 | error: Readable<string | null>; |
| 204 | refresh: () => Promise<void>; |
| 205 | revoke: (sessionId: string) => Promise<void>; |
| 206 | } |
| 207 | |
| 208 | export function createSessionsStore(client?: BrivenAuthClient): SessionsStore { |
| 209 | const auth = client ?? getBrivenAuthContext(); |
| 210 | const sessions = writable<ClientSession[]>([]); |
| 211 | const isLoading = writable(true); |
| 212 | const error = writable<string | null>(null); |
| 213 | |
| 214 | const refresh = async () => { |
| 215 | isLoading.set(true); |
| 216 | error.set(null); |
| 217 | const result = await auth.sessions.list(); |
| 218 | if (result.ok) { |
| 219 | sessions.set(result.sessions); |
| 220 | } else { |
| 221 | error.set(result.message); |
| 222 | } |
| 223 | isLoading.set(false); |
| 224 | }; |
| 225 | |
| 226 | const revoke = async (sessionId: string) => { |
| 227 | const result = await auth.sessions.revoke(sessionId); |
| 228 | if (result.ok) { |
| 229 | await refresh(); |
| 230 | } else { |
| 231 | error.set(result.message); |
| 232 | } |
| 233 | }; |
| 234 | |
| 235 | const unsub = sessions.subscribe(() => {}); |
| 236 | refresh().then(() => unsub()); |
| 237 | |
| 238 | return { sessions, isLoading, error, refresh, revoke }; |
| 239 | } |
| 240 | |
| 241 | // ─── SSO Store ───────────────────────────────────────────────────────────── |
| 242 | |
| 243 | export interface SsoStore { |
| 244 | connections: Readable<Array<Pick<SsoConnection, 'id' | 'name' | 'providerType' | 'domains'>> | null>; |
| 245 | isLoading: Readable<boolean>; |
| 246 | refresh: () => Promise<void>; |
| 247 | } |
| 248 | |
| 249 | export function createSsoStore(client?: BrivenAuthClient): SsoStore { |
| 250 | const auth = client ?? getBrivenAuthContext(); |
| 251 | const connections = writable<Array<Pick<SsoConnection, 'id' | 'name' | 'providerType' | 'domains'>> | null>(null); |
| 252 | const isLoading = writable(true); |
| 253 | |
| 254 | const refresh = async () => { |
| 255 | isLoading.set(true); |
| 256 | const result = await auth.sso.listConnections(); |
| 257 | if (result.ok) connections.set(result.connections); |
| 258 | isLoading.set(false); |
| 259 | }; |
| 260 | |
| 261 | const unsub = connections.subscribe(() => {}); |
| 262 | refresh().then(() => unsub()); |
| 263 | |
| 264 | return { connections, isLoading, refresh }; |
| 265 | } |