gemi 0.122.0 → 0.124.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/dist/ai/index.js +2 -2
- package/dist/ai/index.js.map +1 -1
- package/dist/app/App.d.ts +9 -0
- package/dist/app/App.d.ts.map +1 -1
- package/dist/app/index.js +1 -1
- package/dist/auth/AuthController.d.ts.map +1 -1
- package/dist/bin/gemi.js +4 -1
- package/dist/bin/gemi.js.map +3 -3
- package/dist/bun/plugin.js +1 -1
- package/dist/bun/preload.js +1 -1
- package/dist/{chunk-7ftj43vp.js → chunk-019wz697.js} +2 -2
- package/dist/{chunk-7ftj43vp.js.map → chunk-019wz697.js.map} +1 -1
- package/dist/{chunk-s5k3yq6t.js → chunk-121p4g9j.js} +2 -2
- package/dist/{chunk-s5k3yq6t.js.map → chunk-121p4g9j.js.map} +1 -1
- package/dist/{chunk-tewhms9w.js → chunk-17dndh5z.js} +2 -2
- package/dist/{chunk-3gsv1vv1.js.map → chunk-17dndh5z.js.map} +1 -1
- package/dist/{chunk-r01aqf0q.js → chunk-1qwhty9p.js} +2 -2
- package/dist/{chunk-r01aqf0q.js.map → chunk-1qwhty9p.js.map} +1 -1
- package/dist/{chunk-471htqmz.js → chunk-1rnh9qrp.js} +2 -2
- package/dist/{chunk-471htqmz.js.map → chunk-1rnh9qrp.js.map} +1 -1
- package/dist/{chunk-dedqr5wb.js → chunk-2affxmkn.js} +2 -2
- package/dist/{chunk-dedqr5wb.js.map → chunk-2affxmkn.js.map} +1 -1
- package/dist/{chunk-19dpzp4y.js → chunk-2n4490pk.js} +3 -3
- package/dist/{chunk-19dpzp4y.js.map → chunk-2n4490pk.js.map} +1 -1
- package/dist/{chunk-9xyxbxgk.js → chunk-30xxc6r7.js} +2 -2
- package/dist/{chunk-9xyxbxgk.js.map → chunk-30xxc6r7.js.map} +1 -1
- package/dist/{chunk-vx7hvyvq.js → chunk-34fkx6kb.js} +2 -2
- package/dist/{chunk-vx7hvyvq.js.map → chunk-34fkx6kb.js.map} +1 -1
- package/dist/{chunk-5nj94s66.js → chunk-3hqwz48c.js} +4 -4
- package/dist/{chunk-5nj94s66.js.map → chunk-3hqwz48c.js.map} +1 -1
- package/dist/{chunk-dhbgrv58.js → chunk-3j6s5bmx.js} +1 -1
- package/dist/{chunk-cskqet01.js → chunk-3y294mb8.js} +2 -2
- package/dist/{chunk-cskqet01.js.map → chunk-3y294mb8.js.map} +1 -1
- package/dist/{chunk-qsz9241c.js → chunk-4drnyymm.js} +2 -2
- package/dist/{chunk-qsz9241c.js.map → chunk-4drnyymm.js.map} +1 -1
- package/dist/{chunk-zg6hmc48.js → chunk-5e22tm7m.js} +1 -1
- package/dist/chunk-5j50q4g8.js +57 -0
- package/dist/{chunk-3s21p9n2.js.map → chunk-5j50q4g8.js.map} +19 -9
- package/dist/{chunk-0k02tza1.js → chunk-65s834td.js} +1 -1
- package/dist/{chunk-7mmw0jr2.js → chunk-675kzd7z.js} +2 -2
- package/dist/{chunk-7mmw0jr2.js.map → chunk-675kzd7z.js.map} +1 -1
- package/dist/{chunk-gy81nd9a.js → chunk-6bgb1nhq.js} +2 -2
- package/dist/{chunk-gy81nd9a.js.map → chunk-6bgb1nhq.js.map} +1 -1
- package/dist/chunk-70nnh0y7.js +5 -0
- package/dist/{chunk-htgmy3x7.js.map → chunk-70nnh0y7.js.map} +1 -1
- package/dist/{chunk-er8acmch.js → chunk-75ewjc4a.js} +2 -2
- package/dist/{chunk-er8acmch.js.map → chunk-75ewjc4a.js.map} +1 -1
- package/dist/{chunk-2c0s0561.js → chunk-7gfdts60.js} +2 -2
- package/dist/{chunk-2c0s0561.js.map → chunk-7gfdts60.js.map} +1 -1
- package/dist/{chunk-zefs2b6h.js → chunk-8m2htvjn.js} +2 -2
- package/dist/{chunk-zefs2b6h.js.map → chunk-8m2htvjn.js.map} +1 -1
- package/dist/{chunk-dwedx1jb.js → chunk-92sk56n0.js} +3 -3
- package/dist/{chunk-dwedx1jb.js.map → chunk-92sk56n0.js.map} +1 -1
- package/dist/{chunk-yds7zwc3.js → chunk-96a1459v.js} +2 -2
- package/dist/{chunk-yds7zwc3.js.map → chunk-96a1459v.js.map} +1 -1
- package/dist/{chunk-c7qs1xp3.js → chunk-9tr92nwy.js} +2 -2
- package/dist/{chunk-c7qs1xp3.js.map → chunk-9tr92nwy.js.map} +1 -1
- package/dist/{chunk-1wnnkde0.js → chunk-a3g7y70h.js} +3 -3
- package/dist/{chunk-1wnnkde0.js.map → chunk-a3g7y70h.js.map} +1 -1
- package/dist/{chunk-zrcd9hxr.js → chunk-b5afy6k9.js} +2 -2
- package/dist/{chunk-zrcd9hxr.js.map → chunk-b5afy6k9.js.map} +1 -1
- package/dist/{chunk-c0v61fp5.js → chunk-bnt8kx6x.js} +2 -2
- package/dist/{chunk-c0v61fp5.js.map → chunk-bnt8kx6x.js.map} +1 -1
- package/dist/{chunk-5tdxbwzg.js → chunk-bt9qv2jj.js} +3 -3
- package/dist/{chunk-5tdxbwzg.js.map → chunk-bt9qv2jj.js.map} +1 -1
- package/dist/{chunk-y6wt5b6t.js → chunk-c4jnxmf2.js} +1 -1
- package/dist/{chunk-ep0baa9g.js → chunk-cfdwsvtx.js} +2 -2
- package/dist/{chunk-ep0baa9g.js.map → chunk-cfdwsvtx.js.map} +1 -1
- package/dist/{chunk-51w5ktv2.js → chunk-dxfhhw5h.js} +2 -2
- package/dist/{chunk-51w5ktv2.js.map → chunk-dxfhhw5h.js.map} +1 -1
- package/dist/{chunk-pn2x59ge.js → chunk-f29pktad.js} +3 -3
- package/dist/{chunk-pn2x59ge.js.map → chunk-f29pktad.js.map} +1 -1
- package/dist/chunk-f4cmz31h.js +58 -0
- package/dist/{chunk-3rvn9a76.js.map → chunk-f4cmz31h.js.map} +3 -3
- package/dist/{chunk-0ejjgzwc.js → chunk-fn2qfwgx.js} +2 -2
- package/dist/{chunk-0ejjgzwc.js.map → chunk-fn2qfwgx.js.map} +1 -1
- package/dist/{chunk-pemhmjzp.js → chunk-fvrmptd0.js} +3 -3
- package/dist/{chunk-pemhmjzp.js.map → chunk-fvrmptd0.js.map} +1 -1
- package/dist/{chunk-qy8c0gzr.js → chunk-fw7ds23a.js} +2 -2
- package/dist/{chunk-qy8c0gzr.js.map → chunk-fw7ds23a.js.map} +1 -1
- package/dist/{chunk-fenc5tpv.js → chunk-h3p8qzft.js} +2 -2
- package/dist/{chunk-fenc5tpv.js.map → chunk-h3p8qzft.js.map} +1 -1
- package/dist/{chunk-wjkh2gem.js → chunk-j7v7dft7.js} +1 -1
- package/dist/chunk-jctb7v53.js +4 -0
- package/dist/chunk-jctb7v53.js.map +10 -0
- package/dist/{chunk-wqe47c0j.js → chunk-jys8y39p.js} +2 -2
- package/dist/{chunk-wqe47c0j.js.map → chunk-jys8y39p.js.map} +1 -1
- package/dist/{chunk-m83zm86m.js → chunk-kc924zjz.js} +2 -2
- package/dist/{chunk-m83zm86m.js.map → chunk-kc924zjz.js.map} +1 -1
- package/dist/{chunk-32n8t8x6.js → chunk-kzsjyvvc.js} +2 -2
- package/dist/{chunk-32n8t8x6.js.map → chunk-kzsjyvvc.js.map} +1 -1
- package/dist/{chunk-meg8rtr6.js → chunk-mqtfaqx5.js} +2 -2
- package/dist/{chunk-meg8rtr6.js.map → chunk-mqtfaqx5.js.map} +1 -1
- package/dist/{chunk-7g649j8h.js → chunk-mrswc6qw.js} +2 -2
- package/dist/{chunk-7g649j8h.js.map → chunk-mrswc6qw.js.map} +1 -1
- package/dist/{chunk-gc2dz11k.js → chunk-mvkz22p4.js} +2 -2
- package/dist/{chunk-gc2dz11k.js.map → chunk-mvkz22p4.js.map} +1 -1
- package/dist/{chunk-4acpw92g.js → chunk-n0r5xyk2.js} +3 -3
- package/dist/{chunk-4acpw92g.js.map → chunk-n0r5xyk2.js.map} +1 -1
- package/dist/{chunk-cca0grq3.js → chunk-n1a2zj5z.js} +2 -2
- package/dist/{chunk-cca0grq3.js.map → chunk-n1a2zj5z.js.map} +1 -1
- package/dist/{chunk-d5f1m3d2.js → chunk-nkzmk7v3.js} +2 -2
- package/dist/{chunk-d5f1m3d2.js.map → chunk-nkzmk7v3.js.map} +1 -1
- package/dist/{chunk-3gsv1vv1.js → chunk-nn2jeat2.js} +2 -2
- package/dist/{chunk-tewhms9w.js.map → chunk-nn2jeat2.js.map} +1 -1
- package/dist/chunk-nnarmbjv.js +5 -0
- package/dist/chunk-nnarmbjv.js.map +10 -0
- package/dist/{chunk-h43jp8ws.js → chunk-pe46a5a5.js} +2 -2
- package/dist/{chunk-h43jp8ws.js.map → chunk-pe46a5a5.js.map} +1 -1
- package/dist/{chunk-6yhpprzs.js → chunk-pmyh33v5.js} +2 -2
- package/dist/{chunk-6yhpprzs.js.map → chunk-pmyh33v5.js.map} +1 -1
- package/dist/{chunk-mr94nfp1.js → chunk-pya4rqm2.js} +3 -3
- package/dist/{chunk-mr94nfp1.js.map → chunk-pya4rqm2.js.map} +1 -1
- package/dist/{chunk-jaa6xkeh.js → chunk-qdj8ek8z.js} +2 -2
- package/dist/{chunk-jaa6xkeh.js.map → chunk-qdj8ek8z.js.map} +1 -1
- package/dist/{chunk-0yj0s1pn.js → chunk-qhz0x97z.js} +2 -2
- package/dist/{chunk-0yj0s1pn.js.map → chunk-qhz0x97z.js.map} +1 -1
- package/dist/chunk-qycwjzsv.js +9 -0
- package/dist/chunk-qycwjzsv.js.map +10 -0
- package/dist/{chunk-nz8z8jr1.js → chunk-rpyczdta.js} +3 -3
- package/dist/{chunk-nz8z8jr1.js.map → chunk-rpyczdta.js.map} +1 -1
- package/dist/{chunk-9rr5h818.js → chunk-rtawbqec.js} +3 -3
- package/dist/{chunk-9rr5h818.js.map → chunk-rtawbqec.js.map} +1 -1
- package/dist/{chunk-3wnghdsx.js → chunk-rttrpjqk.js} +2 -2
- package/dist/{chunk-3wnghdsx.js.map → chunk-rttrpjqk.js.map} +1 -1
- package/dist/{chunk-m53fgf20.js → chunk-s5jxc63j.js} +3 -3
- package/dist/{chunk-m53fgf20.js.map → chunk-s5jxc63j.js.map} +1 -1
- package/dist/{chunk-48gmyaxc.js → chunk-sd0c2001.js} +2 -2
- package/dist/{chunk-48gmyaxc.js.map → chunk-sd0c2001.js.map} +1 -1
- package/dist/chunk-skf89vrv.js +5 -0
- package/dist/chunk-skf89vrv.js.map +10 -0
- package/dist/chunk-szn2n976.js +24 -0
- package/dist/{chunk-2yw4yezc.js.map → chunk-szn2n976.js.map} +3 -3
- package/dist/{chunk-gemzbw7f.js → chunk-v2ggafz1.js} +2 -2
- package/dist/{chunk-gemzbw7f.js.map → chunk-v2ggafz1.js.map} +1 -1
- package/dist/{chunk-rhek27sj.js → chunk-v8v5qfnh.js} +3 -3
- package/dist/{chunk-rhek27sj.js.map → chunk-v8v5qfnh.js.map} +1 -1
- package/dist/{chunk-d2c3947c.js → chunk-w3812s34.js} +2 -2
- package/dist/{chunk-d2c3947c.js.map → chunk-w3812s34.js.map} +1 -1
- package/dist/{chunk-tk135gmj.js → chunk-w4z6x54b.js} +3 -3
- package/dist/{chunk-tk135gmj.js.map → chunk-w4z6x54b.js.map} +1 -1
- package/dist/{chunk-65beps2q.js → chunk-w61sphn5.js} +3 -3
- package/dist/{chunk-65beps2q.js.map → chunk-w61sphn5.js.map} +1 -1
- package/dist/{chunk-cvr5624r.js → chunk-wbg2tza5.js} +1 -1
- package/dist/{chunk-9d2d08qm.js → chunk-y434jc1c.js} +2 -2
- package/dist/{chunk-9d2d08qm.js.map → chunk-y434jc1c.js.map} +1 -1
- package/dist/{chunk-shfe0b22.js → chunk-ykz38t0t.js} +2 -2
- package/dist/{chunk-shfe0b22.js.map → chunk-ykz38t0t.js.map} +1 -1
- package/dist/{chunk-krk7mcgm.js → chunk-yzhv93b0.js} +3 -3
- package/dist/{chunk-krk7mcgm.js.map → chunk-yzhv93b0.js.map} +1 -1
- package/dist/{chunk-rhvc1axc.js → chunk-z5jydj5r.js} +2 -2
- package/dist/{chunk-rhvc1axc.js.map → chunk-z5jydj5r.js.map} +1 -1
- package/dist/chunk-zmf3vhex.js +10 -0
- package/dist/{chunk-dyp0wg9e.js.map → chunk-zmf3vhex.js.map} +8 -4
- package/dist/{chunk-sx340k85.js → chunk-zsfwcdqn.js} +2 -2
- package/dist/{chunk-sx340k85.js.map → chunk-zsfwcdqn.js.map} +1 -1
- package/dist/{chunk-4fx8p02c.js → chunk-zsvav1dk.js} +2 -2
- package/dist/{chunk-4fx8p02c.js.map → chunk-zsvav1dk.js.map} +1 -1
- package/dist/chunks/{ThemeProvider-BLI4zpLu.js → ThemeProvider-CbOx8IRl.js} +581 -2
- package/dist/chunks/ThemeProvider-CbOx8IRl.js.map +1 -0
- package/dist/client/auth/useEmailCode.d.ts.map +1 -1
- package/dist/client/auth/useSignIn.d.ts.map +1 -1
- package/dist/client/auth/useSignOut.d.ts.map +1 -1
- package/dist/client/index.d.ts +5 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +216 -8
- package/dist/client/index.js.map +1 -1
- package/dist/client/init.d.ts +8 -0
- package/dist/client/init.d.ts.map +1 -1
- package/dist/client/realtime/FakeSocket.d.ts +47 -0
- package/dist/client/realtime/FakeSocket.d.ts.map +1 -0
- package/dist/client/realtime/RealtimeClient.d.ts +169 -0
- package/dist/client/realtime/RealtimeClient.d.ts.map +1 -0
- package/dist/client/realtime/RealtimeContext.d.ts +8 -0
- package/dist/client/realtime/RealtimeContext.d.ts.map +1 -0
- package/dist/client/realtime/useChannelSubscription.d.ts +32 -0
- package/dist/client/realtime/useChannelSubscription.d.ts.map +1 -0
- package/dist/client/rpc.d.ts +11 -0
- package/dist/client/rpc.d.ts.map +1 -1
- package/dist/client/useChannel.d.ts +87 -0
- package/dist/client/useChannel.d.ts.map +1 -0
- package/dist/client/useMutation.d.ts.map +1 -1
- package/dist/client/useQuery.d.ts +13 -0
- package/dist/client/useQuery.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/console/run.js +2 -2
- package/dist/console/run.js.map +1 -1
- package/dist/container/index.js +2 -2
- package/dist/container/index.js.map +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/index.js +2 -2
- package/dist/email/index.js.map +1 -1
- package/dist/facades/Broadcast.d.ts +68 -0
- package/dist/facades/Broadcast.d.ts.map +1 -0
- package/dist/facades/index.d.ts +1 -0
- package/dist/facades/index.d.ts.map +1 -1
- package/dist/facades/index.js +2 -2
- package/dist/facades/index.js.map +1 -1
- package/dist/foundation/index.js +2 -2
- package/dist/foundation/index.js.map +1 -1
- package/dist/gemi.d.ts +19 -6
- package/dist/http/ChannelRouter.d.ts +171 -0
- package/dist/http/ChannelRouter.d.ts.map +1 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/http/index.js.map +1 -1
- package/dist/i18n/dictionaryRuntime.js +2 -2
- package/dist/i18n/dictionaryRuntime.js.map +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +2 -2
- package/dist/kernel/Kernel.d.ts +6 -0
- package/dist/kernel/Kernel.d.ts.map +1 -1
- package/dist/kernel/index.js +3 -3
- package/dist/kernel/index.js.map +5 -6
- package/dist/kernel/providers.d.ts +2 -2
- package/dist/kernel/providers.d.ts.map +1 -1
- package/dist/orm/context.d.ts.map +1 -1
- package/dist/orm/index.js +2 -2
- package/dist/orm/index.js.map +1 -1
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/httpDev.d.ts +1 -2
- package/dist/server/httpDev.d.ts.map +1 -1
- package/dist/server/httpProd.d.ts +1 -1
- package/dist/server/httpProd.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +3 -3
- package/dist/server/shutdown.d.ts +9 -0
- package/dist/server/shutdown.d.ts.map +1 -1
- package/dist/services/broadcast/BroadcastDriver.d.ts +75 -0
- package/dist/services/broadcast/BroadcastDriver.d.ts.map +1 -0
- package/dist/services/broadcast/BroadcastEvent.d.ts +67 -0
- package/dist/services/broadcast/BroadcastEvent.d.ts.map +1 -0
- package/dist/services/broadcast/BroadcastManager.d.ts +148 -0
- package/dist/services/broadcast/BroadcastManager.d.ts.map +1 -0
- package/dist/services/broadcast/BroadcastServiceProvider.d.ts +26 -0
- package/dist/services/broadcast/BroadcastServiceProvider.d.ts.map +1 -0
- package/dist/services/broadcast/FakeBroadcastManager.d.ts +45 -0
- package/dist/services/broadcast/FakeBroadcastManager.d.ts.map +1 -0
- package/dist/services/broadcast/MemoryBroadcastDriver.d.ts +23 -0
- package/dist/services/broadcast/MemoryBroadcastDriver.d.ts.map +1 -0
- package/dist/services/broadcast/RedisBroadcastDriver.d.ts +155 -0
- package/dist/services/broadcast/RedisBroadcastDriver.d.ts.map +1 -0
- package/dist/services/broadcast/SocketHub.d.ts +201 -0
- package/dist/services/broadcast/SocketHub.d.ts.map +1 -0
- package/dist/services/broadcast/brand.d.ts +16 -0
- package/dist/services/broadcast/brand.d.ts.map +1 -0
- package/dist/services/broadcast/channels.d.ts +77 -0
- package/dist/services/broadcast/channels.d.ts.map +1 -0
- package/dist/services/broadcast/config.d.ts +106 -0
- package/dist/services/broadcast/config.d.ts.map +1 -0
- package/dist/services/broadcast/protocol.d.ts +123 -0
- package/dist/services/broadcast/protocol.d.ts.map +1 -0
- package/dist/services/broadcast/socketTag.d.ts +17 -0
- package/dist/services/broadcast/socketTag.d.ts.map +1 -0
- package/dist/services/broadcast/types.d.ts +27 -0
- package/dist/services/broadcast/types.d.ts.map +1 -0
- package/dist/services/broadcast/wire.d.ts +35 -0
- package/dist/services/broadcast/wire.d.ts.map +1 -0
- package/dist/services/broadcast/workerWarning.d.ts +10 -0
- package/dist/services/broadcast/workerWarning.d.ts.map +1 -0
- package/dist/services/events/EventManager.d.ts +6 -0
- package/dist/services/events/EventManager.d.ts.map +1 -1
- package/dist/services/index.d.ts +10 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +9 -9
- package/dist/services/index.js.map +4 -3
- package/dist/services/queue/WorkerProcess.d.ts.map +1 -1
- package/dist/services/queue/work.js +2 -2
- package/dist/services/queue/work.js.map +5 -4
- package/dist/services/router/config.d.ts +7 -0
- package/dist/services/router/config.d.ts.map +1 -1
- package/dist/stats/route-table.js +1 -1
- package/dist/support/index.js +2 -2
- package/dist/support/index.js.map +1 -1
- package/dist/testing/Page.d.ts +8 -0
- package/dist/testing/Page.d.ts.map +1 -1
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +174 -8
- package/dist/testing/index.js.map +1 -1
- package/dist/vitest/index.js +1 -1
- package/package.json +1 -1
- package/skills/gemi-react-best-practices/SKILL.md +3 -1
- package/skills/gemi-react-best-practices/rules/query-live-over-polling.md +56 -0
- package/dist/chunk-2n3pgfnb.js +0 -5
- package/dist/chunk-2n3pgfnb.js.map +0 -10
- package/dist/chunk-2yw4yezc.js +0 -24
- package/dist/chunk-3rvn9a76.js +0 -58
- package/dist/chunk-3s21p9n2.js +0 -57
- package/dist/chunk-dyp0wg9e.js +0 -10
- package/dist/chunk-htgmy3x7.js +0 -5
- package/dist/chunk-krtybgz1.js +0 -4
- package/dist/chunk-krtybgz1.js.map +0 -10
- package/dist/chunk-s07v65x6.js +0 -9
- package/dist/chunk-s07v65x6.js.map +0 -10
- package/dist/chunks/ThemeProvider-BLI4zpLu.js.map +0 -1
- /package/dist/{chunk-dhbgrv58.js.map → chunk-3j6s5bmx.js.map} +0 -0
- /package/dist/{chunk-zg6hmc48.js.map → chunk-5e22tm7m.js.map} +0 -0
- /package/dist/{chunk-0k02tza1.js.map → chunk-65s834td.js.map} +0 -0
- /package/dist/{chunk-y6wt5b6t.js.map → chunk-c4jnxmf2.js.map} +0 -0
- /package/dist/{chunk-wjkh2gem.js.map → chunk-j7v7dft7.js.map} +0 -0
- /package/dist/{chunk-cvr5624r.js.map → chunk-wbg2tza5.js.map} +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { Server, ServerWebSocket, WebSocketHandler } from "bun";
|
|
2
|
+
import type { Application } from "../../foundation/Application";
|
|
3
|
+
import { type CarriedContext } from "../../http/requestContext";
|
|
4
|
+
import type { BroadcastRevocation } from "./BroadcastDriver";
|
|
5
|
+
import type { BroadcastManager } from "./BroadcastManager";
|
|
6
|
+
/** What the hub keeps on each of its sockets (`ws.data`). */
|
|
7
|
+
export interface GemiSocketData {
|
|
8
|
+
/** Marks a broadcast socket, as opposed to the dev server's HMR relay. */
|
|
9
|
+
readonly gemiSocket: true;
|
|
10
|
+
/** The hub that accepted it. After a dev reload that may not be the current one. */
|
|
11
|
+
readonly hub: SocketHub;
|
|
12
|
+
readonly socketId: string;
|
|
13
|
+
readonly tag: string;
|
|
14
|
+
/** The upgrade request, kept to rebuild a request context for every `sub`. */
|
|
15
|
+
readonly request: {
|
|
16
|
+
url: string;
|
|
17
|
+
headers: [string, string][];
|
|
18
|
+
};
|
|
19
|
+
/** What the global middleware resolved for the upgrade: its user. */
|
|
20
|
+
readonly carried: CarriedContext | null;
|
|
21
|
+
/** The signed-in user's id at the upgrade, for limits and `revoke({ user })`. */
|
|
22
|
+
readonly userId: string | null;
|
|
23
|
+
readonly ip: string | null;
|
|
24
|
+
/** Subscription id → its topic, `null` while it is being authorized. */
|
|
25
|
+
readonly subs: Map<string, Subscription>;
|
|
26
|
+
/** Topic → how many of this socket's subscriptions are on it. */
|
|
27
|
+
readonly topics: Map<string, number>;
|
|
28
|
+
rate: {
|
|
29
|
+
windowStart: number;
|
|
30
|
+
subs: number;
|
|
31
|
+
pings: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The process, IP and user connection counts this socket holds. Taken
|
|
35
|
+
* before the upgrade's first `await`, so concurrent upgrades cannot
|
|
36
|
+
* overshoot the caps, and given back exactly once.
|
|
37
|
+
*/
|
|
38
|
+
reservation: Reservation | null;
|
|
39
|
+
/** The hub has closed it (backpressure, revoke, bye); its close is on the way. */
|
|
40
|
+
closing: boolean;
|
|
41
|
+
closed: boolean;
|
|
42
|
+
}
|
|
43
|
+
interface Subscription {
|
|
44
|
+
pattern: string;
|
|
45
|
+
params: Record<string, string | number>;
|
|
46
|
+
topic: string | null;
|
|
47
|
+
}
|
|
48
|
+
interface Reservation {
|
|
49
|
+
ip: string | null;
|
|
50
|
+
userId: string | null;
|
|
51
|
+
}
|
|
52
|
+
type GemiSocket = ServerWebSocket<GemiSocketData>;
|
|
53
|
+
/**
|
|
54
|
+
* The WebSocket side of broadcasting, one per HTTP server process: accepts
|
|
55
|
+
* upgrades on the configured path, speaks the `gemi.v1` protocol (see
|
|
56
|
+
* `protocol.ts`), authorizes every subscription through the app's
|
|
57
|
+
* `ChannelRouter`, and delivers what the driver hands it with
|
|
58
|
+
* `server.publish`.
|
|
59
|
+
*
|
|
60
|
+
* Nothing here keeps a queue or a log. A socket that falls behind is closed
|
|
61
|
+
* (`1013`), one whose user signs out is closed (`4001`), and a server that
|
|
62
|
+
* stops says `bye` (`1012`); in every case the client reconnects and
|
|
63
|
+
* refetches what it shows.
|
|
64
|
+
*/
|
|
65
|
+
export declare class SocketHub {
|
|
66
|
+
private readonly manager;
|
|
67
|
+
private readonly application;
|
|
68
|
+
static token: string;
|
|
69
|
+
private server;
|
|
70
|
+
private readonly sockets;
|
|
71
|
+
private readonly byTopic;
|
|
72
|
+
private readonly topicReady;
|
|
73
|
+
private readonly perIp;
|
|
74
|
+
private readonly perUser;
|
|
75
|
+
/** Sockets held or being upgraded: the process cap's count. */
|
|
76
|
+
private connections;
|
|
77
|
+
/** Bumped by every topic revocation; see `revokedAt`. */
|
|
78
|
+
private revocationSeq;
|
|
79
|
+
/** Topic → the `revocationSeq` of its last revocation. */
|
|
80
|
+
private readonly revokedAt;
|
|
81
|
+
private closing;
|
|
82
|
+
/** Resolves once every socket `shutdown` closed is gone, or was cut. */
|
|
83
|
+
private closed;
|
|
84
|
+
private onAllClosed;
|
|
85
|
+
private startPromise;
|
|
86
|
+
constructor(manager: BroadcastManager, application: Application);
|
|
87
|
+
private get config();
|
|
88
|
+
/** Whether `req` is for the socket endpoint (an upgrade or not). */
|
|
89
|
+
matches(req: Request): boolean;
|
|
90
|
+
/** Sockets open on this process. */
|
|
91
|
+
get size(): number;
|
|
92
|
+
/**
|
|
93
|
+
* Starts delivering: the driver's frames for this process go out with
|
|
94
|
+
* `server.publish`. Call once the server is listening.
|
|
95
|
+
*/
|
|
96
|
+
start(server: Server<any>): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Answers a request for the socket endpoint: upgrades it, or refuses it
|
|
99
|
+
* with a status saying why. `undefined` once Bun has taken it over.
|
|
100
|
+
*/
|
|
101
|
+
upgrade(req: Request, server: Server<any>): Promise<Response | undefined>;
|
|
102
|
+
private tryUpgrade;
|
|
103
|
+
private authenticate;
|
|
104
|
+
/**
|
|
105
|
+
* `true`, or why the upgrade is refused. A browser always sends `Origin` on
|
|
106
|
+
* a WebSocket upgrade, and cookies ride along with it whatever the page
|
|
107
|
+
* that opened it, so an upgrade carrying cookies must come from an allowed
|
|
108
|
+
* origin and one without `Origin` is refused. A native client sends its
|
|
109
|
+
* token in a header and no cookies, and may leave `Origin` out.
|
|
110
|
+
*/
|
|
111
|
+
private checkOrigin;
|
|
112
|
+
/** Whether `origin` may open a socket. */
|
|
113
|
+
originAllowed(origin: string, req: Request): boolean;
|
|
114
|
+
private reserve;
|
|
115
|
+
private release;
|
|
116
|
+
/** `Bun.serve`'s `websocket` option. */
|
|
117
|
+
get websocket(): WebSocketHandler<GemiSocketData>;
|
|
118
|
+
/** The server-wide socket settings, also for a handler shared with HMR in dev. */
|
|
119
|
+
socketOptions(): {
|
|
120
|
+
readonly maxPayloadLength: number;
|
|
121
|
+
readonly idleTimeout: number;
|
|
122
|
+
readonly backpressureLimit: number;
|
|
123
|
+
readonly closeOnBackpressureLimit: true;
|
|
124
|
+
readonly sendPings: true;
|
|
125
|
+
readonly perMessageDeflate: false;
|
|
126
|
+
};
|
|
127
|
+
onOpen(ws: GemiSocket): void;
|
|
128
|
+
onMessage(ws: GemiSocket, message: string | Buffer): void;
|
|
129
|
+
onClose(ws: GemiSocket): void;
|
|
130
|
+
private rateWindow;
|
|
131
|
+
/**
|
|
132
|
+
* Counts a `sub`. Only `sub` frames count: an `unsub` costs the server
|
|
133
|
+
* nothing to speak of, and counting it would charge a component's every
|
|
134
|
+
* mount and unmount twice.
|
|
135
|
+
*/
|
|
136
|
+
private countSub;
|
|
137
|
+
/** `false` when the socket was closed for pinging too often. */
|
|
138
|
+
private countPing;
|
|
139
|
+
private subscribe;
|
|
140
|
+
/** Runs the channel's authorization for `sub`, as the socket's upgrade request. */
|
|
141
|
+
private authorize;
|
|
142
|
+
private unsubscribeFrame;
|
|
143
|
+
/** Puts `ws` on `topic`, and resolves once the driver delivers the topic here. */
|
|
144
|
+
private join;
|
|
145
|
+
private leave;
|
|
146
|
+
/**
|
|
147
|
+
* The driver's delivery for this process. A socket already over the
|
|
148
|
+
* backpressure limit is closed first (1013): it would only fall further
|
|
149
|
+
* behind, and its client recovers by reconnecting and refetching.
|
|
150
|
+
*/
|
|
151
|
+
deliver(topic: string, frame: string): void;
|
|
152
|
+
/**
|
|
153
|
+
* The driver may have lost frames, revocations included (a Redis
|
|
154
|
+
* subscriber that reconnected). Clients are told to resync, and every
|
|
155
|
+
* subscription is authorized again, as after `revoke({ channel })`, so
|
|
156
|
+
* one whose access a missed revocation ended is denied. Spread over
|
|
157
|
+
* `GAP_REAUTHORIZE_SPREAD_MS`, so a Redis blip does not send every
|
|
158
|
+
* authorization to the database at once.
|
|
159
|
+
*/
|
|
160
|
+
private onGap;
|
|
161
|
+
/** Applies a revocation that reached this process. */
|
|
162
|
+
applyRevocation(revocation: BroadcastRevocation): void;
|
|
163
|
+
/**
|
|
164
|
+
* After `revoke({ channel })`: every subscription `ws` has on `topic` is
|
|
165
|
+
* authorized again, as the socket's upgrade request. The ones refused are
|
|
166
|
+
* denied with `revoked`; the others stay, and get a fresh `subscribed` so
|
|
167
|
+
* the client resyncs whatever was published while the topic was paused.
|
|
168
|
+
* The socket receives nothing on the topic until the answers are in.
|
|
169
|
+
*/
|
|
170
|
+
private reauthorize;
|
|
171
|
+
private sendAll;
|
|
172
|
+
/**
|
|
173
|
+
* Stops accepting sockets and says `bye` to every open one, with a
|
|
174
|
+
* jittered `retryAfter` (1–5 s) so a restart's reconnects do not arrive at
|
|
175
|
+
* once, then closes them (1012). Sockets that have not closed after
|
|
176
|
+
* `terminateAfterMs` are cut. Idempotent.
|
|
177
|
+
*
|
|
178
|
+
* Resolves once every socket has closed, or at `terminateAfterMs` when the
|
|
179
|
+
* rest were cut: by the hub's own count, which the drain waits on instead
|
|
180
|
+
* of Bun's (on a loaded CI runner, Bun's graceful `stop()` was seen to wait
|
|
181
|
+
* on terminated sockets for good).
|
|
182
|
+
*/
|
|
183
|
+
shutdown(options?: {
|
|
184
|
+
terminateAfterMs?: number;
|
|
185
|
+
}): Promise<void>;
|
|
186
|
+
/** Whether `shutdown` ran. */
|
|
187
|
+
get isClosing(): boolean;
|
|
188
|
+
private sayBye;
|
|
189
|
+
/** In the application's scope, as a request is: facades resolve from it. */
|
|
190
|
+
private run;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Whether an `allowedOrigins` entry (or `APP_URL` / `HOST_NAME`) admits
|
|
194
|
+
* `url`. An entry is an origin (`https://app.example.com`), a bare host
|
|
195
|
+
* (`app.example.com`), or a wildcard for the subdomains of a host
|
|
196
|
+
* (`*.example.com`, `https://*.example.com`), which does not admit the host
|
|
197
|
+
* itself.
|
|
198
|
+
*/
|
|
199
|
+
export declare function originMatches(allowed: string, url: URL): boolean;
|
|
200
|
+
export {};
|
|
201
|
+
//# sourceMappingURL=SocketHub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SocketHub.d.ts","sourceRoot":"","sources":["../../../services/broadcast/SocketHub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ChannelTarget } from "./channels";
|
|
2
|
+
/**
|
|
3
|
+
* Marks a `BroadcastEvent` instance. A symbol in the global registry rather
|
|
4
|
+
* than `instanceof`, for the reason `Event` gives: a production build can hold
|
|
5
|
+
* two copies of an event class, and `instanceof` would tell them apart.
|
|
6
|
+
*/
|
|
7
|
+
export declare const BROADCAST_EVENT: unique symbol;
|
|
8
|
+
/** What `EventManager` needs of an event to broadcast it. */
|
|
9
|
+
export interface BroadcastableEvent {
|
|
10
|
+
broadcastOn(): ChannelTarget | string | ReadonlyArray<ChannelTarget | string>;
|
|
11
|
+
broadcastAs(): string;
|
|
12
|
+
broadcastWith(): unknown;
|
|
13
|
+
}
|
|
14
|
+
/** Whether `event` is a `BroadcastEvent`, from any copy of gemi. */
|
|
15
|
+
export declare function isBroadcastEvent(event: unknown): event is BroadcastableEvent;
|
|
16
|
+
//# sourceMappingURL=brand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../../../services/broadcast/brand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMhD;;;;GAIG;AACH,eAAO,MAAM,eAAe,eAAoC,CAAC;AAEjE,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC,WAAW,IAAI,aAAa,GAAG,MAAM,GAAG,aAAa,CAAC,aAAa,GAAG,MAAM,CAAC,CAAC;IAC9E,WAAW,IAAI,MAAM,CAAC;IACtB,aAAa,IAAI,OAAO,CAAC;CAC1B;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAM5E"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel names: patterns, the topics they build, and the checks both go
|
|
3
|
+
* through. Shared by the `Broadcast` facade (which builds the topic an emit
|
|
4
|
+
* goes to), `ChannelRouter` (which builds the topic a socket may join) and the
|
|
5
|
+
* fake (which matches a pattern against recorded topics), so the three can
|
|
6
|
+
* never disagree on what `site.:siteId` with `{ siteId: "abc" }` is called.
|
|
7
|
+
*
|
|
8
|
+
* A pattern is dot-separated segments, each either a literal (`site`) or a
|
|
9
|
+
* param (`:siteId`). A topic is the same with every param filled in:
|
|
10
|
+
* `site.abc123`. Param values are restricted to `[A-Za-z0-9_-]`, so a value
|
|
11
|
+
* can never contain a dot and add a segment, and a topic can always be read
|
|
12
|
+
* back as exactly one pattern's.
|
|
13
|
+
*/
|
|
14
|
+
/** The longest topic, in characters. */
|
|
15
|
+
export declare const MAX_TOPIC_LENGTH = 255;
|
|
16
|
+
/** The longest event name, in characters. */
|
|
17
|
+
export declare const MAX_EVENT_NAME_LENGTH = 64;
|
|
18
|
+
/** The names of a pattern's params: `"site.:siteId"` → `"siteId"`. */
|
|
19
|
+
export type ChannelParamNames<P extends string> = P extends `${infer Head}.${infer Rest}` ? SegmentParam<Head> | ChannelParamNames<Rest> : SegmentParam<P>;
|
|
20
|
+
type SegmentParam<S extends string> = S extends `:${infer Name}` ? Name : never;
|
|
21
|
+
/** The params a pattern takes: `"site.:siteId"` → `{ siteId: string | number }`. */
|
|
22
|
+
export type ChannelParams<P extends string> = {
|
|
23
|
+
[K in ChannelParamNames<P>]: string | number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* `[]` for a pattern without params (or a concrete topic), `[params]` for one
|
|
27
|
+
* with them, so `Broadcast.to("status")` and `Broadcast.to("site.:siteId",
|
|
28
|
+
* { siteId })` both type-check and `Broadcast.to("site.:siteId")` does not.
|
|
29
|
+
*/
|
|
30
|
+
export type ChannelParamsArgs<P extends string> = [ChannelParamNames<P>] extends [never] ? [params?: Record<string, never>] : [params: ChannelParams<P>];
|
|
31
|
+
/**
|
|
32
|
+
* A resolved channel: the topic sockets subscribe to, and the pattern it came
|
|
33
|
+
* from when it was built from one. What `BroadcastEvent.channel()` returns and
|
|
34
|
+
* `Broadcast.to()` accepts.
|
|
35
|
+
*/
|
|
36
|
+
export interface ChannelTarget {
|
|
37
|
+
readonly topic: string;
|
|
38
|
+
readonly pattern?: string;
|
|
39
|
+
}
|
|
40
|
+
/** A pattern, topic or param was refused. The message says which and why. */
|
|
41
|
+
export declare class InvalidChannelError extends Error {
|
|
42
|
+
constructor(message: string);
|
|
43
|
+
}
|
|
44
|
+
/** Whether `value` is a `ChannelTarget` rather than a pattern string. */
|
|
45
|
+
export declare function isChannelTarget(value: unknown): value is ChannelTarget;
|
|
46
|
+
/** The segments of a pattern, checked. Throws `InvalidChannelError`. */
|
|
47
|
+
export declare function parsePattern(pattern: string): string[];
|
|
48
|
+
/** The param names of a pattern, in order. */
|
|
49
|
+
export declare function patternParams(pattern: string): string[];
|
|
50
|
+
/**
|
|
51
|
+
* The topic `pattern` names with `params` filled in. Every param must be
|
|
52
|
+
* given, and nothing else: a missing one, an extra one, or a value outside
|
|
53
|
+
* `[A-Za-z0-9_-]` throws `InvalidChannelError`, so a client can never steer a
|
|
54
|
+
* subscription onto a topic its pattern does not describe.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildTopic(pattern: string, params?: Record<string, unknown>): string;
|
|
57
|
+
/** Checks a concrete topic (no params). Throws `InvalidChannelError`. */
|
|
58
|
+
export declare function assertTopic(topic: string): string;
|
|
59
|
+
/** Checks an event name. Throws `InvalidChannelError`. */
|
|
60
|
+
export declare function assertEventName(event: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Whether `topic` is one `pattern` builds. A pattern without params matches
|
|
63
|
+
* only itself, except `"user"`, which also matches every `user.<id>`: that is
|
|
64
|
+
* the topic the router's `user` channel resolves to.
|
|
65
|
+
*/
|
|
66
|
+
export declare function topicMatches(pattern: string, topic: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The channel `ChannelRouter` resolves from the session rather than from
|
|
69
|
+
* params: a socket that subscribes to `"user"` joins `user.<its user's id>`.
|
|
70
|
+
*/
|
|
71
|
+
export declare const USER_CHANNEL = "user";
|
|
72
|
+
/** The topic of a user's own channel. */
|
|
73
|
+
export declare function userTopic(user: {
|
|
74
|
+
id: unknown;
|
|
75
|
+
} | string | number): string;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=channels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../../services/broadcast/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,wCAAwC;AACxC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,6CAA6C;AAC7C,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAOxC,sEAAsE;AACtE,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,GACrF,YAAY,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAC5C,YAAY,CAAC,CAAC,CAAC,CAAC;AAEpB,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,EAAE,GAAG,IAAI,GAAG,KAAK,CAAC;AAEhF,oFAAoF;AACpF,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,IAAI;KAC3C,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,MAAM;CAC7C,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACpF,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,GAChC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/B;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,6EAA6E;AAC7E,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAMtE;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAqCtD;AAED,8CAA8C;AAC9C,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAIvD;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAkCxF;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASjD;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAarD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAYpE;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,yCAAyC;AACzC,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAGzE"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Application } from "../../foundation/Application";
|
|
2
|
+
import type { BroadcastDriver } from "./BroadcastDriver";
|
|
3
|
+
import type { RedisBroadcastDriverOptions } from "./RedisBroadcastDriver";
|
|
4
|
+
export interface BroadcastConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Which processes see an emit: `"memory"`, `"redis"`, a `BroadcastDriver`,
|
|
7
|
+
* or a function returning one, called once with the application.
|
|
8
|
+
*
|
|
9
|
+
* `"memory"` is the default and reaches the sockets of **this process
|
|
10
|
+
* only**: right for one instance whose web processes also run the jobs,
|
|
11
|
+
* wrong for several replicas or a separate `gemi queue:work` worker. See
|
|
12
|
+
* `MemoryBroadcastDriver`.
|
|
13
|
+
*
|
|
14
|
+
* `"redis"` reaches every process of the app through Redis pub/sub (see
|
|
15
|
+
* `redis` below and `RedisBroadcastDriver`). It needs `SECRET` set, the
|
|
16
|
+
* same on every instance; the boot fails without it.
|
|
17
|
+
*/
|
|
18
|
+
driver?: "memory" | "redis" | BroadcastDriver | ((application: Application) => BroadcastDriver);
|
|
19
|
+
/**
|
|
20
|
+
* The `"redis"` driver's settings. The connection defaults to the app's
|
|
21
|
+
* own (`app/config/redis.ts`: `url`, else `REDIS_URL`, and `options`);
|
|
22
|
+
* set `url` / `options` here to use another server. `prefix` (default
|
|
23
|
+
* `"gemi:bc:"`) names the Redis channels: give each app sharing one Redis
|
|
24
|
+
* its own.
|
|
25
|
+
*/
|
|
26
|
+
redis?: Pick<RedisBroadcastDriverOptions, "url" | "options" | "prefix" | "subscribeTimeoutMs" | "healthCheckMs">;
|
|
27
|
+
/**
|
|
28
|
+
* The largest event frame, in bytes, JSON-encoded with its topic and name.
|
|
29
|
+
* A bigger emit throws `BroadcastPayloadTooLargeError` at the call site.
|
|
30
|
+
* Default `16384` (16 KB). Send ids and a change hint, and let the client
|
|
31
|
+
* fetch the data over HTTP.
|
|
32
|
+
*/
|
|
33
|
+
maxEventBytes?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Frames above this size log a warning, once per event name. Default
|
|
36
|
+
* `4096` (4 KB). `false` turns the warning off.
|
|
37
|
+
*/
|
|
38
|
+
warnEventBytes?: number | false;
|
|
39
|
+
/**
|
|
40
|
+
* The WebSocket endpoint, on the app's own origin. Default
|
|
41
|
+
* `"/__gemi/socket"`. Change it on the client too:
|
|
42
|
+
* `init(RootLayout, { realtime: { path } })`.
|
|
43
|
+
*/
|
|
44
|
+
path?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Origins allowed to open a socket, besides the app's own (the request's
|
|
47
|
+
* own host, and `APP_URL` / `HOST_NAME`). An entry is an origin
|
|
48
|
+
* (`"https://app.example.com"`), a bare host (`"app.example.com"`), or a
|
|
49
|
+
* wildcard for a host's subdomains (`"*.example.com"`,
|
|
50
|
+
* `"https://*.example.com"`), which does not admit the host itself.
|
|
51
|
+
*
|
|
52
|
+
* Subdomains are never allowed by default, `route.domains` ones included:
|
|
53
|
+
* a tenant's subdomain is same-site with the app, so the app's cookies ride
|
|
54
|
+
* along with its upgrade, and a WebSocket has no CORS. List a wildcard only
|
|
55
|
+
* for subdomains whose pages you trust. A cookie-carrying upgrade without
|
|
56
|
+
* an `Origin` header is refused, and so is any upgrade from an origin not
|
|
57
|
+
* allowed.
|
|
58
|
+
*/
|
|
59
|
+
allowedOrigins?: string[];
|
|
60
|
+
/** Open sockets per process. Upgrades past it get a `503`. Default `10000`. */
|
|
61
|
+
maxConnectionsPerProcess?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Open sockets per client IP, per process. Default `100`. The address is
|
|
64
|
+
* the TCP peer unless `GEMI_TRUST_PROXY` says which `X-Forwarded-For` entry
|
|
65
|
+
* to believe: behind a proxy without it, every client shares the proxy's
|
|
66
|
+
* address and this caps the whole app per process (the server warns at
|
|
67
|
+
* boot).
|
|
68
|
+
*/
|
|
69
|
+
maxConnectionsPerIp?: number;
|
|
70
|
+
/** Open sockets per signed-in user, per process. Default `50`. */
|
|
71
|
+
maxConnectionsPerUser?: number;
|
|
72
|
+
/** Subscriptions per socket. Past it a `sub` is denied with `limit`. Default `50`. */
|
|
73
|
+
maxChannelsPerSocket?: number;
|
|
74
|
+
/** The largest frame a client may send, in bytes. Default `16384`. */
|
|
75
|
+
maxInboundMessageBytes?: number;
|
|
76
|
+
/**
|
|
77
|
+
* How many bytes may queue up for one socket before it is closed with
|
|
78
|
+
* `1013`; the client reconnects and refetches. No queue is kept per
|
|
79
|
+
* socket beyond this. Default `1048576` (1 MB).
|
|
80
|
+
*/
|
|
81
|
+
backpressureLimit?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Seconds without a frame or a pong before Bun closes a socket. Bun pings
|
|
84
|
+
* idle sockets on its own. Default `120`.
|
|
85
|
+
*/
|
|
86
|
+
idleTimeout?: number;
|
|
87
|
+
/**
|
|
88
|
+
* How often the client pings, in ms, sent to it in `hello`. A client that
|
|
89
|
+
* hears nothing for twice this reconnects, so a connection a proxy dropped
|
|
90
|
+
* silently is noticed within about a minute. Default `25000`.
|
|
91
|
+
*/
|
|
92
|
+
heartbeatMs?: number;
|
|
93
|
+
/**
|
|
94
|
+
* `sub` frames a socket may send per window (an `unsub` is not counted).
|
|
95
|
+
* Past it a `sub` is denied with `rate_limited`, which the client retries
|
|
96
|
+
* on the same socket after a few seconds; at twice it the socket is closed
|
|
97
|
+
* with `1008`. Default `{ limit: 300, windowMs: 60000 }`.
|
|
98
|
+
*/
|
|
99
|
+
subscribeRate?: {
|
|
100
|
+
limit: number;
|
|
101
|
+
windowMs: number;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export declare function defineBroadcastConfig(config: BroadcastConfig): BroadcastConfig;
|
|
105
|
+
export declare function broadcastConfigDefaults(): Required<BroadcastConfig>;
|
|
106
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../services/broadcast/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAI1E,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,eAAe,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,KAAK,eAAe,CAAC,CAAC;IAEhG;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,IAAI,CACV,2BAA2B,EAC3B,KAAK,GAAG,SAAS,GAAG,QAAQ,GAAG,oBAAoB,GAAG,eAAe,CACtE,CAAC;IAEF;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAEhC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B,+EAA+E;IAC/E,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAElC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,kEAAkE;IAClE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,sFAAsF;IACtF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,sEAAsE;IACtE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,aAAa,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAE9E;AAED,wBAAgB,uBAAuB,IAAI,QAAQ,CAAC,eAAe,CAAC,CAkBnE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `gemi.v1` WebSocket protocol, shared by the server transport
|
|
3
|
+
* (`SocketHub`) and the browser client (`gemi/client`'s connection manager).
|
|
4
|
+
* Types and constants only: this module is imported by the browser bundle, so
|
|
5
|
+
* it must never import a server module.
|
|
6
|
+
*
|
|
7
|
+
* Every frame is a JSON text frame. Binary frames are refused.
|
|
8
|
+
*
|
|
9
|
+
* From the client:
|
|
10
|
+
*
|
|
11
|
+
* - `{op:"sub", id, ch, p?}`: subscribe `id` (chosen by the client, unique per
|
|
12
|
+
* socket) to the channel pattern `ch` with params `p`. The server builds
|
|
13
|
+
* the topic and authorizes it.
|
|
14
|
+
* - `{op:"unsub", id}`
|
|
15
|
+
* - `{op:"ping"}`: answered with `pong`. The client sends one every
|
|
16
|
+
* `heartbeatMs` and reconnects when it hears nothing for twice that.
|
|
17
|
+
*
|
|
18
|
+
* From the server:
|
|
19
|
+
*
|
|
20
|
+
* - `{op:"hello", socketId, tag, heartbeatMs}`: the first frame. `socketId`
|
|
21
|
+
* goes in the `X-Gemi-Socket` header of the client's own requests (for
|
|
22
|
+
* `Broadcast.toOthers`); it is never shown to anyone else. `tag` is what
|
|
23
|
+
* an event frame's `x` carries when it skips this socket.
|
|
24
|
+
* - `{op:"subscribed", id, t}`: `id` joined topic `t`. Sent only once this
|
|
25
|
+
* process receives the topic's events, so the resync the client runs on it
|
|
26
|
+
* cannot miss one.
|
|
27
|
+
* - `{op:"denied", id, code}`: `id` was refused, or later revoked.
|
|
28
|
+
* - `{op:"ev", t, ev, d?, x?}`: an event on topic `t`. Dropped by the socket
|
|
29
|
+
* whose `tag` is `x`.
|
|
30
|
+
* - `{op:"gap"}`: events may have been lost for this socket; resync all.
|
|
31
|
+
* - `{op:"pong"}`
|
|
32
|
+
* - `{op:"bye", code, retryAfter}`: the server is going away (a restart).
|
|
33
|
+
* Reconnect after `retryAfter` ms.
|
|
34
|
+
*/
|
|
35
|
+
/** The WebSocket subprotocol. */
|
|
36
|
+
export declare const BROADCAST_PROTOCOL = "gemi.v1";
|
|
37
|
+
/** The default endpoint, `defineBroadcastConfig({ path })`. */
|
|
38
|
+
export declare const DEFAULT_SOCKET_PATH = "/__gemi/socket";
|
|
39
|
+
/** The request header a client sends its socket id in, for `toOthers`. */
|
|
40
|
+
export declare const SOCKET_ID_HEADER = "x-gemi-socket";
|
|
41
|
+
/** What a subscription id may look like. */
|
|
42
|
+
export declare const SUBSCRIPTION_ID: RegExp;
|
|
43
|
+
/**
|
|
44
|
+
* Close codes the server uses. 1012 and 1013 are the standard "restart" and
|
|
45
|
+
* "try again later"; the 4xxx ones are gemi's own.
|
|
46
|
+
*/
|
|
47
|
+
export declare const CloseCode: {
|
|
48
|
+
/** The client went away (tab closed, socket released). */
|
|
49
|
+
readonly Normal: 1000;
|
|
50
|
+
/** A frame that was not JSON text, or not a known op. */
|
|
51
|
+
readonly UnsupportedData: 1003;
|
|
52
|
+
/** Too many frames, or a frame over the size limit. */
|
|
53
|
+
readonly PolicyViolation: 1008;
|
|
54
|
+
/** The server is restarting; `bye` said when to come back. */
|
|
55
|
+
readonly Restart: 1012;
|
|
56
|
+
/** The socket fell too far behind; reconnect and resync. */
|
|
57
|
+
readonly TryAgainLater: 1013;
|
|
58
|
+
/** The socket's user was signed out or its access revoked. */
|
|
59
|
+
readonly Revoked: 4001;
|
|
60
|
+
};
|
|
61
|
+
/** Why a subscription was refused. */
|
|
62
|
+
export type DeniedCode =
|
|
63
|
+
/** No channel with that pattern. Permanent. */
|
|
64
|
+
"unknown_channel"
|
|
65
|
+
/** Missing, extra or malformed params. Permanent. */
|
|
66
|
+
| "invalid_params"
|
|
67
|
+
/** The channel's authorization said no. */
|
|
68
|
+
| "denied"
|
|
69
|
+
/** Authorization failed (a session store outage, a bug). Retryable. */
|
|
70
|
+
| "error"
|
|
71
|
+
/** The socket has `maxChannelsPerSocket` subscriptions already. */
|
|
72
|
+
| "limit"
|
|
73
|
+
/** Too many `sub`/`unsub` frames; retry later. */
|
|
74
|
+
| "rate_limited"
|
|
75
|
+
/** The subscription was revoked by `Broadcast.revoke`. */
|
|
76
|
+
| "revoked"
|
|
77
|
+
/** The frame was malformed, or the id is already in use. */
|
|
78
|
+
| "invalid";
|
|
79
|
+
export type ClientFrame = {
|
|
80
|
+
op: "sub";
|
|
81
|
+
id: string;
|
|
82
|
+
ch: string;
|
|
83
|
+
p?: Record<string, string | number>;
|
|
84
|
+
} | {
|
|
85
|
+
op: "unsub";
|
|
86
|
+
id: string;
|
|
87
|
+
} | {
|
|
88
|
+
op: "ping";
|
|
89
|
+
};
|
|
90
|
+
export type ServerFrame = {
|
|
91
|
+
op: "hello";
|
|
92
|
+
socketId: string;
|
|
93
|
+
tag: string;
|
|
94
|
+
heartbeatMs: number;
|
|
95
|
+
} | {
|
|
96
|
+
op: "subscribed";
|
|
97
|
+
id: string;
|
|
98
|
+
t: string;
|
|
99
|
+
} | {
|
|
100
|
+
op: "denied";
|
|
101
|
+
id: string;
|
|
102
|
+
code: DeniedCode;
|
|
103
|
+
} | {
|
|
104
|
+
op: "ev";
|
|
105
|
+
t: string;
|
|
106
|
+
ev: string;
|
|
107
|
+
d?: unknown;
|
|
108
|
+
x?: string;
|
|
109
|
+
} | {
|
|
110
|
+
op: "gap";
|
|
111
|
+
} | {
|
|
112
|
+
op: "pong";
|
|
113
|
+
} | {
|
|
114
|
+
op: "bye";
|
|
115
|
+
code: number;
|
|
116
|
+
retryAfter: number;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Whether a refusal is final for this socket's session: resubscribing will
|
|
120
|
+
* get the same answer, so the client does not retry it on its own.
|
|
121
|
+
*/
|
|
122
|
+
export declare function isPermanentDenial(code: DeniedCode): boolean;
|
|
123
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../services/broadcast/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,iCAAiC;AACjC,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAE5C,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAEhD,4CAA4C;AAC5C,eAAO,MAAM,eAAe,QAA0B,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,SAAS;IACpB,0DAA0D;;IAE1D,yDAAyD;;IAEzD,uDAAuD;;IAEvD,8DAA8D;;IAE9D,4DAA4D;;IAE5D,8DAA8D;;CAEtD,CAAC;AAEX,sCAAsC;AACtC,MAAM,MAAM,UAAU;AACpB,+CAA+C;AAC7C,iBAAiB;AACnB,qDAAqD;GACnD,gBAAgB;AAClB,2CAA2C;GACzC,QAAQ;AACV,uEAAuE;GACrE,OAAO;AACT,mEAAmE;GACjE,OAAO;AACT,kDAAkD;GAChD,cAAc;AAChB,0DAA0D;GACxD,SAAS;AACX,4DAA4D;GAC1D,SAAS,CAAC;AAEd,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;CAAE,GAC1E;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnB,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,EAAE,EAAE,YAAY,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,GACb;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GACd;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAE3D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tag an event frame's `x` names a socket by: a keyed hash of its id.
|
|
3
|
+
*
|
|
4
|
+
* Subscribers see `x`, so it must not be the id: the id is what a client
|
|
5
|
+
* sends in `X-Gemi-Socket` to have its own emits skip it, and a subscriber
|
|
6
|
+
* who learned another's id could make the server skip that socket instead.
|
|
7
|
+
* The tag cannot be turned back into the id.
|
|
8
|
+
*
|
|
9
|
+
* Keyed with a key derived from `SECRET`, so every instance of the app
|
|
10
|
+
* computes the same tag for an id (the Redis driver carries frames between
|
|
11
|
+
* them). Without `SECRET` the key is random per process, which is right for
|
|
12
|
+
* one instance and wrong for several; the Redis driver needs `SECRET`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function socketTag(socketId: string): string;
|
|
15
|
+
/** A new socket id: 22 url-safe characters, 128 random bits. */
|
|
16
|
+
export declare function newSocketId(): string;
|
|
17
|
+
//# sourceMappingURL=socketTag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socketTag.d.ts","sourceRoot":"","sources":["../../../services/broadcast/socketTag.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAcD,gEAAgE;AAChE,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BroadcastRPC } from "../../client/rpc";
|
|
2
|
+
/** Any event map: what a channel the app has not typed accepts. */
|
|
3
|
+
export type AnyBroadcastEvents = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* The events a pattern's channel carries, from the app's generated
|
|
6
|
+
* `BroadcastRPC` (its `ChannelRouter`). A pattern the router does not declare,
|
|
7
|
+
* a concrete topic, or a channel without declared events takes any event.
|
|
8
|
+
*/
|
|
9
|
+
export type BroadcastEventsFor<P extends string> = P extends keyof BroadcastRPC ? BroadcastRPC[P] extends {
|
|
10
|
+
events: infer E extends Record<string, unknown>;
|
|
11
|
+
} ? E : AnyBroadcastEvents : AnyBroadcastEvents;
|
|
12
|
+
/** `emit`'s data argument: optional when the event's payload may be undefined. */
|
|
13
|
+
export type BroadcastDataArgs<T> = undefined extends T ? [data?: T] : [data: T];
|
|
14
|
+
/** One emit, as the fake records it. */
|
|
15
|
+
export interface SentBroadcast {
|
|
16
|
+
/** The concrete topic, e.g. `site.abc123`. */
|
|
17
|
+
topic: string;
|
|
18
|
+
/** The pattern the topic was built from, when it was built from one. */
|
|
19
|
+
pattern?: string;
|
|
20
|
+
/** The event name. */
|
|
21
|
+
event: string;
|
|
22
|
+
/** The payload, as emitted. */
|
|
23
|
+
data: unknown;
|
|
24
|
+
/** The socket `toOthers` skipped, if any. */
|
|
25
|
+
except?: string;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../services/broadcast/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,mEAAmE;AACnE,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,YAAY,GAC3E,YAAY,CAAC,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACzE,CAAC,GACD,kBAAkB,GACpB,kBAAkB,CAAC;AAEvB,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,SAAS,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAEhF,wCAAwC;AACxC,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frames an emit becomes. Encoded once per emit and handed to every
|
|
3
|
+
* socket on the topic unchanged (`server.publish`), so a frame names its
|
|
4
|
+
* topic and the client maps the topic to its own subscriptions.
|
|
5
|
+
*
|
|
6
|
+
* ```json
|
|
7
|
+
* {"op":"ev","t":"site.abc123","ev":"changed","d":{"pages":["/about"]},"x":"<socket id>"}
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* `d` is left out when the emit carried no data. `x` is set by an emit made
|
|
11
|
+
* with `Broadcast.toOthers(...)`: one frame goes to every process and every
|
|
12
|
+
* socket, so the socket it names drops it itself.
|
|
13
|
+
*
|
|
14
|
+
* `x` is the skipped socket's *tag*, a keyed hash of its id (`socketTag`),
|
|
15
|
+
* never the id itself. Every subscriber on the topic sees `x`; had it been
|
|
16
|
+
* the id, any of them could have sent it in its own `X-Gemi-Socket` header
|
|
17
|
+
* and made the server skip the victim for that client's emits. The id is
|
|
18
|
+
* only ever in the `hello` the socket's own client receives.
|
|
19
|
+
*/
|
|
20
|
+
export interface BroadcastEventFrame {
|
|
21
|
+
op: "ev";
|
|
22
|
+
/** The topic, e.g. `site.abc123`. */
|
|
23
|
+
t: string;
|
|
24
|
+
/** The event name. */
|
|
25
|
+
ev: string;
|
|
26
|
+
/** The payload, when there is one. */
|
|
27
|
+
d?: unknown;
|
|
28
|
+
/** The tag of the socket that must ignore this frame. */
|
|
29
|
+
x?: string;
|
|
30
|
+
}
|
|
31
|
+
export { SOCKET_ID_HEADER } from "./protocol";
|
|
32
|
+
/** Whether `value` has the shape of a socket id the transport hands out. */
|
|
33
|
+
export declare function isSocketId(value: unknown): value is string;
|
|
34
|
+
export declare function encodeEventFrame(topic: string, event: string, data: unknown, except?: string | null): string;
|
|
35
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../../services/broadcast/wire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,IAAI,CAAC;IACT,qCAAqC;IACrC,CAAC,EAAE,MAAM,CAAC;IACV,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,CAAC,CAAC,EAAE,OAAO,CAAC;IACZ,yDAAyD;IACzD,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAI9C,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE1D;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,MAAM,CAKR"}
|