diff --git a/apps/web/.env.production b/apps/web/.env.production index a6346e9..33bf519 100644 --- a/apps/web/.env.production +++ b/apps/web/.env.production @@ -2,5 +2,4 @@ PUBLIC_ENV=xuyue.cc PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=dd37457027c40b39 PUBLIC_CODE_URL=https://code.xuyue.cc PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc -PUBLIC_SIGNALING_URL=wss://signaling.xuyue.cc PUBLIC_ICONIFY_URL=https://icon.xuyue.cc \ No newline at end of file diff --git a/apps/web/.env.staging b/apps/web/.env.staging index 3e73c7b..05f4ef6 100644 --- a/apps/web/.env.staging +++ b/apps/web/.env.staging @@ -3,4 +3,3 @@ PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13 PUBLIC_CODE_URL=http://10.13.114.114:82 PUBLIC_JUDGE0_URL=http://10.13.114.114:8082 PUBLIC_ICONIFY_URL=http://10.13.114.114:8098 -PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 diff --git a/apps/web/.env.test b/apps/web/.env.test index 823e478..1ed5e55 100644 --- a/apps/web/.env.test +++ b/apps/web/.env.test @@ -3,4 +3,3 @@ PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13 PUBLIC_CODE_URL=http://10.13.114.114:82 PUBLIC_JUDGE0_URL=http://10.13.114.114:8082 PUBLIC_ICONIFY_URL=http://10.13.114.114:8098 -PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md index 7efe1f5..e1de45d 100644 --- a/apps/web/CLAUDE.md +++ b/apps/web/CLAUDE.md @@ -41,8 +41,8 @@ Each feature module (under `oj/` or `admin/`) typically has: - `api.ts` — API calls specific to the feature Shared logic lives in `shared/`: -- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `screenMode` (problem split-screen layout), `loginSummary` (AI activity summary) -- `composables/` — `pagination` (URL-synced), `websocket` (reconnect + heartbeat), `sync` (Yjs/y-webrtc for collaborative editing), `configUpdate` (WS-pushed config sync), `useMermaid` (lazy Mermaid render), `breakpoints`, `maxkb` +- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `screenMode` (problem split-screen layout), `loginSummary` (AI activity summary), `collab` (help-request queue + collab room) +- `composables/` — `pagination` (URL-synced), `websocket` (reconnect + heartbeat), `collabDoc` (Yjs binding for the collab channel), `configUpdate` (WS-pushed config sync), `useMermaid` (lazy Mermaid render), `breakpoints`, `maxkb` - `layout/` — `default.vue` and `admin.vue` layout wrappers - `api.ts` — shared API calls (auth, profile, tags, captcha) @@ -94,7 +94,6 @@ Variables prefixed with `PUBLIC_` are injected at build time. Env files: `.env`, | `PUBLIC_CODE_URL` | Code execution service | | `PUBLIC_JUDGE0_URL` | Judge0 API | | `PUBLIC_MAXKB_URL` | Knowledge base service | -| `PUBLIC_SIGNALING_URL` | WebRTC signaling server | | `PUBLIC_ICONIFY_URL` | Iconify icon CDN | ### Routing @@ -107,7 +106,10 @@ Routes are defined in `src/routes.ts` with two root routes: `ojs` (user-facing) ### Real-time Features - WebSocket via composable in `shared/composables/` for submission status updates -- Yjs + y-webrtc for collaborative editing in the flowchart editor +- Yjs over the `/ws/collab` channel for classroom help requests and collaborative + code editing (students raise a hand, teachers join their editor). The server is a + dumb relay — it authenticates, assigns rooms, and forwards frames without parsing + them. See `docs/specs/2026-08-28-collab-help-request-design.md`. ## Related Repository diff --git a/apps/web/package.json b/apps/web/package.json index 20053ee..52e5714 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -54,7 +54,6 @@ "vue-router": "^5.2.0", "y-codemirror.next": "^0.3.6", "y-protocols": "1.0.7", - "y-webrtc": "^10.3.0", "yjs": "^13.6.32" }, "devDependencies": { diff --git a/apps/web/src/env.d.ts b/apps/web/src/env.d.ts index 5e6ff03..6a6f5a8 100644 --- a/apps/web/src/env.d.ts +++ b/apps/web/src/env.d.ts @@ -6,7 +6,6 @@ interface ImportMetaEnv { readonly PUBLIC_CODE_URL: string readonly PUBLIC_JUDGE0_URL: string readonly PUBLIC_ICONIFY_URL: string - readonly PUBLIC_SIGNALING_URL: string } interface ImportMeta { diff --git a/apps/web/src/oj/composables/syncStatus.ts b/apps/web/src/oj/composables/syncStatus.ts deleted file mode 100644 index fcddcb2..0000000 --- a/apps/web/src/oj/composables/syncStatus.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ref, provide, inject } from "vue" - -/** - * 同步状态管理 composable - * 使用 provide/inject 模式在组件树中共享状态 - */ - -export interface SyncStatusState { - hadConnection: boolean - otherUser?: { name: string; isSuperAdmin: boolean } - lastLeftUser?: { name: string; isSuperAdmin: boolean } // 保存离开之人的信息 -} - -// 提供/注入的 key -export const SYNC_STATUS_KEY = Symbol("syncStatus") - -/** - * 创建同步状态实例 - * 每次调用创建新的状态实例 - */ -export function createSyncStatus() { - const otherUser = ref<{ name: string; isSuperAdmin: boolean }>() - const hadConnection = ref(false) - const lastLeftUser = ref<{ name: string; isSuperAdmin: boolean }>() - - const setOtherUser = (user?: { name: string; isSuperAdmin: boolean }) => { - // 如果之前有其他用户,现在没有了,说明用户离开了 - if (otherUser.value && !user) { - lastLeftUser.value = otherUser.value - } - otherUser.value = user - if (user) { - hadConnection.value = true - } - } - - const reset = () => { - otherUser.value = undefined - hadConnection.value = false - lastLeftUser.value = undefined - } - - return { - otherUser, - hadConnection, - lastLeftUser, - setOtherUser, - reset, - } -} - -/** - * 提供同步状态到子组件 - * 在父组件中调用 - */ -export function provideSyncStatus() { - const syncStatus = createSyncStatus() - provide(SYNC_STATUS_KEY, syncStatus) - return syncStatus -} - -/** - * 注入同步状态 - * 在子组件中调用,获取父组件提供的状态 - */ -export function injectSyncStatus() { - const syncStatus = - inject>(SYNC_STATUS_KEY) - if (!syncStatus) { - throw new Error("syncStatus must be provided by a parent component") - } - return syncStatus -} diff --git a/apps/web/src/oj/problem/components/ContestEditor.vue b/apps/web/src/oj/problem/components/ContestEditor.vue index 316b250..d8964cf 100644 --- a/apps/web/src/oj/problem/components/ContestEditor.vue +++ b/apps/web/src/oj/problem/components/ContestEditor.vue @@ -5,7 +5,6 @@ import { useProblemStore } from "oj/store/problem" import { SOURCES } from "utils/constants" import CodeEditor from "shared/components/CodeEditor.vue" import { useBreakpoints } from "shared/composables/breakpoints" -import { provideSyncStatus } from "oj/composables/syncStatus" import storage from "utils/storage" import type { LANGUAGE } from "utils/types" import Form from "./Form.vue" @@ -18,10 +17,6 @@ const { problem } = storeToRefs(problemStore) const { isDesktop } = useBreakpoints() -// 提供空的同步状态,避免 Form 组件注入错误 -// 在竞赛模式下,同步功能会被 showSyncFeature 自动禁用 -provideSyncStatus() - const contestID = route.params.contestID || null const storageKey = computed( () => diff --git a/apps/web/src/shared/composables/sync.ts b/apps/web/src/shared/composables/sync.ts deleted file mode 100644 index 7dfa0f0..0000000 --- a/apps/web/src/shared/composables/sync.ts +++ /dev/null @@ -1,412 +0,0 @@ -import { useMessage } from "naive-ui" -import { useUserStore } from "../store/user" -import type { EditorView } from "@codemirror/view" -import { Compartment } from "@codemirror/state" -import type { WebrtcProvider } from "y-webrtc" -import type { Doc, Text } from "yjs" - -// 常量定义 -const SYNC_CONSTANTS = { - MAX_ROOM_USERS: 2, - AWARENESS_SYNC_DELAY: 500, - INIT_SYNC_TIMEOUT: 500, - SUPER_ADMIN_COLOR: "#ff6b6b", - REGULAR_USER_COLOR: "#4dabf7", -} as const - -// 错误类型码 -export const SYNC_ERROR_CODES = { - SUPER_ADMIN_LEFT: "SUPER_ADMIN_LEFT", - MISSING_SUPER_ADMIN: "MISSING_SUPER_ADMIN", -} as const - -// 界面和通知文案 -export const SYNC_MESSAGES = { - // 超管离开 - SUPER_ADMIN_LEFT: (name: string) => `超管 ${name} 已离开`, - - // 缺少超管 - MISSING_SUPER_ADMIN: "协同编辑需要超管", - - // 连接成功 - SYNC_ACTIVE: "协同编辑已激活!", - - // 连接断开 - CONNECTION_LOST: "协同编辑已断开", - - // 等待相关 - WAITING_STUDENT: "正在等待学生加入...", - WAITING_ADMIN: "正在等待超管加入...", - - // Form.vue 界面文案 - SYNC_ON: "断开同步", - SYNC_OFF: "开启同步", - SYNCING_WITH: (name: string) => `与 ${name} 同步中`, - STUDENT_LEFT: (name?: string) => (name ? `${name}已离开` : "可以关闭同步"), -} as const - -// 类型定义 -type SyncState = "waiting" | "active" | "error" -type SyncErrorCode = (typeof SYNC_ERROR_CODES)[keyof typeof SYNC_ERROR_CODES] - -interface UserInfo { - name: string - isSuperAdmin: boolean -} - -interface PeersEvent { - webrtcPeers: string[] -} - -interface StatusEvent { - connected: boolean -} - -interface SyncedEvent { - synced: boolean -} - -interface SyncOptions { - problemId: string - editorView: EditorView - onStatusChange?: (status: SyncStatus) => void -} - -export interface SyncStatus { - connected: boolean - roomUsers: number - canSync: boolean - message: string - error?: string - errorCode?: SyncErrorCode - otherUser?: UserInfo -} - -/** - * 代码同步 composable - * 每次调用创建新的同步实例 - */ -export function useCodeSync() { - const userStore = useUserStore() - const message = useMessage() - - // 每次调用创建新的实例变量 - let ydoc: Doc | null = null - let provider: WebrtcProvider | null = null - let ytext: Text | null = null - const collabCompartment = new Compartment() - let currentEditorView: EditorView | null = null - let lastSyncState: SyncState | null = null - let roomUserInfo = new Map() - let hasShownSuperAdminLeftMessage = false - - const updateStatus = ( - status: SyncStatus, - onStatusChange?: (status: SyncStatus) => void, - ) => { - onStatusChange?.(status) - } - - const normalizeClientId = (clientId: number | string): number => { - return typeof clientId === "string" ? parseInt(clientId, 10) : clientId - } - - const checkHasSuperAdmin = (awarenessStates: Map): boolean => { - if (userStore.isSuperAdmin) return true - return Array.from(awarenessStates.values()).some( - (state) => state.user?.isSuperAdmin, - ) - } - - const getOtherUserInfo = ( - awarenessStates: Map, - ): UserInfo | undefined => { - if (!provider) return undefined - - const localClientId = provider.awareness.clientID - for (const [clientId, state] of awarenessStates) { - if (clientId !== localClientId && state.user) { - return { - name: state.user.name, - isSuperAdmin: state.user.isSuperAdmin, - } - } - } - return undefined - } - - const checkIfSuperAdminLeft = ( - removedClientIds: number[], - onStatusChange?: (status: SyncStatus) => void, - ) => { - if (userStore.isSuperAdmin || hasShownSuperAdminLeftMessage) return - - const superAdminInfo = removedClientIds - .map((id) => roomUserInfo.get(id)) - .find((info) => info?.isSuperAdmin) - - if (superAdminInfo) { - hasShownSuperAdminLeftMessage = true - const leftMessage = SYNC_MESSAGES.SUPER_ADMIN_LEFT(superAdminInfo.name) - updateStatus( - { - connected: false, - roomUsers: 0, - canSync: false, - message: leftMessage, - error: leftMessage, - errorCode: SYNC_ERROR_CODES.SUPER_ADMIN_LEFT, - }, - onStatusChange, - ) - message.warning(leftMessage) - stopSync() - } - } - - const checkRoomPermissions = ( - roomUsers: number, - onStatusChange?: (status: SyncStatus) => void, - ) => { - const awarenessStates = provider?.awareness.getStates() - if (!awarenessStates) return - - const hasSuperAdmin = checkHasSuperAdmin(awarenessStates) - const canSync = roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && hasSuperAdmin - const otherUser = getOtherUserInfo(awarenessStates) - - if (roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && !hasSuperAdmin) { - if (lastSyncState === "error") return - - updateStatus( - { - connected: false, - roomUsers, - canSync: false, - message: SYNC_MESSAGES.MISSING_SUPER_ADMIN, - error: SYNC_MESSAGES.MISSING_SUPER_ADMIN, - errorCode: SYNC_ERROR_CODES.MISSING_SUPER_ADMIN, - otherUser, - }, - onStatusChange, - ) - message.error(SYNC_MESSAGES.MISSING_SUPER_ADMIN) - lastSyncState = "error" - stopSync() - return - } else if (canSync) { - updateStatus( - { - connected: true, - roomUsers, - canSync: true, - message: SYNC_MESSAGES.SYNC_ACTIVE, - otherUser, - }, - onStatusChange, - ) - if (lastSyncState !== "active") { - message.success(SYNC_MESSAGES.SYNC_ACTIVE) - lastSyncState = "active" - } - } else { - updateStatus( - { - connected: true, - roomUsers, - canSync: false, - message: - roomUsers === 1 - ? SYNC_MESSAGES.WAITING_STUDENT - : SYNC_MESSAGES.WAITING_ADMIN, - otherUser, - }, - onStatusChange, - ) - lastSyncState = "waiting" - } - } - - const setupContentSync = ( - ytext: Text, - provider: WebrtcProvider, - savedContent: string, - ) => { - let hasInitialized = false - - const initTimeout = setTimeout(() => { - if (!hasInitialized && ytext.length === 0 && savedContent) { - ytext.insert(0, savedContent) - } - hasInitialized = true - }, SYNC_CONSTANTS.INIT_SYNC_TIMEOUT) - - provider.on("synced", (event: SyncedEvent) => { - if (!event.synced || hasInitialized) return - - clearTimeout(initTimeout) - if (ytext.length === 0 && savedContent) { - ytext.insert(0, savedContent) - } - hasInitialized = true - }) - } - - async function startSync(options: SyncOptions): Promise<() => void> { - const { problemId, editorView, onStatusChange } = options - - if (!userStore.isAuthed) { - return () => {} - } - - // 动态导入 yjs 相关模块 - const [Y, { WebrtcProvider }, { yCollab }] = await Promise.all([ - import("yjs"), - import("y-webrtc"), - import("y-codemirror.next"), - ]) - - // 初始化文档和提供者 - ydoc = new Y.Doc() - ytext = ydoc.getText("codemirror") - const roomName = `problem-${problemId}` - - provider = new WebrtcProvider(roomName, ydoc, { - signaling: [import.meta.env.PUBLIC_SIGNALING_URL], - maxConns: 1, - filterBcConns: true, - }) - - // 监听连接状态 - provider.on("status", (event: StatusEvent) => { - if (!event.connected) { - updateStatus( - { - connected: false, - roomUsers: 0, - canSync: false, - message: SYNC_MESSAGES.CONNECTION_LOST, - error: SYNC_MESSAGES.CONNECTION_LOST, - }, - onStatusChange, - ) - message.warning(SYNC_MESSAGES.CONNECTION_LOST) - } - }) - - // 监听用户加入/离开 - provider.on("peers", (event: PeersEvent) => { - const roomUsers = event.webrtcPeers.length + 1 - setTimeout(() => { - checkRoomPermissions(roomUsers, onStatusChange) - }, SYNC_CONSTANTS.AWARENESS_SYNC_DELAY) - }) - - // 监听 awareness 变化 - provider.awareness.on("change", (changes: any) => { - if (!provider) return - - const awarenessStates = provider.awareness.getStates() - - if (changes.removed?.length > 0) { - checkIfSuperAdminLeft(changes.removed, onStatusChange) - } - - awarenessStates.forEach((state, clientId) => { - if (state.user) { - const normalizedId = normalizeClientId(clientId) - roomUserInfo.set(normalizedId, { - name: state.user.name, - isSuperAdmin: state.user.isSuperAdmin, - }) - } - }) - - checkRoomPermissions(awarenessStates.size, onStatusChange) - }) - - // 配置编辑器扩展 - if (editorView && ytext) { - currentEditorView = editorView - const userColor = userStore.isSuperAdmin - ? SYNC_CONSTANTS.SUPER_ADMIN_COLOR - : SYNC_CONSTANTS.REGULAR_USER_COLOR - const userName = userStore.user?.username || "匿名用户" - const savedContent = editorView.state.doc.toString() - - // 设置用户信息 - provider.awareness.setLocalStateField("user", { - name: userName, - color: userColor, - isSuperAdmin: userStore.isSuperAdmin, - }) - - // 清空编辑器并应用协同扩展 - editorView.dispatch({ - changes: { from: 0, to: editorView.state.doc.length, insert: "" }, - }) - - const collabExt = yCollab(ytext, provider.awareness) - editorView.dispatch({ - effects: collabCompartment.reconfigure(collabExt), - }) - - // 设置内容同步 - setupContentSync(ytext, provider, savedContent) - - // 设置初始状态 - const waitingMessage = userStore.isSuperAdmin - ? SYNC_MESSAGES.WAITING_STUDENT - : SYNC_MESSAGES.WAITING_ADMIN - - updateStatus( - { - connected: true, - roomUsers: 1, - canSync: false, - message: waitingMessage, - }, - onStatusChange, - ) - - message.info(waitingMessage) - lastSyncState = "waiting" - } - - return () => stopSync() - } - - function stopSync() { - if (currentEditorView) { - try { - currentEditorView.dispatch({ - effects: collabCompartment.reconfigure([]), - }) - } catch (error) { - console.warn("移除协同编辑扩展失败:", error) - } - currentEditorView = null - } - - provider?.disconnect() - provider?.destroy() - ydoc?.destroy() - - provider = null - ydoc = null - ytext = null - lastSyncState = null - roomUserInfo.clear() - hasShownSuperAdminLeftMessage = false - } - - function getInitialExtension() { - return collabCompartment.of([]) - } - - return { - startSync, - stopSync, - getInitialExtension, - } -} diff --git a/bun.lock b/bun.lock index a53939a..d28cd3d 100644 --- a/bun.lock +++ b/bun.lock @@ -81,7 +81,6 @@ "vue-router": "^5.2.0", "y-codemirror.next": "^0.3.6", "y-protocols": "1.0.7", - "y-webrtc": "^10.3.0", "yjs": "^13.6.32", }, "devDependencies": { @@ -823,8 +822,6 @@ "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "https://registry.npmjs.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="], - "base64-js": ["base64-js@1.5.1", "https://registry.npmjs.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "https://registry.npmjs.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], "birpc": ["birpc@2.9.0", "https://registry.npmjs.com/birpc/-/birpc-2.9.0.tgz", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], @@ -833,8 +830,6 @@ "browserslist-to-esbuild": ["browserslist-to-esbuild@2.1.1", "https://registry.npmjs.com/browserslist-to-esbuild/-/browserslist-to-esbuild-2.1.1.tgz", { "dependencies": { "meow": "^13.0.0" }, "peerDependencies": { "browserslist": "*" }, "bin": { "browserslist-to-esbuild": "cli/index.js" } }, "sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw=="], - "buffer": ["buffer@6.0.3", "https://registry.npmjs.com/buffer/-/buffer-6.0.3.tgz", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - "buffer-from": ["buffer-from@1.1.2", "https://registry.npmjs.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bullmq": ["bullmq@6.2.2", "https://registry.npmjs.com/bullmq/-/bullmq-6.2.2.tgz", { "dependencies": { "cron-parser": "5.10.0", "msgpackr": "2.0.5", "node-abort-controller": "3.1.1", "semver": "7.8.5", "tslib": "2.8.1" }, "peerDependencies": { "bullmq-otel": ">=2.0.0", "ioredis": ">=5.0.0", "pg": ">=8.0.0", "redis": ">=5.0.0" }, "optionalPeers": ["bullmq-otel", "ioredis", "pg", "redis"] }, "sha512-dwpI14djPYpG15C6Wc7c14/rw8zXXbsGWRgVLR/x8TUi/UBl5+WI6xiBVQH1jnxa3Rdf+T7nylqNui0gysMqQg=="], @@ -993,8 +988,6 @@ "entities": ["entities@4.5.0", "https://registry.npmjs.com/entities/-/entities-4.5.0.tgz", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "err-code": ["err-code@3.0.1", "https://registry.npmjs.com/err-code/-/err-code-3.0.1.tgz", {}, "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="], - "es-define-property": ["es-define-property@1.0.1", "https://registry.npmjs.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "https://registry.npmjs.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], @@ -1047,8 +1040,6 @@ "gensync": ["gensync@1.0.0-beta.2", "https://registry.npmjs.com/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - "get-browser-rtc": ["get-browser-rtc@1.1.0", "https://registry.npmjs.com/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", {}, "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="], - "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmjs.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "https://registry.npmjs.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], @@ -1079,12 +1070,8 @@ "iconv-lite": ["iconv-lite@0.6.3", "https://registry.npmjs.com/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "ieee754": ["ieee754@1.2.1", "https://registry.npmjs.com/ieee754/-/ieee754-1.2.1.tgz", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "import-meta-resolve": ["import-meta-resolve@4.2.0", "https://registry.npmjs.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], - "inherits": ["inherits@2.0.4", "https://registry.npmjs.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "internmap": ["internmap@2.0.3", "https://registry.npmjs.com/internmap/-/internmap-2.0.3.tgz", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], "ioredis": ["ioredis@6.0.0", "https://registry.npmjs.com/ioredis/-/ioredis-6.0.0.tgz", { "dependencies": { "@ioredis/commands": "2.0.0", "cluster-key-slot": "1.1.1", "debug": "4.4.3", "denque": "2.1.0", "redis-errors": "1.2.0", "standard-as-callback": "2.1.0" } }, "sha512-f+Dtubxfpf6KYFq7WVXJoOLn0bk4TJrMrN9SzeE+jrWrCWj7XX3fA6vkryafhADX+GMymRxgDJDOI33COkJc0w=="], @@ -1275,12 +1262,6 @@ "quansync": ["quansync@0.2.11", "https://registry.npmjs.com/quansync/-/quansync-0.2.11.tgz", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], - "queue-microtask": ["queue-microtask@1.2.3", "https://registry.npmjs.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "randombytes": ["randombytes@2.1.0", "https://registry.npmjs.com/randombytes/-/randombytes-2.1.0.tgz", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], - - "readable-stream": ["readable-stream@3.6.2", "https://registry.npmjs.com/readable-stream/-/readable-stream-3.6.2.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "readdirp": ["readdirp@5.1.1", "https://registry.npmjs.com/readdirp/-/readdirp-5.1.1.tgz", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], "redis-errors": ["redis-errors@1.2.0", "https://registry.npmjs.com/redis-errors/-/redis-errors-1.2.0.tgz", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], @@ -1311,8 +1292,6 @@ "rw": ["rw@1.3.3", "https://registry.npmjs.com/rw/-/rw-1.3.3.tgz", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], - "safe-buffer": ["safe-buffer@5.2.1", "https://registry.npmjs.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmjs.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "https://registry.npmjs.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], @@ -1323,8 +1302,6 @@ "semver": ["semver@7.8.5", "https://registry.npmjs.com/semver/-/semver-7.8.5.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - "simple-peer": ["simple-peer@9.11.1", "https://registry.npmjs.com/simple-peer/-/simple-peer-9.11.1.tgz", { "dependencies": { "buffer": "^6.0.3", "debug": "^4.3.2", "err-code": "^3.0.1", "get-browser-rtc": "^1.1.0", "queue-microtask": "^1.2.3", "randombytes": "^2.1.0", "readable-stream": "^3.6.0" } }, "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw=="], - "skulpt": ["skulpt@1.2.0", "https://registry.npmjs.com/skulpt/-/skulpt-1.2.0.tgz", { "dependencies": { "jsbi": "^3.1.4" } }, "sha512-T0cv0sdSOXLlIJTuyXSeYJ3TFdWYSZfX2PcBLpRKrKZ3dTbVQXRMiYudSuko2xzcyMif47DS2ShatCwjCbsSsA=="], "slate": ["slate@0.124.1", "https://registry.npmjs.com/slate/-/slate-0.124.1.tgz", {}, "sha512-ii7DwezgvbLAyKtHBIunjTR1kzbNfYLCUKLMzJELlbTZkvHzX4DzN7HKIwcakf6dPxO6AoeT/P7kHOcyTym/hA=="], @@ -1347,8 +1324,6 @@ "strictdom": ["strictdom@1.0.1", "https://registry.npmjs.com/strictdom/-/strictdom-1.0.1.tgz", {}, "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg=="], - "string_decoder": ["string_decoder@1.3.0", "https://registry.npmjs.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - "strip-literal": ["strip-literal@4.0.0", "https://registry.npmjs.com/strip-literal/-/strip-literal-4.0.0.tgz", { "dependencies": { "js-tokens": "^10.0.0" } }, "sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw=="], "style-mod": ["style-mod@4.1.3", "https://registry.npmjs.com/style-mod/-/style-mod-4.1.3.tgz", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], @@ -1409,8 +1384,6 @@ "update-browserslist-db": ["update-browserslist-db@1.3.0", "https://registry.npmjs.com/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="], - "util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmjs.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "uuid": ["uuid@14.0.1", "https://registry.npmjs.com/uuid/-/uuid-14.0.1.tgz", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="], "vdirs": ["vdirs@0.1.8", "https://registry.npmjs.com/vdirs/-/vdirs-0.1.8.tgz", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw=="], @@ -1445,16 +1418,12 @@ "wildcard": ["wildcard@1.1.2", "https://registry.npmjs.com/wildcard/-/wildcard-1.1.2.tgz", {}, "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="], - "ws": ["ws@8.21.2", "https://registry.npmjs.com/ws/-/ws-8.21.2.tgz", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw=="], - "xss": ["xss@1.0.15", "https://registry.npmjs.com/xss/-/xss-1.0.15.tgz", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], "y-codemirror.next": ["y-codemirror.next@0.3.6", "https://registry.npmjs.com/y-codemirror.next/-/y-codemirror.next-0.3.6.tgz", { "dependencies": { "lib0": "^0.2.42" }, "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "yjs": "^13.5.6" } }, "sha512-GnmVXhTe+UtoFbbaSdwhq6gdAQZdIY9az0Xj6HR2J4PO6Yw4w3xaaSssU+6T72WhXAI0wf9FbuY2s0Ms+WrexA=="], "y-protocols": ["y-protocols@1.0.7", "https://registry.npmjs.com/y-protocols/-/y-protocols-1.0.7.tgz", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="], - "y-webrtc": ["y-webrtc@10.3.0", "https://registry.npmjs.com/y-webrtc/-/y-webrtc-10.3.0.tgz", { "dependencies": { "lib0": "^0.2.42", "simple-peer": "^9.11.0", "y-protocols": "^1.0.6" }, "optionalDependencies": { "ws": "^8.14.2" }, "peerDependencies": { "yjs": "^13.6.8" }, "bin": { "y-webrtc-signaling": "bin/server.js" } }, "sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA=="], - "yallist": ["yallist@3.1.1", "https://registry.npmjs.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yaml": ["yaml@2.9.0", "https://registry.npmjs.com/yaml/-/yaml-2.9.0.tgz", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],