vue-parley 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +276 -0
- package/dist/chat.css +3223 -0
- package/dist/chunks/Call-CTIujxDi.cjs +818 -0
- package/dist/chunks/Call-CTIujxDi.cjs.map +1 -0
- package/dist/chunks/Call-Dwuyd-Ut.mjs +818 -0
- package/dist/chunks/Call-Dwuyd-Ut.mjs.map +1 -0
- package/dist/chunks/index-D50Yn7q4.cjs +7031 -0
- package/dist/chunks/index-D50Yn7q4.cjs.map +1 -0
- package/dist/chunks/index-JU_JZkSC.mjs +7010 -0
- package/dist/chunks/index-JU_JZkSC.mjs.map +1 -0
- package/dist/chunks/session-B3KIrj3_.mjs +711 -0
- package/dist/chunks/session-B3KIrj3_.mjs.map +1 -0
- package/dist/chunks/session-BOfkV8PJ.cjs +711 -0
- package/dist/chunks/session-BOfkV8PJ.cjs.map +1 -0
- package/dist/fakes.cjs +335 -0
- package/dist/fakes.cjs.map +1 -0
- package/dist/fakes.mjs +335 -0
- package/dist/fakes.mjs.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +24 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types/app/components/ChatPage.vue.d.ts +60 -0
- package/dist/types/app/components/ChatPage.vue.d.ts.map +1 -0
- package/dist/types/app/components/call/Call.vue.d.ts +8 -0
- package/dist/types/app/components/call/Call.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/ChatBubble.vue.d.ts +64 -0
- package/dist/types/app/components/chat/ChatBubble.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/ChatInput.vue.d.ts +60 -0
- package/dist/types/app/components/chat/ChatInput.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/ChatMessages.vue.d.ts +45 -0
- package/dist/types/app/components/chat/ChatMessages.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/ChatPageBar.vue.d.ts +50 -0
- package/dist/types/app/components/chat/ChatPageBar.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/ChatProfileOverview.vue.d.ts +11 -0
- package/dist/types/app/components/chat/ChatProfileOverview.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/ChatView.vue.d.ts +244 -0
- package/dist/types/app/components/chat/ChatView.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleDivider.vue.d.ts +7 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleDivider.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleImages.vue.d.ts +13 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleImages.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleMeta.vue.d.ts +11 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleMeta.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleOptions.vue.d.ts +16 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleOptions.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleRequest.vue.d.ts +8 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleRequest.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleSelectionMark.vue.d.ts +7 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleSelectionMark.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleStatus.vue.d.ts +7 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleStatus.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleVideo.vue.d.ts +25 -0
- package/dist/types/app/components/chat/chat-bubbles/BubbleVideo.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/FileDisplay.vue.d.ts +13 -0
- package/dist/types/app/components/chat/chat-bubbles/FileDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/ImageGroupDisplay.vue.d.ts +14 -0
- package/dist/types/app/components/chat/chat-bubbles/ImageGroupDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/ReplyPreview.vue.d.ts +10 -0
- package/dist/types/app/components/chat/chat-bubbles/ReplyPreview.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/UploadProgressOverlay.vue.d.ts +10 -0
- package/dist/types/app/components/chat/chat-bubbles/UploadProgressOverlay.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-bubbles/VoiceDisplay.vue.d.ts +12 -0
- package/dist/types/app/components/chat/chat-bubbles/VoiceDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-input/AttachementFileDisplay.vue.d.ts +12 -0
- package/dist/types/app/components/chat/chat-input/AttachementFileDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-input/InputActionBar.vue.d.ts +13 -0
- package/dist/types/app/components/chat/chat-input/InputActionBar.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-input/InputAttachement.vue.d.ts +18 -0
- package/dist/types/app/components/chat/chat-input/InputAttachement.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-input/PermissionPopup.vue.d.ts +8 -0
- package/dist/types/app/components/chat/chat-input/PermissionPopup.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-input/RecordButton.vue.d.ts +27 -0
- package/dist/types/app/components/chat/chat-input/RecordButton.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-input/RecordingStatus.vue.d.ts +12 -0
- package/dist/types/app/components/chat/chat-input/RecordingStatus.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/chat-input/VideoRecordDisplay.vue.d.ts +26 -0
- package/dist/types/app/components/chat/chat-input/VideoRecordDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/contact/ChatContactDisplay.vue.d.ts +8 -0
- package/dist/types/app/components/chat/contact/ChatContactDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/contact/ChatList.vue.d.ts +5 -0
- package/dist/types/app/components/chat/contact/ChatList.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/contact/ChatListSearch.vue.d.ts +16 -0
- package/dist/types/app/components/chat/contact/ChatListSearch.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/contact/ContactAvatar.vue.d.ts +10 -0
- package/dist/types/app/components/chat/contact/ContactAvatar.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/contact/ContactSkeleton.vue.d.ts +3 -0
- package/dist/types/app/components/chat/contact/ContactSkeleton.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/messages/FloatingDateHeader.vue.d.ts +9 -0
- package/dist/types/app/components/chat/messages/FloatingDateHeader.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/messages/MessagesSkeleton.vue.d.ts +8 -0
- package/dist/types/app/components/chat/messages/MessagesSkeleton.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/messages/ScrollToBottomButton.vue.d.ts +11 -0
- package/dist/types/app/components/chat/messages/ScrollToBottomButton.vue.d.ts.map +1 -0
- package/dist/types/app/components/chat/profile/FileDisplay.vue.d.ts +9 -0
- package/dist/types/app/components/chat/profile/FileDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/ConfirmModal.vue.d.ts +21 -0
- package/dist/types/app/components/general/ConfirmModal.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/FileFormatDisplay.vue.d.ts +16 -0
- package/dist/types/app/components/general/FileFormatDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/IconButton.vue.d.ts +13 -0
- package/dist/types/app/components/general/IconButton.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/LoadingStatus.vue.d.ts +16 -0
- package/dist/types/app/components/general/LoadingStatus.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/MediaImage.vue.d.ts +30 -0
- package/dist/types/app/components/general/MediaImage.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/NoDataDisplay.vue.d.ts +7 -0
- package/dist/types/app/components/general/NoDataDisplay.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/ResponsiveDialog.vue.d.ts +28 -0
- package/dist/types/app/components/general/ResponsiveDialog.vue.d.ts.map +1 -0
- package/dist/types/app/components/general/SafeEmojiText.vue.d.ts +10 -0
- package/dist/types/app/components/general/SafeEmojiText.vue.d.ts.map +1 -0
- package/dist/types/app/components/global/BEmojiPicker.vue.d.ts +8 -0
- package/dist/types/app/components/global/BEmojiPicker.vue.d.ts.map +1 -0
- package/dist/types/app/components/global/BIcon.vue.d.ts +11 -0
- package/dist/types/app/components/global/BIcon.vue.d.ts.map +1 -0
- package/dist/types/app/components/global/BVirtualVerticalList.vue.d.ts +30 -0
- package/dist/types/app/components/global/BVirtualVerticalList.vue.d.ts.map +1 -0
- package/dist/types/app/composables/call/media.d.ts +204 -0
- package/dist/types/app/composables/call/media.d.ts.map +1 -0
- package/dist/types/app/composables/call/peer.d.ts +39 -0
- package/dist/types/app/composables/call/peer.d.ts.map +1 -0
- package/dist/types/app/composables/call/peers.d.ts +54 -0
- package/dist/types/app/composables/call/peers.d.ts.map +1 -0
- package/dist/types/app/composables/call/session.d.ts +284 -0
- package/dist/types/app/composables/call/session.d.ts.map +1 -0
- package/dist/types/app/composables/call/signaling.d.ts +36 -0
- package/dist/types/app/composables/call/signaling.d.ts.map +1 -0
- package/dist/types/app/composables/call/useCallUi.d.ts +27 -0
- package/dist/types/app/composables/call/useCallUi.d.ts.map +1 -0
- package/dist/types/app/composables/chat/useChatRecording.d.ts +75 -0
- package/dist/types/app/composables/chat/useChatRecording.d.ts.map +1 -0
- package/dist/types/app/composables/chat/useInputDraft.d.ts +14 -0
- package/dist/types/app/composables/chat/useInputDraft.d.ts.map +1 -0
- package/dist/types/app/composables/useAppPermissions.d.ts +25 -0
- package/dist/types/app/composables/useAppPermissions.d.ts.map +1 -0
- package/dist/types/app/composables/useAppToast.d.ts +13 -0
- package/dist/types/app/composables/useAppToast.d.ts.map +1 -0
- package/dist/types/app/composables/useAttachmentPicker.d.ts +18 -0
- package/dist/types/app/composables/useAttachmentPicker.d.ts.map +1 -0
- package/dist/types/app/composables/useCall.d.ts +91 -0
- package/dist/types/app/composables/useCall.d.ts.map +1 -0
- package/dist/types/app/composables/useChatMessageList.d.ts +18 -0
- package/dist/types/app/composables/useChatMessageList.d.ts.map +1 -0
- package/dist/types/app/composables/useDate.d.ts +7 -0
- package/dist/types/app/composables/useDate.d.ts.map +1 -0
- package/dist/types/app/composables/useDevice.d.ts +32 -0
- package/dist/types/app/composables/useDevice.d.ts.map +1 -0
- package/dist/types/app/composables/useFlippedVirtualScroll.d.ts +22 -0
- package/dist/types/app/composables/useFlippedVirtualScroll.d.ts.map +1 -0
- package/dist/types/app/composables/useHostI18n.d.ts +15 -0
- package/dist/types/app/composables/useHostI18n.d.ts.map +1 -0
- package/dist/types/app/composables/useLocalI18n.d.ts +5 -0
- package/dist/types/app/composables/useLocalI18n.d.ts.map +1 -0
- package/dist/types/app/composables/useLongPress.d.ts +6 -0
- package/dist/types/app/composables/useLongPress.d.ts.map +1 -0
- package/dist/types/app/composables/useRichTextEditor.d.ts +12 -0
- package/dist/types/app/composables/useRichTextEditor.d.ts.map +1 -0
- package/dist/types/app/directives/loading.d.ts +6 -0
- package/dist/types/app/directives/loading.d.ts.map +1 -0
- package/dist/types/app/index.d.ts +31 -0
- package/dist/types/app/index.d.ts.map +1 -0
- package/dist/types/app/plugin.d.ts +28 -0
- package/dist/types/app/plugin.d.ts.map +1 -0
- package/dist/types/app/provider/callProvider.d.ts +7 -0
- package/dist/types/app/provider/callProvider.d.ts.map +1 -0
- package/dist/types/app/stores/callStore.d.ts +1626 -0
- package/dist/types/app/stores/callStore.d.ts.map +1 -0
- package/dist/types/app/stores/chatStore.d.ts +126 -0
- package/dist/types/app/stores/chatStore.d.ts.map +1 -0
- package/dist/types/app/stores/mediaStore.d.ts +18 -0
- package/dist/types/app/stores/mediaStore.d.ts.map +1 -0
- package/dist/types/app/stores/messageStores.d.ts +2460 -0
- package/dist/types/app/stores/messageStores.d.ts.map +1 -0
- package/dist/types/app/stores/profileStore.d.ts +102 -0
- package/dist/types/app/stores/profileStore.d.ts.map +1 -0
- package/dist/types/app/types/components/chat-input.d.ts +4 -0
- package/dist/types/app/types/components/chat-input.d.ts.map +1 -0
- package/dist/types/app/types/components/menu-options.d.ts +7 -0
- package/dist/types/app/types/components/menu-options.d.ts.map +1 -0
- package/dist/types/app/types/components/modal.d.ts +17 -0
- package/dist/types/app/types/components/modal.d.ts.map +1 -0
- package/dist/types/app/types/index.d.ts +257 -0
- package/dist/types/app/types/index.d.ts.map +1 -0
- package/dist/types/app/utils/emojiParser.d.ts +5 -0
- package/dist/types/app/utils/emojiParser.d.ts.map +1 -0
- package/dist/types/app/utils/format.d.ts +4 -0
- package/dist/types/app/utils/format.d.ts.map +1 -0
- package/dist/types/fakes/backend.d.ts +25 -0
- package/dist/types/fakes/backend.d.ts.map +1 -0
- package/dist/types/fakes/call.d.ts +7 -0
- package/dist/types/fakes/call.d.ts.map +1 -0
- package/dist/types/fakes/data.d.ts +13 -0
- package/dist/types/fakes/data.d.ts.map +1 -0
- package/dist/types/fakes/index.d.ts +7 -0
- package/dist/types/fakes/index.d.ts.map +1 -0
- package/dist/types/fakes.d.ts +2 -0
- package/dist/types/i18n/locales/index.d.ts +3557 -0
- package/dist/types/i18n/locales/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +98 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-BOfkV8PJ.cjs","sources":["../../app/composables/call/signaling.ts","../../app/composables/call/peer.ts","../../app/composables/call/peers.ts","../../app/composables/call/media.ts","../../app/composables/call/session.ts"],"sourcesContent":["import type {\n CallHandlers,\n CallMessageSchema,\n SignalData,\n TrackType,\n} from \"~/types\";\nimport { CallMessageType } from \"~/types\";\n\nexport type TrackTypes = { id: string; type: TrackType }[];\n\nexport interface SignalingHandlers {\n /** An SDP offer/answer or ICE candidate addressed to us. */\n onSignal(from: string, name: string, signal: SignalData & { sdp: string }): void;\n /** Someone entered the call. */\n onJoin(from: string, name: string): void;\n /** Someone described which of their streams are camera, screen, … */\n onTrackTypes(from: string, types: TrackTypes): void;\n /** Someone left the call. */\n onHangup(from: string): void;\n}\n\n/**\n * The call's messages over the host's pub/sub (`CallHandlers`), typed. Knows nothing about\n * WebRTC: it only turns calls into messages and messages into callbacks.\n */\nexport function createSignaling(\n handlers: Pick<CallHandlers, \"publisher\" | \"subscriber\" | \"unSubscriber\">,\n self: { id: string; name: () => string },\n) {\n let subscription: number | undefined;\n\n const publish = (message: CallMessageSchema) =>\n handlers.publisher(JSON.stringify(message));\n\n async function listen(on: SignalingHandlers) {\n subscription = await handlers.subscriber(async (message) => {\n const from = message.payload.from;\n if (from === self.id) return;\n\n switch (message.type) {\n case CallMessageType.Signal:\n if (message.payload.to === self.id && message.payload.signal)\n on.onSignal(from, message.payload.name, message.payload.signal);\n break;\n case CallMessageType.Join:\n on.onJoin(from, message.payload.name);\n break;\n case CallMessageType.TrackType:\n on.onTrackTypes(from, message.payload.types);\n break;\n case CallMessageType.Hangup:\n on.onHangup(from);\n break;\n }\n });\n }\n\n function stop() {\n if (subscription !== undefined) void handlers.unSubscriber(subscription);\n subscription = undefined;\n }\n\n return {\n listen,\n stop,\n join: () =>\n publish({\n type: CallMessageType.Join,\n payload: { from: self.id, name: self.name() },\n }),\n trackTypes: (types: TrackTypes) =>\n publish({\n type: CallMessageType.TrackType,\n payload: { from: self.id, types },\n }),\n signal: (to: string, signal: SignalData) =>\n publish({\n type: CallMessageType.Signal,\n payload: {\n from: self.id,\n to,\n name: self.name(),\n signal: signal as SignalData & { sdp: string },\n },\n }),\n /** Rings the other side of `channel`. */\n ring: (channel: string, avatar?: string) =>\n publish({\n type: CallMessageType.Call,\n payload: { from: self.id, name: self.name(), channel, avatar: avatar ?? \"\" },\n }),\n hangup: (channel: string) =>\n publish({\n type: CallMessageType.Hangup,\n payload: { from: self.id, channel },\n }),\n };\n}\n\nexport type Signaling = ReturnType<typeof createSignaling>;\n","import type { SignalData } from \"~/types\";\n\n/** How long to wait for ICE gathering before sending a description anyway (as simple-peer). */\nconst ICE_COMPLETE_TIMEOUT_MS = 5_000;\n\nexport interface PeerEvents {\n /** A message for the other side, to send over signalling. */\n onSignal(signal: SignalData): void;\n /** A new remote stream (once per stream, with its tracks already added). */\n onStream(stream: MediaStream): void;\n /** The connection is gone, from either side. Fires once. */\n onClose(): void;\n onError(error: Error): void;\n}\n\nexport interface PeerOptions extends PeerEvents {\n /** Whether this side makes the offers. Exactly one side of a pair must be the initiator. */\n initiator: boolean;\n stream: MediaStream;\n config: RTCConfiguration;\n}\n\n// Without trickle ICE, the candidates travel inside the SDP; don't advertise trickle support.\nconst withoutTrickle = (sdp: string) => sdp.replace(/a=ice-options:trickle\\s\\n/g, \"\");\n\nconst randomLabel = () =>\n Array.from(crypto.getRandomValues(new Uint8Array(20)), (b) =>\n b.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n\n/**\n * One WebRTC connection to one remote participant, on the plain browser API.\n *\n * It speaks simple-peer's wire protocol (with `trickle: false`), so it interoperates with clients\n * still running the previous, simple-peer based build:\n * - full offers/answers with the ICE candidates inside the SDP;\n * - only the initiator makes offers; the other side asks with `{ type: \"renegotiate\" }` and, for\n * tracks the offer had no room for, `{ type: \"transceiverRequest\" }`, so offers never collide;\n * - a data channel whose closing tells either side that the other one left.\n */\nexport function createPeer(opts: PeerOptions) {\n const { initiator } = opts;\n const pc = new RTCPeerConnection(opts.config);\n\n let destroyed = false;\n let negotiating = false;\n let queuedNegotiation = false;\n let batched = false;\n let firstNegotiation = true;\n let channel: RTCDataChannel | null = null;\n const pendingCandidates: RTCIceCandidateInit[] = [];\n const knownStreams = new Set<string>();\n const requestedTransceivers = new WeakSet<RTCRtpTransceiver>();\n\n function destroy(error?: Error) {\n if (destroyed) return;\n destroyed = true;\n try {\n channel?.close();\n } catch {\n // already closed\n }\n pc.ontrack = pc.ondatachannel = pc.onconnectionstatechange = pc.onsignalingstatechange = null;\n pc.close();\n if (error) opts.onError(error);\n opts.onClose();\n }\n\n const fail = (code: string) => (err: unknown) =>\n destroy(Object.assign(err instanceof Error ? err : new Error(String(err)), { code }));\n\n function setupChannel(dc: RTCDataChannel) {\n channel = dc;\n // The other side destroying its connection closes the channel: that is how a leave is seen\n // without waiting for ICE to time out.\n dc.onclose = () => destroy();\n }\n\n function waitForIce() {\n if (pc.iceGatheringState === \"complete\") return Promise.resolve();\n return new Promise<void>((resolve) => {\n const timer = setTimeout(done, ICE_COMPLETE_TIMEOUT_MS);\n function done() {\n clearTimeout(timer);\n pc.removeEventListener(\"icegatheringstatechange\", check);\n resolve();\n }\n function check() {\n if (pc.iceGatheringState === \"complete\") done();\n }\n pc.addEventListener(\"icegatheringstatechange\", check);\n });\n }\n\n /** Sends our local description once its candidates are in. */\n async function sendDescription() {\n await waitForIce();\n if (destroyed || !pc.localDescription) return;\n opts.onSignal({ type: pc.localDescription.type, sdp: pc.localDescription.sdp });\n }\n\n async function createOffer() {\n if (destroyed) return;\n try {\n const offer = await pc.createOffer();\n await pc.setLocalDescription({ type: \"offer\", sdp: withoutTrickle(offer.sdp ?? \"\") });\n await sendDescription();\n } catch (err) {\n fail(\"ERR_CREATE_OFFER\")(err);\n }\n }\n\n async function createAnswer() {\n if (destroyed) return;\n try {\n const answer = await pc.createAnswer();\n await pc.setLocalDescription({ type: \"answer\", sdp: withoutTrickle(answer.sdp ?? \"\") });\n await sendDescription();\n if (!initiator) requestMissingTransceivers();\n } catch (err) {\n fail(\"ERR_CREATE_ANSWER\")(err);\n }\n }\n\n /** Our tracks the initiator's offer had no m-line for: ask it to add one. */\n function requestMissingTransceivers() {\n for (const transceiver of pc.getTransceivers()) {\n if (transceiver.mid || !transceiver.sender.track || requestedTransceivers.has(transceiver))\n continue;\n requestedTransceivers.add(transceiver);\n opts.onSignal({\n type: \"transceiverRequest\",\n transceiverRequest: { kind: transceiver.sender.track.kind },\n });\n }\n }\n\n function negotiate() {\n if (destroyed) return;\n if (negotiating) {\n queuedNegotiation = true;\n return;\n }\n negotiating = true;\n if (initiator) setTimeout(() => void createOffer(), 0);\n else opts.onSignal({ type: \"renegotiate\", renegotiate: true });\n }\n\n /** Batches changes made in one tick into a single negotiation. */\n function needsNegotiation() {\n if (batched) return;\n batched = true;\n queueMicrotask(() => {\n batched = false;\n // The first negotiation is the initiator's offer; the other side just answers it.\n if (initiator || !firstNegotiation) negotiate();\n firstNegotiation = false;\n });\n }\n\n pc.onsignalingstatechange = () => {\n if (destroyed || pc.signalingState !== \"stable\") return;\n negotiating = false;\n if (queuedNegotiation) {\n queuedNegotiation = false;\n needsNegotiation();\n }\n };\n\n pc.onconnectionstatechange = () => {\n if (pc.connectionState === \"failed\") fail(\"ERR_CONNECTION_FAILURE\")(new Error(\"Connection failed.\"));\n };\n\n pc.ontrack = (event) => {\n for (const stream of event.streams) {\n // One event per stream, even though each of its tracks arrives separately.\n if (knownStreams.has(stream.id)) continue;\n knownStreams.add(stream.id);\n queueMicrotask(() => !destroyed && opts.onStream(stream));\n }\n };\n\n if (initiator) setupChannel(pc.createDataChannel(randomLabel()));\n else pc.ondatachannel = (event) => setupChannel(event.channel);\n\n opts.stream.getTracks().forEach((track) => pc.addTrack(track, opts.stream));\n needsNegotiation();\n\n return {\n /** Applies a message from the other side. */\n signal(data: SignalData) {\n if (destroyed) return;\n if (data.renegotiate && initiator) needsNegotiation();\n if (data.transceiverRequest && initiator) {\n const { kind, init } = data.transceiverRequest;\n pc.addTransceiver(kind, init);\n needsNegotiation();\n }\n if (data.candidate) {\n if (pc.remoteDescription?.type) void pc.addIceCandidate(data.candidate).catch(() => {});\n else pendingCandidates.push(data.candidate);\n }\n if (data.sdp) {\n const type = data.type as RTCSdpType;\n pc.setRemoteDescription({ type, sdp: data.sdp })\n .then(() => {\n if (destroyed) return;\n pendingCandidates.splice(0).forEach((c) => void pc.addIceCandidate(c).catch(() => {}));\n if (type === \"offer\") void createAnswer();\n })\n .catch(fail(\"ERR_SET_REMOTE_DESCRIPTION\"));\n }\n },\n\n addTrack(track: MediaStreamTrack, stream: MediaStream) {\n if (destroyed) return;\n pc.addTrack(track, stream);\n needsNegotiation();\n },\n\n removeTrack(track: MediaStreamTrack) {\n if (destroyed) return;\n const sender = pc.getSenders().find((s) => s.track === track);\n if (!sender) return;\n pc.removeTrack(sender);\n needsNegotiation();\n },\n\n /** Swaps the outgoing video track in place (no renegotiation), or adds it if none is sent. */\n replaceVideoTrack(track: MediaStreamTrack, stream: MediaStream) {\n if (destroyed) return;\n const sender = pc.getSenders().find((s) => s.track?.kind === \"video\");\n if (sender) void sender.replaceTrack(track);\n else this.addTrack(track, stream);\n },\n\n /** Whether we currently send this track. */\n isSending(track: MediaStreamTrack) {\n return pc.getSenders().some((s) => s.track === track);\n },\n\n destroy: () => destroy(),\n };\n}\n\nexport type PeerConnection = ReturnType<typeof createPeer>;\n","import { markRaw, ref, watch, type Ref } from \"vue\";\nimport type { SignalData } from \"~/types\";\nimport { createPeer, type PeerConnection } from \"./peer\";\nimport type { Signaling, TrackTypes } from \"./signaling\";\n\nexport interface RemoteStream {\n name: string;\n stream: MediaStream;\n}\n\nexport interface PeerOptions {\n selfId: string;\n signaling: Signaling;\n localStream: Ref<MediaStream | null>;\n screenStream: Ref<MediaStream | null>;\n /** ICE servers and policy for each new connection, or null when none are usable yet. */\n rtcConfig: () => RTCConfiguration | null;\n log: (...args: unknown[]) => void;\n}\n\n/**\n * One connection per remote participant, plus the streams they send us.\n *\n * Who makes the offer is decided by comparing ids, so both sides agree without a round trip.\n * A peer can't be created before our own media exists; such requests wait until it does.\n */\nexport function usePeers(opts: PeerOptions) {\n const { selfId, signaling, localStream, screenStream, log } = opts;\n\n const peers = ref<Record<string, PeerConnection>>({});\n const remoteVideos = ref<Record<string, RemoteStream>>({});\n const remoteScreens = ref<Record<string, RemoteStream>>({});\n const remoteStreamTypes = ref<Record<string, TrackTypes>>({});\n /** Peers asked for before our media was ready. */\n const pending = ref<Record<string, { name: string }>>({});\n /** Signals that arrived for a peer that doesn't exist yet. */\n const pendingSignals: Record<string, SignalData> = {};\n /** Last SDP applied per peer, so a re-delivered message isn't applied twice. */\n const lastSdp: Record<string, string | undefined> = {};\n\n const isInitiator = (remoteId: string) => selfId > remoteId;\n const has = (remoteId: string) => !!peers.value[remoteId] || !!pending.value[remoteId];\n\n function remove(remoteId: string) {\n const peer = peers.value[remoteId];\n delete peers.value[remoteId];\n peer?.destroy();\n delete remoteVideos.value[remoteId];\n delete remoteScreens.value[remoteId];\n delete pending.value[remoteId];\n delete pendingSignals[remoteId];\n delete lastSdp[remoteId];\n }\n\n /** Whether `stream` is the remote's screen share, going by what they announced. */\n function isScreen(remoteId: string, stream: MediaStream) {\n const announced = remoteStreamTypes.value[remoteId] ?? [];\n const match = announced.find((t) => t.id === stream.id);\n if (match) return match.type === \"screen\";\n // Stream ids didn't survive the trip: fall back to \"they announced a screen\".\n return announced.some((t) => t.type === \"screen\");\n }\n\n function onRemoteStream(remoteId: string, name: string, stream: MediaStream) {\n log(\"stream from\", remoteId, stream.getTracks().map((t) => t.kind));\n const target =\n isScreen(remoteId, stream) && stream.getVideoTracks().length > 0\n ? remoteScreens\n : remoteVideos;\n target.value[remoteId] = { name, stream };\n\n // A stopped screen share leaves an empty stream behind; drop its tile.\n stream.addEventListener(\"removetrack\", () => {\n if (stream.getTracks().length > 0) return;\n if (target.value[remoteId]?.stream === stream) delete target.value[remoteId];\n });\n }\n\n function add(remoteId: string, name: string) {\n if (peers.value[remoteId]) return;\n\n if (!localStream.value) {\n log(\"local media not ready, deferring peer\", remoteId);\n pending.value[remoteId] = { name };\n return;\n }\n\n const config = opts.rtcConfig();\n if (!config) {\n log(\"no ICE servers available, cannot create peer for\", remoteId);\n return;\n }\n\n const initiator = isInitiator(remoteId);\n let peer: PeerConnection;\n try {\n peer = createPeer({\n initiator,\n stream: localStream.value,\n config,\n onSignal: (signal) => void signaling.signal(remoteId, signal),\n onStream: (stream) => onRemoteStream(remoteId, name, stream),\n onError(err) {\n console.error(\"[vue-chat] Peer error for\", remoteId, err);\n // Announce ourselves again so the pair can start over.\n void signaling.join();\n },\n onClose() {\n log(\"peer closed\", remoteId);\n // Only if it is still the current one: a replaced peer closing must not remove its successor.\n if (peers.value[remoteId] === peer) remove(remoteId);\n },\n });\n } catch (err) {\n console.error(\"[vue-chat] Could not create peer connection:\", err);\n return;\n }\n log(\"created peer\", remoteId, \"initiator:\", initiator);\n\n const screen = screenStream.value;\n screen?.getVideoTracks().forEach((track) => peer.addTrack(track, screen));\n\n peers.value[remoteId] = markRaw(peer);\n\n const queued = pendingSignals[remoteId];\n if (queued) {\n delete pendingSignals[remoteId];\n peer.signal(queued);\n }\n }\n\n /** Applies a signal from `remoteId`, creating the peer first if needed. */\n function signal(remoteId: string, name: string, data: SignalData & { sdp: string }) {\n if (data.sdp && lastSdp[remoteId] === data.sdp) return;\n if (!peers.value[remoteId]) add(remoteId, name);\n\n const peer = peers.value[remoteId];\n if (peer) {\n if (data.sdp) lastSdp[remoteId] = data.sdp;\n peer.signal(data);\n } else {\n log(\"peer still deferred, queueing signal for\", remoteId);\n pendingSignals[remoteId] = data;\n }\n }\n\n /** Sends a track to every connected peer, or takes it back. */\n function addTrackToAll(track: MediaStreamTrack, stream: MediaStream) {\n Object.values(peers.value).forEach((peer) => peer.addTrack(track, stream));\n }\n\n function removeTrackFromAll(track: MediaStreamTrack) {\n Object.values(peers.value).forEach((peer) => peer.removeTrack(track));\n }\n\n /** Swaps the outgoing camera track on every peer (after switching cameras). */\n function replaceVideoTrack(track: MediaStreamTrack, stream: MediaStream) {\n Object.values(peers.value).forEach((peer) => peer.replaceVideoTrack(track, stream));\n }\n\n function destroyAll() {\n const all = Object.values(peers.value);\n peers.value = {};\n all.forEach((peer) => peer.destroy());\n }\n\n // Create the peers that were waiting for our media.\n watch(\n localStream,\n (stream) => {\n if (!stream) return;\n const waiting = { ...pending.value };\n pending.value = {};\n for (const [remoteId, { name }] of Object.entries(waiting)) add(remoteId, name);\n },\n { immediate: true },\n );\n\n return {\n peers,\n remoteVideos,\n remoteScreens,\n remoteStreamTypes,\n isInitiator,\n has,\n add,\n remove,\n signal,\n addTrackToAll,\n removeTrackFromAll,\n replaceVideoTrack,\n destroyAll,\n };\n}\n","import { computed, ref } from \"vue\";\nimport type { TrackTypes } from \"./signaling\";\n\nexport interface MediaOptions {\n /** Shows an error; `key` is a `chat.call.errors.*` translation key. */\n notify: (key: string) => void;\n isMobile: () => boolean;\n}\n\n/** Why getUserMedia/getDisplayMedia failed, as a translation key. */\nfunction deviceError(err: unknown, kind: \"mic\" | \"camera\" | \"screen\", isMobile: boolean) {\n const name = (err as { name?: string })?.name;\n if (kind === \"screen\") {\n if (name === \"NotAllowedError\") return \"screenShareDenied\";\n if (name === \"NotFoundError\" || name === \"NotReadableError\") return \"screenAccessError\";\n return isMobile ? \"screenShareMobileMaybeUnavailable\" : \"screenShareStartFailed\";\n }\n const Kind = kind === \"mic\" ? \"Mic\" : \"Camera\";\n if (name === \"NotAllowedError\") return `${kind}Denied`;\n if (name === \"NotFoundError\") return `no${Kind}Found`;\n return `${kind}AccessError`;\n}\n\n/** This participant's camera, microphone and screen share. */\nexport function useCallMedia({ notify, isMobile }: MediaOptions) {\n const error = (key: string) => notify(`chat.call.errors.${key}`);\n\n const localStream = ref<MediaStream | null>(null);\n const screenStream = ref<MediaStream | null>(null);\n /** What each of our streams is, as announced to the others. */\n const trackTypes = ref<TrackTypes>([]);\n const videoTrack = ref<MediaStreamTrack | null>(null);\n const cameras = ref<MediaDeviceInfo[]>([]);\n\n const isAudioOn = ref(true);\n const isVideoOn = ref(true);\n const isScreenSharing = ref(false);\n const isFlashlightOn = ref(false);\n\n const permissions = ref<{\n camera: PermissionState | null;\n microphone: PermissionState | null;\n }>({ camera: null, microphone: null });\n\n const isFlashlightSupported = computed(() => {\n const capabilities = videoTrack.value?.getCapabilities?.() as\n | Record<string, unknown>\n | undefined;\n return !!capabilities && \"torch\" in capabilities;\n });\n\n async function checkPermissions() {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n cameras.value = devices.filter((d) => d.kind === \"videoinput\");\n if (!devices.some((d) => d.kind === \"audioinput\")) {\n error(\"noMicDevice\");\n isAudioOn.value = false;\n }\n if (!cameras.value.length) {\n error(\"noCameraDevice\");\n isVideoOn.value = false;\n }\n\n const query = (name: string) =>\n navigator.permissions.query({ name: name as PermissionName });\n permissions.value.camera = (await query(\"camera\")).state;\n permissions.value.microphone = (await query(\"microphone\")).state;\n if (permissions.value.camera === \"denied\") isVideoOn.value = false;\n if (permissions.value.microphone === \"denied\") isAudioOn.value = false;\n } catch (err) {\n console.error(\"[vue-chat] Error checking permissions:\", err);\n }\n }\n\n /** Gets camera and mic together, or whichever of the two is available. */\n async function start() {\n const { camera, microphone } = permissions.value;\n if (camera === \"denied\" || microphone === \"denied\") {\n if (camera === \"denied\") error(\"cameraDenied\");\n if (microphone === \"denied\") error(\"micDenied\");\n return;\n }\n\n try {\n localStream.value = await navigator.mediaDevices.getUserMedia({\n video: true,\n audio: true,\n });\n trackTypes.value = [\n ...trackTypes.value,\n { id: localStream.value.id, type: \"webcam_audio\" },\n ];\n } catch {\n const tracks: MediaStreamTrack[] = [];\n try {\n const audio = await navigator.mediaDevices.getUserMedia({ audio: true });\n trackTypes.value = [...trackTypes.value, { id: audio.id, type: \"audio\" }];\n tracks.push(...audio.getAudioTracks());\n } catch (err) {\n console.error(\"[vue-chat] Error accessing microphone:\", err);\n error(deviceError(err, \"mic\", isMobile()));\n isAudioOn.value = false;\n }\n try {\n const video = await navigator.mediaDevices.getUserMedia({ video: true });\n trackTypes.value = [...trackTypes.value, { id: video.id, type: \"webcam\" }];\n tracks.push(...video.getVideoTracks());\n } catch (err) {\n console.error(\"[vue-chat] Error accessing camera:\", err);\n error(deviceError(err, \"camera\", isMobile()));\n isVideoOn.value = false;\n }\n if (tracks.length) localStream.value = new MediaStream(tracks);\n }\n\n videoTrack.value = localStream.value?.getVideoTracks()[0] ?? null;\n }\n\n function toggleAudio() {\n isAudioOn.value = !isAudioOn.value;\n const track = localStream.value?.getAudioTracks()[0];\n if (track) track.enabled = isAudioOn.value;\n }\n\n function toggleVideo() {\n isVideoOn.value = !isVideoOn.value;\n const track = localStream.value?.getVideoTracks()[0];\n if (track) track.enabled = isVideoOn.value;\n }\n\n /** Swaps in another camera; returns the new track so peers can be updated. */\n async function switchCamera(deviceId: string) {\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { deviceId: { exact: deviceId } },\n audio: isAudioOn.value,\n });\n const track = stream.getVideoTracks()[0];\n if (!track || !localStream.value) return null;\n\n const old = localStream.value.getVideoTracks()[0];\n if (old) {\n localStream.value.removeTrack(old);\n old.stop();\n }\n localStream.value.addTrack(track);\n videoTrack.value = track;\n return track;\n } catch (err) {\n console.error(\"[vue-chat] Error switching camera:\", err);\n return null;\n }\n }\n\n /** Starts sharing the screen; returns the new stream, or null if there is none. */\n async function startScreenShare(onEnded: () => void) {\n if (screenStream.value) {\n // Resume a paused share.\n screenStream.value.getVideoTracks()[0]!.enabled = true;\n isScreenSharing.value = true;\n return null;\n }\n if (!navigator.mediaDevices?.getDisplayMedia) {\n error(\n isMobile()\n ? \"screenShareMobileUnsupported\"\n : \"screenShareBrowserUnsupported\",\n );\n return null;\n }\n\n try {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: true,\n audio: false,\n });\n screenStream.value = stream;\n trackTypes.value = [...trackTypes.value, { id: stream.id, type: \"screen\" }];\n stream.getTracks().forEach((track) => (track.onended = onEnded));\n isScreenSharing.value = true;\n return stream;\n } catch (err) {\n console.error(\"[vue-chat] Screen share failed:\", err);\n error(deviceError(err, \"screen\", isMobile()));\n isScreenSharing.value = false;\n return null;\n }\n }\n\n /** Stops sharing; returns the stream that was shared so peers can drop it. */\n function stopScreenShare() {\n const stream = screenStream.value;\n if (!stream) return null;\n stream.getTracks().forEach((track) => {\n track.stop();\n track.enabled = false;\n });\n trackTypes.value = trackTypes.value.filter((t) => t.type !== \"screen\");\n screenStream.value = null;\n isScreenSharing.value = false;\n return stream;\n }\n\n async function toggleFlashlight() {\n const track = videoTrack.value;\n if (!track) {\n error(\"cameraUnavailable\");\n return;\n }\n if (!isFlashlightSupported.value) return;\n try {\n await track.applyConstraints({\n advanced: [{ torch: !isFlashlightOn.value } as MediaTrackConstraintSet],\n });\n isFlashlightOn.value = !isFlashlightOn.value;\n } catch (err) {\n console.error(\"[vue-chat] Flashlight toggle failed:\", err);\n error(\"flashlightToggleFailed\");\n }\n }\n\n /** Releases camera, mic and screen. */\n function stopAll() {\n if (isFlashlightOn.value && videoTrack.value) {\n void videoTrack.value\n .applyConstraints({ advanced: [{ torch: false } as MediaTrackConstraintSet] })\n .catch(() => {});\n }\n for (const stream of [localStream.value, screenStream.value]) {\n stream?.getTracks().forEach((track) => {\n track.stop();\n track.enabled = false;\n });\n }\n localStream.value = null;\n screenStream.value = null;\n videoTrack.value = null;\n }\n\n return {\n localStream,\n screenStream,\n trackTypes,\n videoTrack,\n cameras,\n isAudioOn,\n isVideoOn,\n isScreenSharing,\n isFlashlightOn,\n isFlashlightSupported,\n checkPermissions,\n start,\n toggleAudio,\n toggleVideo,\n switchCamera,\n startScreenShare,\n stopScreenShare,\n toggleFlashlight,\n stopAll,\n };\n}\n","import { computed, effectScope } from \"vue\";\nimport { nanoid } from \"nanoid\";\nimport type { CallHandlers } from \"~/types\";\nimport { createSignaling } from \"./signaling\";\nimport { usePeers } from \"./peers\";\nimport { useCallMedia } from \"./media\";\n\nexport interface CallSessionOptions {\n handlers: CallHandlers;\n channel: string;\n self: { id: string; name: () => string; avatar: () => Blob | undefined };\n /** Shows an error; `key` is a translation key. */\n notify: (key: string) => void;\n}\n\nconst toIceUrl = (url: string) =>\n url.startsWith(\"stun:\") || url.startsWith(\"turn:\") ? url : `turn:${url}`;\n\nconst isMobile = () =>\n /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||\n !!window.matchMedia?.(\"(max-width: 768px)\").matches;\n\n/**\n * One call, from joining to hanging up: signalling, peer connections and local media. It lives in\n * the call store, not in a component, so the call carries on while no call view is mounted.\n */\nexport function createCallSession(opts: CallSessionOptions) {\n const { handlers, channel } = opts;\n // Watchers inside (e.g. deferred peers) belong to the call, not to whichever component\n // happened to start it.\n const scope = effectScope(true);\n\n const log = handlers.debug\n ? (...args: unknown[]) => console.log(\"[vue-chat:call]\", ...args)\n : () => {};\n\n // A fresh id per call, so rejoining never collides with our previous, stale peers.\n const selfId = `${opts.self.id}:${nanoid()}`;\n\n const session = scope.run(() => {\n const signaling = createSignaling(handlers, { id: selfId, name: opts.self.name });\n const media = useCallMedia({ notify: opts.notify, isMobile });\n const peers = usePeers({\n selfId,\n signaling,\n localStream: media.localStream,\n screenStream: media.screenStream,\n log,\n rtcConfig() {\n const policy = handlers.iceTransportPolicy ?? \"relay\";\n const cred = handlers.credential;\n const iceServers: RTCIceServer[] = cred?.urls?.length\n ? [{ urls: cred.urls.map(toIceUrl), username: cred.user, credential: cred.pass }]\n : [];\n // Relay-only without a relay can never connect.\n if (policy === \"relay\" && !iceServers.length) return null;\n return { iceTransportPolicy: policy, iceServers };\n },\n });\n\n const participantCount = computed(() => Object.keys(peers.peers.value).length + 1);\n const tileCount = computed(\n () =>\n (media.screenStream.value ? 1 : 0) +\n Object.keys(peers.remoteVideos.value).length +\n Object.keys(peers.remoteScreens.value).length,\n );\n\n return { signaling, media, peers, participantCount, tileCount };\n })!;\n const { signaling, media, peers } = session;\n\n let ended = false;\n\n async function ringOtherSide() {\n const blob = opts.self.avatar();\n let avatar: string | undefined;\n if (blob) {\n const bytes = new Uint8Array(await blob.arrayBuffer());\n avatar = `data:${blob.type};base64,${btoa(String.fromCharCode(...bytes))}`;\n }\n await signaling.ring(channel, avatar);\n }\n\n async function start() {\n await signaling.listen({\n onSignal: (from, name, signal) => peers.signal(from, name, signal),\n onJoin(from, name) {\n log(\"join from\", from, name);\n void signaling.trackTypes(media.trackTypes.value);\n const isNew = !peers.has(from);\n peers.add(from, name);\n // Only whoever is already in the call hears a newcomer's join. If the newcomer has to\n // make the offer, it doesn't know about us yet, so announce ourselves back to it.\n if (isNew && !peers.isInitiator(from)) void signaling.join();\n },\n onTrackTypes(from, types) {\n peers.remoteStreamTypes.value[from] = types;\n },\n onHangup(from) {\n log(\"hangup from\", from);\n peers.remove(from);\n },\n });\n\n await handlers.handleGenerateCred();\n await media.checkPermissions();\n await media.start();\n // Hung up while the camera prompt was open.\n if (ended) return media.stopAll();\n log(\"local media ready:\", media.localStream.value?.getTracks().map((t) => t.kind));\n\n void signaling.trackTypes(media.trackTypes.value);\n void ringOtherSide();\n void signaling.join();\n }\n\n function stopScreenShare() {\n const stream = media.stopScreenShare();\n stream?.getVideoTracks().forEach((track) => peers.removeTrackFromAll(track));\n }\n\n async function toggleScreenShare() {\n if (media.isScreenSharing.value) return stopScreenShare();\n const stream = await media.startScreenShare(stopScreenShare);\n if (!stream) return;\n // Tell the others this stream is a screen before it reaches them.\n await signaling.trackTypes(media.trackTypes.value);\n stream.getVideoTracks().forEach((track) => peers.addTrackToAll(track, stream));\n }\n\n /** Swaps cameras; returns the local stream so a view can refresh its preview. */\n async function switchCamera(deviceId: string) {\n const track = await media.switchCamera(deviceId);\n const stream = media.localStream.value;\n if (track && stream) peers.replaceVideoTrack(track, stream);\n return stream;\n }\n\n /** Leaves the call: tells the others, releases camera and mic, closes every connection. */\n function end() {\n if (ended) return;\n ended = true;\n void signaling.hangup(channel);\n signaling.stop();\n peers.destroyAll();\n media.stopAll();\n scope.stop();\n }\n\n return {\n ...session,\n channel,\n start,\n end,\n toggleScreenShare,\n switchCamera,\n };\n}\n\nexport type CallSession = ReturnType<typeof createCallSession>;\n"],"names":["CallMessageType","ref","signal","markRaw","watch","isMobile","computed","effectScope","nanoid","signaling","media","peers"],"mappings":"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"}
|
package/dist/fakes.cjs
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const toJson = (value) => JSON.parse(
|
|
4
|
+
JSON.stringify(
|
|
5
|
+
value,
|
|
6
|
+
(key, val) => ["prevMessage", "nextMessage", "contact"].includes(key) ? void 0 : val
|
|
7
|
+
)
|
|
8
|
+
);
|
|
9
|
+
const clone = (value) => structuredClone(value);
|
|
10
|
+
function createFakeBackend(data, { latency = 50 } = {}) {
|
|
11
|
+
const { contacts, messages: db } = data;
|
|
12
|
+
const state = {
|
|
13
|
+
failNextSend: false,
|
|
14
|
+
failNextDelete: false,
|
|
15
|
+
failNextEdit: false,
|
|
16
|
+
calls: []
|
|
17
|
+
};
|
|
18
|
+
const log = (fn, ...args) => state.calls.push({ fn, args: toJson(args) });
|
|
19
|
+
const delay = (ms = latency) => new Promise((r) => setTimeout(r, ms));
|
|
20
|
+
const consume = (flag) => {
|
|
21
|
+
const armed = state[flag];
|
|
22
|
+
state[flag] = false;
|
|
23
|
+
return armed;
|
|
24
|
+
};
|
|
25
|
+
let serverId = 0;
|
|
26
|
+
const chat = {
|
|
27
|
+
async fetchConversations(params) {
|
|
28
|
+
log("fetchConversations", params);
|
|
29
|
+
await delay();
|
|
30
|
+
const q = (params.search ?? "").trim().toLowerCase();
|
|
31
|
+
const filtered = contacts.filter((c) => {
|
|
32
|
+
if (q && !`${c.name} ${c.lastName}`.toLowerCase().includes(q)) return false;
|
|
33
|
+
if (params.state === "online") return c.isOnline;
|
|
34
|
+
if (params.state === "active") return c.isActive;
|
|
35
|
+
if (params.state === "ended") return !c.isActive;
|
|
36
|
+
return true;
|
|
37
|
+
});
|
|
38
|
+
const start = (params.page - 1) * params.pageSize;
|
|
39
|
+
return {
|
|
40
|
+
data: clone(filtered.slice(start, start + params.pageSize)),
|
|
41
|
+
hasNextPage: start + params.pageSize < filtered.length
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
async deleteConversation(id) {
|
|
45
|
+
log("deleteConversation", id);
|
|
46
|
+
await delay();
|
|
47
|
+
const i = contacts.findIndex((c) => c.id === id);
|
|
48
|
+
if (i !== -1) contacts.splice(i, 1);
|
|
49
|
+
},
|
|
50
|
+
async endConversation(id) {
|
|
51
|
+
log("endConversation", id);
|
|
52
|
+
await delay();
|
|
53
|
+
const c = contacts.find((c2) => c2.id === id);
|
|
54
|
+
if (c) c.isActive = false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const messages = {
|
|
58
|
+
async sendMessage(message, opts) {
|
|
59
|
+
var _a;
|
|
60
|
+
log("sendMessage", { ...message, date: void 0 });
|
|
61
|
+
if (opts == null ? void 0 : opts.onProgress) {
|
|
62
|
+
for (const progress of [25, 50, 100]) {
|
|
63
|
+
await delay(latency / 2);
|
|
64
|
+
opts.onProgress({ uploaded: progress, total: 100, progress });
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
await delay();
|
|
68
|
+
}
|
|
69
|
+
if (consume("failNextSend")) throw new Error("send failed (fake backend)");
|
|
70
|
+
const saved = {
|
|
71
|
+
...toJson(message),
|
|
72
|
+
date: new Date(message.date),
|
|
73
|
+
id: `srv-${++serverId}`,
|
|
74
|
+
isSent: true
|
|
75
|
+
};
|
|
76
|
+
(_a = db.get(message.conversationId)) == null ? void 0 : _a.push(saved);
|
|
77
|
+
const c = contacts.find((c2) => c2.id === message.conversationId);
|
|
78
|
+
if (c) c.lastMessage = saved;
|
|
79
|
+
return clone(saved);
|
|
80
|
+
},
|
|
81
|
+
async editMessage(id, text2) {
|
|
82
|
+
log("editMessage", id, text2);
|
|
83
|
+
await delay();
|
|
84
|
+
if (consume("failNextEdit")) throw new Error("edit failed (fake backend)");
|
|
85
|
+
for (const list of db.values()) {
|
|
86
|
+
const m = list.find((m2) => m2.id === id);
|
|
87
|
+
if (m) {
|
|
88
|
+
m.text = text2;
|
|
89
|
+
m.isEdited = true;
|
|
90
|
+
return clone(m);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`no message ${id}`);
|
|
94
|
+
},
|
|
95
|
+
async deleteMessages(ids) {
|
|
96
|
+
log("deleteMessages", ids);
|
|
97
|
+
await delay();
|
|
98
|
+
if (consume("failNextDelete")) throw new Error("delete failed (fake backend)");
|
|
99
|
+
for (const [key, list] of db) {
|
|
100
|
+
db.set(
|
|
101
|
+
key,
|
|
102
|
+
list.filter((m) => !ids.includes(m.id))
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
async fetchMessages({ conversationId, page, pageSize }) {
|
|
107
|
+
log("fetchMessages", { conversationId, page, pageSize });
|
|
108
|
+
await delay();
|
|
109
|
+
const list = db.get(conversationId) ?? [];
|
|
110
|
+
const end = list.length - (page - 1) * pageSize;
|
|
111
|
+
return clone(list.slice(Math.max(0, end - pageSize), Math.max(0, end)));
|
|
112
|
+
},
|
|
113
|
+
async markRead(conversationId) {
|
|
114
|
+
log("markRead", conversationId);
|
|
115
|
+
const c = contacts.find((c2) => c2.id === conversationId);
|
|
116
|
+
if (c) c.unreadCount = 0;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const media = {
|
|
120
|
+
async download(url, opts) {
|
|
121
|
+
var _a;
|
|
122
|
+
log("download", url);
|
|
123
|
+
let blob;
|
|
124
|
+
try {
|
|
125
|
+
const res = await fetch(url, { signal: opts == null ? void 0 : opts.signal });
|
|
126
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
127
|
+
blob = await res.blob();
|
|
128
|
+
} catch {
|
|
129
|
+
blob = new Blob(["fake file"], { type: "text/plain" });
|
|
130
|
+
}
|
|
131
|
+
(_a = opts == null ? void 0 : opts.onProgress) == null ? void 0 : _a.call(opts, 100);
|
|
132
|
+
return blob;
|
|
133
|
+
},
|
|
134
|
+
async getFileSize() {
|
|
135
|
+
return 1024;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const attachments = (conversationId, pick) => (db.get(conversationId) ?? []).flatMap(pick);
|
|
139
|
+
const profile = {
|
|
140
|
+
async fetchMedia({ conversationId, page, pageSize }) {
|
|
141
|
+
const all = attachments(conversationId, (m) => m.imageUrl ?? []);
|
|
142
|
+
const start = (page - 1) * pageSize;
|
|
143
|
+
return {
|
|
144
|
+
data: all.slice(start, start + pageSize),
|
|
145
|
+
hasNextPage: start + pageSize < all.length
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
async fetchFiles({ conversationId, page, pageSize }) {
|
|
149
|
+
const all = attachments(conversationId, (m) => m.fileUrl ? [m.fileUrl] : []);
|
|
150
|
+
const start = (page - 1) * pageSize;
|
|
151
|
+
return {
|
|
152
|
+
data: all.slice(start, start + pageSize),
|
|
153
|
+
hasNextPage: start + pageSize < all.length
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return { chat, messages, media, profile, state };
|
|
158
|
+
}
|
|
159
|
+
function createBroadcastCallHandlers(channelName = "vue-chat-fake-call") {
|
|
160
|
+
const channel = new BroadcastChannel(channelName);
|
|
161
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
162
|
+
const published = [];
|
|
163
|
+
let nextId = 0;
|
|
164
|
+
channel.onmessage = (e) => {
|
|
165
|
+
const message = JSON.parse(e.data);
|
|
166
|
+
listeners.forEach((cb) => void cb(message));
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
published,
|
|
170
|
+
// No TURN server: peers on the same machine connect directly.
|
|
171
|
+
credential: { ttl: 3600, user: "", pass: "", urls: [] },
|
|
172
|
+
iceTransportPolicy: "all",
|
|
173
|
+
async handleGenerateCred() {
|
|
174
|
+
},
|
|
175
|
+
async publisher(json) {
|
|
176
|
+
published.push(JSON.parse(json));
|
|
177
|
+
channel.postMessage(json);
|
|
178
|
+
},
|
|
179
|
+
async subscriber(cb) {
|
|
180
|
+
const id = ++nextId;
|
|
181
|
+
listeners.set(id, cb);
|
|
182
|
+
return id;
|
|
183
|
+
},
|
|
184
|
+
async unSubscriber(id) {
|
|
185
|
+
listeners.delete(id);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const minutesAgo = (m) => new Date(now - m * 6e4);
|
|
191
|
+
function contact(id, name, lastName, extra = {}) {
|
|
192
|
+
return {
|
|
193
|
+
id,
|
|
194
|
+
name,
|
|
195
|
+
lastName,
|
|
196
|
+
isOnline: false,
|
|
197
|
+
lastSeen: minutesAgo(90),
|
|
198
|
+
imageUrl: "",
|
|
199
|
+
isActive: true,
|
|
200
|
+
birthDate: /* @__PURE__ */ new Date("1995-01-01"),
|
|
201
|
+
serviceType: "video-call",
|
|
202
|
+
userType: ["user"],
|
|
203
|
+
...extra
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function text(conversationId, n, senderId, body, minutes) {
|
|
207
|
+
return {
|
|
208
|
+
id: `${conversationId}-m${n}`,
|
|
209
|
+
conversationId,
|
|
210
|
+
date: minutesAgo(minutes),
|
|
211
|
+
type: "text",
|
|
212
|
+
text: body,
|
|
213
|
+
isEdited: false,
|
|
214
|
+
senderId,
|
|
215
|
+
isSent: true,
|
|
216
|
+
isRead: true
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function withLastMessages(data) {
|
|
220
|
+
for (const c of data.contacts) {
|
|
221
|
+
const list = data.messages.get(c.id) ?? [];
|
|
222
|
+
c.lastMessage = list[list.length - 1];
|
|
223
|
+
}
|
|
224
|
+
return data;
|
|
225
|
+
}
|
|
226
|
+
function e2eData(userId) {
|
|
227
|
+
return withLastMessages({
|
|
228
|
+
contacts: [
|
|
229
|
+
contact("c1", "Sara", "Ahmadi", { isOnline: true }),
|
|
230
|
+
contact("c2", "Nima", "Karimi", { serviceType: "chat" }),
|
|
231
|
+
contact("c3", "Leila", "Moradi", { isActive: false })
|
|
232
|
+
],
|
|
233
|
+
messages: /* @__PURE__ */ new Map([
|
|
234
|
+
[
|
|
235
|
+
"c1",
|
|
236
|
+
[
|
|
237
|
+
text("c1", 1, "c1", "Hello from Sara", 30),
|
|
238
|
+
text("c1", 2, userId, "Hi Sara, how are you?", 20),
|
|
239
|
+
text("c1", 3, "c1", "Ready for the video call?", 10)
|
|
240
|
+
]
|
|
241
|
+
],
|
|
242
|
+
[
|
|
243
|
+
"c2",
|
|
244
|
+
[
|
|
245
|
+
{
|
|
246
|
+
...text("c2", 0, "c2", "", 70),
|
|
247
|
+
type: "image",
|
|
248
|
+
imageUrl: ["https://example.com/photo.png"]
|
|
249
|
+
},
|
|
250
|
+
text("c2", 1, "c2", "Nima says hi", 60)
|
|
251
|
+
]
|
|
252
|
+
],
|
|
253
|
+
["c3", [text("c3", 1, "c3", "This conversation is over", 600)]]
|
|
254
|
+
])
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function seeded(seed) {
|
|
258
|
+
const next = () => (seed = seed * 16807 % 2147483647) / 2147483647;
|
|
259
|
+
return {
|
|
260
|
+
next,
|
|
261
|
+
int: (min, max) => Math.floor(next() * (max - min + 1)) + min,
|
|
262
|
+
pick: (items) => items[Math.floor(next() * items.length)]
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const FIRST_NAMES = ["علی", "محمد", "حسین", "رضا", "امیر", "سعید", "مهدی", "فاطمه", "زهرا", "مریم", "سارا", "نازنین", "لیلا", "مهسا", "پویا", "آرش", "نیما", "الهام", "پریسا", "مینا"];
|
|
266
|
+
const LAST_NAMES = ["محمدی", "حسینی", "رضایی", "احمدی", "کریمی", "موسوی", "هاشمی", "جعفری", "صادقی", "نوری", "عباسی", "امیری", "فرهادی", "قاسمی", "طاهری", "مرادی"];
|
|
267
|
+
const TEXTS = [
|
|
268
|
+
"سلام، وقت بخیر",
|
|
269
|
+
"ممنون بابت پیگیری",
|
|
270
|
+
"بله، مشکلی نیست",
|
|
271
|
+
"فایل رو فرستادم، چک کنید",
|
|
272
|
+
"جلسه فردا سر ساعت ۱۰ برگزار میشه",
|
|
273
|
+
"آیا امکان تغییر زمان وجود داره؟",
|
|
274
|
+
"پروژه آماده است و میتونید بررسی کنید",
|
|
275
|
+
"با تشکر از همکاری خوبتون",
|
|
276
|
+
"من منتظر پاسخ شما هستم",
|
|
277
|
+
"مورد تایید شد",
|
|
278
|
+
"خسته نباشید",
|
|
279
|
+
"فردا تماس میگیرم"
|
|
280
|
+
];
|
|
281
|
+
const FILES = ["report.pdf", "invoice.xlsx", "contract.docx", "data.csv"];
|
|
282
|
+
function demoData(userId, count = 40) {
|
|
283
|
+
const rng = seeded(42);
|
|
284
|
+
const contacts = [];
|
|
285
|
+
const messages = /* @__PURE__ */ new Map();
|
|
286
|
+
for (let i = 1; i <= count; i++) {
|
|
287
|
+
const id = `contact-${i}`;
|
|
288
|
+
const isOnline = rng.next() < 0.4;
|
|
289
|
+
contacts.push(
|
|
290
|
+
contact(id, rng.pick(FIRST_NAMES), rng.pick(LAST_NAMES), {
|
|
291
|
+
isOnline,
|
|
292
|
+
lastSeen: minutesAgo(isOnline ? rng.int(1, 30) : rng.int(60, 10080)),
|
|
293
|
+
imageUrl: `https://i.pravatar.cc/150?img=${i % 70 + 1}`,
|
|
294
|
+
isActive: rng.next() < 0.8,
|
|
295
|
+
serviceType: rng.pick(["chat", "video-call", "voice-call"]),
|
|
296
|
+
unreadCount: rng.next() < 0.3 ? rng.int(1, 12) : 0
|
|
297
|
+
})
|
|
298
|
+
);
|
|
299
|
+
const history = [];
|
|
300
|
+
let minutes = rng.int(60, 4e3);
|
|
301
|
+
const length = rng.int(5, 60);
|
|
302
|
+
for (let n = 1; n <= length; n++) {
|
|
303
|
+
minutes -= rng.int(1, 60);
|
|
304
|
+
const senderId = rng.next() < 0.5 ? userId : id;
|
|
305
|
+
const base = text(id, n, senderId, rng.pick(TEXTS), Math.max(minutes, 1));
|
|
306
|
+
const roll = rng.next();
|
|
307
|
+
if (roll < 0.1) {
|
|
308
|
+
history.push({
|
|
309
|
+
...base,
|
|
310
|
+
type: "image",
|
|
311
|
+
text: "",
|
|
312
|
+
imageUrl: [`https://picsum.photos/seed/${id}-${n}/400/300`]
|
|
313
|
+
});
|
|
314
|
+
} else if (roll < 0.18) {
|
|
315
|
+
const fileName = rng.pick(FILES);
|
|
316
|
+
history.push({
|
|
317
|
+
...base,
|
|
318
|
+
type: "file",
|
|
319
|
+
text: "",
|
|
320
|
+
fileName,
|
|
321
|
+
fileUrl: `https://example.com/files/${fileName}`
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
history.push(base);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
messages.set(id, history);
|
|
328
|
+
}
|
|
329
|
+
return withLastMessages({ contacts, messages });
|
|
330
|
+
}
|
|
331
|
+
exports.createBroadcastCallHandlers = createBroadcastCallHandlers;
|
|
332
|
+
exports.createFakeBackend = createFakeBackend;
|
|
333
|
+
exports.demoData = demoData;
|
|
334
|
+
exports.e2eData = e2eData;
|
|
335
|
+
//# sourceMappingURL=fakes.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fakes.cjs","sources":["../fakes/backend.ts","../fakes/call.ts","../fakes/data.ts"],"sourcesContent":["// In-memory implementations of every handler interface the chat needs, shared by the demo and\n// the e2e harness. `state` lets tests make the next call fail and see which handlers ran.\nimport type {\n ChatHandlers,\n MediaHandlers,\n Message,\n MessagesHandlers,\n ProfileHandlers,\n} from \"../app/types\";\nimport type { FakeData } from \"./data\";\n\nexport interface FakeBackendState {\n failNextSend: boolean;\n failNextDelete: boolean;\n failNextEdit: boolean;\n /** Every handler call, with its arguments as JSON. */\n calls: { fn: string; args: unknown[] }[];\n}\n\nexport interface FakeBackend {\n chat: ChatHandlers;\n messages: MessagesHandlers;\n media: MediaHandlers;\n profile: ProfileHandlers;\n state: FakeBackendState;\n}\n\nexport interface FakeBackendOptions {\n /** Simulated network latency in ms. */\n latency?: number;\n}\n\n/**\n * JSON round-trip, as a network hop would do. Drops the view-only links the UI adds to messages\n * (neighbours, contact), which are circular and never meant for a server.\n */\nconst toJson = (value: unknown) =>\n JSON.parse(\n JSON.stringify(value, (key, val) =>\n [\"prevMessage\", \"nextMessage\", \"contact\"].includes(key) ? undefined : val,\n ),\n );\n\n// Handlers hand out copies, like a real server, so stores never share objects with the \"database\".\nconst clone = <T>(value: T): T => structuredClone(value);\n\nexport function createFakeBackend(\n data: FakeData,\n { latency = 50 }: FakeBackendOptions = {},\n): FakeBackend {\n const { contacts, messages: db } = data;\n const state: FakeBackendState = {\n failNextSend: false,\n failNextDelete: false,\n failNextEdit: false,\n calls: [],\n };\n const log = (fn: string, ...args: unknown[]) =>\n state.calls.push({ fn, args: toJson(args) });\n const delay = (ms = latency) => new Promise((r) => setTimeout(r, ms));\n /** Returns whether `flag` was armed, disarming it. */\n const consume = (flag: \"failNextSend\" | \"failNextDelete\" | \"failNextEdit\") => {\n const armed = state[flag];\n state[flag] = false;\n return armed;\n };\n let serverId = 0;\n\n const chat: ChatHandlers = {\n async fetchConversations(params) {\n log(\"fetchConversations\", params);\n await delay();\n const q = (params.search ?? \"\").trim().toLowerCase();\n const filtered = contacts.filter((c) => {\n if (q && !`${c.name} ${c.lastName}`.toLowerCase().includes(q)) return false;\n if (params.state === \"online\") return c.isOnline;\n if (params.state === \"active\") return c.isActive;\n if (params.state === \"ended\") return !c.isActive;\n return true;\n });\n const start = (params.page - 1) * params.pageSize;\n return {\n data: clone(filtered.slice(start, start + params.pageSize)),\n hasNextPage: start + params.pageSize < filtered.length,\n };\n },\n async deleteConversation(id) {\n log(\"deleteConversation\", id);\n await delay();\n const i = contacts.findIndex((c) => c.id === id);\n if (i !== -1) contacts.splice(i, 1);\n },\n async endConversation(id) {\n log(\"endConversation\", id);\n await delay();\n const c = contacts.find((c) => c.id === id);\n if (c) c.isActive = false;\n },\n };\n\n const messages: MessagesHandlers = {\n async sendMessage(message, opts) {\n log(\"sendMessage\", { ...message, date: undefined });\n if (opts?.onProgress) {\n for (const progress of [25, 50, 100]) {\n await delay(latency / 2);\n opts.onProgress({ uploaded: progress, total: 100, progress });\n }\n } else {\n await delay();\n }\n if (consume(\"failNextSend\")) throw new Error(\"send failed (fake backend)\");\n\n const saved: Message = {\n ...toJson(message),\n date: new Date(message.date),\n id: `srv-${++serverId}`,\n isSent: true,\n };\n db.get(message.conversationId)?.push(saved);\n const c = contacts.find((c) => c.id === message.conversationId);\n if (c) c.lastMessage = saved;\n return clone(saved);\n },\n async editMessage(id, text) {\n log(\"editMessage\", id, text);\n await delay();\n if (consume(\"failNextEdit\")) throw new Error(\"edit failed (fake backend)\");\n for (const list of db.values()) {\n const m = list.find((m) => m.id === id);\n if (m) {\n m.text = text;\n m.isEdited = true;\n return clone(m);\n }\n }\n throw new Error(`no message ${id}`);\n },\n async deleteMessages(ids) {\n log(\"deleteMessages\", ids);\n await delay();\n if (consume(\"failNextDelete\")) throw new Error(\"delete failed (fake backend)\");\n for (const [key, list] of db) {\n db.set(\n key,\n list.filter((m) => !ids.includes(m.id)),\n );\n }\n },\n async fetchMessages({ conversationId, page, pageSize }) {\n log(\"fetchMessages\", { conversationId, page, pageSize });\n await delay();\n const list = db.get(conversationId) ?? [];\n // Newest page first, oldest-to-newest within the page.\n const end = list.length - (page - 1) * pageSize;\n return clone(list.slice(Math.max(0, end - pageSize), Math.max(0, end)));\n },\n async markRead(conversationId) {\n log(\"markRead\", conversationId);\n const c = contacts.find((c) => c.id === conversationId);\n if (c) c.unreadCount = 0;\n },\n };\n\n const media: MediaHandlers = {\n async download(url, opts) {\n log(\"download\", url);\n // Real URLs (the demo's pictures) are fetched; made-up ones get a placeholder file.\n let blob: Blob;\n try {\n const res = await fetch(url, { signal: opts?.signal });\n if (!res.ok) throw new Error(String(res.status));\n blob = await res.blob();\n } catch {\n blob = new Blob([\"fake file\"], { type: \"text/plain\" });\n }\n opts?.onProgress?.(100);\n return blob;\n },\n async getFileSize() {\n return 1024;\n },\n };\n\n const attachments = (conversationId: string, pick: (m: Message) => string[]) =>\n (db.get(conversationId) ?? []).flatMap(pick);\n\n const profile: ProfileHandlers = {\n async fetchMedia({ conversationId, page, pageSize }) {\n const all = attachments(conversationId, (m) => m.imageUrl ?? []);\n const start = (page - 1) * pageSize;\n return {\n data: all.slice(start, start + pageSize),\n hasNextPage: start + pageSize < all.length,\n };\n },\n async fetchFiles({ conversationId, page, pageSize }) {\n const all = attachments(conversationId, (m) => (m.fileUrl ? [m.fileUrl] : []));\n const start = (page - 1) * pageSize;\n return {\n data: all.slice(start, start + pageSize),\n hasNextPage: start + pageSize < all.length,\n };\n },\n };\n\n return { chat, messages, media, profile, state };\n}\n","// Call signalling over BroadcastChannel, so two tabs of the same browser can call each other just\n// as two clients behind a real pub/sub would. No server needed.\nimport type { CallHandlers, CallMessageSchema } from \"../app/types\";\n\nexport interface FakeCallHandlers extends CallHandlers {\n /** Everything this tab published, oldest first. */\n published: CallMessageSchema[];\n}\n\nexport function createBroadcastCallHandlers(\n channelName = \"vue-chat-fake-call\",\n): FakeCallHandlers {\n const channel = new BroadcastChannel(channelName);\n const listeners = new Map<number, (m: CallMessageSchema) => Promise<void>>();\n const published: CallMessageSchema[] = [];\n let nextId = 0;\n\n channel.onmessage = (e) => {\n const message = JSON.parse(e.data) as CallMessageSchema;\n listeners.forEach((cb) => void cb(message));\n };\n\n return {\n published,\n // No TURN server: peers on the same machine connect directly.\n credential: { ttl: 3600, user: \"\", pass: \"\", urls: [] },\n iceTransportPolicy: \"all\",\n async handleGenerateCred() {},\n async publisher(json) {\n published.push(JSON.parse(json));\n channel.postMessage(json);\n },\n async subscriber(cb) {\n const id = ++nextId;\n listeners.set(id, cb);\n return id;\n },\n async unSubscriber(id) {\n listeners.delete(id);\n },\n };\n}\n\n","// Seed data for the fake backend: a small fixed set the e2e specs assert against, and a larger\n// generated one for the demo. Both are deterministic.\nimport type { Contact, Message } from \"../app/types\";\n\nexport interface FakeData {\n contacts: Contact[];\n messages: Map<string, Message[]>;\n}\n\nconst now = Date.now();\nconst minutesAgo = (m: number) => new Date(now - m * 60_000);\n\nfunction contact(\n id: string,\n name: string,\n lastName: string,\n extra: Partial<Contact> = {},\n): Contact {\n return {\n id,\n name,\n lastName,\n isOnline: false,\n lastSeen: minutesAgo(90),\n imageUrl: \"\",\n isActive: true,\n birthDate: new Date(\"1995-01-01\"),\n serviceType: \"video-call\",\n userType: [\"user\"],\n ...extra,\n };\n}\n\nfunction text(\n conversationId: string,\n n: number,\n senderId: string,\n body: string,\n minutes: number,\n): Message {\n return {\n id: `${conversationId}-m${n}`,\n conversationId,\n date: minutesAgo(minutes),\n type: \"text\",\n text: body,\n isEdited: false,\n senderId,\n isSent: true,\n isRead: true,\n };\n}\n\nfunction withLastMessages(data: FakeData): FakeData {\n for (const c of data.contacts) {\n const list = data.messages.get(c.id) ?? [];\n c.lastMessage = list[list.length - 1];\n }\n return data;\n}\n\n/**\n * Three conversations the e2e specs rely on: c1 active, online, with calls; c2 chat-only;\n * c3 ended.\n */\nexport function e2eData(userId: string): FakeData {\n return withLastMessages({\n contacts: [\n contact(\"c1\", \"Sara\", \"Ahmadi\", { isOnline: true }),\n contact(\"c2\", \"Nima\", \"Karimi\", { serviceType: \"chat\" }),\n contact(\"c3\", \"Leila\", \"Moradi\", { isActive: false }),\n ],\n messages: new Map([\n [\n \"c1\",\n [\n text(\"c1\", 1, \"c1\", \"Hello from Sara\", 30),\n text(\"c1\", 2, userId, \"Hi Sara, how are you?\", 20),\n text(\"c1\", 3, \"c1\", \"Ready for the video call?\", 10),\n ],\n ],\n [\n \"c2\",\n [\n {\n ...text(\"c2\", 0, \"c2\", \"\", 70),\n type: \"image\",\n imageUrl: [\"https://example.com/photo.png\"],\n },\n text(\"c2\", 1, \"c2\", \"Nima says hi\", 60),\n ],\n ],\n [\"c3\", [text(\"c3\", 1, \"c3\", \"This conversation is over\", 600)]],\n ]),\n });\n}\n\n/** Park–Miller PRNG, so the demo looks the same on every load. */\nfunction seeded(seed: number) {\n const next = () => (seed = (seed * 16807) % 2147483647) / 2147483647;\n return {\n next,\n int: (min: number, max: number) => Math.floor(next() * (max - min + 1)) + min,\n pick: <T>(items: readonly T[]) => items[Math.floor(next() * items.length)]!,\n };\n}\n\nconst FIRST_NAMES = [\"علی\", \"محمد\", \"حسین\", \"رضا\", \"امیر\", \"سعید\", \"مهدی\", \"فاطمه\", \"زهرا\", \"مریم\", \"سارا\", \"نازنین\", \"لیلا\", \"مهسا\", \"پویا\", \"آرش\", \"نیما\", \"الهام\", \"پریسا\", \"مینا\"] as const;\nconst LAST_NAMES = [\"محمدی\", \"حسینی\", \"رضایی\", \"احمدی\", \"کریمی\", \"موسوی\", \"هاشمی\", \"جعفری\", \"صادقی\", \"نوری\", \"عباسی\", \"امیری\", \"فرهادی\", \"قاسمی\", \"طاهری\", \"مرادی\"] as const;\nconst TEXTS = [\n \"سلام، وقت بخیر\",\n \"ممنون بابت پیگیری\",\n \"بله، مشکلی نیست\",\n \"فایل رو فرستادم، چک کنید\",\n \"جلسه فردا سر ساعت ۱۰ برگزار میشه\",\n \"آیا امکان تغییر زمان وجود داره؟\",\n \"پروژه آماده است و میتونید بررسی کنید\",\n \"با تشکر از همکاری خوبتون\",\n \"من منتظر پاسخ شما هستم\",\n \"مورد تایید شد\",\n \"خسته نباشید\",\n \"فردا تماس میگیرم\",\n] as const;\nconst FILES = [\"report.pdf\", \"invoice.xlsx\", \"contract.docx\", \"data.csv\"] as const;\n\n/** A realistic-looking Persian dataset for the demo: `count` conversations with history. */\nexport function demoData(userId: string, count = 40): FakeData {\n const rng = seeded(42);\n const contacts: Contact[] = [];\n const messages = new Map<string, Message[]>();\n\n for (let i = 1; i <= count; i++) {\n const id = `contact-${i}`;\n const isOnline = rng.next() < 0.4;\n contacts.push(\n contact(id, rng.pick(FIRST_NAMES), rng.pick(LAST_NAMES), {\n isOnline,\n lastSeen: minutesAgo(isOnline ? rng.int(1, 30) : rng.int(60, 10_080)),\n imageUrl: `https://i.pravatar.cc/150?img=${(i % 70) + 1}`,\n isActive: rng.next() < 0.8,\n serviceType: rng.pick([\"chat\", \"video-call\", \"voice-call\"] as const),\n unreadCount: rng.next() < 0.3 ? rng.int(1, 12) : 0,\n }),\n );\n\n const history: Message[] = [];\n let minutes = rng.int(60, 4_000);\n const length = rng.int(5, 60);\n for (let n = 1; n <= length; n++) {\n minutes -= rng.int(1, 60);\n const senderId = rng.next() < 0.5 ? userId : id;\n const base = text(id, n, senderId, rng.pick(TEXTS), Math.max(minutes, 1));\n const roll = rng.next();\n if (roll < 0.1) {\n history.push({\n ...base,\n type: \"image\",\n text: \"\",\n imageUrl: [`https://picsum.photos/seed/${id}-${n}/400/300`],\n });\n } else if (roll < 0.18) {\n const fileName = rng.pick(FILES);\n history.push({\n ...base,\n type: \"file\",\n text: \"\",\n fileName,\n fileUrl: `https://example.com/files/${fileName}`,\n });\n } else {\n history.push(base);\n }\n }\n messages.set(id, history);\n }\n\n return withLastMessages({ contacts, messages });\n}\n"],"names":["c","text","m"],"mappings":";;AAoCA,MAAM,SAAS,CAAC,UACd,KAAK;AAAA,EACH,KAAK;AAAA,IAAU;AAAA,IAAO,CAAC,KAAK,QAC1B,CAAC,eAAe,eAAe,SAAS,EAAE,SAAS,GAAG,IAAI,SAAY;AAAA,EAAA;AAE1E;AAGF,MAAM,QAAQ,CAAI,UAAgB,gBAAgB,KAAK;AAEhD,SAAS,kBACd,MACA,EAAE,UAAU,GAAA,IAA2B,CAAA,GAC1B;AACb,QAAM,EAAE,UAAU,UAAU,GAAA,IAAO;AACnC,QAAM,QAA0B;AAAA,IAC9B,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,OAAO,CAAA;AAAA,EAAC;AAEV,QAAM,MAAM,CAAC,OAAe,SAC1B,MAAM,MAAM,KAAK,EAAE,IAAI,MAAM,OAAO,IAAI,GAAG;AAC7C,QAAM,QAAQ,CAAC,KAAK,YAAY,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAEpE,QAAM,UAAU,CAAC,SAA6D;AAC5E,UAAM,QAAQ,MAAM,IAAI;AACxB,UAAM,IAAI,IAAI;AACd,WAAO;AAAA,EACT;AACA,MAAI,WAAW;AAEf,QAAM,OAAqB;AAAA,IACzB,MAAM,mBAAmB,QAAQ;AAC/B,UAAI,sBAAsB,MAAM;AAChC,YAAM,MAAA;AACN,YAAM,KAAK,OAAO,UAAU,IAAI,KAAA,EAAO,YAAA;AACvC,YAAM,WAAW,SAAS,OAAO,CAAC,MAAM;AACtC,YAAI,KAAK,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,QAAQ,GAAG,YAAA,EAAc,SAAS,CAAC,EAAG,QAAO;AACtE,YAAI,OAAO,UAAU,SAAU,QAAO,EAAE;AACxC,YAAI,OAAO,UAAU,SAAU,QAAO,EAAE;AACxC,YAAI,OAAO,UAAU,QAAS,QAAO,CAAC,EAAE;AACxC,eAAO;AAAA,MACT,CAAC;AACD,YAAM,SAAS,OAAO,OAAO,KAAK,OAAO;AACzC,aAAO;AAAA,QACL,MAAM,MAAM,SAAS,MAAM,OAAO,QAAQ,OAAO,QAAQ,CAAC;AAAA,QAC1D,aAAa,QAAQ,OAAO,WAAW,SAAS;AAAA,MAAA;AAAA,IAEpD;AAAA,IACA,MAAM,mBAAmB,IAAI;AAC3B,UAAI,sBAAsB,EAAE;AAC5B,YAAM,MAAA;AACN,YAAM,IAAI,SAAS,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AAC/C,UAAI,MAAM,GAAI,UAAS,OAAO,GAAG,CAAC;AAAA,IACpC;AAAA,IACA,MAAM,gBAAgB,IAAI;AACxB,UAAI,mBAAmB,EAAE;AACzB,YAAM,MAAA;AACN,YAAM,IAAI,SAAS,KAAK,CAACA,OAAMA,GAAE,OAAO,EAAE;AAC1C,UAAI,KAAK,WAAW;AAAA,IACtB;AAAA,EAAA;AAGF,QAAM,WAA6B;AAAA,IACjC,MAAM,YAAY,SAAS,MAAM;;AAC/B,UAAI,eAAe,EAAE,GAAG,SAAS,MAAM,QAAW;AAClD,UAAI,6BAAM,YAAY;AACpB,mBAAW,YAAY,CAAC,IAAI,IAAI,GAAG,GAAG;AACpC,gBAAM,MAAM,UAAU,CAAC;AACvB,eAAK,WAAW,EAAE,UAAU,UAAU,OAAO,KAAK,UAAU;AAAA,QAC9D;AAAA,MACF,OAAO;AACL,cAAM,MAAA;AAAA,MACR;AACA,UAAI,QAAQ,cAAc,EAAG,OAAM,IAAI,MAAM,4BAA4B;AAEzE,YAAM,QAAiB;AAAA,QACrB,GAAG,OAAO,OAAO;AAAA,QACjB,MAAM,IAAI,KAAK,QAAQ,IAAI;AAAA,QAC3B,IAAI,OAAO,EAAE,QAAQ;AAAA,QACrB,QAAQ;AAAA,MAAA;AAEV,eAAG,IAAI,QAAQ,cAAc,MAA7B,mBAAgC,KAAK;AACrC,YAAM,IAAI,SAAS,KAAK,CAACA,OAAMA,GAAE,OAAO,QAAQ,cAAc;AAC9D,UAAI,KAAK,cAAc;AACvB,aAAO,MAAM,KAAK;AAAA,IACpB;AAAA,IACA,MAAM,YAAY,IAAIC,OAAM;AAC1B,UAAI,eAAe,IAAIA,KAAI;AAC3B,YAAM,MAAA;AACN,UAAI,QAAQ,cAAc,EAAG,OAAM,IAAI,MAAM,4BAA4B;AACzE,iBAAW,QAAQ,GAAG,UAAU;AAC9B,cAAM,IAAI,KAAK,KAAK,CAACC,OAAMA,GAAE,OAAO,EAAE;AACtC,YAAI,GAAG;AACL,YAAE,OAAOD;AACT,YAAE,WAAW;AACb,iBAAO,MAAM,CAAC;AAAA,QAChB;AAAA,MACF;AACA,YAAM,IAAI,MAAM,cAAc,EAAE,EAAE;AAAA,IACpC;AAAA,IACA,MAAM,eAAe,KAAK;AACxB,UAAI,kBAAkB,GAAG;AACzB,YAAM,MAAA;AACN,UAAI,QAAQ,gBAAgB,EAAG,OAAM,IAAI,MAAM,8BAA8B;AAC7E,iBAAW,CAAC,KAAK,IAAI,KAAK,IAAI;AAC5B,WAAG;AAAA,UACD;AAAA,UACA,KAAK,OAAO,CAAC,MAAM,CAAC,IAAI,SAAS,EAAE,EAAE,CAAC;AAAA,QAAA;AAAA,MAE1C;AAAA,IACF;AAAA,IACA,MAAM,cAAc,EAAE,gBAAgB,MAAM,YAAY;AACtD,UAAI,iBAAiB,EAAE,gBAAgB,MAAM,UAAU;AACvD,YAAM,MAAA;AACN,YAAM,OAAO,GAAG,IAAI,cAAc,KAAK,CAAA;AAEvC,YAAM,MAAM,KAAK,UAAU,OAAO,KAAK;AACvC,aAAO,MAAM,KAAK,MAAM,KAAK,IAAI,GAAG,MAAM,QAAQ,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC,CAAC;AAAA,IACxE;AAAA,IACA,MAAM,SAAS,gBAAgB;AAC7B,UAAI,YAAY,cAAc;AAC9B,YAAM,IAAI,SAAS,KAAK,CAACD,OAAMA,GAAE,OAAO,cAAc;AACtD,UAAI,KAAK,cAAc;AAAA,IACzB;AAAA,EAAA;AAGF,QAAM,QAAuB;AAAA,IAC3B,MAAM,SAAS,KAAK,MAAM;;AACxB,UAAI,YAAY,GAAG;AAEnB,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,6BAAM,QAAQ;AACrD,YAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,OAAO,IAAI,MAAM,CAAC;AAC/C,eAAO,MAAM,IAAI,KAAA;AAAA,MACnB,QAAQ;AACN,eAAO,IAAI,KAAK,CAAC,WAAW,GAAG,EAAE,MAAM,cAAc;AAAA,MACvD;AACA,yCAAM,eAAN,8BAAmB;AACnB,aAAO;AAAA,IACT;AAAA,IACA,MAAM,cAAc;AAClB,aAAO;AAAA,IACT;AAAA,EAAA;AAGF,QAAM,cAAc,CAAC,gBAAwB,UAC1C,GAAG,IAAI,cAAc,KAAK,IAAI,QAAQ,IAAI;AAE7C,QAAM,UAA2B;AAAA,IAC/B,MAAM,WAAW,EAAE,gBAAgB,MAAM,YAAY;AACnD,YAAM,MAAM,YAAY,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE;AAC/D,YAAM,SAAS,OAAO,KAAK;AAC3B,aAAO;AAAA,QACL,MAAM,IAAI,MAAM,OAAO,QAAQ,QAAQ;AAAA,QACvC,aAAa,QAAQ,WAAW,IAAI;AAAA,MAAA;AAAA,IAExC;AAAA,IACA,MAAM,WAAW,EAAE,gBAAgB,MAAM,YAAY;AACnD,YAAM,MAAM,YAAY,gBAAgB,CAAC,MAAO,EAAE,UAAU,CAAC,EAAE,OAAO,IAAI,CAAA,CAAG;AAC7E,YAAM,SAAS,OAAO,KAAK;AAC3B,aAAO;AAAA,QACL,MAAM,IAAI,MAAM,OAAO,QAAQ,QAAQ;AAAA,QACvC,aAAa,QAAQ,WAAW,IAAI;AAAA,MAAA;AAAA,IAExC;AAAA,EAAA;AAGF,SAAO,EAAE,MAAM,UAAU,OAAO,SAAS,MAAA;AAC3C;ACtMO,SAAS,4BACd,cAAc,sBACI;AAClB,QAAM,UAAU,IAAI,iBAAiB,WAAW;AAChD,QAAM,gCAAgB,IAAA;AACtB,QAAM,YAAiC,CAAA;AACvC,MAAI,SAAS;AAEb,UAAQ,YAAY,CAAC,MAAM;AACzB,UAAM,UAAU,KAAK,MAAM,EAAE,IAAI;AACjC,cAAU,QAAQ,CAAC,OAAO,KAAK,GAAG,OAAO,CAAC;AAAA,EAC5C;AAEA,SAAO;AAAA,IACL;AAAA;AAAA,IAEA,YAAY,EAAE,KAAK,MAAM,MAAM,IAAI,MAAM,IAAI,MAAM,GAAC;AAAA,IACpD,oBAAoB;AAAA,IACpB,MAAM,qBAAqB;AAAA,IAAC;AAAA,IAC5B,MAAM,UAAU,MAAM;AACpB,gBAAU,KAAK,KAAK,MAAM,IAAI,CAAC;AAC/B,cAAQ,YAAY,IAAI;AAAA,IAC1B;AAAA,IACA,MAAM,WAAW,IAAI;AACnB,YAAM,KAAK,EAAE;AACb,gBAAU,IAAI,IAAI,EAAE;AACpB,aAAO;AAAA,IACT;AAAA,IACA,MAAM,aAAa,IAAI;AACrB,gBAAU,OAAO,EAAE;AAAA,IACrB;AAAA,EAAA;AAEJ;AChCA,MAAM,MAAM,KAAK,IAAA;AACjB,MAAM,aAAa,CAAC,MAAc,IAAI,KAAK,MAAM,IAAI,GAAM;AAE3D,SAAS,QACP,IACA,MACA,UACA,QAA0B,CAAA,GACjB;AACT,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,UAAU,WAAW,EAAE;AAAA,IACvB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW,oBAAI,KAAK,YAAY;AAAA,IAChC,aAAa;AAAA,IACb,UAAU,CAAC,MAAM;AAAA,IACjB,GAAG;AAAA,EAAA;AAEP;AAEA,SAAS,KACP,gBACA,GACA,UACA,MACA,SACS;AACT,SAAO;AAAA,IACL,IAAI,GAAG,cAAc,KAAK,CAAC;AAAA,IAC3B;AAAA,IACA,MAAM,WAAW,OAAO;AAAA,IACxB,MAAM;AAAA,IACN,MAAM;AAAA,IACN,UAAU;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,EAAA;AAEZ;AAEA,SAAS,iBAAiB,MAA0B;AAClD,aAAW,KAAK,KAAK,UAAU;AAC7B,UAAM,OAAO,KAAK,SAAS,IAAI,EAAE,EAAE,KAAK,CAAA;AACxC,MAAE,cAAc,KAAK,KAAK,SAAS,CAAC;AAAA,EACtC;AACA,SAAO;AACT;AAMO,SAAS,QAAQ,QAA0B;AAChD,SAAO,iBAAiB;AAAA,IACtB,UAAU;AAAA,MACR,QAAQ,MAAM,QAAQ,UAAU,EAAE,UAAU,MAAM;AAAA,MAClD,QAAQ,MAAM,QAAQ,UAAU,EAAE,aAAa,QAAQ;AAAA,MACvD,QAAQ,MAAM,SAAS,UAAU,EAAE,UAAU,OAAO;AAAA,IAAA;AAAA,IAEtD,8BAAc,IAAI;AAAA,MAChB;AAAA,QACE;AAAA,QACA;AAAA,UACE,KAAK,MAAM,GAAG,MAAM,mBAAmB,EAAE;AAAA,UACzC,KAAK,MAAM,GAAG,QAAQ,yBAAyB,EAAE;AAAA,UACjD,KAAK,MAAM,GAAG,MAAM,6BAA6B,EAAE;AAAA,QAAA;AAAA,MACrD;AAAA,MAEF;AAAA,QACE;AAAA,QACA;AAAA,UACE;AAAA,YACE,GAAG,KAAK,MAAM,GAAG,MAAM,IAAI,EAAE;AAAA,YAC7B,MAAM;AAAA,YACN,UAAU,CAAC,+BAA+B;AAAA,UAAA;AAAA,UAE5C,KAAK,MAAM,GAAG,MAAM,gBAAgB,EAAE;AAAA,QAAA;AAAA,MACxC;AAAA,MAEF,CAAC,MAAM,CAAC,KAAK,MAAM,GAAG,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAAA,IAAA,CAC/D;AAAA,EAAA,CACF;AACH;AAGA,SAAS,OAAO,MAAc;AAC5B,QAAM,OAAO,OAAO,OAAQ,OAAO,QAAS,cAAc;AAC1D,SAAO;AAAA,IACL;AAAA,IACA,KAAK,CAAC,KAAa,QAAgB,KAAK,MAAM,KAAA,KAAU,MAAM,MAAM,EAAE,IAAI;AAAA,IAC1E,MAAM,CAAI,UAAwB,MAAM,KAAK,MAAM,KAAA,IAAS,MAAM,MAAM,CAAC;AAAA,EAAA;AAE7E;AAEA,MAAM,cAAc,CAAC,OAAO,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,SAAS,SAAS,MAAM;AACrL,MAAM,aAAa,CAAC,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS,QAAQ,SAAS,SAAS,UAAU,SAAS,SAAS,OAAO;AAClK,MAAM,QAAQ;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,QAAQ,CAAC,cAAc,gBAAgB,iBAAiB,UAAU;AAGjE,SAAS,SAAS,QAAgB,QAAQ,IAAc;AAC7D,QAAM,MAAM,OAAO,EAAE;AACrB,QAAM,WAAsB,CAAA;AAC5B,QAAM,+BAAe,IAAA;AAErB,WAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,UAAM,KAAK,WAAW,CAAC;AACvB,UAAM,WAAW,IAAI,KAAA,IAAS;AAC9B,aAAS;AAAA,MACP,QAAQ,IAAI,IAAI,KAAK,WAAW,GAAG,IAAI,KAAK,UAAU,GAAG;AAAA,QACvD;AAAA,QACA,UAAU,WAAW,WAAW,IAAI,IAAI,GAAG,EAAE,IAAI,IAAI,IAAI,IAAI,KAAM,CAAC;AAAA,QACpE,UAAU,iCAAkC,IAAI,KAAM,CAAC;AAAA,QACvD,UAAU,IAAI,KAAA,IAAS;AAAA,QACvB,aAAa,IAAI,KAAK,CAAC,QAAQ,cAAc,YAAY,CAAU;AAAA,QACnE,aAAa,IAAI,SAAS,MAAM,IAAI,IAAI,GAAG,EAAE,IAAI;AAAA,MAAA,CAClD;AAAA,IAAA;AAGH,UAAM,UAAqB,CAAA;AAC3B,QAAI,UAAU,IAAI,IAAI,IAAI,GAAK;AAC/B,UAAM,SAAS,IAAI,IAAI,GAAG,EAAE;AAC5B,aAAS,IAAI,GAAG,KAAK,QAAQ,KAAK;AAChC,iBAAW,IAAI,IAAI,GAAG,EAAE;AACxB,YAAM,WAAW,IAAI,KAAA,IAAS,MAAM,SAAS;AAC7C,YAAM,OAAO,KAAK,IAAI,GAAG,UAAU,IAAI,KAAK,KAAK,GAAG,KAAK,IAAI,SAAS,CAAC,CAAC;AACxE,YAAM,OAAO,IAAI,KAAA;AACjB,UAAI,OAAO,KAAK;AACd,gBAAQ,KAAK;AAAA,UACX,GAAG;AAAA,UACH,MAAM;AAAA,UACN,MAAM;AAAA,UACN,UAAU,CAAC,8BAA8B,EAAE,IAAI,CAAC,UAAU;AAAA,QAAA,CAC3D;AAAA,MACH,WAAW,OAAO,MAAM;AACtB,cAAM,WAAW,IAAI,KAAK,KAAK;AAC/B,gBAAQ,KAAK;AAAA,UACX,GAAG;AAAA,UACH,MAAM;AAAA,UACN,MAAM;AAAA,UACN;AAAA,UACA,SAAS,6BAA6B,QAAQ;AAAA,QAAA,CAC/C;AAAA,MACH,OAAO;AACL,gBAAQ,KAAK,IAAI;AAAA,MACnB;AAAA,IACF;AACA,aAAS,IAAI,IAAI,OAAO;AAAA,EAC1B;AAEA,SAAO,iBAAiB,EAAE,UAAU,UAAU;AAChD;;;;;"}
|