@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.
- package/.editorconfig +14 -0
- package/.github/ISSUE_TEMPLATE.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/.github/dependabot.yml +14 -0
- package/.github/stale.yml +26 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/release_pr.yml +43 -0
- package/.github/workflows/run-tests.yml +62 -0
- package/.gitmodules +3 -0
- package/.prettierrc +2 -0
- package/CHANGELOG.md +928 -0
- package/DELTA_COMPRESSION.md +365 -0
- package/DELTA_USAGE.md +179 -0
- package/IMPLEMENTATION_SUMMARY.md +261 -0
- package/IMPORT_GUIDE.md +638 -0
- package/LIBRARY_STRUCTURE_ANALYSIS.md +940 -0
- package/LICENCE +19 -0
- package/Makefile +14 -0
- package/README.md +709 -0
- package/TAG_FILTERING_CLIENT.md +471 -0
- package/bower.json +19 -0
- package/bun.lock +2695 -0
- package/dist/node/filter.js +252 -0
- package/dist/node/filter.js.map +1 -0
- package/dist/node/pusher.js +4434 -0
- package/dist/node/pusher.js.map +1 -0
- package/dist/web/filter.mjs +252 -0
- package/dist/web/filter.mjs.map +1 -0
- package/dist/web/pusher.mjs +5889 -0
- package/dist/web/pusher.mjs.map +1 -0
- package/examples/delta-compression-example.html +372 -0
- package/examples/delta-seamless-example.html +185 -0
- package/index.d.ts +36 -0
- package/integration_tests_server/index.js +176 -0
- package/integration_tests_server/package-lock.json +1177 -0
- package/integration_tests_server/package.json +15 -0
- package/interactive/.env +16 -0
- package/interactive/CONFLATION_TEST.md +73 -0
- package/interactive/DELTA_COMPRESSION_TESTING.md +262 -0
- package/interactive/bun.lock +208 -0
- package/interactive/package-lock.json +1075 -0
- package/interactive/package.json +32 -0
- package/interactive/public/app.js +1363 -0
- package/interactive/public/bundle-entry.js +14 -0
- package/interactive/public/conflation-test.html +508 -0
- package/interactive/public/conflation-test.js +785 -0
- package/interactive/public/delta-compression.js +1090 -0
- package/interactive/public/dist/bundle.js +5857 -0
- package/interactive/public/index.html +392 -0
- package/interactive/public/main.js +20 -0
- package/interactive/public/style.css +823 -0
- package/interactive/server.js +246 -0
- package/interactive/test-bundle.html +89 -0
- package/interactive/test-delta.js +146 -0
- package/node.js +1 -0
- package/package.json +94 -0
- package/pusher-with-encryption/index.js +1 -0
- package/react-native/index.d.ts +29 -0
- package/react-native/index.js +1 -0
- package/spec/config/jasmine/helpers/reporter.js +14 -0
- package/spec/config/jasmine/integration.json +13 -0
- package/spec/config/jasmine/unit.json +13 -0
- package/spec/config/jasmine/webpack.integration.js +33 -0
- package/spec/config/jasmine/webpack.unit.js +30 -0
- package/spec/config/karma/available_browsers.json +4957 -0
- package/spec/config/karma/config.ci.js +25 -0
- package/spec/config/karma/config.common.js +50 -0
- package/spec/config/karma/config.integration.js +26 -0
- package/spec/config/karma/config.unit.js +10 -0
- package/spec/config/karma/config.worker.js +34 -0
- package/spec/config/karma/integration.js +24 -0
- package/spec/config/karma/unit.js +20 -0
- package/spec/javascripts/helpers/mocks.js +274 -0
- package/spec/javascripts/helpers/node/integration.js +33 -0
- package/spec/javascripts/helpers/node/mock-dom-dependencies.ts +1 -0
- package/spec/javascripts/helpers/pusher_integration.js +1 -0
- package/spec/javascripts/helpers/pusher_integration_class.ts +12 -0
- package/spec/javascripts/helpers/timers/promises.js +9 -0
- package/spec/javascripts/helpers/waitsFor.js +37 -0
- package/spec/javascripts/helpers/web/integration.js +44 -0
- package/spec/javascripts/helpers/worker/mock-dom-dependencies.js +1 -0
- package/spec/javascripts/integration/core/cluster_config_spec.js +153 -0
- package/spec/javascripts/integration/core/falling_back_spec.js +195 -0
- package/spec/javascripts/integration/core/pusher_spec/index.js +68 -0
- package/spec/javascripts/integration/core/pusher_spec/test_builder.js +715 -0
- package/spec/javascripts/integration/core/timeout_configuration_spec.js +200 -0
- package/spec/javascripts/integration/core/transport_lists_spec.js +103 -0
- package/spec/javascripts/integration/index.node.js +12 -0
- package/spec/javascripts/integration/index.web.js +63 -0
- package/spec/javascripts/integration/index.worker.js +13 -0
- package/spec/javascripts/integration/web/dom/jsonp_spec.js +97 -0
- package/spec/javascripts/integration/web/dom/script_request_spec.js +90 -0
- package/spec/javascripts/polyfills/index.js +105 -0
- package/spec/javascripts/unit/core/channels/channel_spec.js +355 -0
- package/spec/javascripts/unit/core/channels/channels_spec.js +94 -0
- package/spec/javascripts/unit/core/channels/encrypted_channel_spec.js +343 -0
- package/spec/javascripts/unit/core/channels/presence_channel_spec.js +553 -0
- package/spec/javascripts/unit/core/channels/private_channel_spec.js +182 -0
- package/spec/javascripts/unit/core/config_spec.js +507 -0
- package/spec/javascripts/unit/core/connection/connection_manager_spec.js +656 -0
- package/spec/javascripts/unit/core/connection/connection_spec.js +286 -0
- package/spec/javascripts/unit/core/connection/handshake_spec.js +160 -0
- package/spec/javascripts/unit/core/connection/protocol_spec.js +420 -0
- package/spec/javascripts/unit/core/defaults_spec.js +26 -0
- package/spec/javascripts/unit/core/events_dispatcher_spec.js +385 -0
- package/spec/javascripts/unit/core/http/http_polling_socket_spec.js +60 -0
- package/spec/javascripts/unit/core/http/http_request_spec.js +185 -0
- package/spec/javascripts/unit/core/http/http_socket_spec.js +370 -0
- package/spec/javascripts/unit/core/http/http_streaming_socket_spec.js +56 -0
- package/spec/javascripts/unit/core/http/http_xhr_request_spec.js +164 -0
- package/spec/javascripts/unit/core/logger_spec.js +133 -0
- package/spec/javascripts/unit/core/pusher_spec.js +613 -0
- package/spec/javascripts/unit/core/pusher_with_encryption_spec.js +18 -0
- package/spec/javascripts/unit/core/strategies/best_connected_ever_strategy_spec.js +104 -0
- package/spec/javascripts/unit/core/strategies/delayed_strategy_spec.js +95 -0
- package/spec/javascripts/unit/core/strategies/first_connected_strategy_spec.js +68 -0
- package/spec/javascripts/unit/core/strategies/if_strategy_spec.js +165 -0
- package/spec/javascripts/unit/core/strategies/sequential_strategy_spec.js +213 -0
- package/spec/javascripts/unit/core/strategies/transport_strategy_spec.js +250 -0
- package/spec/javascripts/unit/core/strategies/websocket_prioritized_cached_strategy_spec.js +400 -0
- package/spec/javascripts/unit/core/timeline/timeline_spec.js +153 -0
- package/spec/javascripts/unit/core/transports/assistant_to_the_transport_manager_spec.js +223 -0
- package/spec/javascripts/unit/core/transports/hosts_and_ports_spec.js +85 -0
- package/spec/javascripts/unit/core/transports/transport_connection_spec.js +585 -0
- package/spec/javascripts/unit/core/transports/transport_manager_spec.js +64 -0
- package/spec/javascripts/unit/core/user_spec.js +303 -0
- package/spec/javascripts/unit/core/utils/periodic_timer_spec.js +74 -0
- package/spec/javascripts/unit/core/utils/timers_spec.js +157 -0
- package/spec/javascripts/unit/core/utils/url_store_spec.js +14 -0
- package/spec/javascripts/unit/core/watchlist_spec.js +48 -0
- package/spec/javascripts/unit/core_with_runtime/auth/channel_authorizer_spec.js +137 -0
- package/spec/javascripts/unit/core_with_runtime/auth/deprecated_channel_authorizer_spec.js +48 -0
- package/spec/javascripts/unit/core_with_runtime/auth/user_authorizer_spec.js +128 -0
- package/spec/javascripts/unit/core_with_runtime/readme.md +5 -0
- package/spec/javascripts/unit/index.node.js +11 -0
- package/spec/javascripts/unit/index.web.js +12 -0
- package/spec/javascripts/unit/index.worker.js +11 -0
- package/spec/javascripts/unit/isomorphic/transports/hosts_and_ports_spec.js +82 -0
- package/spec/javascripts/unit/isomorphic/transports/transports_spec.js +202 -0
- package/spec/javascripts/unit/node/timeline_sender_spec.js +83 -0
- package/spec/javascripts/unit/web/dom/dependency_loader_spec.js +249 -0
- package/spec/javascripts/unit/web/dom/jsonp_request_spec.js +130 -0
- package/spec/javascripts/unit/web/dom/script_receiver_factory_spec.js +68 -0
- package/spec/javascripts/unit/web/http/http_xdomain_request_spec.js +222 -0
- package/spec/javascripts/unit/web/pusher_authorizer_spec.js +64 -0
- package/spec/javascripts/unit/web/timeline/timeline_sender_spec.js +131 -0
- package/spec/javascripts/unit/web/transports/hosts_and_ports_spec.js +127 -0
- package/spec/javascripts/unit/web/transports/transports_spec.js +444 -0
- package/spec/javascripts/unit/worker/channel_authorizer_spec.js +156 -0
- package/spec/javascripts/unit/worker/timeline_sender_spec.js +76 -0
- package/src/core/auth/auth_transports.ts +18 -0
- package/src/core/auth/channel_authorizer.ts +64 -0
- package/src/core/auth/deprecated_channel_authorizer.ts +56 -0
- package/src/core/auth/options.ts +76 -0
- package/src/core/auth/user_authenticator.ts +62 -0
- package/src/core/base64.ts +49 -0
- package/src/core/channels/channel.ts +173 -0
- package/src/core/channels/channel_table.ts +7 -0
- package/src/core/channels/channels.ts +86 -0
- package/src/core/channels/encrypted_channel.ts +150 -0
- package/src/core/channels/filter.ts +342 -0
- package/src/core/channels/members.ts +80 -0
- package/src/core/channels/metadata.ts +5 -0
- package/src/core/channels/presence_channel.ts +113 -0
- package/src/core/channels/private_channel.ts +25 -0
- package/src/core/config.ts +189 -0
- package/src/core/connection/callbacks.ts +21 -0
- package/src/core/connection/connection.ts +160 -0
- package/src/core/connection/connection_manager.ts +371 -0
- package/src/core/connection/connection_manager_options.ts +14 -0
- package/src/core/connection/handshake/handshake_payload.ts +10 -0
- package/src/core/connection/handshake/index.ts +90 -0
- package/src/core/connection/protocol/action.ts +8 -0
- package/src/core/connection/protocol/message-types.ts +11 -0
- package/src/core/connection/protocol/protocol.ts +166 -0
- package/src/core/defaults.ts +66 -0
- package/src/core/delta/channel_state.ts +194 -0
- package/src/core/delta/decoders.ts +129 -0
- package/src/core/delta/index.ts +10 -0
- package/src/core/delta/manager.ts +504 -0
- package/src/core/delta/types.ts +60 -0
- package/src/core/errors.ts +69 -0
- package/src/core/events/callback.ts +6 -0
- package/src/core/events/callback_registry.ts +75 -0
- package/src/core/events/callback_table.ts +7 -0
- package/src/core/events/dispatcher.ts +84 -0
- package/src/core/http/ajax.ts +24 -0
- package/src/core/http/http_factory.ts +16 -0
- package/src/core/http/http_polling_socket.ts +24 -0
- package/src/core/http/http_request.ts +81 -0
- package/src/core/http/http_socket.ts +220 -0
- package/src/core/http/http_streaming_socket.ts +19 -0
- package/src/core/http/request_hooks.ts +9 -0
- package/src/core/http/socket_hooks.ts +11 -0
- package/src/core/http/state.ts +7 -0
- package/src/core/http/url_location.ts +6 -0
- package/src/core/logger.ts +66 -0
- package/src/core/options.ts +61 -0
- package/src/core/pusher-licence.js +7 -0
- package/src/core/pusher-with-encryption.js +1 -0
- package/src/core/pusher-with-encryption.ts +14 -0
- package/src/core/pusher.js +10 -0
- package/src/core/pusher.ts +412 -0
- package/src/core/reachability.ts +7 -0
- package/src/core/socket.ts +14 -0
- package/src/core/strategies/best_connected_ever_strategy.ts +81 -0
- package/src/core/strategies/delayed_strategy.ts +48 -0
- package/src/core/strategies/first_connected_strategy.ts +31 -0
- package/src/core/strategies/if_strategy.ts +34 -0
- package/src/core/strategies/sequential_strategy.ts +129 -0
- package/src/core/strategies/strategy.ts +8 -0
- package/src/core/strategies/strategy_builder.ts +67 -0
- package/src/core/strategies/strategy_options.ts +18 -0
- package/src/core/strategies/strategy_runner.ts +6 -0
- package/src/core/strategies/transport_strategy.ts +144 -0
- package/src/core/strategies/websocket_prioritized_cached_strategy.ts +157 -0
- package/src/core/timeline/level.ts +7 -0
- package/src/core/timeline/timeline.ts +90 -0
- package/src/core/timeline/timeline_sender.ts +33 -0
- package/src/core/timeline/timeline_transport.ts +11 -0
- package/src/core/transports/assistant_to_the_transport_manager.ts +104 -0
- package/src/core/transports/ping_delay_options.ts +7 -0
- package/src/core/transports/transport.ts +54 -0
- package/src/core/transports/transport_connection.ts +241 -0
- package/src/core/transports/transport_connection_options.ts +8 -0
- package/src/core/transports/transport_hooks.ts +16 -0
- package/src/core/transports/transport_manager.ts +52 -0
- package/src/core/transports/transports_table.ts +12 -0
- package/src/core/transports/url_scheme.ts +13 -0
- package/src/core/transports/url_schemes.ts +47 -0
- package/src/core/user.ts +186 -0
- package/src/core/util.ts +34 -0
- package/src/core/utils/collections.ts +353 -0
- package/src/core/utils/factory.ts +79 -0
- package/src/core/utils/flat_promise.ts +10 -0
- package/src/core/utils/timers/abstract_timer.ts +39 -0
- package/src/core/utils/timers/index.ts +39 -0
- package/src/core/utils/timers/scheduling.ts +11 -0
- package/src/core/utils/timers/timed_callback.ts +5 -0
- package/src/core/utils/url_store.ts +48 -0
- package/src/core/watchlist.ts +31 -0
- package/src/d.ts/constants/index.d.ts +5 -0
- package/src/d.ts/faye-websocket/faye-websocket.d.ts +21 -0
- package/src/d.ts/global/global.d.ts +1 -0
- package/src/d.ts/module/module.d.ts +12 -0
- package/src/d.ts/tweetnacl-util/index.d.ts +6 -0
- package/src/d.ts/window/events.d.ts +4 -0
- package/src/d.ts/window/sockjs.d.ts +3 -0
- package/src/d.ts/window/websocket.d.ts +4 -0
- package/src/d.ts/window/xmlhttprequest.d.ts +3 -0
- package/src/filter.ts +5 -0
- package/src/index.ts +8 -0
- package/src/runtimes/interface.ts +60 -0
- package/src/runtimes/isomorphic/auth/xhr_auth.ts +90 -0
- package/src/runtimes/isomorphic/default_strategy.ts +155 -0
- package/src/runtimes/isomorphic/http/http.ts +32 -0
- package/src/runtimes/isomorphic/http/http_xhr_request.ts +35 -0
- package/src/runtimes/isomorphic/runtime.ts +62 -0
- package/src/runtimes/isomorphic/timeline/xhr_timeline.ts +50 -0
- package/src/runtimes/isomorphic/transports/transport_connection_initializer.ts +19 -0
- package/src/runtimes/isomorphic/transports/transports.ts +83 -0
- package/src/runtimes/node/net_info.ts +10 -0
- package/src/runtimes/node/runtime.ts +68 -0
- package/src/runtimes/react-native/net_info.ts +42 -0
- package/src/runtimes/react-native/runtime.ts +65 -0
- package/src/runtimes/web/auth/jsonp_auth.ts +51 -0
- package/src/runtimes/web/browser.ts +24 -0
- package/src/runtimes/web/default_strategy.ts +201 -0
- package/src/runtimes/web/dom/dependencies.ts +16 -0
- package/src/runtimes/web/dom/dependency_loader.ts +93 -0
- package/src/runtimes/web/dom/json2.js +486 -0
- package/src/runtimes/web/dom/jsonp_request.ts +52 -0
- package/src/runtimes/web/dom/script_receiver.ts +8 -0
- package/src/runtimes/web/dom/script_receiver_factory.ts +57 -0
- package/src/runtimes/web/dom/script_request.ts +85 -0
- package/src/runtimes/web/http/http.ts +8 -0
- package/src/runtimes/web/http/http_xdomain_request.ts +37 -0
- package/src/runtimes/web/net_info.ts +50 -0
- package/src/runtimes/web/runtime.ts +174 -0
- package/src/runtimes/web/timeline/jsonp_timeline.ts +34 -0
- package/src/runtimes/web/transports/transport_connection_initializer.ts +39 -0
- package/src/runtimes/web/transports/transports.ts +67 -0
- package/src/runtimes/worker/auth/fetch_auth.ts +69 -0
- package/src/runtimes/worker/net_info.ts +10 -0
- package/src/runtimes/worker/runtime.ts +75 -0
- package/src/runtimes/worker/timeline/fetch_timeline.ts +39 -0
- package/tsconfig.json +18 -0
- package/types/spec/javascripts/helpers/node/mock-dom-dependencies.d.ts +1 -0
- package/types/spec/javascripts/helpers/pusher_integration_class.d.ts +4 -0
- package/types/src/core/auth/auth_transports.d.ts +9 -0
- package/types/src/core/auth/channel_authorizer.d.ts +3 -0
- package/types/src/core/auth/deprecated_channel_authorizer.d.ts +18 -0
- package/types/src/core/auth/options.d.ts +48 -0
- package/types/src/core/auth/user_authenticator.d.ts +3 -0
- package/types/src/core/base64.d.ts +1 -0
- package/types/src/core/channels/channel.d.ts +25 -0
- package/types/src/core/channels/channel_table.d.ts +5 -0
- package/types/src/core/channels/channels.d.ts +12 -0
- package/types/src/core/channels/encrypted_channel.d.ts +15 -0
- package/types/src/core/channels/filter.d.ts +33 -0
- package/types/src/core/channels/members.d.ts +14 -0
- package/types/src/core/channels/metadata.d.ts +4 -0
- package/types/src/core/channels/presence_channel.d.ts +13 -0
- package/types/src/core/channels/private_channel.d.ts +5 -0
- package/types/src/core/config.d.ts +31 -0
- package/types/src/core/connection/callbacks.d.ts +18 -0
- package/types/src/core/connection/connection.d.ts +16 -0
- package/types/src/core/connection/connection_manager.d.ts +50 -0
- package/types/src/core/connection/connection_manager_options.d.ts +11 -0
- package/types/src/core/connection/handshake/handshake_payload.d.ts +8 -0
- package/types/src/core/connection/handshake/index.d.ts +12 -0
- package/types/src/core/connection/protocol/action.d.ts +7 -0
- package/types/src/core/connection/protocol/message-types.d.ts +10 -0
- package/types/src/core/connection/protocol/protocol.d.ts +10 -0
- package/types/src/core/defaults.d.ts +26 -0
- package/types/src/core/delta/channel_state.d.ts +23 -0
- package/types/src/core/delta/decoders.d.ts +12 -0
- package/types/src/core/delta/index.d.ts +4 -0
- package/types/src/core/delta/manager.d.ts +27 -0
- package/types/src/core/delta/types.d.ts +50 -0
- package/types/src/core/errors.d.ts +28 -0
- package/types/src/core/events/callback.d.ts +5 -0
- package/types/src/core/events/callback_registry.d.ts +11 -0
- package/types/src/core/events/callback_table.d.ts +5 -0
- package/types/src/core/events/dispatcher.d.ts +14 -0
- package/types/src/core/http/ajax.d.ts +16 -0
- package/types/src/core/http/http_factory.d.ts +13 -0
- package/types/src/core/http/http_polling_socket.d.ts +3 -0
- package/types/src/core/http/http_request.d.ts +17 -0
- package/types/src/core/http/http_socket.d.ts +32 -0
- package/types/src/core/http/http_streaming_socket.d.ts +3 -0
- package/types/src/core/http/request_hooks.d.ts +6 -0
- package/types/src/core/http/socket_hooks.d.ts +8 -0
- package/types/src/core/http/state.d.ts +6 -0
- package/types/src/core/http/url_location.d.ts +5 -0
- package/types/src/core/logger.d.ts +11 -0
- package/types/src/core/options.d.ts +36 -0
- package/types/src/core/pusher-with-encryption.d.ts +5 -0
- package/types/src/core/pusher.d.ts +56 -0
- package/types/src/core/reachability.d.ts +5 -0
- package/types/src/core/socket.d.ts +12 -0
- package/types/src/core/strategies/best_connected_ever_strategy.d.ts +10 -0
- package/types/src/core/strategies/delayed_strategy.d.ts +15 -0
- package/types/src/core/strategies/first_connected_strategy.d.ts +8 -0
- package/types/src/core/strategies/if_strategy.d.ts +10 -0
- package/types/src/core/strategies/sequential_strategy.d.ts +16 -0
- package/types/src/core/strategies/strategy.d.ts +6 -0
- package/types/src/core/strategies/strategy_builder.d.ts +5 -0
- package/types/src/core/strategies/strategy_options.d.ts +16 -0
- package/types/src/core/strategies/strategy_runner.d.ts +5 -0
- package/types/src/core/strategies/transport_strategy.d.ts +15 -0
- package/types/src/core/strategies/websocket_prioritized_cached_strategy.d.ts +20 -0
- package/types/src/core/timeline/level.d.ts +6 -0
- package/types/src/core/timeline/timeline.d.ts +25 -0
- package/types/src/core/timeline/timeline_sender.d.ts +13 -0
- package/types/src/core/timeline/timeline_transport.d.ts +6 -0
- package/types/src/core/transports/assistant_to_the_transport_manager.d.ts +14 -0
- package/types/src/core/transports/ping_delay_options.d.ts +6 -0
- package/types/src/core/transports/transport.d.ts +8 -0
- package/types/src/core/transports/transport_connection.d.ts +35 -0
- package/types/src/core/transports/transport_connection_options.d.ts +6 -0
- package/types/src/core/transports/transport_hooks.d.ts +13 -0
- package/types/src/core/transports/transport_manager.d.ts +14 -0
- package/types/src/core/transports/transports_table.d.ts +10 -0
- package/types/src/core/transports/url_scheme.d.ts +11 -0
- package/types/src/core/transports/url_schemes.d.ts +4 -0
- package/types/src/core/user.d.ts +21 -0
- package/types/src/core/util.d.ts +8 -0
- package/types/src/core/utils/collections.d.ts +18 -0
- package/types/src/core/utils/factory.d.ts +29 -0
- package/types/src/core/utils/flat_promise.d.ts +6 -0
- package/types/src/core/utils/timers/abstract_timer.d.ts +10 -0
- package/types/src/core/utils/timers/index.d.ts +9 -0
- package/types/src/core/utils/timers/scheduling.d.ts +8 -0
- package/types/src/core/utils/timers/timed_callback.d.ts +4 -0
- package/types/src/core/utils/url_store.d.ts +4 -0
- package/types/src/core/watchlist.d.ts +8 -0
- package/types/src/runtimes/interface.d.ts +43 -0
- package/types/src/runtimes/isomorphic/auth/xhr_auth.d.ts +3 -0
- package/types/src/runtimes/isomorphic/default_strategy.d.ts +5 -0
- package/types/src/runtimes/isomorphic/http/http.d.ts +3 -0
- package/types/src/runtimes/isomorphic/http/http_xhr_request.d.ts +3 -0
- package/types/src/runtimes/isomorphic/runtime.d.ts +2 -0
- package/types/src/runtimes/isomorphic/timeline/xhr_timeline.d.ts +6 -0
- package/types/src/runtimes/isomorphic/transports/transport_connection_initializer.d.ts +1 -0
- package/types/src/runtimes/isomorphic/transports/transports.d.ts +5 -0
- package/types/src/runtimes/node/net_info.d.ts +6 -0
- package/types/src/runtimes/node/runtime.d.ts +3 -0
- package/types/src/runtimes/react-native/net_info.d.ts +8 -0
- package/types/src/runtimes/react-native/runtime.d.ts +3 -0
- package/types/src/runtimes/web/auth/jsonp_auth.d.ts +3 -0
- package/types/src/runtimes/web/browser.d.ts +19 -0
- package/types/src/runtimes/web/default_strategy.d.ts +5 -0
- package/types/src/runtimes/web/dom/dependencies.d.ts +4 -0
- package/types/src/runtimes/web/dom/dependency_loader.d.ts +10 -0
- package/types/src/runtimes/web/dom/jsonp_request.d.ts +10 -0
- package/types/src/runtimes/web/dom/script_receiver.d.ts +7 -0
- package/types/src/runtimes/web/dom/script_receiver_factory.d.ts +10 -0
- package/types/src/runtimes/web/dom/script_request.d.ts +9 -0
- package/types/src/runtimes/web/http/http.d.ts +2 -0
- package/types/src/runtimes/web/http/http_xdomain_request.d.ts +3 -0
- package/types/src/runtimes/web/net_info.d.ts +7 -0
- package/types/src/runtimes/web/runtime.d.ts +3 -0
- package/types/src/runtimes/web/timeline/jsonp_timeline.d.ts +6 -0
- package/types/src/runtimes/web/transports/transport_connection_initializer.d.ts +1 -0
- package/types/src/runtimes/web/transports/transports.d.ts +2 -0
- package/types/src/runtimes/worker/auth/fetch_auth.d.ts +3 -0
- package/types/src/runtimes/worker/net_info.d.ts +6 -0
- package/types/src/runtimes/worker/runtime.d.ts +3 -0
- package/types/src/runtimes/worker/timeline/fetch_timeline.d.ts +6 -0
- package/vite.config.js +52 -0
- package/vite.config.node.js +72 -0
- package/webpack/config.node.js +26 -0
- package/webpack/config.react-native.js +35 -0
- package/webpack/config.shared.js +50 -0
- package/webpack/config.web.js +36 -0
- package/webpack/config.worker.js +42 -0
- package/webpack/dev.server.js +17 -0
- package/webpack/hosting_config.js +6 -0
- package/with-encryption/index.d.ts +29 -0
- package/with-encryption/index.js +4 -0
- package/worker/index.d.ts +29 -0
- package/worker/index.js +1 -0
- package/worker/with-encryption/index.d.ts +29 -0
- 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);
|