summaryrefslogtreecommitdiffhomepage
path: root/cloud/web/src/components
diff options
context:
space:
mode:
authorFrank <[email protected]>2025-08-29 23:32:17 -0400
committerFrank <[email protected]>2025-08-29 23:32:17 -0400
commit37f284f9a97d3354143d64fc76c2eb9f7d9ccf9e (patch)
tree053db9abcb2178c71b22ebeadd07f920750ef5d2 /cloud/web/src/components
parent0178eab29bda2f1b37a29543cd313ede48ad3977 (diff)
downloadopencode-37f284f9a97d3354143d64fc76c2eb9f7d9ccf9e.tar.gz
opencode-37f284f9a97d3354143d64fc76c2eb9f7d9ccf9e.zip
wip: cloud
Diffstat (limited to 'cloud/web/src/components')
-rw-r--r--cloud/web/src/components/context-account.tsx99
-rw-r--r--cloud/web/src/components/context-openauth.tsx180
-rw-r--r--cloud/web/src/components/context-theme.tsx39
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,
- }
- })