@cendarsoss/pusher-js 8.4.11

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.
Files changed (425) hide show
  1. package/.editorconfig +14 -0
  2. package/.github/ISSUE_TEMPLATE.md +11 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  4. package/.github/dependabot.yml +14 -0
  5. package/.github/stale.yml +26 -0
  6. package/.github/workflows/release.yml +112 -0
  7. package/.github/workflows/release_pr.yml +43 -0
  8. package/.github/workflows/run-tests.yml +62 -0
  9. package/.gitmodules +3 -0
  10. package/.prettierrc +2 -0
  11. package/CHANGELOG.md +928 -0
  12. package/DELTA_COMPRESSION.md +365 -0
  13. package/DELTA_USAGE.md +179 -0
  14. package/IMPLEMENTATION_SUMMARY.md +261 -0
  15. package/IMPORT_GUIDE.md +638 -0
  16. package/LIBRARY_STRUCTURE_ANALYSIS.md +940 -0
  17. package/LICENCE +19 -0
  18. package/Makefile +14 -0
  19. package/README.md +709 -0
  20. package/TAG_FILTERING_CLIENT.md +471 -0
  21. package/bower.json +19 -0
  22. package/bun.lock +2695 -0
  23. package/dist/node/filter.js +252 -0
  24. package/dist/node/filter.js.map +1 -0
  25. package/dist/node/pusher.js +4434 -0
  26. package/dist/node/pusher.js.map +1 -0
  27. package/dist/web/filter.mjs +252 -0
  28. package/dist/web/filter.mjs.map +1 -0
  29. package/dist/web/pusher.mjs +5889 -0
  30. package/dist/web/pusher.mjs.map +1 -0
  31. package/examples/delta-compression-example.html +372 -0
  32. package/examples/delta-seamless-example.html +185 -0
  33. package/index.d.ts +36 -0
  34. package/integration_tests_server/index.js +176 -0
  35. package/integration_tests_server/package-lock.json +1177 -0
  36. package/integration_tests_server/package.json +15 -0
  37. package/interactive/.env +16 -0
  38. package/interactive/CONFLATION_TEST.md +73 -0
  39. package/interactive/DELTA_COMPRESSION_TESTING.md +262 -0
  40. package/interactive/bun.lock +208 -0
  41. package/interactive/package-lock.json +1075 -0
  42. package/interactive/package.json +32 -0
  43. package/interactive/public/app.js +1363 -0
  44. package/interactive/public/bundle-entry.js +14 -0
  45. package/interactive/public/conflation-test.html +508 -0
  46. package/interactive/public/conflation-test.js +785 -0
  47. package/interactive/public/delta-compression.js +1090 -0
  48. package/interactive/public/dist/bundle.js +5857 -0
  49. package/interactive/public/index.html +392 -0
  50. package/interactive/public/main.js +20 -0
  51. package/interactive/public/style.css +823 -0
  52. package/interactive/server.js +246 -0
  53. package/interactive/test-bundle.html +89 -0
  54. package/interactive/test-delta.js +146 -0
  55. package/node.js +1 -0
  56. package/package.json +94 -0
  57. package/pusher-with-encryption/index.js +1 -0
  58. package/react-native/index.d.ts +29 -0
  59. package/react-native/index.js +1 -0
  60. package/spec/config/jasmine/helpers/reporter.js +14 -0
  61. package/spec/config/jasmine/integration.json +13 -0
  62. package/spec/config/jasmine/unit.json +13 -0
  63. package/spec/config/jasmine/webpack.integration.js +33 -0
  64. package/spec/config/jasmine/webpack.unit.js +30 -0
  65. package/spec/config/karma/available_browsers.json +4957 -0
  66. package/spec/config/karma/config.ci.js +25 -0
  67. package/spec/config/karma/config.common.js +50 -0
  68. package/spec/config/karma/config.integration.js +26 -0
  69. package/spec/config/karma/config.unit.js +10 -0
  70. package/spec/config/karma/config.worker.js +34 -0
  71. package/spec/config/karma/integration.js +24 -0
  72. package/spec/config/karma/unit.js +20 -0
  73. package/spec/javascripts/helpers/mocks.js +274 -0
  74. package/spec/javascripts/helpers/node/integration.js +33 -0
  75. package/spec/javascripts/helpers/node/mock-dom-dependencies.ts +1 -0
  76. package/spec/javascripts/helpers/pusher_integration.js +1 -0
  77. package/spec/javascripts/helpers/pusher_integration_class.ts +12 -0
  78. package/spec/javascripts/helpers/timers/promises.js +9 -0
  79. package/spec/javascripts/helpers/waitsFor.js +37 -0
  80. package/spec/javascripts/helpers/web/integration.js +44 -0
  81. package/spec/javascripts/helpers/worker/mock-dom-dependencies.js +1 -0
  82. package/spec/javascripts/integration/core/cluster_config_spec.js +153 -0
  83. package/spec/javascripts/integration/core/falling_back_spec.js +195 -0
  84. package/spec/javascripts/integration/core/pusher_spec/index.js +68 -0
  85. package/spec/javascripts/integration/core/pusher_spec/test_builder.js +715 -0
  86. package/spec/javascripts/integration/core/timeout_configuration_spec.js +200 -0
  87. package/spec/javascripts/integration/core/transport_lists_spec.js +103 -0
  88. package/spec/javascripts/integration/index.node.js +12 -0
  89. package/spec/javascripts/integration/index.web.js +63 -0
  90. package/spec/javascripts/integration/index.worker.js +13 -0
  91. package/spec/javascripts/integration/web/dom/jsonp_spec.js +97 -0
  92. package/spec/javascripts/integration/web/dom/script_request_spec.js +90 -0
  93. package/spec/javascripts/polyfills/index.js +105 -0
  94. package/spec/javascripts/unit/core/channels/channel_spec.js +355 -0
  95. package/spec/javascripts/unit/core/channels/channels_spec.js +94 -0
  96. package/spec/javascripts/unit/core/channels/encrypted_channel_spec.js +343 -0
  97. package/spec/javascripts/unit/core/channels/presence_channel_spec.js +553 -0
  98. package/spec/javascripts/unit/core/channels/private_channel_spec.js +182 -0
  99. package/spec/javascripts/unit/core/config_spec.js +507 -0
  100. package/spec/javascripts/unit/core/connection/connection_manager_spec.js +656 -0
  101. package/spec/javascripts/unit/core/connection/connection_spec.js +286 -0
  102. package/spec/javascripts/unit/core/connection/handshake_spec.js +160 -0
  103. package/spec/javascripts/unit/core/connection/protocol_spec.js +420 -0
  104. package/spec/javascripts/unit/core/defaults_spec.js +26 -0
  105. package/spec/javascripts/unit/core/events_dispatcher_spec.js +385 -0
  106. package/spec/javascripts/unit/core/http/http_polling_socket_spec.js +60 -0
  107. package/spec/javascripts/unit/core/http/http_request_spec.js +185 -0
  108. package/spec/javascripts/unit/core/http/http_socket_spec.js +370 -0
  109. package/spec/javascripts/unit/core/http/http_streaming_socket_spec.js +56 -0
  110. package/spec/javascripts/unit/core/http/http_xhr_request_spec.js +164 -0
  111. package/spec/javascripts/unit/core/logger_spec.js +133 -0
  112. package/spec/javascripts/unit/core/pusher_spec.js +613 -0
  113. package/spec/javascripts/unit/core/pusher_with_encryption_spec.js +18 -0
  114. package/spec/javascripts/unit/core/strategies/best_connected_ever_strategy_spec.js +104 -0
  115. package/spec/javascripts/unit/core/strategies/delayed_strategy_spec.js +95 -0
  116. package/spec/javascripts/unit/core/strategies/first_connected_strategy_spec.js +68 -0
  117. package/spec/javascripts/unit/core/strategies/if_strategy_spec.js +165 -0
  118. package/spec/javascripts/unit/core/strategies/sequential_strategy_spec.js +213 -0
  119. package/spec/javascripts/unit/core/strategies/transport_strategy_spec.js +250 -0
  120. package/spec/javascripts/unit/core/strategies/websocket_prioritized_cached_strategy_spec.js +400 -0
  121. package/spec/javascripts/unit/core/timeline/timeline_spec.js +153 -0
  122. package/spec/javascripts/unit/core/transports/assistant_to_the_transport_manager_spec.js +223 -0
  123. package/spec/javascripts/unit/core/transports/hosts_and_ports_spec.js +85 -0
  124. package/spec/javascripts/unit/core/transports/transport_connection_spec.js +585 -0
  125. package/spec/javascripts/unit/core/transports/transport_manager_spec.js +64 -0
  126. package/spec/javascripts/unit/core/user_spec.js +303 -0
  127. package/spec/javascripts/unit/core/utils/periodic_timer_spec.js +74 -0
  128. package/spec/javascripts/unit/core/utils/timers_spec.js +157 -0
  129. package/spec/javascripts/unit/core/utils/url_store_spec.js +14 -0
  130. package/spec/javascripts/unit/core/watchlist_spec.js +48 -0
  131. package/spec/javascripts/unit/core_with_runtime/auth/channel_authorizer_spec.js +137 -0
  132. package/spec/javascripts/unit/core_with_runtime/auth/deprecated_channel_authorizer_spec.js +48 -0
  133. package/spec/javascripts/unit/core_with_runtime/auth/user_authorizer_spec.js +128 -0
  134. package/spec/javascripts/unit/core_with_runtime/readme.md +5 -0
  135. package/spec/javascripts/unit/index.node.js +11 -0
  136. package/spec/javascripts/unit/index.web.js +12 -0
  137. package/spec/javascripts/unit/index.worker.js +11 -0
  138. package/spec/javascripts/unit/isomorphic/transports/hosts_and_ports_spec.js +82 -0
  139. package/spec/javascripts/unit/isomorphic/transports/transports_spec.js +202 -0
  140. package/spec/javascripts/unit/node/timeline_sender_spec.js +83 -0
  141. package/spec/javascripts/unit/web/dom/dependency_loader_spec.js +249 -0
  142. package/spec/javascripts/unit/web/dom/jsonp_request_spec.js +130 -0
  143. package/spec/javascripts/unit/web/dom/script_receiver_factory_spec.js +68 -0
  144. package/spec/javascripts/unit/web/http/http_xdomain_request_spec.js +222 -0
  145. package/spec/javascripts/unit/web/pusher_authorizer_spec.js +64 -0
  146. package/spec/javascripts/unit/web/timeline/timeline_sender_spec.js +131 -0
  147. package/spec/javascripts/unit/web/transports/hosts_and_ports_spec.js +127 -0
  148. package/spec/javascripts/unit/web/transports/transports_spec.js +444 -0
  149. package/spec/javascripts/unit/worker/channel_authorizer_spec.js +156 -0
  150. package/spec/javascripts/unit/worker/timeline_sender_spec.js +76 -0
  151. package/src/core/auth/auth_transports.ts +18 -0
  152. package/src/core/auth/channel_authorizer.ts +64 -0
  153. package/src/core/auth/deprecated_channel_authorizer.ts +56 -0
  154. package/src/core/auth/options.ts +76 -0
  155. package/src/core/auth/user_authenticator.ts +62 -0
  156. package/src/core/base64.ts +49 -0
  157. package/src/core/channels/channel.ts +173 -0
  158. package/src/core/channels/channel_table.ts +7 -0
  159. package/src/core/channels/channels.ts +86 -0
  160. package/src/core/channels/encrypted_channel.ts +150 -0
  161. package/src/core/channels/filter.ts +342 -0
  162. package/src/core/channels/members.ts +80 -0
  163. package/src/core/channels/metadata.ts +5 -0
  164. package/src/core/channels/presence_channel.ts +113 -0
  165. package/src/core/channels/private_channel.ts +25 -0
  166. package/src/core/config.ts +189 -0
  167. package/src/core/connection/callbacks.ts +21 -0
  168. package/src/core/connection/connection.ts +160 -0
  169. package/src/core/connection/connection_manager.ts +371 -0
  170. package/src/core/connection/connection_manager_options.ts +14 -0
  171. package/src/core/connection/handshake/handshake_payload.ts +10 -0
  172. package/src/core/connection/handshake/index.ts +90 -0
  173. package/src/core/connection/protocol/action.ts +8 -0
  174. package/src/core/connection/protocol/message-types.ts +11 -0
  175. package/src/core/connection/protocol/protocol.ts +166 -0
  176. package/src/core/defaults.ts +66 -0
  177. package/src/core/delta/channel_state.ts +194 -0
  178. package/src/core/delta/decoders.ts +129 -0
  179. package/src/core/delta/index.ts +10 -0
  180. package/src/core/delta/manager.ts +504 -0
  181. package/src/core/delta/types.ts +60 -0
  182. package/src/core/errors.ts +69 -0
  183. package/src/core/events/callback.ts +6 -0
  184. package/src/core/events/callback_registry.ts +75 -0
  185. package/src/core/events/callback_table.ts +7 -0
  186. package/src/core/events/dispatcher.ts +84 -0
  187. package/src/core/http/ajax.ts +24 -0
  188. package/src/core/http/http_factory.ts +16 -0
  189. package/src/core/http/http_polling_socket.ts +24 -0
  190. package/src/core/http/http_request.ts +81 -0
  191. package/src/core/http/http_socket.ts +220 -0
  192. package/src/core/http/http_streaming_socket.ts +19 -0
  193. package/src/core/http/request_hooks.ts +9 -0
  194. package/src/core/http/socket_hooks.ts +11 -0
  195. package/src/core/http/state.ts +7 -0
  196. package/src/core/http/url_location.ts +6 -0
  197. package/src/core/logger.ts +66 -0
  198. package/src/core/options.ts +61 -0
  199. package/src/core/pusher-licence.js +7 -0
  200. package/src/core/pusher-with-encryption.js +1 -0
  201. package/src/core/pusher-with-encryption.ts +14 -0
  202. package/src/core/pusher.js +10 -0
  203. package/src/core/pusher.ts +412 -0
  204. package/src/core/reachability.ts +7 -0
  205. package/src/core/socket.ts +14 -0
  206. package/src/core/strategies/best_connected_ever_strategy.ts +81 -0
  207. package/src/core/strategies/delayed_strategy.ts +48 -0
  208. package/src/core/strategies/first_connected_strategy.ts +31 -0
  209. package/src/core/strategies/if_strategy.ts +34 -0
  210. package/src/core/strategies/sequential_strategy.ts +129 -0
  211. package/src/core/strategies/strategy.ts +8 -0
  212. package/src/core/strategies/strategy_builder.ts +67 -0
  213. package/src/core/strategies/strategy_options.ts +18 -0
  214. package/src/core/strategies/strategy_runner.ts +6 -0
  215. package/src/core/strategies/transport_strategy.ts +144 -0
  216. package/src/core/strategies/websocket_prioritized_cached_strategy.ts +157 -0
  217. package/src/core/timeline/level.ts +7 -0
  218. package/src/core/timeline/timeline.ts +90 -0
  219. package/src/core/timeline/timeline_sender.ts +33 -0
  220. package/src/core/timeline/timeline_transport.ts +11 -0
  221. package/src/core/transports/assistant_to_the_transport_manager.ts +104 -0
  222. package/src/core/transports/ping_delay_options.ts +7 -0
  223. package/src/core/transports/transport.ts +54 -0
  224. package/src/core/transports/transport_connection.ts +241 -0
  225. package/src/core/transports/transport_connection_options.ts +8 -0
  226. package/src/core/transports/transport_hooks.ts +16 -0
  227. package/src/core/transports/transport_manager.ts +52 -0
  228. package/src/core/transports/transports_table.ts +12 -0
  229. package/src/core/transports/url_scheme.ts +13 -0
  230. package/src/core/transports/url_schemes.ts +47 -0
  231. package/src/core/user.ts +186 -0
  232. package/src/core/util.ts +34 -0
  233. package/src/core/utils/collections.ts +353 -0
  234. package/src/core/utils/factory.ts +79 -0
  235. package/src/core/utils/flat_promise.ts +10 -0
  236. package/src/core/utils/timers/abstract_timer.ts +39 -0
  237. package/src/core/utils/timers/index.ts +39 -0
  238. package/src/core/utils/timers/scheduling.ts +11 -0
  239. package/src/core/utils/timers/timed_callback.ts +5 -0
  240. package/src/core/utils/url_store.ts +48 -0
  241. package/src/core/watchlist.ts +31 -0
  242. package/src/d.ts/constants/index.d.ts +5 -0
  243. package/src/d.ts/faye-websocket/faye-websocket.d.ts +21 -0
  244. package/src/d.ts/global/global.d.ts +1 -0
  245. package/src/d.ts/module/module.d.ts +12 -0
  246. package/src/d.ts/tweetnacl-util/index.d.ts +6 -0
  247. package/src/d.ts/window/events.d.ts +4 -0
  248. package/src/d.ts/window/sockjs.d.ts +3 -0
  249. package/src/d.ts/window/websocket.d.ts +4 -0
  250. package/src/d.ts/window/xmlhttprequest.d.ts +3 -0
  251. package/src/filter.ts +5 -0
  252. package/src/index.ts +8 -0
  253. package/src/runtimes/interface.ts +60 -0
  254. package/src/runtimes/isomorphic/auth/xhr_auth.ts +90 -0
  255. package/src/runtimes/isomorphic/default_strategy.ts +155 -0
  256. package/src/runtimes/isomorphic/http/http.ts +32 -0
  257. package/src/runtimes/isomorphic/http/http_xhr_request.ts +35 -0
  258. package/src/runtimes/isomorphic/runtime.ts +62 -0
  259. package/src/runtimes/isomorphic/timeline/xhr_timeline.ts +50 -0
  260. package/src/runtimes/isomorphic/transports/transport_connection_initializer.ts +19 -0
  261. package/src/runtimes/isomorphic/transports/transports.ts +83 -0
  262. package/src/runtimes/node/net_info.ts +10 -0
  263. package/src/runtimes/node/runtime.ts +68 -0
  264. package/src/runtimes/react-native/net_info.ts +42 -0
  265. package/src/runtimes/react-native/runtime.ts +65 -0
  266. package/src/runtimes/web/auth/jsonp_auth.ts +51 -0
  267. package/src/runtimes/web/browser.ts +24 -0
  268. package/src/runtimes/web/default_strategy.ts +201 -0
  269. package/src/runtimes/web/dom/dependencies.ts +16 -0
  270. package/src/runtimes/web/dom/dependency_loader.ts +93 -0
  271. package/src/runtimes/web/dom/json2.js +486 -0
  272. package/src/runtimes/web/dom/jsonp_request.ts +52 -0
  273. package/src/runtimes/web/dom/script_receiver.ts +8 -0
  274. package/src/runtimes/web/dom/script_receiver_factory.ts +57 -0
  275. package/src/runtimes/web/dom/script_request.ts +85 -0
  276. package/src/runtimes/web/http/http.ts +8 -0
  277. package/src/runtimes/web/http/http_xdomain_request.ts +37 -0
  278. package/src/runtimes/web/net_info.ts +50 -0
  279. package/src/runtimes/web/runtime.ts +174 -0
  280. package/src/runtimes/web/timeline/jsonp_timeline.ts +34 -0
  281. package/src/runtimes/web/transports/transport_connection_initializer.ts +39 -0
  282. package/src/runtimes/web/transports/transports.ts +67 -0
  283. package/src/runtimes/worker/auth/fetch_auth.ts +69 -0
  284. package/src/runtimes/worker/net_info.ts +10 -0
  285. package/src/runtimes/worker/runtime.ts +75 -0
  286. package/src/runtimes/worker/timeline/fetch_timeline.ts +39 -0
  287. package/tsconfig.json +18 -0
  288. package/types/spec/javascripts/helpers/node/mock-dom-dependencies.d.ts +1 -0
  289. package/types/spec/javascripts/helpers/pusher_integration_class.d.ts +4 -0
  290. package/types/src/core/auth/auth_transports.d.ts +9 -0
  291. package/types/src/core/auth/channel_authorizer.d.ts +3 -0
  292. package/types/src/core/auth/deprecated_channel_authorizer.d.ts +18 -0
  293. package/types/src/core/auth/options.d.ts +48 -0
  294. package/types/src/core/auth/user_authenticator.d.ts +3 -0
  295. package/types/src/core/base64.d.ts +1 -0
  296. package/types/src/core/channels/channel.d.ts +25 -0
  297. package/types/src/core/channels/channel_table.d.ts +5 -0
  298. package/types/src/core/channels/channels.d.ts +12 -0
  299. package/types/src/core/channels/encrypted_channel.d.ts +15 -0
  300. package/types/src/core/channels/filter.d.ts +33 -0
  301. package/types/src/core/channels/members.d.ts +14 -0
  302. package/types/src/core/channels/metadata.d.ts +4 -0
  303. package/types/src/core/channels/presence_channel.d.ts +13 -0
  304. package/types/src/core/channels/private_channel.d.ts +5 -0
  305. package/types/src/core/config.d.ts +31 -0
  306. package/types/src/core/connection/callbacks.d.ts +18 -0
  307. package/types/src/core/connection/connection.d.ts +16 -0
  308. package/types/src/core/connection/connection_manager.d.ts +50 -0
  309. package/types/src/core/connection/connection_manager_options.d.ts +11 -0
  310. package/types/src/core/connection/handshake/handshake_payload.d.ts +8 -0
  311. package/types/src/core/connection/handshake/index.d.ts +12 -0
  312. package/types/src/core/connection/protocol/action.d.ts +7 -0
  313. package/types/src/core/connection/protocol/message-types.d.ts +10 -0
  314. package/types/src/core/connection/protocol/protocol.d.ts +10 -0
  315. package/types/src/core/defaults.d.ts +26 -0
  316. package/types/src/core/delta/channel_state.d.ts +23 -0
  317. package/types/src/core/delta/decoders.d.ts +12 -0
  318. package/types/src/core/delta/index.d.ts +4 -0
  319. package/types/src/core/delta/manager.d.ts +27 -0
  320. package/types/src/core/delta/types.d.ts +50 -0
  321. package/types/src/core/errors.d.ts +28 -0
  322. package/types/src/core/events/callback.d.ts +5 -0
  323. package/types/src/core/events/callback_registry.d.ts +11 -0
  324. package/types/src/core/events/callback_table.d.ts +5 -0
  325. package/types/src/core/events/dispatcher.d.ts +14 -0
  326. package/types/src/core/http/ajax.d.ts +16 -0
  327. package/types/src/core/http/http_factory.d.ts +13 -0
  328. package/types/src/core/http/http_polling_socket.d.ts +3 -0
  329. package/types/src/core/http/http_request.d.ts +17 -0
  330. package/types/src/core/http/http_socket.d.ts +32 -0
  331. package/types/src/core/http/http_streaming_socket.d.ts +3 -0
  332. package/types/src/core/http/request_hooks.d.ts +6 -0
  333. package/types/src/core/http/socket_hooks.d.ts +8 -0
  334. package/types/src/core/http/state.d.ts +6 -0
  335. package/types/src/core/http/url_location.d.ts +5 -0
  336. package/types/src/core/logger.d.ts +11 -0
  337. package/types/src/core/options.d.ts +36 -0
  338. package/types/src/core/pusher-with-encryption.d.ts +5 -0
  339. package/types/src/core/pusher.d.ts +56 -0
  340. package/types/src/core/reachability.d.ts +5 -0
  341. package/types/src/core/socket.d.ts +12 -0
  342. package/types/src/core/strategies/best_connected_ever_strategy.d.ts +10 -0
  343. package/types/src/core/strategies/delayed_strategy.d.ts +15 -0
  344. package/types/src/core/strategies/first_connected_strategy.d.ts +8 -0
  345. package/types/src/core/strategies/if_strategy.d.ts +10 -0
  346. package/types/src/core/strategies/sequential_strategy.d.ts +16 -0
  347. package/types/src/core/strategies/strategy.d.ts +6 -0
  348. package/types/src/core/strategies/strategy_builder.d.ts +5 -0
  349. package/types/src/core/strategies/strategy_options.d.ts +16 -0
  350. package/types/src/core/strategies/strategy_runner.d.ts +5 -0
  351. package/types/src/core/strategies/transport_strategy.d.ts +15 -0
  352. package/types/src/core/strategies/websocket_prioritized_cached_strategy.d.ts +20 -0
  353. package/types/src/core/timeline/level.d.ts +6 -0
  354. package/types/src/core/timeline/timeline.d.ts +25 -0
  355. package/types/src/core/timeline/timeline_sender.d.ts +13 -0
  356. package/types/src/core/timeline/timeline_transport.d.ts +6 -0
  357. package/types/src/core/transports/assistant_to_the_transport_manager.d.ts +14 -0
  358. package/types/src/core/transports/ping_delay_options.d.ts +6 -0
  359. package/types/src/core/transports/transport.d.ts +8 -0
  360. package/types/src/core/transports/transport_connection.d.ts +35 -0
  361. package/types/src/core/transports/transport_connection_options.d.ts +6 -0
  362. package/types/src/core/transports/transport_hooks.d.ts +13 -0
  363. package/types/src/core/transports/transport_manager.d.ts +14 -0
  364. package/types/src/core/transports/transports_table.d.ts +10 -0
  365. package/types/src/core/transports/url_scheme.d.ts +11 -0
  366. package/types/src/core/transports/url_schemes.d.ts +4 -0
  367. package/types/src/core/user.d.ts +21 -0
  368. package/types/src/core/util.d.ts +8 -0
  369. package/types/src/core/utils/collections.d.ts +18 -0
  370. package/types/src/core/utils/factory.d.ts +29 -0
  371. package/types/src/core/utils/flat_promise.d.ts +6 -0
  372. package/types/src/core/utils/timers/abstract_timer.d.ts +10 -0
  373. package/types/src/core/utils/timers/index.d.ts +9 -0
  374. package/types/src/core/utils/timers/scheduling.d.ts +8 -0
  375. package/types/src/core/utils/timers/timed_callback.d.ts +4 -0
  376. package/types/src/core/utils/url_store.d.ts +4 -0
  377. package/types/src/core/watchlist.d.ts +8 -0
  378. package/types/src/runtimes/interface.d.ts +43 -0
  379. package/types/src/runtimes/isomorphic/auth/xhr_auth.d.ts +3 -0
  380. package/types/src/runtimes/isomorphic/default_strategy.d.ts +5 -0
  381. package/types/src/runtimes/isomorphic/http/http.d.ts +3 -0
  382. package/types/src/runtimes/isomorphic/http/http_xhr_request.d.ts +3 -0
  383. package/types/src/runtimes/isomorphic/runtime.d.ts +2 -0
  384. package/types/src/runtimes/isomorphic/timeline/xhr_timeline.d.ts +6 -0
  385. package/types/src/runtimes/isomorphic/transports/transport_connection_initializer.d.ts +1 -0
  386. package/types/src/runtimes/isomorphic/transports/transports.d.ts +5 -0
  387. package/types/src/runtimes/node/net_info.d.ts +6 -0
  388. package/types/src/runtimes/node/runtime.d.ts +3 -0
  389. package/types/src/runtimes/react-native/net_info.d.ts +8 -0
  390. package/types/src/runtimes/react-native/runtime.d.ts +3 -0
  391. package/types/src/runtimes/web/auth/jsonp_auth.d.ts +3 -0
  392. package/types/src/runtimes/web/browser.d.ts +19 -0
  393. package/types/src/runtimes/web/default_strategy.d.ts +5 -0
  394. package/types/src/runtimes/web/dom/dependencies.d.ts +4 -0
  395. package/types/src/runtimes/web/dom/dependency_loader.d.ts +10 -0
  396. package/types/src/runtimes/web/dom/jsonp_request.d.ts +10 -0
  397. package/types/src/runtimes/web/dom/script_receiver.d.ts +7 -0
  398. package/types/src/runtimes/web/dom/script_receiver_factory.d.ts +10 -0
  399. package/types/src/runtimes/web/dom/script_request.d.ts +9 -0
  400. package/types/src/runtimes/web/http/http.d.ts +2 -0
  401. package/types/src/runtimes/web/http/http_xdomain_request.d.ts +3 -0
  402. package/types/src/runtimes/web/net_info.d.ts +7 -0
  403. package/types/src/runtimes/web/runtime.d.ts +3 -0
  404. package/types/src/runtimes/web/timeline/jsonp_timeline.d.ts +6 -0
  405. package/types/src/runtimes/web/transports/transport_connection_initializer.d.ts +1 -0
  406. package/types/src/runtimes/web/transports/transports.d.ts +2 -0
  407. package/types/src/runtimes/worker/auth/fetch_auth.d.ts +3 -0
  408. package/types/src/runtimes/worker/net_info.d.ts +6 -0
  409. package/types/src/runtimes/worker/runtime.d.ts +3 -0
  410. package/types/src/runtimes/worker/timeline/fetch_timeline.d.ts +6 -0
  411. package/vite.config.js +52 -0
  412. package/vite.config.node.js +72 -0
  413. package/webpack/config.node.js +26 -0
  414. package/webpack/config.react-native.js +35 -0
  415. package/webpack/config.shared.js +50 -0
  416. package/webpack/config.web.js +36 -0
  417. package/webpack/config.worker.js +42 -0
  418. package/webpack/dev.server.js +17 -0
  419. package/webpack/hosting_config.js +6 -0
  420. package/with-encryption/index.d.ts +29 -0
  421. package/with-encryption/index.js +4 -0
  422. package/worker/index.d.ts +29 -0
  423. package/worker/index.js +1 -0
  424. package/worker/with-encryption/index.d.ts +29 -0
  425. package/worker/with-encryption/index.js +1 -0
@@ -0,0 +1,1090 @@
1
+ /**
2
+ * Delta Compression Client Library for Sockudo
3
+ *
4
+ * This library provides client-side delta compression support for WebSocket messages.
5
+ * It supports both Fossil Delta and Xdelta3 (VCDIFF) algorithms.
6
+ *
7
+ * ## Features
8
+ * - Automatic delta decoding and message reconstruction
9
+ * - Support for multiple algorithms (fossil, xdelta3)
10
+ * - Per-channel base message tracking
11
+ * - Sequence number validation and error recovery
12
+ * - Bandwidth savings statistics
13
+ *
14
+ * ## Usage
15
+ *
16
+ * ```javascript
17
+ * const pusher = new Pusher(appKey, options);
18
+ * const deltaManager = new DeltaCompressionManager(pusher, {
19
+ * algorithms: ['fossil', 'xdelta3'],
20
+ * onStats: (stats) => console.log('Bandwidth saved:', stats.bandwidthSavedPercent)
21
+ * });
22
+ *
23
+ * // Delta compression is automatically enabled
24
+ * const channel = pusher.subscribe('my-channel');
25
+ * channel.bind('my-event', (data) => {
26
+ * // Data is automatically reconstructed from deltas
27
+ * console.log(data);
28
+ * });
29
+ * ```
30
+ */
31
+
32
+ (function (global) {
33
+ "use strict";
34
+
35
+ // ============================================================================
36
+ // Utility Functions
37
+ // ============================================================================
38
+
39
+ /**
40
+ * Base64 decode a string to Uint8Array
41
+ */
42
+ function base64ToBytes(base64) {
43
+ const binaryString = atob(base64);
44
+ const bytes = new Uint8Array(binaryString.length);
45
+ for (let i = 0; i < binaryString.length; i++) {
46
+ bytes[i] = binaryString.charCodeAt(i);
47
+ }
48
+ return bytes;
49
+ }
50
+
51
+ /**
52
+ * Convert Uint8Array to string
53
+ */
54
+ function bytesToString(bytes) {
55
+ return new TextDecoder().decode(bytes);
56
+ }
57
+
58
+ /**
59
+ * Convert string to Uint8Array
60
+ */
61
+ function stringToBytes(str) {
62
+ return new TextEncoder().encode(str);
63
+ }
64
+
65
+ // ============================================================================
66
+ // Delta Algorithms
67
+ // ============================================================================
68
+
69
+ /**
70
+ * Fossil Delta decoder
71
+ */
72
+ class FossilDeltaDecoder {
73
+ static isAvailable() {
74
+ return typeof fossilDelta !== "undefined" && fossilDelta.apply;
75
+ }
76
+
77
+ static apply(base, delta) {
78
+ if (!this.isAvailable()) {
79
+ throw new Error("Fossil Delta library not loaded");
80
+ }
81
+
82
+ try {
83
+ // fossilDelta.apply expects Uint8Array inputs
84
+ const baseBytes = typeof base === "string" ? stringToBytes(base) : base;
85
+ const deltaBytes =
86
+ typeof delta === "string" ? stringToBytes(delta) : delta;
87
+
88
+ const result = fossilDelta.apply(baseBytes, deltaBytes);
89
+ return bytesToString(result);
90
+ } catch (error) {
91
+ throw new Error(`Fossil delta decode failed: ${error.message}`);
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Xdelta3 (VCDIFF) decoder
98
+ */
99
+ class Xdelta3Decoder {
100
+ static isAvailable() {
101
+ // Check for vcdiff-decoder library
102
+ return typeof VCDiffDecoder !== "undefined";
103
+ }
104
+
105
+ static apply(base, delta) {
106
+ if (!this.isAvailable()) {
107
+ throw new Error("Xdelta3/VCDIFF library not loaded");
108
+ }
109
+
110
+ try {
111
+ const baseBytes = typeof base === "string" ? stringToBytes(base) : base;
112
+ const deltaBytes =
113
+ typeof delta === "string" ? stringToBytes(delta) : delta;
114
+
115
+ const decoder = new VCDiffDecoder();
116
+ const result = decoder.decode(baseBytes, deltaBytes);
117
+ return bytesToString(result);
118
+ } catch (error) {
119
+ throw new Error(`Xdelta3 decode failed: ${error.message}`);
120
+ }
121
+ }
122
+ }
123
+
124
+ // ============================================================================
125
+ // Channel State
126
+ // ============================================================================
127
+
128
+ /**
129
+ * Per-channel delta state tracking with conflation key support
130
+ */
131
+ class ChannelState {
132
+ constructor(channelName) {
133
+ this.channelName = channelName;
134
+ this.conflationKey = null; // e.g., "asset", "device_id"
135
+ this.maxMessagesPerKey = 10;
136
+
137
+ // Conflation key caches: Map<conflationKeyValue, Array<CachedMessage>>
138
+ // e.g., { "BTC": [{content: {...}, seq: 1}, ...], "ETH": [...] }
139
+ this.conflationCaches = new Map();
140
+
141
+ // Legacy single-base tracking (for non-conflation channels)
142
+ this.baseMessage = null;
143
+ this.baseSequence = null;
144
+ this.lastSequence = null;
145
+
146
+ // Statistics
147
+ this.deltaCount = 0;
148
+ this.fullMessageCount = 0;
149
+ }
150
+
151
+ /**
152
+ * Initialize cache from server sync
153
+ */
154
+ initializeFromCacheSync(data) {
155
+ this.conflationKey = data.conflation_key || null;
156
+ this.maxMessagesPerKey = data.max_messages_per_key || 10;
157
+ this.conflationCaches.clear();
158
+
159
+ // Load all conflation group caches
160
+ if (data.states) {
161
+ for (const [key, messages] of Object.entries(data.states)) {
162
+ const cache = messages.map((msg) => ({
163
+ content: msg.content,
164
+ sequence: msg.seq,
165
+ }));
166
+ this.conflationCaches.set(key, cache);
167
+ }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Set new base message (legacy - for non-conflation channels)
173
+ */
174
+ setBase(message, sequence) {
175
+ this.baseMessage = message;
176
+ this.baseSequence = sequence;
177
+ this.lastSequence = sequence;
178
+ }
179
+
180
+ /**
181
+ * Get base message for a conflation key at specific index
182
+ */
183
+ getBaseMessage(conflationKeyValue, baseIndex) {
184
+ if (!this.conflationKey) {
185
+ // Legacy mode: return single base
186
+ return this.baseMessage;
187
+ }
188
+
189
+ const key = conflationKeyValue || "";
190
+ const cache = this.conflationCaches.get(key);
191
+
192
+ if (!cache || baseIndex >= cache.length) {
193
+ return null;
194
+ }
195
+
196
+ return cache[baseIndex].content;
197
+ }
198
+
199
+ /**
200
+ * Add or update message in conflation cache
201
+ */
202
+ updateConflationCache(conflationKeyValue, message, sequence) {
203
+ const key = conflationKeyValue || "";
204
+ let cache = this.conflationCaches.get(key);
205
+
206
+ if (!cache) {
207
+ cache = [];
208
+ this.conflationCaches.set(key, cache);
209
+ }
210
+
211
+ // Add message to cache
212
+ cache.push({ content: message, sequence });
213
+
214
+ // Enforce max size (FIFO eviction)
215
+ while (cache.length > this.maxMessagesPerKey) {
216
+ cache.shift();
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Check if we have a valid base
222
+ */
223
+ hasBase() {
224
+ if (this.conflationKey) {
225
+ return this.conflationCaches.size > 0;
226
+ }
227
+ return this.baseMessage !== null && this.baseSequence !== null;
228
+ }
229
+
230
+ /**
231
+ * Validate sequence number
232
+ */
233
+ isValidSequence(sequence) {
234
+ if (this.lastSequence === null) {
235
+ return true; // First message
236
+ }
237
+ return sequence > this.lastSequence;
238
+ }
239
+
240
+ /**
241
+ * Update sequence after processing a message
242
+ */
243
+ updateSequence(sequence) {
244
+ this.lastSequence = sequence;
245
+ }
246
+
247
+ /**
248
+ * Record delta received
249
+ */
250
+ recordDelta() {
251
+ this.deltaCount++;
252
+ }
253
+
254
+ /**
255
+ * Record full message received
256
+ */
257
+ recordFullMessage() {
258
+ this.fullMessageCount++;
259
+ }
260
+
261
+ /**
262
+ * Get statistics
263
+ */
264
+ getStats() {
265
+ return {
266
+ channelName: this.channelName,
267
+ conflationKey: this.conflationKey,
268
+ conflationGroupCount: this.conflationCaches.size,
269
+ deltaCount: this.deltaCount,
270
+ fullMessageCount: this.fullMessageCount,
271
+ totalMessages: this.deltaCount + this.fullMessageCount,
272
+ };
273
+ }
274
+ }
275
+
276
+ // ============================================================================
277
+ // Delta Compression Manager
278
+ // ============================================================================
279
+
280
+ /**
281
+ * Main delta compression manager
282
+ */
283
+ class DeltaCompressionManager {
284
+ constructor(pusher, options = {}) {
285
+ this.pusher = pusher;
286
+ this.options = {
287
+ algorithms: options.algorithms || ["fossil", "xdelta3"],
288
+ autoEnable: options.autoEnable !== false,
289
+ onStats: options.onStats || null,
290
+ onError: options.onError || null,
291
+ debug: options.debug || false,
292
+ };
293
+
294
+ // State
295
+ this.enabled = false;
296
+ this.channelStates = new Map();
297
+ this.stats = {
298
+ totalMessages: 0,
299
+ deltaMessages: 0,
300
+ fullMessages: 0,
301
+ totalBytesWithoutCompression: 0,
302
+ totalBytesWithCompression: 0,
303
+ errors: 0,
304
+ };
305
+
306
+ // Detect available algorithms
307
+ this.availableAlgorithms = this._detectAvailableAlgorithms();
308
+
309
+ if (this.availableAlgorithms.length === 0) {
310
+ console.warn(
311
+ "[DeltaCompression] No delta algorithms available. Please include fossil-delta or vcdiff-decoder libraries.",
312
+ );
313
+ return;
314
+ }
315
+
316
+ // Initialize
317
+ if (this.options.autoEnable) {
318
+ this._initialize();
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Detect which algorithm libraries are loaded
324
+ */
325
+ _detectAvailableAlgorithms() {
326
+ const available = [];
327
+
328
+ if (FossilDeltaDecoder.isAvailable()) {
329
+ available.push("fossil");
330
+ this._log("Fossil Delta decoder available");
331
+ }
332
+
333
+ if (Xdelta3Decoder.isAvailable()) {
334
+ available.push("xdelta3");
335
+ this._log("Xdelta3 decoder available");
336
+ }
337
+
338
+ return available;
339
+ }
340
+
341
+ /**
342
+ * Initialize delta compression
343
+ */
344
+ _initialize() {
345
+ // Wait for connection
346
+ if (this.pusher.connection.state === "connected") {
347
+ this._enableDeltaCompression();
348
+ } else {
349
+ this.pusher.connection.bind("connected", () => {
350
+ this._enableDeltaCompression();
351
+ });
352
+ }
353
+
354
+ // Bind to global events for delta messages
355
+ this._bindDeltaEvents();
356
+ }
357
+
358
+ /**
359
+ * Send enable request to server
360
+ */
361
+ _enableDeltaCompression() {
362
+ if (this.enabled) {
363
+ return;
364
+ }
365
+
366
+ // Filter to only algorithms we support AND server supports
367
+ const supportedAlgorithms = this.availableAlgorithms.filter((algo) =>
368
+ this.options.algorithms.includes(algo),
369
+ );
370
+
371
+ if (supportedAlgorithms.length === 0) {
372
+ this._log("No mutually supported algorithms");
373
+ return;
374
+ }
375
+
376
+ // Send enable request
377
+ const enableMessage = {
378
+ event: "pusher:enable_delta_compression",
379
+ data: JSON.stringify({
380
+ algorithms: supportedAlgorithms,
381
+ }),
382
+ };
383
+
384
+ this._log("Sending enable request", supportedAlgorithms);
385
+
386
+ // Access internal pusher connection to send message
387
+ if (this.pusher.connection.socket) {
388
+ this.pusher.connection.socket.send(JSON.stringify(enableMessage));
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Bind to delta-related events
394
+ */
395
+ _bindDeltaEvents() {
396
+ // Listen for enable confirmation
397
+ this.pusher.connection.bind(
398
+ "pusher:delta_compression_enabled",
399
+ (data) => {
400
+ this.enabled = data.enabled;
401
+ this._log("Delta compression enabled", data);
402
+ },
403
+ );
404
+
405
+ // Listen for cache sync (conflation keys)
406
+ this.pusher.connection.bind("pusher:delta_cache_sync", (data) => {
407
+ this._handleCacheSync(data);
408
+ });
409
+
410
+ // Listen for delta messages on all channels
411
+ // We need to intercept the raw WebSocket messages to get channel context
412
+ let retryCount = 0;
413
+ const setupInterceptor = () => {
414
+ // Debug: log what's available on first try
415
+ if (retryCount === 0 && this.pusher.connection) {
416
+ this._log(
417
+ "Connection object keys:",
418
+ Object.keys(this.pusher.connection),
419
+ );
420
+ }
421
+
422
+ // Check for socket - it might be under connection.socket or connection.socket_
423
+ const socket =
424
+ this.pusher.connection?.socket || this.pusher.connection?.socket_;
425
+
426
+ if (!socket) {
427
+ retryCount++;
428
+ if (retryCount <= 3) {
429
+ this._log(
430
+ `setupInterceptor: No socket available yet, retry ${retryCount}/3`,
431
+ );
432
+ setTimeout(() => setupInterceptor(), 100);
433
+ } else {
434
+ this._log(
435
+ "setupInterceptor: Giving up on WebSocket interceptor, using bind_global fallback",
436
+ );
437
+ this._setupFallbackBinding();
438
+ }
439
+ return;
440
+ }
441
+
442
+ const originalOnMessage = socket.onmessage;
443
+
444
+ this._log("WebSocket interceptor installed successfully!");
445
+
446
+ socket.onmessage = (event) => {
447
+ // IMPORTANT: Call original handler FIRST so Pusher processes the message
448
+ if (originalOnMessage) {
449
+ originalOnMessage.call(socket, event);
450
+ }
451
+
452
+ // Then track for our bandwidth stats
453
+ try {
454
+ const message = JSON.parse(event.data);
455
+
456
+ // Track delta messages for bandwidth stats
457
+ if (
458
+ message.event === "pusher:delta" &&
459
+ message.channel &&
460
+ message.data
461
+ ) {
462
+ const parsedData =
463
+ typeof message.data === "string"
464
+ ? JSON.parse(message.data)
465
+ : message.data;
466
+ this._log("Intercepted pusher:delta message", {
467
+ channel: message.channel,
468
+ seq: parsedData.seq,
469
+ });
470
+ this._trackDeltaMessage(message.channel, parsedData);
471
+
472
+ // Also handle delta decompression
473
+ this._handleDeltaMessage(message);
474
+ }
475
+
476
+ // Track regular messages with delta sequence for bandwidth stats
477
+ if (
478
+ message.channel &&
479
+ message.data &&
480
+ message.event !== "pusher:delta" &&
481
+ !message.event.startsWith("pusher:")
482
+ ) {
483
+ const parsedData =
484
+ typeof message.data === "string"
485
+ ? JSON.parse(message.data)
486
+ : message.data;
487
+ if (
488
+ parsedData &&
489
+ typeof parsedData === "object" &&
490
+ "__delta_seq" in parsedData
491
+ ) {
492
+ this._log("Intercepted full message with __delta_seq", {
493
+ channel: message.channel,
494
+ seq: parsedData.__delta_seq,
495
+ });
496
+ this._trackFullMessage(message.channel, parsedData);
497
+ this._handleRegularMessage(
498
+ message.channel,
499
+ message.event,
500
+ parsedData,
501
+ );
502
+ }
503
+ }
504
+ } catch (e) {
505
+ this._log("Error in interceptor", e);
506
+ }
507
+ };
508
+ };
509
+
510
+ // Setup interceptor now or wait for connection
511
+ if (this.pusher.connection.state === "connected") {
512
+ this._log("Connection already established, setting up interceptor");
513
+ setupInterceptor();
514
+ } else {
515
+ this._log("Waiting for connection before setting up interceptor");
516
+ this.pusher.connection.bind("connected", () => {
517
+ this._log("Connection established, setting up interceptor");
518
+ setupInterceptor();
519
+ });
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Setup fallback binding using bind_global (when WebSocket interception fails)
525
+ */
526
+ _setupFallbackBinding() {
527
+ this._log("Setting up fallback event binding");
528
+
529
+ // Store reference to all subscribed channels to track full messages
530
+ const subscribedChannels = new Set();
531
+
532
+ // First, bind to any already-subscribed channels
533
+ const allChannels = this.pusher.allChannels();
534
+ this._log(
535
+ `Found ${allChannels.length} existing channels, binding to them`,
536
+ );
537
+ allChannels.forEach((existingChannel) => {
538
+ subscribedChannels.add(existingChannel.name);
539
+ existingChannel.bind_global((eventName, data) => {
540
+ try {
541
+ if (
542
+ eventName.startsWith("pusher:") ||
543
+ eventName.startsWith("pusher_internal:")
544
+ ) {
545
+ return;
546
+ }
547
+
548
+ if (data && typeof data === "object" && "__delta_seq" in data) {
549
+ const messageJson = JSON.stringify(data);
550
+ this.stats.totalMessages++;
551
+ this.stats.fullMessages++;
552
+ this.stats.totalBytesWithoutCompression += messageJson.length;
553
+ this.stats.totalBytesWithCompression += messageJson.length;
554
+
555
+ if (data.__conflation_key !== undefined) {
556
+ this.stats.uniqueKeys.add(data.__conflation_key);
557
+ }
558
+
559
+ this._log("Tracked full message via fallback (existing)", {
560
+ channel: existingChannel.name,
561
+ size: messageJson.length,
562
+ seq: data.__delta_seq,
563
+ });
564
+ this._updateStats();
565
+
566
+ // IMPORTANT: Also initialize channel state for delta decompression
567
+ this._handleRegularMessage(existingChannel.name, eventName, data);
568
+ }
569
+ } catch (e) {
570
+ this._log("Error tracking full message on existing channel", e);
571
+ }
572
+ });
573
+ });
574
+
575
+ // Wrap subscribe to track new channels
576
+ const originalSubscribe = this.pusher.subscribe.bind(this.pusher);
577
+ this.pusher.subscribe = (channelName) => {
578
+ subscribedChannels.add(channelName);
579
+ const channel = originalSubscribe(channelName);
580
+
581
+ // Bind to all events on this channel to track full messages
582
+ channel.bind_global((eventName, data) => {
583
+ try {
584
+ // Skip Pusher protocol events
585
+ if (
586
+ eventName.startsWith("pusher:") ||
587
+ eventName.startsWith("pusher_internal:")
588
+ ) {
589
+ return;
590
+ }
591
+
592
+ // Track full messages with delta sequence
593
+ if (data && typeof data === "object" && "__delta_seq" in data) {
594
+ const messageJson = JSON.stringify(data);
595
+ this.stats.totalMessages++;
596
+ this.stats.fullMessages++;
597
+ this.stats.totalBytesWithoutCompression += messageJson.length;
598
+ this.stats.totalBytesWithCompression += messageJson.length;
599
+
600
+ if (data.__conflation_key !== undefined) {
601
+ this.stats.uniqueKeys.add(data.__conflation_key);
602
+ }
603
+
604
+ this._log("Tracked full message via fallback", {
605
+ channel: channelName,
606
+ size: messageJson.length,
607
+ seq: data.__delta_seq,
608
+ });
609
+ this._updateStats();
610
+
611
+ // IMPORTANT: Also initialize channel state for delta decompression
612
+ this._handleRegularMessage(channelName, eventName, data);
613
+ }
614
+ } catch (e) {
615
+ this._log("Error tracking full message", e);
616
+ }
617
+ });
618
+
619
+ return channel;
620
+ };
621
+
622
+ // Bind to global pusher:delta events for delta tracking AND decompression
623
+ this.pusher.bind_global((eventName, data) => {
624
+ try {
625
+ if (eventName === "pusher:delta") {
626
+ // Track delta message stats
627
+ if (data && typeof data === "object") {
628
+ const parsedData =
629
+ typeof data === "string" ? JSON.parse(data) : data;
630
+ if (parsedData.delta) {
631
+ const deltaBytes = base64ToBytes(parsedData.delta);
632
+
633
+ this.stats.totalMessages++;
634
+ this.stats.deltaMessages++;
635
+ this.stats.totalBytesWithCompression += deltaBytes.length;
636
+
637
+ // Estimate original size - typical price update is ~200 bytes
638
+ // We can't decompress without base messages (server isn't sending __delta_seq in full messages)
639
+ const estimatedOriginalSize = 200;
640
+ this.stats.totalBytesWithoutCompression +=
641
+ estimatedOriginalSize;
642
+
643
+ this._log("Tracked delta via fallback", {
644
+ deltaSize: deltaBytes.length,
645
+ estimatedOriginal: estimatedOriginalSize,
646
+ seq: parsedData.seq,
647
+ });
648
+
649
+ this._updateStats();
650
+ }
651
+ }
652
+ }
653
+ } catch (e) {
654
+ this._log("Error in fallback binding", e);
655
+ }
656
+
657
+ return true; // Allow event to propagate
658
+ });
659
+ }
660
+
661
+ /**
662
+ * Handle cache sync message (conflation keys)
663
+ */
664
+ _handleCacheSync(rawData) {
665
+ try {
666
+ // Parse if string
667
+ const data =
668
+ typeof rawData === "string" ? JSON.parse(rawData) : rawData;
669
+ const parsedData =
670
+ typeof data.data === "string" ? JSON.parse(data.data) : data.data;
671
+ const channel = data.channel;
672
+
673
+ this._log("Received cache sync", {
674
+ channel,
675
+ conflationKey: parsedData.conflation_key,
676
+ groupCount: Object.keys(parsedData.states || {}).length,
677
+ });
678
+
679
+ // Get or create channel state
680
+ let channelState = this.channelStates.get(channel);
681
+ if (!channelState) {
682
+ channelState = new ChannelState(channel);
683
+ this.channelStates.set(channel, channelState);
684
+ }
685
+
686
+ // Initialize from cache sync
687
+ channelState.initializeFromCacheSync(parsedData);
688
+
689
+ this._log("Cache initialized", channelState.getStats());
690
+ } catch (error) {
691
+ this._error("Failed to handle cache sync", error);
692
+ }
693
+ }
694
+
695
+ /**
696
+ * Handle delta-compressed message
697
+ */
698
+ _handleDeltaMessage(message) {
699
+ try {
700
+ // message is the full WebSocket message envelope: {event, channel, data}
701
+ const channel = message.channel;
702
+ const parsedData =
703
+ typeof message.data === "string"
704
+ ? JSON.parse(message.data)
705
+ : message.data;
706
+
707
+ const event = parsedData.event;
708
+ const delta = parsedData.delta;
709
+ const sequence = parsedData.seq;
710
+ const algorithm = parsedData.algorithm || "fossil";
711
+ const conflationKey = parsedData.conflation_key;
712
+ const baseIndex = parsedData.base_index;
713
+
714
+ this._log("Received delta message", {
715
+ channel,
716
+ event,
717
+ sequence,
718
+ algorithm,
719
+ conflationKey,
720
+ baseIndex,
721
+ deltaSize: delta.length,
722
+ });
723
+
724
+ // Get channel state
725
+ let channelState = this.channelStates.get(channel);
726
+ if (!channelState) {
727
+ this._error(`No channel state for ${channel}`);
728
+ this._requestResync(channel);
729
+ return false;
730
+ }
731
+
732
+ // Get base message
733
+ let baseMessage;
734
+ if (channelState.conflationKey) {
735
+ // Conflation mode: get specific base by key and index
736
+ baseMessage = channelState.getBaseMessage(conflationKey, baseIndex);
737
+ if (!baseMessage) {
738
+ this._error(
739
+ `No base message for channel ${channel}, key ${conflationKey}, index ${baseIndex}`,
740
+ );
741
+ this._requestResync(channel);
742
+ return false;
743
+ }
744
+ } else {
745
+ // Legacy mode: single base message
746
+ baseMessage = channelState.baseMessage;
747
+ if (!baseMessage) {
748
+ this._error(`No base message for channel ${channel}`);
749
+ this._requestResync(channel);
750
+ return false;
751
+ }
752
+ }
753
+
754
+ // Decode base64 delta
755
+ const deltaBytes = base64ToBytes(delta);
756
+
757
+ // Apply delta based on algorithm
758
+ let reconstructedMessage;
759
+ if (algorithm === "fossil") {
760
+ reconstructedMessage = FossilDeltaDecoder.apply(
761
+ baseMessage,
762
+ deltaBytes,
763
+ );
764
+ } else if (algorithm === "xdelta3") {
765
+ reconstructedMessage = Xdelta3Decoder.apply(baseMessage, deltaBytes);
766
+ } else {
767
+ throw new Error(`Unknown algorithm: ${algorithm}`);
768
+ }
769
+
770
+ // Update conflation cache with reconstructed message
771
+ if (channelState.conflationKey) {
772
+ channelState.updateConflationCache(
773
+ conflationKey,
774
+ reconstructedMessage,
775
+ sequence,
776
+ );
777
+ }
778
+
779
+ // Update state
780
+ channelState.updateSequence(sequence);
781
+ channelState.recordDelta();
782
+
783
+ // Note: Bandwidth stats are tracked in _trackDeltaMessage, but we need to add
784
+ // the reconstructed message size here since we didn't know it before decompression
785
+ this.stats.totalBytesWithoutCompression += reconstructedMessage.length;
786
+ this._updateStats();
787
+
788
+ // Emit the reconstructed event
789
+ const pusherChannel = this.pusher.channel(channel);
790
+ if (pusherChannel) {
791
+ const parsedMessageData = JSON.parse(reconstructedMessage);
792
+ pusherChannel.emit(event, parsedMessageData);
793
+ }
794
+
795
+ this._log("Delta applied successfully", {
796
+ channel,
797
+ event,
798
+ conflationKey,
799
+ originalSize: reconstructedMessage.length,
800
+ deltaSize: deltaBytes.length,
801
+ compressionRatio:
802
+ ((deltaBytes.length / reconstructedMessage.length) * 100).toFixed(
803
+ 1,
804
+ ) + "%",
805
+ });
806
+
807
+ return false; // Prevent original event from propagating
808
+ } catch (error) {
809
+ this._error("Delta decode failed", error);
810
+ this.stats.errors++;
811
+ return false;
812
+ }
813
+ }
814
+
815
+ /**
816
+ * Track delta message for bandwidth stats
817
+ */
818
+ _trackDeltaMessage(channelName, deltaData) {
819
+ try {
820
+ const deltaBytes = base64ToBytes(deltaData.delta || "");
821
+
822
+ // Update stats - delta message received
823
+ this.stats.totalMessages++;
824
+ this.stats.deltaMessages++;
825
+ this.stats.totalBytesWithCompression += deltaBytes.length;
826
+
827
+ this._log("Tracked delta message", {
828
+ channel: channelName,
829
+ deltaSize: deltaBytes.length,
830
+ seq: deltaData.seq,
831
+ });
832
+
833
+ this._updateStats();
834
+ } catch (e) {
835
+ this._log("Error tracking delta message", e);
836
+ }
837
+ }
838
+
839
+ /**
840
+ * Track full message for bandwidth stats
841
+ */
842
+ _trackFullMessage(channelName, data) {
843
+ try {
844
+ const messageJson = JSON.stringify(data);
845
+ const messageSize = messageJson.length;
846
+
847
+ // Update stats - full message received
848
+ this.stats.totalMessages++;
849
+ this.stats.fullMessages++;
850
+ this.stats.totalBytesWithoutCompression += messageSize;
851
+ this.stats.totalBytesWithCompression += messageSize;
852
+
853
+ // Track conflation key
854
+ if (data.__conflation_key !== undefined) {
855
+ this.stats.uniqueKeys.add(data.__conflation_key);
856
+ }
857
+
858
+ this._log("Tracked full message", {
859
+ channel: channelName,
860
+ size: messageSize,
861
+ seq: data.__delta_seq,
862
+ conflationKey: data.__conflation_key,
863
+ });
864
+
865
+ this._updateStats();
866
+ } catch (e) {
867
+ this._log("Error tracking full message", e);
868
+ }
869
+ }
870
+
871
+ /**
872
+ * Handle regular (full) message
873
+ */
874
+ _handleRegularMessage(channelName, eventName, data) {
875
+ // Check if this message contains delta sequence markers
876
+ if (data && typeof data === "object" && "__delta_seq" in data) {
877
+ const sequence = data.__delta_seq;
878
+ const conflationKey = data.__conflation_key;
879
+
880
+ // Find which channel this message belongs to by checking all subscribed channels
881
+ let targetChannelName = null;
882
+ for (const channelName of this.channelStates.keys()) {
883
+ const channel = this.pusher.channel(channelName);
884
+ if (channel && channel.subscribed) {
885
+ targetChannelName = channelName;
886
+ break;
887
+ }
888
+ }
889
+
890
+ // If no existing channel state, assume it's for the first subscribed channel
891
+ if (!targetChannelName) {
892
+ const allChannels = this.pusher.allChannels();
893
+ if (allChannels.length > 0) {
894
+ targetChannelName = allChannels[0].name;
895
+ }
896
+ }
897
+
898
+ if (!targetChannelName) {
899
+ this._log("Cannot determine channel for message", {
900
+ eventName,
901
+ sequence,
902
+ });
903
+ return true;
904
+ }
905
+
906
+ // This is a full message with delta tracking
907
+ const messageJson = JSON.stringify(data);
908
+
909
+ let channelState = this.channelStates.get(targetChannelName);
910
+ if (!channelState) {
911
+ channelState = new ChannelState(targetChannelName);
912
+ this.channelStates.set(targetChannelName, channelState);
913
+ }
914
+
915
+ // Update cache
916
+ if (channelState.conflationKey && conflationKey !== undefined) {
917
+ // Conflation mode: update specific conflation group cache
918
+ channelState.updateConflationCache(
919
+ conflationKey,
920
+ messageJson,
921
+ sequence,
922
+ );
923
+
924
+ this._log("Stored full message (conflation)", {
925
+ channel: targetChannelName,
926
+ conflationKey,
927
+ sequence,
928
+ size: messageJson.length,
929
+ });
930
+ } else {
931
+ // Legacy mode: update single base
932
+ channelState.setBase(messageJson, sequence);
933
+
934
+ this._log("Stored full message", {
935
+ channel: targetChannelName,
936
+ sequence,
937
+ size: messageJson.length,
938
+ });
939
+ }
940
+
941
+ channelState.recordFullMessage();
942
+
943
+ // Update stats
944
+ this.stats.totalMessages++;
945
+ this.stats.fullMessages++;
946
+ this.stats.totalBytesWithoutCompression += messageJson.length;
947
+ this.stats.totalBytesWithCompression += messageJson.length;
948
+ this._updateStats();
949
+ }
950
+
951
+ return true; // Allow event to propagate normally
952
+ }
953
+
954
+ /**
955
+ * Request resync for a channel
956
+ */
957
+ _requestResync(channel) {
958
+ const resyncMessage = {
959
+ event: "pusher:delta_sync_error",
960
+ data: JSON.stringify({
961
+ channel,
962
+ }),
963
+ };
964
+
965
+ this._log("Requesting resync for channel", channel);
966
+
967
+ if (this.pusher.connection.socket) {
968
+ this.pusher.connection.socket.send(JSON.stringify(resyncMessage));
969
+ }
970
+
971
+ // Clear channel state
972
+ this.channelStates.delete(channel);
973
+ }
974
+
975
+ /**
976
+ * Update and emit stats
977
+ */
978
+ _updateStats() {
979
+ if (this.options.onStats) {
980
+ const bandwidthSaved =
981
+ this.stats.totalBytesWithoutCompression -
982
+ this.stats.totalBytesWithCompression;
983
+ const bandwidthSavedPercent =
984
+ this.stats.totalBytesWithoutCompression > 0
985
+ ? (
986
+ (bandwidthSaved / this.stats.totalBytesWithoutCompression) *
987
+ 100
988
+ ).toFixed(1)
989
+ : 0;
990
+
991
+ this.options.onStats({
992
+ ...this.stats,
993
+ bandwidthSaved,
994
+ bandwidthSavedPercent: parseFloat(bandwidthSavedPercent),
995
+ });
996
+ }
997
+ }
998
+
999
+ /**
1000
+ * Get current statistics
1001
+ */
1002
+ getStats() {
1003
+ const bandwidthSaved =
1004
+ this.stats.totalBytesWithoutCompression -
1005
+ this.stats.totalBytesWithCompression;
1006
+ const bandwidthSavedPercent =
1007
+ this.stats.totalBytesWithoutCompression > 0
1008
+ ? (
1009
+ (bandwidthSaved / this.stats.totalBytesWithoutCompression) *
1010
+ 100
1011
+ ).toFixed(1)
1012
+ : 0;
1013
+
1014
+ // Get per-channel statistics
1015
+ const channelStats = [];
1016
+ for (const [channelName, channelState] of this.channelStates) {
1017
+ channelStats.push(channelState.getStats());
1018
+ }
1019
+
1020
+ return {
1021
+ ...this.stats,
1022
+ bandwidthSaved,
1023
+ bandwidthSavedPercent: parseFloat(bandwidthSavedPercent),
1024
+ enabled: this.enabled,
1025
+ availableAlgorithms: this.availableAlgorithms,
1026
+ channelCount: this.channelStates.size,
1027
+ channels: channelStats,
1028
+ };
1029
+ }
1030
+
1031
+ /**
1032
+ * Reset statistics
1033
+ */
1034
+ resetStats() {
1035
+ this.stats = {
1036
+ totalMessages: 0,
1037
+ deltaMessages: 0,
1038
+ fullMessages: 0,
1039
+ totalBytesWithoutCompression: 0,
1040
+ totalBytesWithCompression: 0,
1041
+ errors: 0,
1042
+ };
1043
+ this._updateStats();
1044
+ }
1045
+
1046
+ /**
1047
+ * Clear channel state (useful for testing)
1048
+ */
1049
+ clearChannelState(channel) {
1050
+ if (channel) {
1051
+ this.channelStates.delete(channel);
1052
+ } else {
1053
+ this.channelStates.clear();
1054
+ }
1055
+ }
1056
+
1057
+ /**
1058
+ * Log message (if debug enabled)
1059
+ */
1060
+ _log(...args) {
1061
+ if (this.options.debug) {
1062
+ console.log("[DeltaCompression]", ...args);
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Log error
1068
+ */
1069
+ _error(...args) {
1070
+ console.error("[DeltaCompression]", ...args);
1071
+ if (this.options.onError) {
1072
+ this.options.onError(args);
1073
+ }
1074
+ }
1075
+ }
1076
+
1077
+ // ============================================================================
1078
+ // Export
1079
+ // ============================================================================
1080
+
1081
+ // Export to global scope
1082
+ global.DeltaCompressionManager = DeltaCompressionManager;
1083
+
1084
+ // Also export utility classes for advanced usage
1085
+ global.DeltaCompression = {
1086
+ Manager: DeltaCompressionManager,
1087
+ FossilDecoder: FossilDeltaDecoder,
1088
+ Xdelta3Decoder: Xdelta3Decoder,
1089
+ };
1090
+ })(window);