{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {initializeApp, type FirebaseApp} from 'firebase/app'\nimport {\n  child,\n  getDatabase,\n  onChildAdded,\n  onDisconnect,\n  onValue,\n  push,\n  ref,\n  remove,\n  set,\n  type DatabaseReference\n} from 'firebase/database'\nimport {\n  createTopicStrategy,\n  fromJson,\n  libName,\n  selfId,\n  type BaseRelayConfig,\n  type BaseRoomConfig,\n  type JoinRoom,\n  type StrategyMessage\n} from '@trystero-p2p/core'\n\nconst presencePath = '_'\nconst defaultRootPath = `__${libName.toLowerCase()}__`\nconst dbs: Record<string, ReturnType<typeof getDatabase>> = {}\nconst presenceRefs: Record<string, DatabaseReference> = {}\nconst presenceMessages: Record<string, Record<string, unknown>> = {}\nconst presenceCleanups: Record<string, () => void> = {}\nconst subscriptionTokens: Record<string, symbol> = {}\n\nexport type FirebaseRelayConfig = BaseRelayConfig & {\n  firebaseApp?: FirebaseApp\n  firebasePath?: string\n}\n\nexport type FirebaseRoomConfig = BaseRoomConfig & {\n  relayConfig?: FirebaseRelayConfig\n}\n\nconst getPath = (...xs: string[]): string => xs.join('/')\nconst roomKey = (rootTopic: string, selfTopic: string): string =>\n  `${rootTopic}|${selfTopic}`\n\nconst initDb = (config: FirebaseRoomConfig): ReturnType<typeof getDatabase> =>\n  config.relayConfig?.firebaseApp\n    ? (dbs[\n        config.relayConfig.firebaseApp.options.databaseURL ?? config.appId\n      ] ??= getDatabase(config.relayConfig.firebaseApp))\n    : (dbs[config.appId] ??= getDatabase(\n        initializeApp({databaseURL: config.appId})\n      ))\n\nexport const joinRoom: JoinRoom<FirebaseRoomConfig> = createTopicStrategy({\n  reannounceOnDisconnect: false,\n\n  init: config =>\n    ref(initDb(config), config.relayConfig?.firebasePath ?? defaultRootPath),\n\n  subscribeTopic: (rootRef, topic, onMessage, context) => {\n    const {rootTopic, selfTopic, kind} = context\n    const roomRef = child(rootRef, rootTopic)\n    const key = roomKey(rootTopic, selfTopic)\n    const subscriptionToken = (subscriptionTokens[key] ??= Symbol(key))\n    const unsubFns: Array<() => void> = []\n\n    if (kind === 'self') {\n      const selfRef = child(roomRef, selfTopic)\n      const peerSignals: Record<string, Record<string, boolean>> = {}\n\n      const selfCleanup = onChildAdded(selfRef, data => {\n        const peerId = data.key\n\n        if (!peerId || peerId === presencePath) {\n          return\n        }\n\n        unsubFns.push(\n          onChildAdded(data.ref, nestedData => {\n            peerSignals[peerId] ??= {}\n\n            if (nestedData.key && nestedData.key in peerSignals[peerId]) {\n              return\n            }\n\n            if (nestedData.key) {\n              peerSignals[peerId][nestedData.key] = true\n            }\n\n            void onMessage(topic, nestedData.val() as StrategyMessage)\n            void remove(nestedData.ref)\n          })\n        )\n      })\n\n      return () => {\n        selfCleanup()\n        unsubFns.forEach(unsub => unsub())\n\n        if (subscriptionTokens[key] === subscriptionToken) {\n          void remove(selfRef)\n        }\n      }\n    }\n\n    const pendingRoomOwners: StrategyMessage[] = []\n\n    const processRoomEntry = (value: Record<string, unknown> | null): void => {\n      const owner = value?.[presencePath]\n\n      if (!owner) {\n        return\n      }\n\n      if (!didSyncRoom) {\n        pendingRoomOwners.push(owner as Record<string, unknown>)\n        return\n      }\n\n      void onMessage(topic, owner as StrategyMessage)\n    }\n\n    let didSyncRoom = false\n\n    unsubFns.push(\n      onValue(\n        roomRef,\n        () => {\n          didSyncRoom = true\n          pendingRoomOwners.forEach(owner => {\n            void onMessage(topic, owner)\n          })\n          pendingRoomOwners.length = 0\n        },\n        {onlyOnce: true}\n      ),\n\n      onChildAdded(roomRef, data => {\n        processRoomEntry(data.val() as Record<string, unknown> | null)\n      })\n    )\n\n    return () => {\n      unsubFns.forEach(unsub => unsub())\n\n      if (subscriptionTokens[key] !== subscriptionToken) {\n        return\n      }\n\n      if (presenceRefs[key]) {\n        presenceCleanups[key]?.()\n        void remove(presenceRefs[key])\n        delete presenceRefs[key]\n        delete presenceMessages[key]\n        delete presenceCleanups[key]\n      }\n\n      delete subscriptionTokens[key]\n    }\n  },\n\n  publishTopic: (rootRef, topic, msg, {kind, rootTopic, selfTopic}) => {\n    const roomRef = child(rootRef, rootTopic)\n    const key = roomKey(rootTopic, selfTopic)\n\n    if (kind === 'announce') {\n      presenceMessages[key] = {\n        [presencePath]:\n          typeof msg === 'string' ? fromJson<Record<string, unknown>>(msg) : msg\n      }\n\n      const presenceRef =\n        presenceRefs[key] ?? (presenceRefs[key] = push(roomRef))\n\n      if (!presenceCleanups[key]) {\n        let didConnect = false\n\n        void onDisconnect(presenceRef).remove()\n        void set(presenceRef, presenceMessages[key])\n        presenceCleanups[key] = onValue(\n          child(rootRef.root, '.info/connected'),\n          snapshot => {\n            if (snapshot.val() !== true) {\n              return\n            }\n\n            const isReconnect = didConnect\n            didConnect = true\n\n            if (!isReconnect) {\n              return\n            }\n\n            void onDisconnect(presenceRef)\n              .remove()\n              .then(() => {\n                if (presenceRefs[key] === presenceRef) {\n                  void set(presenceRef, presenceMessages[key])\n                }\n              })\n          }\n        )\n      }\n\n      return\n    }\n\n    const signalRef = push(child(roomRef, getPath(topic, selfId)))\n\n    void onDisconnect(signalRef).remove()\n    void set(signalRef, msg)\n    return undefined\n  },\n\n  unpublishTopic: (rootRef, _topic, {rootTopic, selfTopic}) => {\n    const key = roomKey(rootTopic, selfTopic)\n\n    if (presenceRefs[key]) {\n      presenceCleanups[key]?.()\n      void remove(presenceRefs[key])\n      delete presenceRefs[key]\n      delete presenceMessages[key]\n      delete presenceCleanups[key]\n    }\n  }\n})\n\nexport {selfId}\n\nexport type * from '@trystero-p2p/core'\n"],"mappings":"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"}