mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Merge branch 'main' of https://github.com/stackframe-inc/stack
This commit is contained in:
commit
a0781f1e44
@ -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} />
|
||||
))}
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
126
packages/stack-shared/src/utils/maps.tsx
Normal file
126
packages/stack-shared/src/utils/maps.tsx
Normal 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";
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user