This commit is contained in:
Zai Shi 2024-03-05 12:00:29 +08:00
commit a0781f1e44
6 changed files with 172 additions and 25 deletions

View File

@ -58,7 +58,7 @@ export default function ProvidersClient() {
No auth providers enabled yet. Add one from the available providers below!
</Paragraph>
) : (
<AccordionGroup variant="outlined" sx={{ margin: "var(--AspectRatio-margin)" }}>
<AccordionGroup sx={{ margin: "var(--AspectRatio-margin)" }}>
{oauthProviders.map((provider) => (
<ProviderAccordion key={provider.id} provider={provider} />
))}

View File

@ -2,24 +2,29 @@
import { StackAdminInterface } from "@stackframe/stack-shared";
import React from "react";
import { useStrictMemo } from "@stackframe/stack-shared/src/hooks/use-strict-memo";
import { useUser } from "@stackframe/stack";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { cacheFunction } from "@stackframe/stack-shared/dist/utils/caches";
import { CurrentUser } from "@stackframe/stack/dist/lib/stack-app";
const StackAdminInterfaceContext = React.createContext<StackAdminInterface | null>(null);
const createAdminInterface = cacheFunction((baseUrl: string, projectId: string, user: CurrentUser) => {
return new StackAdminInterface({
baseUrl,
projectId,
internalAdminAccessToken: user.accessToken ?? throwErr("User must have an access token"),
});
});
export function AdminAppProvider(props: { projectId: string, children: React.ReactNode }) {
const user = useUser({ or: "redirect" });
const stackAdminApp = useStrictMemo(() => {
return new StackAdminInterface({
baseUrl: process.env.NEXT_PUBLIC_STACK_URL || throwErr('missing NEXT_PUBLIC_STACK_URL environment variable'),
projectId: props.projectId,
// TODO refresh the access token
internalAdminAccessToken: user.accessToken ?? throwErr("User must have an access token"),
});
}, [props.projectId, user]);
const stackAdminApp = createAdminInterface(
process.env.NEXT_PUBLIC_STACK_URL || throwErr('missing NEXT_PUBLIC_STACK_URL environment variable'),
props.projectId,
user,
);
return (
<StackAdminInterfaceContext.Provider value={stackAdminApp}>

View File

@ -1,6 +1,25 @@
import { DependenciesMap } from "./maps";
import { RateLimitOptions, ReactPromise, rateLimited } from "./promises";
import { AsyncStore, ReadonlyAsyncStore } from "./stores";
/**
* Can be used to cache the result of a function call, for example for the `use` hook in React.
*/
export function cacheFunction<F extends Function>(f: F): F {
const dependenciesMap = new DependenciesMap<any, any>();
return ((...args: any) => {
if (dependenciesMap.has(args)) {
return dependenciesMap.get(args);
}
const value = f(...args);
dependenciesMap.set(args, value);
return value;
}) as any as F;
}
export class AsyncCache<K extends object, T> {
private _map: WeakMap<K, AsyncValueCache<T>> = new Map();

View File

@ -0,0 +1,126 @@
import { Result } from "./results";
export class MaybeWeakMap<K, V> {
private readonly _primitiveMap: Map<K, V>;
private readonly _weakMap: WeakMap<K & WeakKey, V>;
constructor(entries?: readonly (readonly [K, V])[] | null) {
const entriesArray = [...entries ?? []];
this._primitiveMap = new Map(entriesArray.filter((e) => !this._isAllowedInWeakMap(e[0])));
this._weakMap = new WeakMap(entriesArray.filter((e): e is [K & WeakKey, V] => this._isAllowedInWeakMap(e[0])));
}
private _isAllowedInWeakMap(key: K): key is K & WeakKey {
return (typeof key === "object" && key !== null) || (typeof key === "symbol" && Symbol.keyFor(key) === undefined);
}
get(key: K): V | undefined {
if (this._isAllowedInWeakMap(key)) {
return this._weakMap.get(key);
} else {
return this._primitiveMap.get(key);
}
}
set(key: K, value: V): this {
if (this._isAllowedInWeakMap(key)) {
this._weakMap.set(key, value);
} else {
this._primitiveMap.set(key, value);
}
return this;
}
delete(key: K): boolean {
if (this._isAllowedInWeakMap(key)) {
return this._weakMap.delete(key);
} else {
return this._primitiveMap.delete(key);
}
}
has(key: K): boolean {
if (this._isAllowedInWeakMap(key)) {
return this._weakMap.has(key);
} else {
return this._primitiveMap.has(key);
}
}
[Symbol.toStringTag] = "MaybeWeakMap";
}
type DependenciesMapInner<V> = (
& { map: MaybeWeakMap<unknown, DependenciesMapInner<V>> }
& (
| { hasValue: true, value: V }
| { hasValue: false, value: undefined }
)
);
export class DependenciesMap<K extends any[], V> {
private _inner: DependenciesMapInner<V> = { map: new MaybeWeakMap(), hasValue: false, value: undefined };
private _valueToResult(inner: DependenciesMapInner<V>): Result<V, void> {
if (inner.hasValue) {
return Result.ok(inner.value);
} else {
return Result.error(undefined);
}
}
private _unwrapFromInner(dependencies: any[], inner: DependenciesMapInner<V>): Result<V, void> {
if ((dependencies.length === 0)) {
return this._valueToResult(inner);
} else {
const [key, ...rest] = dependencies;
const newInner = inner.map.get(key);
if (!newInner) {
return Result.error(undefined);
}
return this._unwrapFromInner(rest, newInner);
}
}
private _setInInner(dependencies: any[], value: Result<V, void>, inner: DependenciesMapInner<V>): Result<V, void> {
if (dependencies.length === 0) {
const res = this._valueToResult(inner);
if (value.status === "ok") {
inner.hasValue = true;
inner.value = value.data;
} else {
inner.hasValue = false;
inner.value = undefined;
}
return res;
} else {
const [key, ...rest] = dependencies;
let newInner = inner.map.get(key);
if (!newInner) {
inner.map.set(key, newInner = { map: new MaybeWeakMap(), hasValue: false, value: undefined });
}
return this._setInInner(rest, value, newInner);
}
}
get(dependencies: K): V | undefined {
return Result.or(this._unwrapFromInner(dependencies, this._inner), undefined);
}
set(dependencies: K, value: V): this {
this._setInInner(dependencies, Result.ok(value), this._inner);
return this;
}
delete(dependencies: K): boolean {
return this._setInInner(dependencies, Result.error(undefined), this._inner).status === "ok";
}
has(dependencies: K): boolean {
return this._unwrapFromInner(dependencies, this._inner).status === "ok";
}
[Symbol.toStringTag] = "DependenciesMap";
}

View File

@ -1,14 +1,14 @@
import { generateRandomCodeVerifier, generateRandomState, calculatePKCECodeChallenge } from "oauth4webapi";
import Cookies from "js-cookie";
import { isClient } from "../utils/next";
import { cookies } from '@stackframe/stack-sc';
import { cookies as rscCookies } from '@stackframe/stack-sc';
export function getCookie(name: string): string | null {
// TODO the differentiating factor should be RCC vs. RSC, not whether it's a client
if (isClient()) {
return Cookies.get(name) ?? null;
if (rscCookies) {
return rscCookies().get(name)?.value ?? null;
} else {
return cookies().get(name)?.value ?? null;
return Cookies.get(name) ?? null;
}
}
@ -21,20 +21,18 @@ export function setOrDeleteCookie(name: string, value: string | null) {
}
export function deleteCookie(name: string) {
// TODO the differentiating factor should be RCC vs. RSC, not whether it's a client
if (isClient()) {
Cookies.remove(name);
if (rscCookies) {
rscCookies().delete(name);
} else {
cookies().delete(name);
Cookies.remove(name);
}
}
export function setCookie(name: string, value: string) {
// TODO the differentiating factor should be RCC vs. RSC, not whether it's a client
if (isClient()) {
Cookies.set(name, value);
if (rscCookies) {
rscCookies().set(name, value);
} else {
cookies().set(name, value);
Cookies.set(name, value);
}
}

View File

@ -1,9 +1,8 @@
"use client";
import { cache, use } from "react";
import { use } from "react";
import { StackClientApp, StackClientAppJson, stackAppInternalsSymbol } from "../lib/stack-app";
import React from "react";
import { useStrictMemo } from "@stackframe/stack-shared/dist/hooks/use-strict-memo";
export const StackContext = React.createContext<null | {
app: StackClientApp<true>,