- Cache nextjs build

- Uncensor sentry(for diagnostics)
- Refactor ICC
- Completed proper event system for ICC
- Fixed every pages being dynamic because of adminCheck on route layout
- Fixed home page loading time
- Make all states refresh everytime SSE reconnects
- Fixed OG for artifact admin
This commit is contained in:
Dreamgineer
2025-12-29 01:04:34 +07:00 Unverified
parent 4c35eeef52
commit 75405ade99
16 changed files with 125 additions and 88 deletions
+9 -2
View File
@@ -54,8 +54,15 @@ COPY --chown=1001 . .
# Deployment versioning # Deployment versioning
RUN bun -e "const id = Bun.randomUUIDv7(); Bun.write('.version', id); Bun.write('.env.deployment', 'NEXT_DEPLOYMENT_ID='+id)" RUN bun -e "const id = Bun.randomUUIDv7(); Bun.write('.version', id); Bun.write('.env.deployment', 'NEXT_DEPLOYMENT_ID='+id)"
# Run patcher (background) + Next.js build (foreground), then kill patcher # Bind mount recursively makes non-existent directories as root, instead of configured user
RUN bun run build RUN mkdir -p .next
# Build nextjs
RUN --mount=type=cache,target=/home/container/.next/cache,uid=1001,gid=1001 \
bun run build
# Fix nextjs caching(bind mount removes it after build process)
RUN mkdir -p .next/cache
### Stage 3: runner ### ### Stage 3: runner ###
FROM oven/bun:canary-alpine AS runner FROM oven/bun:canary-alpine AS runner
+2 -2
View File
@@ -6,7 +6,7 @@ import { CdnChooser } from "@/components/chooser";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { comms } from "@/lib/comms"; import { shared } from "@/lib/comms";
import { syncAmber, toggleEnka } from "./api"; import { syncAmber, toggleEnka } from "./api";
import { Section } from "./page"; import { Section } from "./page";
@@ -18,7 +18,7 @@ export function SettingsServicesSection({
const [syncing, setSyncing] = useState(false); const [syncing, setSyncing] = useState(false);
const [syncResult, setSyncResult] = useState(""); const [syncResult, setSyncResult] = useState("");
const [enka, setEnka] = useState<boolean>(); const [enka, setEnka] = useState<boolean>();
const [debug] = comms.var("debug"); const [debug] = shared.state("debug");
useEffect(() => { useEffect(() => {
if (typeof enka !== "boolean") return setEnka(enkaInitial); if (typeof enka !== "boolean") return setEnka(enkaInitial);
+4
View File
@@ -35,6 +35,7 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { setLimit, toggleCheck, toggleLock } from "@/lib/api"; import { setLimit, toggleCheck, toggleLock } from "@/lib/api";
import { shared } from "@/lib/comms";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export function SidebarLink({ export function SidebarLink({
@@ -207,6 +208,9 @@ export function SubmissionList({
export function Watcher() { export function Watcher() {
const router = useRouter(); const router = useRouter();
const count = useRef<number | undefined>(undefined); const count = useRef<number | undefined>(undefined);
// Reload after connection restored
shared.signal("sync", () => router.refresh());
useEffect(() => { useEffect(() => {
const es = new EventSource(`/api/artifact/ev`); const es = new EventSource(`/api/artifact/ev`);
es.addEventListener("update", () => router.refresh()); es.addEventListener("update", () => router.refresh());
+4
View File
@@ -29,6 +29,10 @@ import { db } from "@/lib/db";
import { submissions } from "@/lib/db/schema"; import { submissions } from "@/lib/db/schema";
import { LimitManager, SubmissionList, Watcher } from "./client"; import { LimitManager, SubmissionList, Watcher } from "./client";
export const metadata = {
title: "เสือกไอดีชาวบ้าน (แอดมิน)",
};
export default async function AdminLayout({ export default async function AdminLayout({
children, children,
}: { }: {
-4
View File
@@ -1,7 +1,3 @@
export const metadata = {
title: "เสือกไอดีชาวบ้าน (แอดมิน)",
};
export default function AdminPage() { export default function AdminPage() {
return <div></div>; return <div></div>;
} }
+4 -4
View File
@@ -12,7 +12,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { getCharacters } from "@/lib/api"; import { getCharacters } from "@/lib/api";
import { comms } from "@/lib/comms"; import { shared } from "@/lib/comms";
import { uidRegex } from "@/lib/const"; import { uidRegex } from "@/lib/const";
import type { characters } from "@/lib/db/schema"; import type { characters } from "@/lib/db/schema";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -159,7 +159,7 @@ function Placeholder({ className, ...props }: React.ComponentProps<"div">) {
export function Disclaimer() { export function Disclaimer() {
const [agreed, setAgreed] = useState(false); const [agreed, setAgreed] = useState(false);
const [updated] = comms.var("updated"); const [updated] = shared.state("updated");
if (agreed || updated) return; if (agreed || updated) return;
return ( return (
@@ -181,9 +181,9 @@ export function ClearCookie() {
} }
export function WarningDialog() { export function WarningDialog() {
const [_manual, _setManual] = comms.var("manual"); const [_manual, _setManual] = shared.state("manual");
comms.event("beforeSubmit", () => { shared.signal("beforeSubmit", () => {
_manual; _manual;
}); });
} }
+1 -3
View File
@@ -3,7 +3,6 @@ import { Toaster } from "sonner";
import Background from "#/bg.jpg"; import Background from "#/bg.jpg";
import Image from "@/components/image"; import Image from "@/components/image";
import { SentryDevToolbar } from "@/components/sentry"; import { SentryDevToolbar } from "@/components/sentry";
import { adminCheck } from "@/lib/auth";
import Providers, { VersionCheck } from "../client"; import Providers, { VersionCheck } from "../client";
export default async function UiLayout({ export default async function UiLayout({
@@ -11,7 +10,6 @@ export default async function UiLayout({
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const isAdmin = await adminCheck();
return ( return (
<> <>
<Image <Image
@@ -22,7 +20,7 @@ export default async function UiLayout({
<Providers> <Providers>
<AnimatePresence mode="wait">{children}</AnimatePresence> <AnimatePresence mode="wait">{children}</AnimatePresence>
<VersionCheck /> <VersionCheck />
{isAdmin && <SentryDevToolbar />} <SentryDevToolbar />
</Providers> </Providers>
<Toaster theme="dark" richColors closeButton /> <Toaster theme="dark" richColors closeButton />
</> </>
+6 -6
View File
@@ -41,8 +41,8 @@ export default function Home() {
src={ArtifactLogo} src={ArtifactLogo}
alt="เสือกไอดีชาวบ้าน" alt="เสือกไอดีชาวบ้าน"
className="w-22" className="w-22"
width={500} width={100}
height={500} height={100}
/> />
</ItemMedia> </ItemMedia>
<ItemContent> <ItemContent>
@@ -66,8 +66,8 @@ export default function Home() {
src={RubgramLogo} src={RubgramLogo}
alt="รับกรรมแทนคนดู" alt="รับกรรมแทนคนดู"
className="w-22" className="w-22"
width={500} width={100}
height={500} height={100}
/> />
</ItemMedia> </ItemMedia>
<ItemContent> <ItemContent>
@@ -90,8 +90,8 @@ export default function Home() {
src={TierlistLogo} src={TierlistLogo}
alt="จัดเทียร์ลิสต์อบิส" alt="จัดเทียร์ลิสต์อบิส"
className="w-22" className="w-22"
width={500} width={100}
height={500} height={100}
/> />
</ItemMedia> </ItemMedia>
<ItemContent> <ItemContent>
+5 -2
View File
@@ -34,7 +34,7 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { comms } from "@/lib/comms"; import { shared } from "@/lib/comms";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { setFree, setLimit, toggleCheck, toggleLock } from "../api"; import { setFree, setLimit, toggleCheck, toggleLock } from "../api";
@@ -244,7 +244,7 @@ export function SubmissionList({
}[]; }[];
}) { }) {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const [debug] = comms.var("debug"); const [debug] = shared.state("debug");
return ( return (
<> <>
@@ -275,6 +275,9 @@ export function SubmissionList({
export function Watcher() { export function Watcher() {
const router = useRouter(); const router = useRouter();
const count = useRef<number | undefined>(undefined); const count = useRef<number | undefined>(undefined);
// Reload after connection restored
shared.signal("sync", () => router.refresh());
useEffect(() => { useEffect(() => {
const es = new EventSource(`/api/rubgram/ev`); const es = new EventSource(`/api/rubgram/ev`);
es.addEventListener("update", () => router.refresh()); es.addEventListener("update", () => router.refresh());
+5 -5
View File
@@ -16,7 +16,7 @@ import {
MultiSelectValue, MultiSelectValue,
} from "@/components/ui/multi-select"; } from "@/components/ui/multi-select";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { comms } from "@/lib/comms"; import { shared } from "@/lib/comms";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
calcPrice, calcPrice,
@@ -33,7 +33,7 @@ export function WelcomeScreening({
session: Awaited<ReturnType<typeof getDiscordSession>>; session: Awaited<ReturnType<typeof getDiscordSession>>;
}) { }) {
const [agreed, setAgreed] = useState(false); const [agreed, setAgreed] = useState(false);
const [updated] = comms.var("updated"); const [updated] = shared.state("updated");
if (agreed || (updated && session)) return; if (agreed || (updated && session)) return;
return ( return (
@@ -76,7 +76,7 @@ export function ServiceSelector({
types, types,
allDiscount, allDiscount,
}: Awaited<ReturnType<typeof getEndgameConfig>>) { }: Awaited<ReturnType<typeof getEndgameConfig>>) {
const [selected, setSelected] = comms.var("rubgram.services"); const [selected, setSelected] = shared.state("rubgram.services");
return ( return (
<div className="grid gap-2"> <div className="grid gap-2">
@@ -119,7 +119,7 @@ export function ServiceSelector({
} }
export function PriceEstimation() { export function PriceEstimation() {
const [selected] = comms.var("rubgram.services"); const [selected] = shared.state("rubgram.services");
const [label, setLabel] = useState("ทั้งหมด 0 บาท"); const [label, setLabel] = useState("ทั้งหมด 0 บาท");
useEffect(() => { useEffect(() => {
@@ -202,7 +202,7 @@ export function Countdown({ time }: { time: Date }) {
export function CancelButton({ sid }: { sid: string }) { export function CancelButton({ sid }: { sid: string }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [, setServices] = comms.var("rubgram.services"); const [, setServices] = shared.state("rubgram.services");
return ( return (
<Button <Button
+15 -12
View File
@@ -13,6 +13,7 @@ import {
Fragment, Fragment,
type ReactNode, type ReactNode,
type RefObject, type RefObject,
useCallback,
useContext, useContext,
useEffect, useEffect,
useLayoutEffect, useLayoutEffect,
@@ -47,7 +48,7 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { tlPlacements, tlState } from "@/lib/api"; import { tlPlacements, tlState } from "@/lib/api";
import { comms } from "@/lib/comms"; import { shared } from "@/lib/comms";
import type { import type {
characters, characters,
tierlistBadges, tierlistBadges,
@@ -122,7 +123,7 @@ export function TierList({
download: boolean; download: boolean;
ev: "unknown" | "connecting" | "ready"; ev: "unknown" | "connecting" | "ready";
}>({ upload: false, download: false, ev: "unknown" }); }>({ upload: false, download: false, ev: "unknown" });
const [updated] = comms.var("updated"); const [updated] = shared.state("updated");
//#region error handling //#region error handling
useEffect(() => { useEffect(() => {
@@ -136,17 +137,19 @@ export function TierList({
//#endregion //#endregion
//#region states fetching //#region states fetching
useEffect(() => { const fetchStates = useCallback(async () => {
async function fetchStates() { setEvStatus((x) => ({ ...x, download: true }));
setEvStatus((x) => ({ ...x, download: true })); setStates(
setStates( await fetch(`/api/tl/${version.id}/states`)
await fetch(`/api/tl/${version.id}/states`) .then((r) => r.json())
.then((r) => r.json()) .finally(() => setEvStatus((x) => ({ ...x, download: false }))),
.finally(() => setEvStatus((x) => ({ ...x, download: false }))), );
);
}
fetchStates();
}, [version.id]); }, [version.id]);
// Reload after connection restored
shared.signal("sync", fetchStates);
useEffect(() => {
fetchStates();
}, [fetchStates]);
useEffect(() => { useEffect(() => {
setEvStatus((x) => ({ ...x, ev: "connecting" })); setEvStatus((x) => ({ ...x, ev: "connecting" }));
+11 -9
View File
@@ -1,13 +1,13 @@
"use client"; "use client";
import { ProgressProvider } from "@bprogress/next/app"; import { ProgressProvider } from "@bprogress/next/app";
import { useEffect, useRef, useState } from "react"; import { use, useEffect, useRef, useState } from "react";
import { useKey } from "react-use"; import { useKey } from "react-use";
import ReconnectingEventSource from "reconnecting-eventsource"; import ReconnectingEventSource from "reconnecting-eventsource";
import { toast } from "sonner"; import { toast } from "sonner";
import { stringify } from "yaml"; import { stringify } from "yaml";
import { CdnChooserProvider } from "@/components/chooser"; import { CdnChooserProvider } from "@/components/chooser";
import CommsProvider, { comms } from "@/lib/comms"; import IccProvider, { IccContext, shared } from "@/lib/comms";
export default function Providers({ children }: { children: React.ReactNode }) { export default function Providers({ children }: { children: React.ReactNode }) {
return ( return (
@@ -18,7 +18,7 @@ export default function Providers({ children }: { children: React.ReactNode }) {
shallowRouting shallowRouting
> >
<CdnChooserProvider> <CdnChooserProvider>
<CommsProvider>{children}</CommsProvider> <IccProvider>{children}</IccProvider>
</CdnChooserProvider> </CdnChooserProvider>
</ProgressProvider> </ProgressProvider>
); );
@@ -26,10 +26,11 @@ export default function Providers({ children }: { children: React.ReactNode }) {
export function VersionCheck({ headless = false }: { headless?: boolean }) { export function VersionCheck({ headless = false }: { headless?: boolean }) {
const ver = useRef<string>(""); const ver = useRef<string>("");
const [debug, setDebug] = comms.var("debug"); const { emit } = use(IccContext);
const [, setConnected] = comms.var("connected"); const [debug, setDebug] = shared.state("debug");
const [, setUpdated] = comms.var("updated"); const [, setConnected] = shared.state("connected");
const [debugData] = comms.raw(); const [, setUpdated] = shared.state("updated");
const [debugData] = shared.raw();
const [readyForUpdate, setReadyForUpdate] = useState(false); const [readyForUpdate, setReadyForUpdate] = useState(false);
useKey("F2", () => { useKey("F2", () => {
@@ -40,7 +41,7 @@ export function VersionCheck({ headless = false }: { headless?: boolean }) {
useEffect(() => { useEffect(() => {
if (!readyForUpdate && window.location.hash === "#update") { if (!readyForUpdate && window.location.hash === "#update") {
setUpdated(true); setUpdated(true);
toast.success("คุณอยู่ในเวอร์ชั่นล่าสุดแล้ว"); setTimeout(() => toast.success("คุณอยู่ในเวอร์ชั่นล่าสุดแล้ว"), 1000);
const url = new URL(window.location.href); const url = new URL(window.location.href);
url.hash = ""; url.hash = "";
window.history.replaceState("", "", url); window.history.replaceState("", "", url);
@@ -49,6 +50,7 @@ export function VersionCheck({ headless = false }: { headless?: boolean }) {
const es = new ReconnectingEventSource(`/api/active`, {}); const es = new ReconnectingEventSource(`/api/active`, {});
es.addEventListener("version", (d) => { es.addEventListener("version", (d) => {
setConnected(true); setConnected(true);
emit("sync");
const newVersion = JSON.parse(d.data); const newVersion = JSON.parse(d.data);
const version = ver.current; const version = ver.current;
if (!version) console.log("Client version:", newVersion); if (!version) console.log("Client version:", newVersion);
@@ -84,7 +86,7 @@ export function VersionCheck({ headless = false }: { headless?: boolean }) {
); );
}; };
return () => es.close(); return () => es.close();
}, [headless, setConnected, setUpdated, readyForUpdate]); }, [headless, setConnected, setUpdated, readyForUpdate, emit]);
return debug ? ( return debug ? (
<div className="absolute bottom-0 right-0 m-2 px-1 rounded flex flex-col gap-2 bg-card border active:pointer-events-none opacity-20 hover:opacity-100 transition-opacity"> <div className="absolute bottom-0 right-0 m-2 px-1 rounded flex flex-col gap-2 bg-card border active:pointer-events-none opacity-20 hover:opacity-100 transition-opacity">
+1 -1
View File
@@ -1,5 +1,5 @@
import { Kanit } from "next/font/google"; import { Kanit } from "next/font/google";
import { Watcher } from "@/app/(ui)/artifact/admin/client"; import { Watcher } from "@/app/(ui)/rubgram/admin/client";
import { VersionCheck } from "@/app/client"; import { VersionCheck } from "@/app/client";
import AutoFitText from "@/components/fit"; import AutoFitText from "@/components/fit";
import { getArtifactConfig } from "@/lib/api"; import { getArtifactConfig } from "@/lib/api";
+2 -2
View File
@@ -1,10 +1,10 @@
"use client"; "use client";
import { useSentryToolbar } from "@sentry/toolbar"; import { useSentryToolbar } from "@sentry/toolbar";
import { comms } from "@/lib/comms"; import { shared } from "@/lib/comms";
export function SentryDevToolbar() { export function SentryDevToolbar() {
const [debug] = comms.var("debug"); const [debug] = shared.state("debug");
useSentryToolbar({ useSentryToolbar({
// Remember to conditionally enable the Toolbar. // Remember to conditionally enable the Toolbar.
+6 -1
View File
@@ -8,7 +8,12 @@ Sentry.init({
dsn: "https://9a918fb166b0abde6cbb698191ac61c9@o4510549315420160.ingest.us.sentry.io/4510549315616768", dsn: "https://9a918fb166b0abde6cbb698191ac61c9@o4510549315420160.ingest.us.sentry.io/4510549315616768",
// Add optional integrations for additional features // Add optional integrations for additional features
integrations: [Sentry.replayIntegration()], integrations: [
Sentry.replayIntegration({
maskAllText: false,
blockAllMedia: false,
}),
],
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1, tracesSampleRate: 1,
+50 -35
View File
@@ -9,7 +9,7 @@ import {
useState, useState,
} from "react"; } from "react";
type WebsiteComms = Partial<{ type SharedStates = Partial<{
manual: boolean; manual: boolean;
debug: boolean; debug: boolean;
connected: boolean; connected: boolean;
@@ -18,24 +18,30 @@ type WebsiteComms = Partial<{
_raw: Record<string, unknown>; _raw: Record<string, unknown>;
}>; }>;
type WebsiteSignals = { beforeSubmit?: undefined } & WebsiteComms; type SharedSignals = {
beforeSubmit?: undefined;
sync?: undefined;
} & SharedStates;
type WebsiteCommsMutator = { type IccMutator = {
get<K extends keyof WebsiteComms>(key: K): WebsiteComms[K]; get<K extends keyof SharedStates>(key: K): SharedStates[K];
set<K extends keyof WebsiteComms>(key: K, value: WebsiteComms[K]): void; set<K extends keyof SharedStates>(key: K, value: SharedStates[K]): void;
on<K extends keyof WebsiteSignals>( on<K extends keyof SharedSignals>(
key: K, key: K,
listener: (value: WebsiteSignals[K]) => void, listener: (value: SharedSignals[K]) => void,
): () => void; ): () => void;
off<K extends keyof WebsiteSignals>( off<K extends keyof SharedSignals>(
key: K, key: K,
listener: (value: WebsiteSignals[K]) => void, listener: (value: SharedSignals[K]) => void,
): void;
emit<K extends keyof SharedSignals>(
key: K,
...args: SharedSignals[K] extends undefined ? [] : [value: SharedSignals[K]]
): void; ): void;
emit<K extends keyof WebsiteSignals>(key: K, value: WebsiteSignals[K]): void;
active: boolean; active: boolean;
}; };
export const CommsContext = createContext<WebsiteCommsMutator>({ export const IccContext = createContext<IccMutator>({
get: () => { get: () => {
console.error("ICC used outside of provider."); console.error("ICC used outside of provider.");
return undefined; return undefined;
@@ -47,18 +53,24 @@ export const CommsContext = createContext<WebsiteCommsMutator>({
active: false, active: false,
}); });
export default function CommsProvider({ export default function IccProvider({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const storeRef = useRef<WebsiteComms>({}); const storeRef = useRef<SharedStates>({});
const listeners = useRef<{ const listeners = useRef<{
[K in keyof WebsiteSignals]?: Array<(value: WebsiteSignals[K]) => void>; [K in keyof SharedSignals]?: Array<
(
...args: SharedSignals[K] extends undefined
? []
: [value: SharedSignals[K]]
) => void
>;
}>({}); }>({});
const contextValue = useRef<WebsiteCommsMutator>({ const contextValue = useRef<IccMutator>({
get: (k) => storeRef.current[k], get: (k) => storeRef.current[k],
set(k, v) { set(k, v) {
storeRef.current = { ...storeRef.current, [k]: v }; storeRef.current = { ...storeRef.current, [k]: v };
@@ -68,7 +80,9 @@ export default function CommsProvider({
const { _raw, ...withoutRaw } = storeRef.current; const { _raw, ...withoutRaw } = storeRef.current;
contextValue.current.set("_raw", withoutRaw); contextValue.current.set("_raw", withoutRaw);
} }
contextValue.current.emit(k, v); contextValue.current.emit(
...([k, v] as unknown as Parameters<typeof contextValue.current.emit>),
);
}, },
on(k, listener) { on(k, listener) {
// @ts-expect-error typescript screams for some impossible static type conflict // @ts-expect-error typescript screams for some impossible static type conflict
@@ -81,26 +95,27 @@ export default function CommsProvider({
(l) => l !== listener, (l) => l !== listener,
); );
}, },
emit: (k, value) => { emit(k, value = undefined) {
// biome-ignore lint/suspicious/useIterableCallbackReturn: l returns void (listeners.current[k] || []).map((l) =>
(listeners.current[k] || []).forEach((l) => l(value)); l(...([value] as Parameters<typeof l>)),
);
}, },
active: true, active: true,
}); });
return <CommsContext value={contextValue.current}>{children}</CommsContext>; return <IccContext value={contextValue.current}>{children}</IccContext>;
} }
export const comms = { export const shared = {
var<K extends keyof WebsiteComms>( state<K extends keyof SharedStates>(
name: K, name: K,
): [ ): [
WebsiteComms[K], SharedStates[K],
( (
value: WebsiteComms[K] | ((prev: WebsiteComms[K]) => WebsiteComms[K]), value: SharedStates[K] | ((prev: SharedStates[K]) => SharedStates[K]),
) => void, ) => void,
] { ] {
const comms = use(CommsContext); const comms = use(IccContext);
if (!comms.active) { if (!comms.active) {
console.warn("ICC used outside of provider. Using local state instead."); console.warn("ICC used outside of provider. Using local state instead.");
} }
@@ -108,45 +123,45 @@ export const comms = {
comms.active ? comms.get(name) : undefined, comms.active ? comms.get(name) : undefined,
); );
useEffect(() => { useEffect(() => {
function listener(value: WebsiteComms[typeof name]) { function listener(value: SharedStates[typeof name]) {
setState(value); setState(value);
} }
return comms.on(name, listener); return comms.on(name, listener);
}, [name]); }, [comms, name]);
const mutator = useCallback( const mutator = useCallback(
( (
value: value:
| WebsiteComms[typeof name] | SharedStates[typeof name]
| ((prev: WebsiteComms[typeof name]) => WebsiteComms[typeof name]), | ((prev: SharedStates[typeof name]) => SharedStates[typeof name]),
) => { ) => {
const v = typeof value === "function" ? value(comms.get(name)) : value; const v = typeof value === "function" ? value(comms.get(name)) : value;
if (comms.active) comms.set(name, v); if (comms.active) comms.set(name, v);
else setState(v); else setState(v);
}, },
[name], [comms, name],
); );
return [state, mutator] as const; return [state, mutator] as const;
}, },
event<K extends keyof WebsiteSignals>( signal<K extends keyof SharedSignals>(
key: K, key: K,
listener: (ev: WebsiteSignals[K]) => void, listener: (ev: SharedSignals[K]) => void,
) { ) {
const comms = use(CommsContext); const comms = use(IccContext);
const listenerRef = useRef(listener); const listenerRef = useRef(listener);
useEffect(() => { useEffect(() => {
listenerRef.current = listener; listenerRef.current = listener;
}, [listener]); }, [listener]);
useEffect(() => { useEffect(() => {
const stableListener = (value: WebsiteSignals[K]) => { const stableListener = (value: SharedSignals[K]) => {
listenerRef.current(value); listenerRef.current(value);
}; };
return comms.on(key, stableListener); return comms.on(key, stableListener);
}, [comms, key]); }, [comms, key]);
}, },
raw() { raw() {
return [comms.var("_raw")[0]]; return [shared.state("_raw")[0]];
}, },
}; };