diff options
| author | Frank <[email protected]> | 2025-08-29 23:32:17 -0400 |
|---|---|---|
| committer | Frank <[email protected]> | 2025-08-29 23:32:17 -0400 |
| commit | 37f284f9a97d3354143d64fc76c2eb9f7d9ccf9e (patch) | |
| tree | 053db9abcb2178c71b22ebeadd07f920750ef5d2 /cloud/web/src/components | |
| parent | 0178eab29bda2f1b37a29543cd313ede48ad3977 (diff) | |
| download | opencode-37f284f9a97d3354143d64fc76c2eb9f7d9ccf9e.tar.gz opencode-37f284f9a97d3354143d64fc76c2eb9f7d9ccf9e.zip | |
wip: cloud
Diffstat (limited to 'cloud/web/src/components')
| -rw-r--r-- | cloud/web/src/components/context-account.tsx | 99 | ||||
| -rw-r--r-- | cloud/web/src/components/context-openauth.tsx | 180 | ||||
| -rw-r--r-- | cloud/web/src/components/context-theme.tsx | 39 |
3 files changed, 0 insertions, 318 deletions
diff --git a/cloud/web/src/components/context-account.tsx b/cloud/web/src/components/context-account.tsx deleted file mode 100644 index e6aabafd3..000000000 --- a/cloud/web/src/components/context-account.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { createContext, createEffect, ParentProps, Suspense, useContext } from "solid-js" -import { makePersisted } from "@solid-primitives/storage" -import { createStore } from "solid-js/store" -import { useOpenAuth } from "./context-openauth" -import { createAsync } from "@solidjs/router" -import { isServer } from "solid-js/web" - -type Storage = { - accounts: Record< - string, - { - id: string - email: string - workspaces: { - id: string - name: string - slug: string - }[] - } - > -} - -const context = createContext<ReturnType<typeof init>>() - -function init() { - const auth = useOpenAuth() - const [store, setStore] = makePersisted( - createStore<Storage>({ - accounts: {}, - }), - { - name: "opencontrol.account", - }, - ) - - async function refresh(id: string) { - return fetch(import.meta.env.VITE_API_URL + "/rest/account", { - headers: { - authorization: `Bearer ${await auth.access(id)}`, - }, - }) - .then((val) => val.json()) - .then((val) => setStore("accounts", id, val as any)) - } - - createEffect((previous: string[]) => { - if (Object.keys(auth.all).length === 0) { - return [] - } - for (const item of Object.values(auth.all)) { - if (previous.includes(item.id)) continue - refresh(item.id) - } - return Object.keys(auth.all) - }, [] as string[]) - - const result = { - get all() { - return Object.keys(auth.all) - .map((id) => store.accounts[id]) - .filter(Boolean) - }, - get current() { - if (!auth.subject) return undefined - return store.accounts[auth.subject.id] - }, - refresh, - get ready() { - return Object.keys(auth.all).length === result.all.length - }, - } - - return result -} - -export function AccountProvider(props: ParentProps) { - const ctx = init() - const resource = createAsync(async () => { - await new Promise<void>((resolve) => { - if (isServer) return resolve() - createEffect(() => { - if (ctx.ready) resolve() - }) - }) - return null - }) - return ( - <Suspense> - {resource()} - <context.Provider value={ctx}>{props.children}</context.Provider> - </Suspense> - ) -} - -export function useAccount() { - const result = useContext(context) - if (!result) throw new Error("no account context") - return result -} diff --git a/cloud/web/src/components/context-openauth.tsx b/cloud/web/src/components/context-openauth.tsx deleted file mode 100644 index bd6a45dd1..000000000 --- a/cloud/web/src/components/context-openauth.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { createClient } from "@openauthjs/openauth/client" -import { makePersisted } from "@solid-primitives/storage" -import { createAsync } from "@solidjs/router" -import { - batch, - createContext, - createEffect, - createResource, - createSignal, - onMount, - ParentProps, - Show, - Suspense, - useContext, -} from "solid-js" -import { createStore, produce } from "solid-js/store" -import { isServer } from "solid-js/web" - -interface Storage { - subjects: Record<string, SubjectInfo> - current?: string -} - -interface Context { - all: Record<string, SubjectInfo> - subject?: SubjectInfo - switch(id: string): void - logout(id: string): void - access(id?: string): Promise<string | undefined> - authorize(opts?: AuthorizeOptions): void -} - -export interface AuthorizeOptions { - redirectPath?: string - provider?: string -} - -interface SubjectInfo { - id: string - refresh: string -} - -interface AuthContextOpts { - issuer: string - clientID: string -} - -const context = createContext<Context>() - -export function OpenAuthProvider(props: ParentProps<AuthContextOpts>) { - const client = createClient({ - issuer: props.issuer, - clientID: props.clientID, - }) - const [storage, setStorage] = makePersisted( - createStore<Storage>({ - subjects: {}, - }), - { - name: `${props.issuer}.auth`, - }, - ) - - const resource = createAsync(async () => { - if (isServer) return true - const hash = new URLSearchParams(window.location.search.substring(1)) - const code = hash.get("code") - const state = hash.get("state") - if (code && state) { - const oldState = sessionStorage.getItem("openauth.state") - const verifier = sessionStorage.getItem("openauth.verifier") - const redirect = sessionStorage.getItem("openauth.redirect") - if (redirect && verifier && oldState === state) { - const result = await client.exchange(code, redirect, verifier) - if (!result.err) { - const id = result.tokens.refresh.split(":").slice(0, -1).join(":") - batch(() => { - setStorage("subjects", id, { - id: id, - refresh: result.tokens.refresh, - }) - setStorage("current", id) - }) - } - } - } - return true - }) - - async function authorize(opts?: AuthorizeOptions) { - const redirect = new URL(window.location.origin + (opts?.redirectPath ?? "/")).toString() - const authorize = await client.authorize(redirect, "code", { - pkce: true, - provider: opts?.provider, - }) - sessionStorage.setItem("openauth.state", authorize.challenge.state) - sessionStorage.setItem("openauth.redirect", redirect) - if (authorize.challenge.verifier) sessionStorage.setItem("openauth.verifier", authorize.challenge.verifier) - window.location.href = authorize.url - } - - const accessCache = new Map<string, string>() - const pendingRequests = new Map<string, Promise<any>>() - async function access(id: string) { - const pending = pendingRequests.get(id) - if (pending) return pending - const promise = (async () => { - const existing = accessCache.get(id) - const subject = storage.subjects[id] - const access = await client.refresh(subject.refresh, { - access: existing, - }) - if (access.err) { - pendingRequests.delete(id) - ctx.logout(id) - return - } - if (access.tokens) { - setStorage("subjects", id, "refresh", access.tokens.refresh) - accessCache.set(id, access.tokens.access) - } - pendingRequests.delete(id) - return access.tokens?.access || existing! - })() - pendingRequests.set(id, promise) - return promise - } - - const ctx: Context = { - get all() { - return storage.subjects - }, - get subject() { - if (!storage.current) return - return storage.subjects[storage.current!] - }, - switch(id: string) { - if (!storage.subjects[id]) return - setStorage("current", id) - }, - authorize, - logout(id: string) { - if (!storage.subjects[id]) return - setStorage( - produce((s) => { - delete s.subjects[id] - if (s.current === id) s.current = Object.keys(s.subjects)[0] - }), - ) - }, - async access(id?: string) { - id = id || storage.current - if (!id) return - return access(id || storage.current!) - }, - } - - createEffect(() => { - if (!resource()) return - if (storage.current) return - const [first] = Object.keys(storage.subjects) - if (first) { - setStorage("current", first) - return - } - }) - - return ( - <> - {resource()} - <context.Provider value={ctx}>{props.children}</context.Provider> - </> - ) -} - -export function useOpenAuth() { - const result = useContext(context) - if (!result) throw new Error("no auth context") - return result -} diff --git a/cloud/web/src/components/context-theme.tsx b/cloud/web/src/components/context-theme.tsx deleted file mode 100644 index 7800aeca0..000000000 --- a/cloud/web/src/components/context-theme.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createStore } from "solid-js/store" -import { makePersisted } from "@solid-primitives/storage" -import { createEffect } from "solid-js" -import { createInitializedContext } from "../util/context" -import { isServer } from "solid-js/web" - -interface Storage { - mode: "light" | "dark" -} - -export const { provider: ThemeProvider, use: useTheme } = - createInitializedContext("ThemeContext", () => { - const [store, setStore] = makePersisted( - createStore<Storage>({ - mode: - !isServer && - window.matchMedia && - window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light", - }), - { - name: "theme", - }, - ) - createEffect(() => { - document.documentElement.setAttribute("data-color-mode", store.mode) - }) - - return { - setMode(mode: Storage["mode"]) { - setStore("mode", mode) - }, - get mode() { - return store.mode - }, - ready: true, - } - }) |
