From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/chat/protocols/facebook/components.conf | 15 + comm/chat/protocols/facebook/facebook.sys.mjs | 56 + .../protocols/facebook/icons/prpl-facebook-32.png | Bin 0 -> 1193 bytes .../protocols/facebook/icons/prpl-facebook-48.png | Bin 0 -> 1521 bytes .../protocols/facebook/icons/prpl-facebook.png | Bin 0 -> 552 bytes comm/chat/protocols/facebook/jar.mn | 9 + comm/chat/protocols/facebook/moz.build | 14 + comm/chat/protocols/gtalk/components.conf | 15 + comm/chat/protocols/gtalk/gtalk.sys.mjs | 60 + comm/chat/protocols/gtalk/icons/prpl-gtalk-32.png | Bin 0 -> 2024 bytes comm/chat/protocols/gtalk/icons/prpl-gtalk-48.png | Bin 0 -> 3168 bytes comm/chat/protocols/gtalk/icons/prpl-gtalk.png | Bin 0 -> 865 bytes comm/chat/protocols/gtalk/jar.mn | 9 + comm/chat/protocols/gtalk/moz.build | 14 + comm/chat/protocols/irc/components.conf | 15 + comm/chat/protocols/irc/icons/prpl-irc-32.png | Bin 0 -> 695 bytes comm/chat/protocols/irc/icons/prpl-irc-48.png | Bin 0 -> 1003 bytes comm/chat/protocols/irc/icons/prpl-irc.png | Bin 0 -> 454 bytes comm/chat/protocols/irc/irc.sys.mjs | 122 + comm/chat/protocols/irc/ircAccount.sys.mjs | 2296 ++++++ comm/chat/protocols/irc/ircBase.sys.mjs | 1768 +++++ comm/chat/protocols/irc/ircCAP.sys.mjs | 170 + comm/chat/protocols/irc/ircCTCP.sys.mjs | 291 + comm/chat/protocols/irc/ircCommands.sys.mjs | 599 ++ comm/chat/protocols/irc/ircDCC.sys.mjs | 66 + comm/chat/protocols/irc/ircEchoMessage.sys.mjs | 41 + .../protocols/irc/ircHandlerPriorities.sys.mjs | 16 + comm/chat/protocols/irc/ircHandlers.sys.mjs | 306 + comm/chat/protocols/irc/ircISUPPORT.sys.mjs | 246 + comm/chat/protocols/irc/ircMultiPrefix.sys.mjs | 60 + comm/chat/protocols/irc/ircNonStandard.sys.mjs | 262 + comm/chat/protocols/irc/ircSASL.sys.mjs | 179 + comm/chat/protocols/irc/ircServerTime.sys.mjs | 80 + comm/chat/protocols/irc/ircServices.sys.mjs | 317 + comm/chat/protocols/irc/ircUtils.sys.mjs | 303 + comm/chat/protocols/irc/ircWatchMonitor.sys.mjs | 467 ++ comm/chat/protocols/irc/jar.mn | 9 + comm/chat/protocols/irc/moz.build | 33 + comm/chat/protocols/irc/test/test_ctcpColoring.js | 72 + comm/chat/protocols/irc/test/test_ctcpDequote.js | 55 + .../chat/protocols/irc/test/test_ctcpFormatting.js | 59 + comm/chat/protocols/irc/test/test_ctcpQuote.js | 64 + comm/chat/protocols/irc/test/test_ircCAP.js | 236 + comm/chat/protocols/irc/test/test_ircChannel.js | 187 + comm/chat/protocols/irc/test/test_ircCommands.js | 218 + comm/chat/protocols/irc/test/test_ircMessage.js | 336 + .../chat/protocols/irc/test/test_ircNonStandard.js | 209 + comm/chat/protocols/irc/test/test_ircProtocol.js | 20 + comm/chat/protocols/irc/test/test_ircServerTime.js | 130 + .../protocols/irc/test/test_sendBufferedCommand.js | 199 + comm/chat/protocols/irc/test/test_setMode.js | 70 + .../protocols/irc/test/test_splitLongMessages.js | 44 + comm/chat/protocols/irc/test/test_tryNewNick.js | 148 + comm/chat/protocols/irc/test/xpcshell.ini | 18 + comm/chat/protocols/jsTest/components.conf | 15 + comm/chat/protocols/jsTest/jsTestProtocol.sys.mjs | 145 + comm/chat/protocols/jsTest/moz.build | 13 + comm/chat/protocols/matrix/components.conf | 15 + comm/chat/protocols/matrix/icons/README | 5 + .../chat/protocols/matrix/icons/prpl-matrix-32.png | Bin 0 -> 693 bytes .../chat/protocols/matrix/icons/prpl-matrix-48.png | Bin 0 -> 1012 bytes comm/chat/protocols/matrix/icons/prpl-matrix.png | Bin 0 -> 145 bytes comm/chat/protocols/matrix/jar.mn | 9 + .../protocols/matrix/lib/@matrix-org/olm/LICENSE | 177 + .../protocols/matrix/lib/@matrix-org/olm/olm.js | 163 + .../protocols/matrix/lib/@matrix-org/olm/olm.wasm | Bin 0 -> 153573 bytes comm/chat/protocols/matrix/lib/README.md | 174 + .../chat/protocols/matrix/lib/another-json/LICENSE | 177 + .../matrix/lib/another-json/another-json.js | 93 + comm/chat/protocols/matrix/lib/base-x/LICENSE.md | 22 + comm/chat/protocols/matrix/lib/base-x/index.js | 119 + comm/chat/protocols/matrix/lib/bs58/LICENSE | 21 + comm/chat/protocols/matrix/lib/bs58/index.js | 4 + .../chat/protocols/matrix/lib/content-type/LICENSE | 22 + .../protocols/matrix/lib/content-type/index.js | 225 + comm/chat/protocols/matrix/lib/events/LICENSE | 22 + comm/chat/protocols/matrix/lib/events/events.js | 497 ++ .../lib/matrix-events-sdk/ExtensibleEvents.js | 189 + .../matrix/lib/matrix-events-sdk/IPartialEvent.js | 5 + .../lib/matrix-events-sdk/InvalidEventError.js | 69 + .../protocols/matrix/lib/matrix-events-sdk/LICENSE | 201 + .../matrix/lib/matrix-events-sdk/NamespacedMap.js | 149 + .../lib/matrix-events-sdk/NamespacedValue.js | 166 + .../lib/matrix-events-sdk/events/EmoteEvent.js | 99 + .../matrix-events-sdk/events/ExtensibleEvent.js | 60 + .../lib/matrix-events-sdk/events/MessageEvent.js | 214 + .../lib/matrix-events-sdk/events/NoticeEvent.js | 99 + .../lib/matrix-events-sdk/events/PollEndEvent.js | 138 + .../matrix-events-sdk/events/PollResponseEvent.js | 198 + .../lib/matrix-events-sdk/events/PollStartEvent.js | 287 + .../lib/matrix-events-sdk/events/message_types.js | 74 + .../lib/matrix-events-sdk/events/poll_types.js | 70 + .../matrix-events-sdk/events/relationship_types.js | 34 + .../matrix/lib/matrix-events-sdk/index.js | 278 + .../interpreters/legacy/MRoomMessage.js | 62 + .../interpreters/modern/MMessage.js | 40 + .../matrix-events-sdk/interpreters/modern/MPoll.js | 41 + .../matrix/lib/matrix-events-sdk/types.js | 49 + .../matrix-events-sdk/utility/MessageMatchers.js | 59 + .../matrix/lib/matrix-events-sdk/utility/events.js | 51 + .../matrix-sdk/@types/IIdentityServerProvider.js | 5 + .../matrix/lib/matrix-sdk/@types/PushRules.js | 101 + .../matrix/lib/matrix-sdk/@types/another-json.d.js | 1 + .../protocols/matrix/lib/matrix-sdk/@types/auth.js | 68 + .../matrix/lib/matrix-sdk/@types/beacon.js | 126 + .../matrix/lib/matrix-sdk/@types/crypto.js | 5 + .../matrix/lib/matrix-sdk/@types/event.js | 240 + .../lib/matrix-sdk/@types/extensible_events.js | 121 + .../matrix/lib/matrix-sdk/@types/global.d.js | 6 + .../lib/matrix-sdk/@types/local_notifications.js | 5 + .../matrix/lib/matrix-sdk/@types/location.js | 72 + .../matrix/lib/matrix-sdk/@types/partials.js | 63 + .../matrix/lib/matrix-sdk/@types/polls.js | 93 + .../matrix/lib/matrix-sdk/@types/read_receipts.js | 33 + .../matrix/lib/matrix-sdk/@types/requests.js | 5 + .../matrix/lib/matrix-sdk/@types/search.js | 35 + .../matrix/lib/matrix-sdk/@types/signed.js | 5 + .../matrix/lib/matrix-sdk/@types/spaces.js | 5 + .../matrix/lib/matrix-sdk/@types/synapse.js | 5 + .../protocols/matrix/lib/matrix-sdk/@types/sync.js | 30 + .../matrix/lib/matrix-sdk/@types/threepids.js | 27 + .../matrix/lib/matrix-sdk/@types/topic.js | 63 + .../protocols/matrix/lib/matrix-sdk/@types/uia.js | 5 + comm/chat/protocols/matrix/lib/matrix-sdk/LICENSE | 177 + .../matrix/lib/matrix-sdk/NamespacedValue.js | 123 + .../protocols/matrix/lib/matrix-sdk/ReEmitter.js | 89 + .../matrix/lib/matrix-sdk/ToDeviceMessageQueue.js | 133 + .../matrix/lib/matrix-sdk/autodiscovery.js | 429 ++ .../matrix/lib/matrix-sdk/browser-index.js | 58 + .../chat/protocols/matrix/lib/matrix-sdk/client.js | 7660 ++++++++++++++++++++ .../lib/matrix-sdk/common-crypto/CryptoBackend.js | 5 + .../matrix/lib/matrix-sdk/content-helpers.js | 266 + .../matrix/lib/matrix-sdk/content-repo.js | 74 + .../protocols/matrix/lib/matrix-sdk/crypto-api.js | 105 + .../lib/matrix-sdk/crypto-api/verification.js | 46 + .../matrix/lib/matrix-sdk/crypto/CrossSigning.js | 703 ++ .../matrix/lib/matrix-sdk/crypto/DeviceList.js | 860 +++ .../lib/matrix-sdk/crypto/EncryptionSetup.js | 342 + .../matrix/lib/matrix-sdk/crypto/OlmDevice.js | 1162 +++ .../crypto/OutgoingRoomKeyRequestManager.js | 406 ++ .../matrix/lib/matrix-sdk/crypto/RoomList.js | 60 + .../matrix/lib/matrix-sdk/crypto/SecretSharing.js | 199 + .../matrix/lib/matrix-sdk/crypto/SecretStorage.js | 119 + .../protocols/matrix/lib/matrix-sdk/crypto/aes.js | 127 + .../lib/matrix-sdk/crypto/algorithms/base.js | 226 + .../lib/matrix-sdk/crypto/algorithms/index.js | 18 + .../lib/matrix-sdk/crypto/algorithms/megolm.js | 1682 +++++ .../matrix/lib/matrix-sdk/crypto/algorithms/olm.js | 276 + .../protocols/matrix/lib/matrix-sdk/crypto/api.js | 12 + .../matrix/lib/matrix-sdk/crypto/backup.js | 651 ++ .../matrix/lib/matrix-sdk/crypto/crypto.js | 60 + .../matrix/lib/matrix-sdk/crypto/dehydration.js | 237 + .../lib/matrix-sdk/crypto/device-converter.js | 47 + .../matrix/lib/matrix-sdk/crypto/deviceinfo.js | 152 + .../matrix/lib/matrix-sdk/crypto/index.js | 3427 +++++++++ .../matrix/lib/matrix-sdk/crypto/key_passphrase.js | 69 + .../matrix/lib/matrix-sdk/crypto/keybackup.js | 5 + .../matrix/lib/matrix-sdk/crypto/olmlib.js | 480 ++ .../matrix/lib/matrix-sdk/crypto/recoverykey.js | 60 + .../matrix/lib/matrix-sdk/crypto/store/base.js | 5 + .../crypto/store/indexeddb-crypto-store-backend.js | 913 +++ .../crypto/store/indexeddb-crypto-store.js | 599 ++ .../crypto/store/localStorage-crypto-store.js | 329 + .../matrix-sdk/crypto/store/memory-crypto-store.js | 439 ++ .../lib/matrix-sdk/crypto/verification/Base.js | 345 + .../lib/matrix-sdk/crypto/verification/Error.js | 100 + .../crypto/verification/IllegalMethod.js | 46 + .../lib/matrix-sdk/crypto/verification/QRCode.js | 269 + .../lib/matrix-sdk/crypto/verification/SAS.js | 454 ++ .../matrix-sdk/crypto/verification/SASDecimal.js | 39 + .../crypto/verification/request/Channel.js | 5 + .../crypto/verification/request/InRoomChannel.js | 349 + .../crypto/verification/request/ToDeviceChannel.js | 322 + .../verification/request/VerificationRequest.js | 870 +++ .../protocols/matrix/lib/matrix-sdk/embedded.js | 261 + .../chat/protocols/matrix/lib/matrix-sdk/errors.js | 62 + .../matrix/lib/matrix-sdk/event-mapper.js | 86 + .../extensible_events_v1/ExtensibleEvent.js | 63 + .../extensible_events_v1/InvalidEventError.js | 31 + .../extensible_events_v1/MessageEvent.js | 138 + .../extensible_events_v1/PollEndEvent.js | 93 + .../extensible_events_v1/PollResponseEvent.js | 140 + .../extensible_events_v1/PollStartEvent.js | 191 + .../matrix-sdk/extensible_events_v1/utilities.js | 40 + .../protocols/matrix/lib/matrix-sdk/feature.js | 78 + .../matrix/lib/matrix-sdk/filter-component.js | 171 + .../chat/protocols/matrix/lib/matrix-sdk/filter.js | 212 + .../matrix/lib/matrix-sdk/http-api/errors.js | 83 + .../matrix/lib/matrix-sdk/http-api/fetch.js | 265 + .../matrix/lib/matrix-sdk/http-api/index.js | 240 + .../matrix/lib/matrix-sdk/http-api/interface.js | 27 + .../matrix/lib/matrix-sdk/http-api/method.js | 29 + .../matrix/lib/matrix-sdk/http-api/prefix.js | 39 + .../matrix/lib/matrix-sdk/http-api/utils.js | 143 + comm/chat/protocols/matrix/lib/matrix-sdk/index.js | 43 + .../matrix/lib/matrix-sdk/indexeddb-helpers.js | 56 + .../matrix/lib/matrix-sdk/indexeddb-worker.js | 12 + .../matrix/lib/matrix-sdk/interactive-auth.js | 510 ++ .../chat/protocols/matrix/lib/matrix-sdk/logger.js | 80 + .../chat/protocols/matrix/lib/matrix-sdk/matrix.js | 546 ++ .../matrix/lib/matrix-sdk/models/MSC3089Branch.js | 227 + .../lib/matrix-sdk/models/MSC3089TreeSpace.js | 508 ++ .../lib/matrix-sdk/models/ToDeviceMessage.js | 5 + .../matrix/lib/matrix-sdk/models/beacon.js | 181 + .../matrix/lib/matrix-sdk/models/device.js | 80 + .../matrix/lib/matrix-sdk/models/event-context.js | 116 + .../matrix/lib/matrix-sdk/models/event-status.js | 35 + .../lib/matrix-sdk/models/event-timeline-set.js | 809 +++ .../matrix/lib/matrix-sdk/models/event-timeline.js | 469 ++ .../matrix/lib/matrix-sdk/models/event.js | 1442 ++++ .../lib/matrix-sdk/models/invites-ignorer.js | 358 + .../protocols/matrix/lib/matrix-sdk/models/poll.js | 237 + .../matrix/lib/matrix-sdk/models/read-receipt.js | 260 + .../lib/matrix-sdk/models/related-relations.js | 41 + .../lib/matrix-sdk/models/relations-container.js | 135 + .../matrix/lib/matrix-sdk/models/relations.js | 336 + .../matrix/lib/matrix-sdk/models/room-member.js | 363 + .../matrix/lib/matrix-sdk/models/room-state.js | 931 +++ .../matrix/lib/matrix-sdk/models/room-summary.js | 34 + .../protocols/matrix/lib/matrix-sdk/models/room.js | 3079 ++++++++ .../matrix/lib/matrix-sdk/models/search-result.js | 58 + .../matrix/lib/matrix-sdk/models/thread.js | 649 ++ .../lib/matrix-sdk/models/typed-event-emitter.js | 200 + .../protocols/matrix/lib/matrix-sdk/models/user.js | 211 + .../matrix/lib/matrix-sdk/pushprocessor.js | 676 ++ .../matrix/lib/matrix-sdk/randomstring.js | 44 + .../matrix/lib/matrix-sdk/realtime-callbacks.js | 179 + .../matrix/lib/matrix-sdk/receipt-accumulator.js | 169 + .../lib/matrix-sdk/rendezvous/MSC3906Rendezvous.js | 240 + .../lib/matrix-sdk/rendezvous/RendezvousChannel.js | 5 + .../lib/matrix-sdk/rendezvous/RendezvousCode.js | 5 + .../lib/matrix-sdk/rendezvous/RendezvousError.js | 29 + .../rendezvous/RendezvousFailureReason.js | 36 + .../lib/matrix-sdk/rendezvous/RendezvousIntent.js | 27 + .../matrix-sdk/rendezvous/RendezvousTransport.js | 5 + .../channels/MSC3903ECDHv2RendezvousChannel.js | 194 + .../lib/matrix-sdk/rendezvous/channels/index.js | 16 + .../matrix/lib/matrix-sdk/rendezvous/index.js | 82 + .../MSC3886SimpleHttpRendezvousTransport.js | 176 + .../lib/matrix-sdk/rendezvous/transports/index.js | 16 + .../matrix/lib/matrix-sdk/room-hierarchy.js | 133 + .../matrix-sdk/rust-crypto/CrossSigningIdentity.js | 93 + .../lib/matrix-sdk/rust-crypto/KeyClaimManager.js | 78 + .../rust-crypto/OutgoingRequestProcessor.js | 117 + .../lib/matrix-sdk/rust-crypto/RoomEncryptor.js | 124 + .../lib/matrix-sdk/rust-crypto/browserify-index.js | 31 + .../matrix/lib/matrix-sdk/rust-crypto/constants.js | 25 + .../lib/matrix-sdk/rust-crypto/device-converter.js | 121 + .../matrix/lib/matrix-sdk/rust-crypto/index.js | 54 + .../lib/matrix-sdk/rust-crypto/rust-crypto.js | 574 ++ .../protocols/matrix/lib/matrix-sdk/scheduler.js | 314 + .../matrix/lib/matrix-sdk/secret-storage.js | 431 ++ .../matrix/lib/matrix-sdk/service-types.js | 27 + .../matrix/lib/matrix-sdk/sliding-sync-sdk.js | 861 +++ .../matrix/lib/matrix-sdk/sliding-sync.js | 795 ++ .../protocols/matrix/lib/matrix-sdk/store/index.js | 5 + .../lib/matrix-sdk/store/indexeddb-backend.js | 5 + .../matrix-sdk/store/indexeddb-local-backend.js | 569 ++ .../matrix-sdk/store/indexeddb-remote-backend.js | 200 + .../lib/matrix-sdk/store/indexeddb-store-worker.js | 151 + .../matrix/lib/matrix-sdk/store/indexeddb.js | 329 + .../store/local-storage-events-emitter.js | 43 + .../matrix/lib/matrix-sdk/store/memory.js | 418 ++ .../protocols/matrix/lib/matrix-sdk/store/stub.js | 262 + .../matrix/lib/matrix-sdk/sync-accumulator.js | 474 ++ comm/chat/protocols/matrix/lib/matrix-sdk/sync.js | 1594 ++++ .../matrix/lib/matrix-sdk/timeline-window.js | 462 ++ comm/chat/protocols/matrix/lib/matrix-sdk/utils.js | 754 ++ .../matrix/lib/matrix-sdk/webrtc/audioContext.js | 52 + .../protocols/matrix/lib/matrix-sdk/webrtc/call.js | 2364 ++++++ .../lib/matrix-sdk/webrtc/callEventHandler.js | 339 + .../matrix/lib/matrix-sdk/webrtc/callEventTypes.js | 19 + .../matrix/lib/matrix-sdk/webrtc/callFeed.js | 294 + .../matrix/lib/matrix-sdk/webrtc/groupCall.js | 1213 ++++ .../lib/matrix-sdk/webrtc/groupCallEventHandler.js | 181 + .../matrix/lib/matrix-sdk/webrtc/mediaHandler.js | 395 + .../webrtc/stats/callStatsReportGatherer.js | 194 + .../webrtc/stats/callStatsReportSummary.js | 5 + .../lib/matrix-sdk/webrtc/stats/connectionStats.js | 34 + .../webrtc/stats/connectionStatsBuilder.js | 33 + .../webrtc/stats/connectionStatsReportBuilder.js | 127 + .../lib/matrix-sdk/webrtc/stats/groupCallStats.js | 80 + .../webrtc/stats/media/mediaSsrcHandler.js | 62 + .../webrtc/stats/media/mediaTrackHandler.js | 69 + .../webrtc/stats/media/mediaTrackStats.js | 150 + .../webrtc/stats/media/mediaTrackStatsHandler.js | 82 + .../lib/matrix-sdk/webrtc/stats/statsReport.js | 28 + .../matrix-sdk/webrtc/stats/statsReportEmitter.js | 36 + .../webrtc/stats/summaryStatsReportGatherer.js | 103 + .../matrix-sdk/webrtc/stats/trackStatsBuilder.js | 172 + .../lib/matrix-sdk/webrtc/stats/transportStats.js | 5 + .../webrtc/stats/transportStatsBuilder.js | 40 + .../lib/matrix-sdk/webrtc/stats/valueFormatter.js | 31 + .../lib/matrix-widget-api/ClientWidgetApi.js | 1126 +++ .../protocols/matrix/lib/matrix-widget-api/LICENSE | 201 + .../matrix/lib/matrix-widget-api/Symbols.js | 27 + .../matrix/lib/matrix-widget-api/WidgetApi.js | 808 +++ .../lib/matrix-widget-api/driver/WidgetDriver.js | 239 + .../matrix/lib/matrix-widget-api/index.js | 512 ++ .../lib/matrix-widget-api/interfaces/ApiVersion.js | 45 + .../matrix-widget-api/interfaces/Capabilities.js | 69 + .../interfaces/CapabilitiesAction.js | 6 + .../interfaces/ContentLoadedAction.js | 6 + .../interfaces/GetOpenIDAction.js | 29 + .../interfaces/ICustomWidgetData.js | 6 + .../interfaces/IJitsiWidgetData.js | 6 + .../lib/matrix-widget-api/interfaces/IRoomEvent.js | 6 + .../interfaces/IStickerpickerWidgetData.js | 6 + .../lib/matrix-widget-api/interfaces/IWidget.js | 6 + .../interfaces/IWidgetApiErrorResponse.js | 30 + .../interfaces/IWidgetApiRequest.js | 6 + .../interfaces/IWidgetApiResponse.js | 6 + .../interfaces/ModalButtonKind.js | 31 + .../interfaces/ModalWidgetActions.js | 30 + .../matrix-widget-api/interfaces/NavigateAction.js | 6 + .../interfaces/OpenIDCredentialsAction.js | 6 + .../interfaces/ReadEventAction.js | 6 + .../interfaces/ReadRelationsAction.js | 6 + .../interfaces/ScreenshotAction.js | 6 + .../interfaces/SendEventAction.js | 6 + .../interfaces/SendToDeviceAction.js | 6 + .../interfaces/SetModalButtonEnabledAction.js | 6 + .../matrix-widget-api/interfaces/StickerAction.js | 6 + .../matrix-widget-api/interfaces/StickyAction.js | 6 + .../interfaces/SupportedVersionsAction.js | 6 + .../interfaces/TurnServerActions.js | 6 + .../interfaces/UserDirectorySearchAction.js | 6 + .../interfaces/VisibilityAction.js | 6 + .../interfaces/WidgetApiAction.js | 59 + .../interfaces/WidgetApiDirection.js | 38 + .../interfaces/WidgetConfigAction.js | 6 + .../lib/matrix-widget-api/interfaces/WidgetKind.js | 29 + .../lib/matrix-widget-api/interfaces/WidgetType.js | 29 + .../matrix/lib/matrix-widget-api/models/Widget.js | 142 + .../models/WidgetEventCapability.js | 237 + .../lib/matrix-widget-api/models/WidgetParser.js | 152 + .../lib/matrix-widget-api/models/validation/url.js | 39 + .../matrix-widget-api/models/validation/utils.js | 28 + .../matrix-widget-api/templating/url-template.js | 59 + .../lib/matrix-widget-api/transport/ITransport.js | 6 + .../transport/PostmessageTransport.js | 222 + .../lib/matrix-widget-api/util/SimpleObservable.js | 68 + comm/chat/protocols/matrix/lib/moz.build | 365 + comm/chat/protocols/matrix/lib/p-retry/index.js | 85 + comm/chat/protocols/matrix/lib/p-retry/license | 9 + comm/chat/protocols/matrix/lib/retry/License | 21 + comm/chat/protocols/matrix/lib/retry/index.js | 1 + comm/chat/protocols/matrix/lib/retry/lib/retry.js | 100 + .../matrix/lib/retry/lib/retry_operation.js | 162 + .../protocols/matrix/lib/sdp-transform/LICENSE | 22 + .../protocols/matrix/lib/sdp-transform/grammar.js | 494 ++ .../protocols/matrix/lib/sdp-transform/index.js | 11 + .../protocols/matrix/lib/sdp-transform/parser.js | 124 + .../protocols/matrix/lib/sdp-transform/writer.js | 114 + comm/chat/protocols/matrix/lib/unhomoglyph/LICENSE | 22 + .../protocols/matrix/lib/unhomoglyph/data.json | 6313 ++++++++++++++++ .../chat/protocols/matrix/lib/unhomoglyph/index.js | 20 + comm/chat/protocols/matrix/matrix-sdk.sys.mjs | 220 + comm/chat/protocols/matrix/matrix.sys.mjs | 93 + comm/chat/protocols/matrix/matrixAccount.sys.mjs | 3495 +++++++++ comm/chat/protocols/matrix/matrixCommands.sys.mjs | 490 ++ .../protocols/matrix/matrixMessageContent.sys.mjs | 377 + .../protocols/matrix/matrixPowerLevels.sys.mjs | 82 + .../protocols/matrix/matrixTextForEvent.sys.mjs | 330 + comm/chat/protocols/matrix/moz.build | 29 + comm/chat/protocols/matrix/shims/empty.js | 16 + comm/chat/protocols/matrix/shims/loglevel.js | 73 + comm/chat/protocols/matrix/shims/moz.build | 14 + comm/chat/protocols/matrix/shims/safe-buffer.js | 48 + comm/chat/protocols/matrix/shims/uuid.js | 13 + comm/chat/protocols/matrix/test/head.js | 291 + .../protocols/matrix/test/test_matrixAccount.js | 399 + .../protocols/matrix/test/test_matrixCommands.js | 177 + .../protocols/matrix/test/test_matrixMessage.js | 441 ++ .../matrix/test/test_matrixMessageContent.js | 652 ++ .../matrix/test/test_matrixPowerLevels.js | 204 + comm/chat/protocols/matrix/test/test_matrixRoom.js | 928 +++ .../matrix/test/test_matrixTextForEvent.js | 834 +++ .../protocols/matrix/test/test_roomTypeChange.js | 54 + comm/chat/protocols/matrix/test/xpcshell.ini | 12 + comm/chat/protocols/odnoklassniki/components.conf | 15 + .../odnoklassniki/icons/prpl-odnoklassniki-32.png | Bin 0 -> 2165 bytes .../odnoklassniki/icons/prpl-odnoklassniki-48.png | Bin 0 -> 2649 bytes .../odnoklassniki/icons/prpl-odnoklassniki.png | Bin 0 -> 753 bytes comm/chat/protocols/odnoklassniki/jar.mn | 9 + comm/chat/protocols/odnoklassniki/moz.build | 14 + .../protocols/odnoklassniki/odnoklassniki.sys.mjs | 83 + comm/chat/protocols/twitter/components.conf | 15 + .../protocols/twitter/icons/prpl-twitter-32.png | Bin 0 -> 554 bytes .../protocols/twitter/icons/prpl-twitter-48.png | Bin 0 -> 721 bytes .../protocols/twitter/icons/prpl-twitter-left.png | Bin 0 -> 563 bytes comm/chat/protocols/twitter/icons/prpl-twitter.png | Bin 0 -> 319 bytes comm/chat/protocols/twitter/jar.mn | 10 + comm/chat/protocols/twitter/moz.build | 14 + comm/chat/protocols/twitter/twitter.sys.mjs | 62 + comm/chat/protocols/xmpp/.eslintrc.js | 12 + comm/chat/protocols/xmpp/components.conf | 15 + comm/chat/protocols/xmpp/icons/prpl-jabber-32.png | Bin 0 -> 1725 bytes comm/chat/protocols/xmpp/icons/prpl-jabber-48.png | Bin 0 -> 2536 bytes comm/chat/protocols/xmpp/icons/prpl-jabber.png | Bin 0 -> 768 bytes comm/chat/protocols/xmpp/jar.mn | 5 + comm/chat/protocols/xmpp/lib/README.md | 6 + comm/chat/protocols/xmpp/lib/moz.build | 8 + comm/chat/protocols/xmpp/lib/sax/LICENSE | 41 + comm/chat/protocols/xmpp/lib/sax/sax.js | 1648 +++++ comm/chat/protocols/xmpp/moz.build | 26 + comm/chat/protocols/xmpp/sax.sys.mjs | 7 + comm/chat/protocols/xmpp/test/test_authmechs.js | 160 + comm/chat/protocols/xmpp/test/test_dnsSrv.js | 112 + .../xmpp/test/test_parseJidAndNormalization.js | 104 + comm/chat/protocols/xmpp/test/test_parseVCard.js | 139 + comm/chat/protocols/xmpp/test/test_saslPrep.js | 66 + comm/chat/protocols/xmpp/test/test_xmppParser.js | 135 + comm/chat/protocols/xmpp/test/test_xmppXml.js | 103 + comm/chat/protocols/xmpp/test/xpcshell.ini | 11 + comm/chat/protocols/xmpp/xmpp-authmechs.sys.mjs | 561 ++ comm/chat/protocols/xmpp/xmpp-base.sys.mjs | 3421 +++++++++ comm/chat/protocols/xmpp/xmpp-commands.sys.mjs | 347 + comm/chat/protocols/xmpp/xmpp-session.sys.mjs | 764 ++ comm/chat/protocols/xmpp/xmpp-xml.sys.mjs | 508 ++ comm/chat/protocols/xmpp/xmpp.sys.mjs | 106 + comm/chat/protocols/yahoo/components.conf | 15 + comm/chat/protocols/yahoo/icons/prpl-yahoo-32.png | Bin 0 -> 1438 bytes comm/chat/protocols/yahoo/icons/prpl-yahoo-48.png | Bin 0 -> 2439 bytes comm/chat/protocols/yahoo/icons/prpl-yahoo.png | Bin 0 -> 531 bytes comm/chat/protocols/yahoo/jar.mn | 9 + comm/chat/protocols/yahoo/moz.build | 14 + comm/chat/protocols/yahoo/yahoo.sys.mjs | 60 + 428 files changed, 102814 insertions(+) create mode 100644 comm/chat/protocols/facebook/components.conf create mode 100644 comm/chat/protocols/facebook/facebook.sys.mjs create mode 100644 comm/chat/protocols/facebook/icons/prpl-facebook-32.png create mode 100644 comm/chat/protocols/facebook/icons/prpl-facebook-48.png create mode 100644 comm/chat/protocols/facebook/icons/prpl-facebook.png create mode 100644 comm/chat/protocols/facebook/jar.mn create mode 100644 comm/chat/protocols/facebook/moz.build create mode 100644 comm/chat/protocols/gtalk/components.conf create mode 100644 comm/chat/protocols/gtalk/gtalk.sys.mjs create mode 100644 comm/chat/protocols/gtalk/icons/prpl-gtalk-32.png create mode 100644 comm/chat/protocols/gtalk/icons/prpl-gtalk-48.png create mode 100644 comm/chat/protocols/gtalk/icons/prpl-gtalk.png create mode 100644 comm/chat/protocols/gtalk/jar.mn create mode 100644 comm/chat/protocols/gtalk/moz.build create mode 100644 comm/chat/protocols/irc/components.conf create mode 100644 comm/chat/protocols/irc/icons/prpl-irc-32.png create mode 100644 comm/chat/protocols/irc/icons/prpl-irc-48.png create mode 100644 comm/chat/protocols/irc/icons/prpl-irc.png create mode 100644 comm/chat/protocols/irc/irc.sys.mjs create mode 100644 comm/chat/protocols/irc/ircAccount.sys.mjs create mode 100644 comm/chat/protocols/irc/ircBase.sys.mjs create mode 100644 comm/chat/protocols/irc/ircCAP.sys.mjs create mode 100644 comm/chat/protocols/irc/ircCTCP.sys.mjs create mode 100644 comm/chat/protocols/irc/ircCommands.sys.mjs create mode 100644 comm/chat/protocols/irc/ircDCC.sys.mjs create mode 100644 comm/chat/protocols/irc/ircEchoMessage.sys.mjs create mode 100644 comm/chat/protocols/irc/ircHandlerPriorities.sys.mjs create mode 100644 comm/chat/protocols/irc/ircHandlers.sys.mjs create mode 100644 comm/chat/protocols/irc/ircISUPPORT.sys.mjs create mode 100644 comm/chat/protocols/irc/ircMultiPrefix.sys.mjs create mode 100644 comm/chat/protocols/irc/ircNonStandard.sys.mjs create mode 100644 comm/chat/protocols/irc/ircSASL.sys.mjs create mode 100644 comm/chat/protocols/irc/ircServerTime.sys.mjs create mode 100644 comm/chat/protocols/irc/ircServices.sys.mjs create mode 100644 comm/chat/protocols/irc/ircUtils.sys.mjs create mode 100644 comm/chat/protocols/irc/ircWatchMonitor.sys.mjs create mode 100644 comm/chat/protocols/irc/jar.mn create mode 100644 comm/chat/protocols/irc/moz.build create mode 100644 comm/chat/protocols/irc/test/test_ctcpColoring.js create mode 100644 comm/chat/protocols/irc/test/test_ctcpDequote.js create mode 100644 comm/chat/protocols/irc/test/test_ctcpFormatting.js create mode 100644 comm/chat/protocols/irc/test/test_ctcpQuote.js create mode 100644 comm/chat/protocols/irc/test/test_ircCAP.js create mode 100644 comm/chat/protocols/irc/test/test_ircChannel.js create mode 100644 comm/chat/protocols/irc/test/test_ircCommands.js create mode 100644 comm/chat/protocols/irc/test/test_ircMessage.js create mode 100644 comm/chat/protocols/irc/test/test_ircNonStandard.js create mode 100644 comm/chat/protocols/irc/test/test_ircProtocol.js create mode 100644 comm/chat/protocols/irc/test/test_ircServerTime.js create mode 100644 comm/chat/protocols/irc/test/test_sendBufferedCommand.js create mode 100644 comm/chat/protocols/irc/test/test_setMode.js create mode 100644 comm/chat/protocols/irc/test/test_splitLongMessages.js create mode 100644 comm/chat/protocols/irc/test/test_tryNewNick.js create mode 100644 comm/chat/protocols/irc/test/xpcshell.ini create mode 100644 comm/chat/protocols/jsTest/components.conf create mode 100644 comm/chat/protocols/jsTest/jsTestProtocol.sys.mjs create mode 100644 comm/chat/protocols/jsTest/moz.build create mode 100644 comm/chat/protocols/matrix/components.conf create mode 100644 comm/chat/protocols/matrix/icons/README create mode 100644 comm/chat/protocols/matrix/icons/prpl-matrix-32.png create mode 100644 comm/chat/protocols/matrix/icons/prpl-matrix-48.png create mode 100644 comm/chat/protocols/matrix/icons/prpl-matrix.png create mode 100644 comm/chat/protocols/matrix/jar.mn create mode 100644 comm/chat/protocols/matrix/lib/@matrix-org/olm/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/@matrix-org/olm/olm.js create mode 100755 comm/chat/protocols/matrix/lib/@matrix-org/olm/olm.wasm create mode 100644 comm/chat/protocols/matrix/lib/README.md create mode 100644 comm/chat/protocols/matrix/lib/another-json/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/another-json/another-json.js create mode 100644 comm/chat/protocols/matrix/lib/base-x/LICENSE.md create mode 100644 comm/chat/protocols/matrix/lib/base-x/index.js create mode 100644 comm/chat/protocols/matrix/lib/bs58/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/bs58/index.js create mode 100644 comm/chat/protocols/matrix/lib/content-type/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/content-type/index.js create mode 100644 comm/chat/protocols/matrix/lib/events/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/events/events.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/ExtensibleEvents.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/IPartialEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/InvalidEventError.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/NamespacedMap.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/NamespacedValue.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/EmoteEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/ExtensibleEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/MessageEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/NoticeEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/PollEndEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/PollResponseEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/PollStartEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/message_types.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/poll_types.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/events/relationship_types.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/interpreters/legacy/MRoomMessage.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/interpreters/modern/MMessage.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/interpreters/modern/MPoll.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/types.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/utility/MessageMatchers.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-events-sdk/utility/events.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/IIdentityServerProvider.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/PushRules.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/another-json.d.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/auth.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/beacon.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/crypto.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/event.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/extensible_events.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/global.d.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/local_notifications.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/location.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/partials.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/polls.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/read_receipts.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/requests.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/search.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/signed.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/spaces.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/synapse.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/sync.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/threepids.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/topic.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/@types/uia.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/NamespacedValue.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/ReEmitter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/ToDeviceMessageQueue.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/autodiscovery.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/browser-index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/client.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/common-crypto/CryptoBackend.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/content-helpers.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/content-repo.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto-api.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto-api/verification.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/CrossSigning.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/DeviceList.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/EncryptionSetup.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/OlmDevice.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/OutgoingRoomKeyRequestManager.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/RoomList.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/SecretSharing.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/SecretStorage.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/aes.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/algorithms/base.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/algorithms/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/algorithms/megolm.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/algorithms/olm.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/api.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/backup.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/crypto.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/dehydration.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/device-converter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/deviceinfo.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/key_passphrase.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/keybackup.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/olmlib.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/recoverykey.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/store/base.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/store/indexeddb-crypto-store-backend.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/store/indexeddb-crypto-store.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/store/localStorage-crypto-store.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/store/memory-crypto-store.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/Base.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/Error.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/IllegalMethod.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/QRCode.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/SAS.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/SASDecimal.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/request/Channel.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/request/InRoomChannel.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/request/ToDeviceChannel.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/crypto/verification/request/VerificationRequest.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/embedded.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/errors.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/event-mapper.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/extensible_events_v1/ExtensibleEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/extensible_events_v1/InvalidEventError.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/extensible_events_v1/MessageEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/extensible_events_v1/PollEndEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/extensible_events_v1/PollResponseEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/extensible_events_v1/PollStartEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/extensible_events_v1/utilities.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/feature.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/filter-component.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/filter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/http-api/errors.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/http-api/fetch.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/http-api/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/http-api/interface.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/http-api/method.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/http-api/prefix.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/http-api/utils.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/indexeddb-helpers.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/indexeddb-worker.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/interactive-auth.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/logger.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/matrix.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/MSC3089Branch.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/MSC3089TreeSpace.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/ToDeviceMessage.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/beacon.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/device.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/event-context.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/event-status.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/event-timeline-set.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/event-timeline.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/event.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/invites-ignorer.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/poll.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/read-receipt.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/related-relations.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/relations-container.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/relations.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/room-member.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/room-state.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/room-summary.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/room.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/search-result.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/thread.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/typed-event-emitter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/models/user.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/pushprocessor.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/randomstring.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/realtime-callbacks.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/receipt-accumulator.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/MSC3906Rendezvous.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousChannel.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousCode.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousError.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousFailureReason.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousIntent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/RendezvousTransport.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/MSC3903ECDHv2RendezvousChannel.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/channels/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/MSC3886SimpleHttpRendezvousTransport.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rendezvous/transports/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/room-hierarchy.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/CrossSigningIdentity.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/KeyClaimManager.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/OutgoingRequestProcessor.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/RoomEncryptor.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/browserify-index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/constants.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/device-converter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/rust-crypto/rust-crypto.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/scheduler.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/secret-storage.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/service-types.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/sliding-sync-sdk.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/sliding-sync.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/indexeddb-backend.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/indexeddb-local-backend.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/indexeddb-remote-backend.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/indexeddb-store-worker.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/indexeddb.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/local-storage-events-emitter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/memory.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/store/stub.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/sync-accumulator.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/sync.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/timeline-window.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/utils.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/audioContext.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/call.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/callEventHandler.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/callEventTypes.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/callFeed.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/groupCall.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/groupCallEventHandler.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/mediaHandler.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/callStatsReportGatherer.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/callStatsReportSummary.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/connectionStats.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/connectionStatsBuilder.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/connectionStatsReportBuilder.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/groupCallStats.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/media/mediaSsrcHandler.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/media/mediaTrackHandler.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/media/mediaTrackStats.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/media/mediaTrackStatsHandler.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/statsReport.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/statsReportEmitter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/summaryStatsReportGatherer.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/trackStatsBuilder.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/transportStats.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/transportStatsBuilder.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/valueFormatter.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/ClientWidgetApi.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/Symbols.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/WidgetApi.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/driver/WidgetDriver.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/index.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ApiVersion.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/Capabilities.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/CapabilitiesAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ContentLoadedAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/GetOpenIDAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ICustomWidgetData.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/IJitsiWidgetData.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/IRoomEvent.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/IStickerpickerWidgetData.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/IWidget.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/IWidgetApiErrorResponse.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/IWidgetApiRequest.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/IWidgetApiResponse.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ModalButtonKind.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ModalWidgetActions.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/NavigateAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/OpenIDCredentialsAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ReadEventAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ReadRelationsAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/ScreenshotAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/SendEventAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/SendToDeviceAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/SetModalButtonEnabledAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/StickerAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/StickyAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/SupportedVersionsAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/TurnServerActions.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/UserDirectorySearchAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/VisibilityAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/WidgetApiAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/WidgetApiDirection.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/WidgetConfigAction.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/WidgetKind.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/interfaces/WidgetType.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/models/Widget.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/models/WidgetEventCapability.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/models/WidgetParser.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/models/validation/url.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/models/validation/utils.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/templating/url-template.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/transport/ITransport.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/transport/PostmessageTransport.js create mode 100644 comm/chat/protocols/matrix/lib/matrix-widget-api/util/SimpleObservable.js create mode 100644 comm/chat/protocols/matrix/lib/moz.build create mode 100644 comm/chat/protocols/matrix/lib/p-retry/index.js create mode 100644 comm/chat/protocols/matrix/lib/p-retry/license create mode 100644 comm/chat/protocols/matrix/lib/retry/License create mode 100644 comm/chat/protocols/matrix/lib/retry/index.js create mode 100644 comm/chat/protocols/matrix/lib/retry/lib/retry.js create mode 100644 comm/chat/protocols/matrix/lib/retry/lib/retry_operation.js create mode 100644 comm/chat/protocols/matrix/lib/sdp-transform/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/sdp-transform/grammar.js create mode 100644 comm/chat/protocols/matrix/lib/sdp-transform/index.js create mode 100644 comm/chat/protocols/matrix/lib/sdp-transform/parser.js create mode 100644 comm/chat/protocols/matrix/lib/sdp-transform/writer.js create mode 100644 comm/chat/protocols/matrix/lib/unhomoglyph/LICENSE create mode 100644 comm/chat/protocols/matrix/lib/unhomoglyph/data.json create mode 100644 comm/chat/protocols/matrix/lib/unhomoglyph/index.js create mode 100644 comm/chat/protocols/matrix/matrix-sdk.sys.mjs create mode 100644 comm/chat/protocols/matrix/matrix.sys.mjs create mode 100644 comm/chat/protocols/matrix/matrixAccount.sys.mjs create mode 100644 comm/chat/protocols/matrix/matrixCommands.sys.mjs create mode 100644 comm/chat/protocols/matrix/matrixMessageContent.sys.mjs create mode 100644 comm/chat/protocols/matrix/matrixPowerLevels.sys.mjs create mode 100644 comm/chat/protocols/matrix/matrixTextForEvent.sys.mjs create mode 100644 comm/chat/protocols/matrix/moz.build create mode 100644 comm/chat/protocols/matrix/shims/empty.js create mode 100644 comm/chat/protocols/matrix/shims/loglevel.js create mode 100644 comm/chat/protocols/matrix/shims/moz.build create mode 100644 comm/chat/protocols/matrix/shims/safe-buffer.js create mode 100644 comm/chat/protocols/matrix/shims/uuid.js create mode 100644 comm/chat/protocols/matrix/test/head.js create mode 100644 comm/chat/protocols/matrix/test/test_matrixAccount.js create mode 100644 comm/chat/protocols/matrix/test/test_matrixCommands.js create mode 100644 comm/chat/protocols/matrix/test/test_matrixMessage.js create mode 100644 comm/chat/protocols/matrix/test/test_matrixMessageContent.js create mode 100644 comm/chat/protocols/matrix/test/test_matrixPowerLevels.js create mode 100644 comm/chat/protocols/matrix/test/test_matrixRoom.js create mode 100644 comm/chat/protocols/matrix/test/test_matrixTextForEvent.js create mode 100644 comm/chat/protocols/matrix/test/test_roomTypeChange.js create mode 100644 comm/chat/protocols/matrix/test/xpcshell.ini create mode 100644 comm/chat/protocols/odnoklassniki/components.conf create mode 100644 comm/chat/protocols/odnoklassniki/icons/prpl-odnoklassniki-32.png create mode 100644 comm/chat/protocols/odnoklassniki/icons/prpl-odnoklassniki-48.png create mode 100644 comm/chat/protocols/odnoklassniki/icons/prpl-odnoklassniki.png create mode 100644 comm/chat/protocols/odnoklassniki/jar.mn create mode 100644 comm/chat/protocols/odnoklassniki/moz.build create mode 100644 comm/chat/protocols/odnoklassniki/odnoklassniki.sys.mjs create mode 100644 comm/chat/protocols/twitter/components.conf create mode 100644 comm/chat/protocols/twitter/icons/prpl-twitter-32.png create mode 100644 comm/chat/protocols/twitter/icons/prpl-twitter-48.png create mode 100644 comm/chat/protocols/twitter/icons/prpl-twitter-left.png create mode 100644 comm/chat/protocols/twitter/icons/prpl-twitter.png create mode 100644 comm/chat/protocols/twitter/jar.mn create mode 100644 comm/chat/protocols/twitter/moz.build create mode 100644 comm/chat/protocols/twitter/twitter.sys.mjs create mode 100644 comm/chat/protocols/xmpp/.eslintrc.js create mode 100644 comm/chat/protocols/xmpp/components.conf create mode 100644 comm/chat/protocols/xmpp/icons/prpl-jabber-32.png create mode 100644 comm/chat/protocols/xmpp/icons/prpl-jabber-48.png create mode 100644 comm/chat/protocols/xmpp/icons/prpl-jabber.png create mode 100644 comm/chat/protocols/xmpp/jar.mn create mode 100644 comm/chat/protocols/xmpp/lib/README.md create mode 100644 comm/chat/protocols/xmpp/lib/moz.build create mode 100644 comm/chat/protocols/xmpp/lib/sax/LICENSE create mode 100644 comm/chat/protocols/xmpp/lib/sax/sax.js create mode 100644 comm/chat/protocols/xmpp/moz.build create mode 100644 comm/chat/protocols/xmpp/sax.sys.mjs create mode 100644 comm/chat/protocols/xmpp/test/test_authmechs.js create mode 100644 comm/chat/protocols/xmpp/test/test_dnsSrv.js create mode 100644 comm/chat/protocols/xmpp/test/test_parseJidAndNormalization.js create mode 100644 comm/chat/protocols/xmpp/test/test_parseVCard.js create mode 100644 comm/chat/protocols/xmpp/test/test_saslPrep.js create mode 100644 comm/chat/protocols/xmpp/test/test_xmppParser.js create mode 100644 comm/chat/protocols/xmpp/test/test_xmppXml.js create mode 100644 comm/chat/protocols/xmpp/test/xpcshell.ini create mode 100644 comm/chat/protocols/xmpp/xmpp-authmechs.sys.mjs create mode 100644 comm/chat/protocols/xmpp/xmpp-base.sys.mjs create mode 100644 comm/chat/protocols/xmpp/xmpp-commands.sys.mjs create mode 100644 comm/chat/protocols/xmpp/xmpp-session.sys.mjs create mode 100644 comm/chat/protocols/xmpp/xmpp-xml.sys.mjs create mode 100644 comm/chat/protocols/xmpp/xmpp.sys.mjs create mode 100644 comm/chat/protocols/yahoo/components.conf create mode 100644 comm/chat/protocols/yahoo/icons/prpl-yahoo-32.png create mode 100644 comm/chat/protocols/yahoo/icons/prpl-yahoo-48.png create mode 100644 comm/chat/protocols/yahoo/icons/prpl-yahoo.png create mode 100644 comm/chat/protocols/yahoo/jar.mn create mode 100644 comm/chat/protocols/yahoo/moz.build create mode 100644 comm/chat/protocols/yahoo/yahoo.sys.mjs (limited to 'comm/chat/protocols') diff --git a/comm/chat/protocols/facebook/components.conf b/comm/chat/protocols/facebook/components.conf new file mode 100644 index 0000000000..b5a023f678 --- /dev/null +++ b/comm/chat/protocols/facebook/components.conf @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{1d1d0bc5-610c-472f-b2cb-4b89857d80dc}', + 'contract_ids': ['@mozilla.org/chat/facebook;1'], + 'esModule': 'resource:///modules/facebook.sys.mjs', + 'constructor': 'FacebookProtocol', + 'categories': {'im-protocol-plugin': 'prpl-facebook'}, + }, +] diff --git a/comm/chat/protocols/facebook/facebook.sys.mjs b/comm/chat/protocols/facebook/facebook.sys.mjs new file mode 100644 index 0000000000..048e81b4a9 --- /dev/null +++ b/comm/chat/protocols/facebook/facebook.sys.mjs @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { l10nHelper } from "resource:///modules/imXPCOMUtils.sys.mjs"; +import { + GenericAccountPrototype, + GenericProtocolPrototype, +} from "resource:///modules/jsProtoHelper.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter(lazy, "_", () => + l10nHelper("chrome://chat/locale/facebook.properties") +); + +function FacebookAccount(aProtoInstance, aImAccount) { + this._init(aProtoInstance, aImAccount); +} +FacebookAccount.prototype = { + __proto__: GenericAccountPrototype, + + connect() { + this.WARN( + "As Facebook deprecated its XMPP gateway, it is currently not " + + "possible to connect to Facebook Chat. See bug 1141674." + ); + this.reportDisconnecting( + Ci.prplIAccount.ERROR_OTHER_ERROR, + lazy._("facebook.disabled") + ); + this.reportDisconnected(); + }, + + // Nothing to do. + unInit() {}, + remove() {}, +}; + +export function FacebookProtocol() {} +FacebookProtocol.prototype = { + __proto__: GenericProtocolPrototype, + get normalizedName() { + return "facebook"; + }, + get name() { + return lazy._("facebook.chat.name"); + }, + get iconBaseURI() { + return "chrome://prpl-facebook/skin/"; + }, + getAccount(aImAccount) { + return new FacebookAccount(this, aImAccount); + }, +}; diff --git a/comm/chat/protocols/facebook/icons/prpl-facebook-32.png b/comm/chat/protocols/facebook/icons/prpl-facebook-32.png new file mode 100644 index 0000000000..77e6d358b6 Binary files /dev/null and b/comm/chat/protocols/facebook/icons/prpl-facebook-32.png differ diff --git a/comm/chat/protocols/facebook/icons/prpl-facebook-48.png b/comm/chat/protocols/facebook/icons/prpl-facebook-48.png new file mode 100644 index 0000000000..2501acaab5 Binary files /dev/null and b/comm/chat/protocols/facebook/icons/prpl-facebook-48.png differ diff --git a/comm/chat/protocols/facebook/icons/prpl-facebook.png b/comm/chat/protocols/facebook/icons/prpl-facebook.png new file mode 100644 index 0000000000..bc42cf9b0b Binary files /dev/null and b/comm/chat/protocols/facebook/icons/prpl-facebook.png differ diff --git a/comm/chat/protocols/facebook/jar.mn b/comm/chat/protocols/facebook/jar.mn new file mode 100644 index 0000000000..24c5e8fef6 --- /dev/null +++ b/comm/chat/protocols/facebook/jar.mn @@ -0,0 +1,9 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +chat.jar: +% skin prpl-facebook classic/1.0 %skin/classic/prpl/facebook/ + skin/classic/prpl/facebook/icon32.png (icons/prpl-facebook-32.png) + skin/classic/prpl/facebook/icon48.png (icons/prpl-facebook-48.png) + skin/classic/prpl/facebook/icon.png (icons/prpl-facebook.png) diff --git a/comm/chat/protocols/facebook/moz.build b/comm/chat/protocols/facebook/moz.build new file mode 100644 index 0000000000..d07bb3a8f9 --- /dev/null +++ b/comm/chat/protocols/facebook/moz.build @@ -0,0 +1,14 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ["jar.mn"] + +EXTRA_JS_MODULES += [ + "facebook.sys.mjs", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] diff --git a/comm/chat/protocols/gtalk/components.conf b/comm/chat/protocols/gtalk/components.conf new file mode 100644 index 0000000000..a736ae37a2 --- /dev/null +++ b/comm/chat/protocols/gtalk/components.conf @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{38a224c1-6748-49a9-8ab2-efc362b1000d}', + 'contract_ids': ['@mozilla.org/chat/gtalk;1'], + 'esModule': 'resource:///modules/gtalk.sys.mjs', + 'constructor': 'GTalkProtocol', + 'categories': {'im-protocol-plugin': 'prpl-gtalk'}, + }, +] diff --git a/comm/chat/protocols/gtalk/gtalk.sys.mjs b/comm/chat/protocols/gtalk/gtalk.sys.mjs new file mode 100644 index 0000000000..ca5b7c33a6 --- /dev/null +++ b/comm/chat/protocols/gtalk/gtalk.sys.mjs @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { l10nHelper } from "resource:///modules/imXPCOMUtils.sys.mjs"; +import { + GenericAccountPrototype, + GenericProtocolPrototype, +} from "resource:///modules/jsProtoHelper.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter(lazy, "_", () => + l10nHelper("chrome://chat/locale/xmpp.properties") +); + +function GTalkAccount(aProtoInstance, aImAccount) { + this._init(aProtoInstance, aImAccount); +} +GTalkAccount.prototype = { + __proto__: GenericAccountPrototype, + connect() { + this.WARN( + "As Google deprecated its XMPP gateway, it is currently not " + + "possible to connect to Google Talk. See bug 1645217." + ); + this.reportDisconnecting( + Ci.prplIAccount.ERROR_OTHER_ERROR, + lazy._("gtalk.disabled") + ); + this.reportDisconnected(); + }, + + // Nothing to do. + unInit() {}, + remove() {}, +}; + +export function GTalkProtocol() {} +GTalkProtocol.prototype = { + __proto__: GenericProtocolPrototype, + get normalizedName() { + return "gtalk"; + }, + get name() { + return lazy._("gtalk.protocolName"); + }, + get iconBaseURI() { + return "chrome://prpl-gtalk/skin/"; + }, + getAccount(aImAccount) { + return new GTalkAccount(this, aImAccount); + }, + // GTalk accounts which were configured with OAuth2 do not have a password set. + // Show the above error on connect instead of a "needs password" error. + get noPassword() { + return true; + }, +}; diff --git a/comm/chat/protocols/gtalk/icons/prpl-gtalk-32.png b/comm/chat/protocols/gtalk/icons/prpl-gtalk-32.png new file mode 100644 index 0000000000..8390ff8f3e Binary files /dev/null and b/comm/chat/protocols/gtalk/icons/prpl-gtalk-32.png differ diff --git a/comm/chat/protocols/gtalk/icons/prpl-gtalk-48.png b/comm/chat/protocols/gtalk/icons/prpl-gtalk-48.png new file mode 100644 index 0000000000..e0352ac69f Binary files /dev/null and b/comm/chat/protocols/gtalk/icons/prpl-gtalk-48.png differ diff --git a/comm/chat/protocols/gtalk/icons/prpl-gtalk.png b/comm/chat/protocols/gtalk/icons/prpl-gtalk.png new file mode 100644 index 0000000000..396b967c65 Binary files /dev/null and b/comm/chat/protocols/gtalk/icons/prpl-gtalk.png differ diff --git a/comm/chat/protocols/gtalk/jar.mn b/comm/chat/protocols/gtalk/jar.mn new file mode 100644 index 0000000000..6f2d510e4e --- /dev/null +++ b/comm/chat/protocols/gtalk/jar.mn @@ -0,0 +1,9 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +chat.jar: +% skin prpl-gtalk classic/1.0 %skin/classic/prpl/gtalk/ + skin/classic/prpl/gtalk/icon32.png (icons/prpl-gtalk-32.png) + skin/classic/prpl/gtalk/icon48.png (icons/prpl-gtalk-48.png) + skin/classic/prpl/gtalk/icon.png (icons/prpl-gtalk.png) diff --git a/comm/chat/protocols/gtalk/moz.build b/comm/chat/protocols/gtalk/moz.build new file mode 100644 index 0000000000..b147aeaf01 --- /dev/null +++ b/comm/chat/protocols/gtalk/moz.build @@ -0,0 +1,14 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ["jar.mn"] + +EXTRA_JS_MODULES += [ + "gtalk.sys.mjs", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] diff --git a/comm/chat/protocols/irc/components.conf b/comm/chat/protocols/irc/components.conf new file mode 100644 index 0000000000..08a9674884 --- /dev/null +++ b/comm/chat/protocols/irc/components.conf @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{607b2c0b-9504-483f-ad62-41de09238aec}', + 'contract_ids': ['@mozilla.org/chat/irc;1'], + 'esModule': 'resource:///modules/irc.sys.mjs', + 'constructor': 'ircProtocol', + 'categories': {'im-protocol-plugin': 'prpl-irc'}, + }, +] diff --git a/comm/chat/protocols/irc/icons/prpl-irc-32.png b/comm/chat/protocols/irc/icons/prpl-irc-32.png new file mode 100644 index 0000000000..003103914c Binary files /dev/null and b/comm/chat/protocols/irc/icons/prpl-irc-32.png differ diff --git a/comm/chat/protocols/irc/icons/prpl-irc-48.png b/comm/chat/protocols/irc/icons/prpl-irc-48.png new file mode 100644 index 0000000000..606425fabb Binary files /dev/null and b/comm/chat/protocols/irc/icons/prpl-irc-48.png differ diff --git a/comm/chat/protocols/irc/icons/prpl-irc.png b/comm/chat/protocols/irc/icons/prpl-irc.png new file mode 100644 index 0000000000..19d578deda Binary files /dev/null and b/comm/chat/protocols/irc/icons/prpl-irc.png differ diff --git a/comm/chat/protocols/irc/irc.sys.mjs b/comm/chat/protocols/irc/irc.sys.mjs new file mode 100644 index 0000000000..087dbf28d8 --- /dev/null +++ b/comm/chat/protocols/irc/irc.sys.mjs @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { l10nHelper } from "resource:///modules/imXPCOMUtils.sys.mjs"; +import { GenericProtocolPrototype } from "resource:///modules/jsProtoHelper.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter(lazy, "_", () => + l10nHelper("chrome://chat/locale/irc.properties") +); +ChromeUtils.defineESModuleGetters(lazy, { + ircAccount: "resource:///modules/ircAccount.sys.mjs", +}); + +export function ircProtocol() { + // ircCommands.jsm exports one variable: commands. Import this directly into + // the protocol object. + this.commands = ChromeUtils.importESModule( + "resource:///modules/ircCommands.sys.mjs" + ).commands; + this.registerCommands(); +} + +ircProtocol.prototype = { + __proto__: GenericProtocolPrototype, + get name() { + return "IRC"; + }, + get normalizedName() { + return "irc"; + }, + get iconBaseURI() { + return "chrome://prpl-irc/skin/"; + }, + get usernameEmptyText() { + return lazy._("irc.usernameHint"); + }, + + usernameSplits: [ + { + get label() { + return lazy._("options.server"); + }, + separator: "@", + defaultValue: "irc.libera.chat", + }, + ], + + splitUsername(aName) { + let splitter = aName.lastIndexOf("@"); + if (splitter === -1) { + return []; + } + return [aName.slice(0, splitter), aName.slice(splitter + 1)]; + }, + + options: { + port: { + get label() { + return lazy._("options.port"); + }, + default: 6697, + }, + ssl: { + get label() { + return lazy._("options.ssl"); + }, + default: true, + }, + // TODO We should attempt to auto-detect encoding instead. + encoding: { + get label() { + return lazy._("options.encoding"); + }, + default: "UTF-8", + }, + quitmsg: { + get label() { + return lazy._("options.quitMessage"); + }, + get default() { + return Services.prefs.getCharPref("chat.irc.defaultQuitMessage"); + }, + }, + partmsg: { + get label() { + return lazy._("options.partMessage"); + }, + default: "", + }, + showServerTab: { + get label() { + return lazy._("options.showServerTab"); + }, + default: false, + }, + alternateNicks: { + get label() { + return lazy._("options.alternateNicks"); + }, + default: "", + }, + }, + + get chatHasTopic() { + return true; + }, + get slashCommandsNative() { + return true; + }, + // Passwords in IRC are optional, and are needed for certain functionality. + get passwordOptional() { + return true; + }, + + getAccount(aImAccount) { + return new lazy.ircAccount(this, aImAccount); + }, +}; diff --git a/comm/chat/protocols/irc/ircAccount.sys.mjs b/comm/chat/protocols/irc/ircAccount.sys.mjs new file mode 100644 index 0000000000..6a127e16cb --- /dev/null +++ b/comm/chat/protocols/irc/ircAccount.sys.mjs @@ -0,0 +1,2296 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { + ClassInfo, + executeSoon, + l10nHelper, + nsSimpleEnumerator, +} from "resource:///modules/imXPCOMUtils.sys.mjs"; +import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs"; +import { IMServices } from "resource:///modules/IMServices.sys.mjs"; +import { + ctcpFormatToHTML, + kListRefreshInterval, +} from "resource:///modules/ircUtils.sys.mjs"; +import { + GenericAccountPrototype, + GenericAccountBuddyPrototype, + GenericConvIMPrototype, + GenericConvChatPrototype, + GenericConvChatBuddyPrototype, + GenericConversationPrototype, + TooltipInfo, +} from "resource:///modules/jsProtoHelper.sys.mjs"; +import { NormalizedMap } from "resource:///modules/NormalizedMap.sys.mjs"; +import { Socket } from "resource:///modules/socket.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs", + PluralForm: "resource://gre/modules/PluralForm.sys.mjs", + ircHandlers: "resource:///modules/ircHandlers.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "_conv", () => + l10nHelper("chrome://chat/locale/conversations.properties") +); +XPCOMUtils.defineLazyGetter(lazy, "_", () => + l10nHelper("chrome://chat/locale/irc.properties") +); + +/* + * Parses a raw IRC message into an object (see section 2.3 of RFC 2812). This + * returns an object with the following fields: + * rawMessage The initial message string received without any processing. + * command A string that is the command or response code. + * params An array of strings for the parameters. The last parameter is + * stripped of its : prefix. + * origin The user's nickname or the server who sent the message. Can be + * a host (e.g. irc.mozilla.org) or an IPv4 address (e.g. 1.2.3.4) + * or an IPv6 address (e.g. 3ffe:1900:4545:3:200:f8ff:fe21:67cf). + * user The user's username, note that this can be undefined. + * host The user's hostname, note that this can be undefined. + * source A "nicely" formatted combination of user & host, which is + * @ or if host is undefined. + * tags A Map with tags stored as key-value-pair. The value is a decoded + * string or undefined if the tag has no value. + * + * There are cases (e.g. localhost) where it cannot be easily determined if a + * message is from a server or from a user, thus the usage of a generic "origin" + * instead of "nickname" or "servername". + * + * Inputs: + * aData The raw string to parse, it should already have the \r\n + * stripped from the end. + * aOrigin The default origin to use for unprefixed messages. + */ +export function ircMessage(aData, aOrigin) { + let message = { rawMessage: aData }; + let temp; + + // Splits the raw string into five parts. The third part, the command, is + // required. A raw string looks like: + // ["@" " "] [":" " "] [" " ]* [":" ] + // : /[^ ]+/ + // : :( | [["!" ] "@" ]) + // : /[^ ]+/ + // : /[^ ]+/ + // : /.+/ + // See http://joshualuckers.nl/2010/01/10/regular-expression-to-match-raw-irc-messages/ + // Note that this expression is slightly more aggressive in matching than RFC + // 2812 would allow. It allows for empty parameters (besides the last + // parameter, which can always be empty), by allowing multiple spaces. + // (This is for compatibility with Unreal's 432 response, which returns an + // empty first parameter.) It also allows a trailing space after the + // s when no is present (also occurs with Unreal). + if ( + !(temp = aData.match( + /^(?:@([^ ]+) )?(?::([^ ]+) )?([^ ]+)((?: +[^: ][^ ]*)*)? *(?::([\s\S]*))?$/ + )) + ) { + throw new Error("Couldn't parse message: \"" + aData + '"'); + } + + message.command = temp[3]; + // Space separated parameters. Since we expect a space as the first thing + // here, we want to ignore the first value (which is empty). + message.params = temp[4] ? temp[4].split(" ").slice(1) : []; + // Last parameter can contain spaces or be an empty string. + if (temp[5] !== undefined) { + message.params.push(temp[5]); + } + + // Handle the prefix part of the message per RFC 2812 Section 2.3. + + // If no prefix is given, assume the current server is the origin. + if (!temp[2]) { + temp[2] = aOrigin; + } + + // Split the prefix into separate nickname, username and hostname fields as: + // :(servername|(nickname[[!user]@host])) + [message.origin, message.user, message.host] = temp[2].split(/[!@]/); + + // Store the tags in a Map, see IRCv3.2 Message Tags. + message.tags = new Map(); + + if (temp[1]) { + let tags = temp[1].split(";"); + tags.forEach(tag => { + let [key, value] = tag.split("="); + + if (value) { + // Unescape tag values according to this mapping: + // \\ = \ + // \n = LF + // \r = CR + // \s = SPACE + // \: = ; + // everything else stays identical. + value = value.replace(/\\(.)/g, (str, type) => { + if (type == "\\") { + return "\\"; + } else if (type == "n") { + return "\n"; + } else if (type == "r") { + return "\r"; + } else if (type == "s") { + return " "; + } else if (type == ":") { + return ";"; + } + // Ignore the backslash, not specified by the spec, but as it says + // backslashes must be escaped this case should not occur in a valid + // tag value. + return type; + }); + } + // The tag key can typically have the form of example.com/aaa for vendor + // defined tags. The spec wants any unicode characters in URLs to be + // in punycode (xn--). These are not unescaped to their unicode value. + message.tags.set(key, value); + }); + } + + // It is occasionally useful to have a "source" which is a combination of + // user@host. + if (message.user) { + message.source = message.user + "@" + message.host; + } else if (message.host) { + message.source = message.host; + } else { + message.source = ""; + } + + return message; +} + +// This handles a mode change string for both channels and participants. A mode +// change string is of the form: +// aAddNewMode is true if modes are being added, false otherwise. +// aNewModes is an array of mode characters. +function _setMode(aAddNewMode, aNewModes) { + // Check each mode being added/removed. + for (let newMode of aNewModes) { + let hasMode = this._modes.has(newMode); + // If the mode is in the list of modes and we want to remove it. + if (hasMode && !aAddNewMode) { + this._modes.delete(newMode); + } else if (!hasMode && aAddNewMode) { + // If the mode is not in the list of modes and we want to add it. + this._modes.add(newMode); + } + } +} + +function TagMessage(aMessage, aTagName) { + this.message = aMessage; + this.tagName = aTagName; + this.tagValue = aMessage.tags.get(aTagName); +} + +// Properties / methods shared by both ircChannel and ircConversation. +export var GenericIRCConversation = { + _observedNicks: [], + // This is set to true after a message is sent to notify the 401 + // ERR_NOSUCHNICK handler to write an error message to the conversation. + _pendingMessage: false, + _waitingForNick: false, + + normalizeNick(aNick) { + return this._account.normalizeNick(aNick); + }, + + // This will calculate the maximum number of bytes that are left for a message + // typed by the user by calculate the amount of bytes that would be used by + // the IRC messaging. + getMaxMessageLength() { + // Build the shortest possible message that could be sent to other users. + let baseMessage = + ":" + + this._account._nickname + + this._account.prefix + + " " + + this._account.buildMessage("PRIVMSG", this.name) + + " :\r\n"; + return ( + this._account.maxMessageLength - this._account.countBytes(baseMessage) + ); + }, + /** + * @param {string} aWho - Message author's username. + * @param {string} aMessage - Message text. + * @param {object} aObject - Other properties to set on the imMessage. + */ + handleTags(aWho, aMessage, aObject) { + let messageProps = aObject; + if ("tags" in aObject && lazy.ircHandlers.hasTagHandlers) { + // Merge extra info for the handler into the props. + messageProps = Object.assign( + { + who: aWho, + message: aMessage, + get originalMessage() { + return aMessage; + }, + }, + messageProps + ); + for (let tag of aObject.tags.keys()) { + // Unhandled tags may be common, since a tag does not have to be handled + // with a tag handler, it may also be handled by a message command handler. + lazy.ircHandlers.handleTag( + this._account, + new TagMessage(messageProps, tag) + ); + } + + // Remove helper prop for tag handlers. We don't want to remove the other + // ones, since they might have been changed and will override aWho and + // aMessage in the imMessage constructor. + delete messageProps.originalMessage; + } + // Remove the IRC tags, as those were passed in just for this step. + delete messageProps.tags; + return messageProps; + }, + // Apply CTCP formatting before displaying. + prepareForDisplaying(aMsg) { + aMsg.displayMessage = ctcpFormatToHTML(aMsg.displayMessage); + GenericConversationPrototype.prepareForDisplaying.apply(this, arguments); + }, + prepareForSending(aOutgoingMessage) { + // Split the message by line breaks and send each one individually. + let messages = aOutgoingMessage.message.split(/[\r\n]+/); + + let maxLength = this.getMaxMessageLength(); + + // Attempt to smartly split a string into multiple lines (based on the + // maximum number of characters the message can contain). + for (let i = 0; i < messages.length; ++i) { + let message = messages[i]; + let length = this._account.countBytes(message); + // The message is short enough. + if (length <= maxLength) { + continue; + } + + // Find the location of a space before the maximum length. + let index = message.lastIndexOf(" ", maxLength); + + // Remove the current message and insert the two new ones. If no space was + // found, cut the first message to the maximum length and start the second + // message one character after that. If a space was found, exclude it. + messages.splice( + i, + 1, + message.substr(0, index == -1 ? maxLength : index), + message.substr(index + 1 || maxLength) + ); + } + + return messages; + }, + dispatchMessage(message, action = false, isNotice = false) { + if (!message.length) { + return; + } + + if (action) { + if (!this._account.sendCTCPMessage(this.name, false, "ACTION", message)) { + this.writeMessage( + this._account._currentServerName, + lazy._("error.sendMessageFailed"), + { + error: true, + system: true, + } + ); + return; + } + } else if ( + !this._account.sendMessage(isNotice ? "NOTICE" : "PRIVMSG", [ + this.name, + message, + ]) + ) { + this.writeMessage( + this._account._currentServerName, + lazy._("error.sendMessageFailed"), + { + error: true, + system: true, + } + ); + return; + } + + // By default the server doesn't send the message back, but this can be + // enabled with the echo-message capability. If this is not enabled, just + // assume the message was received and immediately show it. + if (!this._account._activeCAPs.has("echo-message")) { + this.writeMessage( + this._account.imAccount.alias || + this._account.imAccount.statusInfo.displayName || + this._account._nickname, + message, + { + outgoing: true, + notification: isNotice, + action, + } + ); + } + + this._pendingMessage = true; + }, + // IRC doesn't support typing notifications, but it does have a maximum + // message length. + sendTyping(aString) { + let longestLineLength = Math.max.apply( + null, + aString.split("\n").map(this._account.countBytes, this._account) + ); + return this.getMaxMessageLength() - longestLineLength; + }, + + requestCurrentWhois(aNick) { + if (!this._observedNicks.length) { + Services.obs.addObserver(this, "user-info-received"); + } + this._observedNicks.push(this.normalizeNick(aNick)); + this._account.requestCurrentWhois(aNick); + }, + + observe(aSubject, aTopic, aData) { + if (aTopic != "user-info-received") { + return; + } + + let nick = this.normalizeNick(aData); + let nickIndex = this._observedNicks.indexOf(nick); + if (nickIndex == -1) { + return; + } + + // Remove the nick from the list of nicks that are being waited to received. + this._observedNicks.splice(nickIndex, 1); + + // If this is the last nick, remove the observer. + if (!this._observedNicks.length) { + Services.obs.removeObserver(this, "user-info-received"); + } + + // If we are waiting for the conversation name, set it. + let account = this._account; + if (this._waitingForNick && nick == this.normalizedName) { + if (account.whoisInformation.has(nick)) { + this.updateNick(account.whoisInformation.get(nick).nick); + } + delete this._waitingForNick; + return; + } + + // Otherwise, print the requested whois information. + let type = { system: true, noLog: true }; + // RFC 2812 errors 401 and 406 result in there being no entry for the nick. + if (!account.whoisInformation.has(nick)) { + this.writeMessage(null, lazy._("message.unknownNick", nick), type); + return; + } + // If the nick is offline, tell the user. In that case, it's WHOWAS info. + let msgType = "message.whois"; + if ("offline" in account.whoisInformation.get(nick)) { + msgType = "message.whowas"; + } + let msg = lazy._(msgType, account.whoisInformation.get(nick).nick); + + // Iterate over each field. + for (let elt of aSubject.QueryInterface(Ci.nsISimpleEnumerator)) { + switch (elt.type) { + case Ci.prplITooltipInfo.pair: + case Ci.prplITooltipInfo.sectionHeader: + msg += "\n" + lazy._("message.whoisEntry", elt.label, elt.value); + break; + case Ci.prplITooltipInfo.sectionBreak: + break; + case Ci.prplITooltipInfo.status: + if (elt.label != Ci.imIStatusInfo.STATUS_AWAY) { + break; + } + // The away message has no tooltipInfo.pair entry. + msg += + "\n" + + lazy._("message.whoisEntry", lazy._("tooltip.away"), elt.value); + break; + } + } + this.writeMessage(null, msg, type); + }, + + unInitIRCConversation() { + this._account.removeConversation(this.name); + if (this._observedNicks.length) { + Services.obs.removeObserver(this, "user-info-received"); + } + }, +}; + +export function ircChannel(aAccount, aName, aNick) { + this._init(aAccount, aName, aNick); + this._participants = new NormalizedMap(this.normalizeNick.bind(this)); + this._modes = new Set(); + this._observedNicks = []; + this.banMasks = []; +} + +ircChannel.prototype = { + __proto__: GenericConvChatPrototype, + _modes: null, + _receivedInitialMode: false, + // For IRC you're not in a channel until the JOIN command is received, open + // all channels (initially) as left. + _left: true, + // True while we are rejoining a channel previously parted by the user. + _rejoined: false, + banMasks: [], + + // Section 3.2.2 of RFC 2812. + part(aMessage) { + let params = [this.name]; + + // If a valid message was given, use it as the part message. + // Otherwise, fall back to the default part message, if it exists. + let msg = aMessage || this._account.getString("partmsg"); + if (msg) { + params.push(msg); + } + + this._account.sendMessage("PART", params); + + // Remove reconnection information. + delete this.chatRoomFields; + }, + + close() { + // Part the room if we're connected. + if (this._account.connected && !this.left) { + this.part(); + } + GenericConvChatPrototype.close.call(this); + }, + + unInit() { + this.unInitIRCConversation(); + GenericConvChatPrototype.unInit.call(this); + }, + + // Use the normalized nick in order to properly notify the observers. + getNormalizedChatBuddyName(aNick) { + return this.normalizeNick(aNick); + }, + + getParticipant(aNick, aNotifyObservers) { + if (this._participants.has(aNick)) { + return this._participants.get(aNick); + } + + let participant = new ircParticipant(aNick, this); + this._participants.set(aNick, participant); + + // Add the participant to the whois table if it is not already there. + this._account.setWhois(participant._name); + + if (aNotifyObservers) { + this.notifyObservers( + new nsSimpleEnumerator([participant]), + "chat-buddy-add" + ); + } + return participant; + }, + + /* + * Add/remove modes from this channel. + * + * aNewMode is the new mode string, it MUST begin with + or -. + * aModeParams is a list of ordered string parameters for the mode string. + * aSetter is the nick of the person (or service) that set the mode. + */ + setMode(aNewMode, aModeParams, aSetter) { + // Save this for a comparison after the new modes have been set. + let previousTopicSettable = this.topicSettable; + + const hostMaskExp = /^.+!.+@.+$/; + function getNextParam() { + // If there's no next parameter, throw a warning. + if (!aModeParams.length) { + this.WARN("Mode parameter expected!"); + return undefined; + } + return aModeParams.pop(); + } + function peekNextParam() { + // Non-destructively gets the next param. + if (!aModeParams.length) { + return undefined; + } + return aModeParams.slice(-1)[0]; + } + + // Are modes being added or removed? + if (aNewMode[0] != "+" && aNewMode[0] != "-") { + this.WARN("Invalid mode string: " + aNewMode); + return; + } + let addNewMode = aNewMode[0] == "+"; + + // Check each mode being added and update the user. + let channelModes = []; + let userModes = new NormalizedMap(this.normalizeNick.bind(this)); + let msg; + + for (let i = aNewMode.length - 1; i > 0; --i) { + // Since some modes are conflicted between different server + // implementations, check if a participant with that name exists. If this + // is true, then update the mode of the ConvChatBuddy. + if ( + this._account.memberStatuses.includes(aNewMode[i]) && + aModeParams.length && + this._participants.has(peekNextParam()) + ) { + // Store the new modes for this nick (so each participant's mode is only + // updated once). + let nick = getNextParam(); + if (!userModes.has(nick)) { + userModes.set(nick, []); + } + userModes.get(nick).push(aNewMode[i]); + + // Don't use this mode as a channel mode. + continue; + } else if (aNewMode[i] == "k") { + // Channel key. + let newFields = this.name; + if (addNewMode) { + let key = getNextParam(); + // A new channel key was set, display a message if this key is not + // already known. + if ( + this.chatRoomFields && + this.chatRoomFields.getValue("password") == key + ) { + continue; + } + msg = lazy._("message.channelKeyAdded", aSetter, key); + newFields += " " + key; + } else { + msg = lazy._("message.channelKeyRemoved", aSetter); + } + + this.writeMessage(aSetter, msg, { system: true }); + // Store the new fields for reconnect. + this.chatRoomFields = + this._account.getChatRoomDefaultFieldValues(newFields); + } else if (aNewMode[i] == "b") { + // A banmask was added or removed. + let banMask = getNextParam(); + let msgKey = "message.banMask"; + if (addNewMode) { + this.banMasks.push(banMask); + msgKey += "Added"; + } else { + this.banMasks = this.banMasks.filter(aBanMask => banMask != aBanMask); + msgKey += "Removed"; + } + this.writeMessage(aSetter, lazy._(msgKey, banMask, aSetter), { + system: true, + }); + } else if (["e", "I", "l"].includes(aNewMode[i])) { + // TODO The following have parameters that must be accounted for. + getNextParam(); + } else if ( + aNewMode[i] == "R" && + aModeParams.length && + peekNextParam().match(hostMaskExp) + ) { + // REOP_LIST takes a mask as a parameter, since R is a conflicted mode, + // try to match the parameter. Implemented by IRCNet. + // TODO The parameter must be acounted for. + getNextParam(); + } + // TODO From RFC 2811: a, i, m, n, q, p, s, r, t, l, e, I. + + // Keep track of the channel modes in the order they were received. + channelModes.unshift(aNewMode[i]); + } + + if (aModeParams.length) { + this.WARN("Unused mode parameters: " + aModeParams.join(", ")); + } + + // Update the mode of each participant. + for (let [nick, mode] of userModes.entries()) { + this.getParticipant(nick).setMode(addNewMode, mode, aSetter); + } + + // If the topic can now be set (and it couldn't previously) or vice versa, + // notify the UI. Note that this status can change by either a channel mode + // or a user mode changing. + if (this.topicSettable != previousTopicSettable) { + this.notifyObservers(this, "chat-update-topic"); + } + + // If no channel modes were being set, don't display a message for it. + if (!channelModes.length) { + return; + } + + // Store the channel modes. + _setMode.call(this, addNewMode, channelModes); + + // Notify the UI of changes. + msg = lazy._( + "message.channelmode", + aNewMode[0] + channelModes.join(""), + aSetter + ); + this.writeMessage(aSetter, msg, { system: true }); + + this._receivedInitialMode = true; + }, + + setModesFromRestriction(aRestriction) { + // First remove all types from the list of modes. + for (let key in this._account.channelRestrictionToModeMap) { + let mode = this._account.channelRestrictionToModeMap[key]; + this._modes.delete(mode); + } + + // Add the new mode onto the list. + if (aRestriction in this._account.channelRestrictionToModeMap) { + let mode = this._account.channelRestrictionToModeMap[aRestriction]; + if (mode) { + this._modes.add(mode); + } + } + }, + + get topic() { + return this._topic; + }, // can't add a setter without redefining the getter + set topic(aTopic) { + // Note that the UI isn't updated here because the server will echo back the + // TOPIC to us and we'll set it on receive. + this._account.sendMessage("TOPIC", [this.name, aTopic]); + }, + get topicSettable() { + // Don't use getParticipant since we don't want to lazily create it! + let participant = this._participants.get(this.nick); + + // We must be in the room to set the topic. + if (!participant) { + return false; + } + + // If the channel mode is +t, hops and ops can set the topic; otherwise + // everyone can. + return !this._modes.has("t") || participant.admin || participant.moderator; + }, + writeMessage(aWho, aMsg, aObject) { + const messageProps = this.handleTags(aWho, aMsg, aObject); + GenericConvChatPrototype.writeMessage.call(this, aWho, aMsg, messageProps); + }, +}; +Object.assign(ircChannel.prototype, GenericIRCConversation); + +function ircParticipant(aName, aConv) { + this._name = aName; + this._conv = aConv; + this._account = aConv._account; + this._modes = new Set(); + + // Handle multi-prefix modes. + let i; + for ( + i = 0; + i < this._name.length && this._name[i] in this._account.userPrefixToModeMap; + ++i + ) { + let mode = this._account.userPrefixToModeMap[this._name[i]]; + if (mode) { + this._modes.add(mode); + } + } + this._name = this._name.slice(i); +} +ircParticipant.prototype = { + __proto__: GenericConvChatBuddyPrototype, + + setMode(aAddNewMode, aNewModes, aSetter) { + _setMode.call(this, aAddNewMode, aNewModes); + + // Notify the UI of changes. + let msg = lazy._( + "message.usermode", + (aAddNewMode ? "+" : "-") + aNewModes.join(""), + this.name, + aSetter + ); + this._conv.writeMessage(aSetter, msg, { system: true }); + this._conv.notifyObservers(this, "chat-buddy-update"); + }, + + get voiced() { + return this._modes.has("v"); + }, + get moderator() { + return this._modes.has("h"); + }, + get admin() { + return this._modes.has("o"); + }, + get founder() { + return this._modes.has("O") || this._modes.has("q"); + }, + get typing() { + return false; + }, +}; + +export function ircConversation(aAccount, aName) { + let nick = aAccount.normalize(aName); + if (aAccount.whoisInformation.has(nick)) { + aName = aAccount.whoisInformation.get(nick).nick; + } + + this._init(aAccount, aName); + this._observedNicks = []; + + // Fetch correctly capitalized name. + // Always request the info as it may be out of date. + this._waitingForNick = true; + this.requestCurrentWhois(aName); +} + +ircConversation.prototype = { + __proto__: GenericConvIMPrototype, + get buddy() { + return this._account.buddies.get(this.name); + }, + + unInit() { + this.unInitIRCConversation(); + GenericConvIMPrototype.unInit.call(this); + }, + + updateNick(aNewNick) { + this._name = aNewNick; + this.notifyObservers(null, "update-conv-title"); + }, + writeMessage(aWho, aMsg, aObject) { + const messageProps = this.handleTags(aWho, aMsg, aObject); + GenericConvIMPrototype.writeMessage.call(this, aWho, aMsg, messageProps); + }, +}; +Object.assign(ircConversation.prototype, GenericIRCConversation); + +function ircSocket(aAccount) { + this._account = aAccount; + this._initCharsetConverter(); +} +ircSocket.prototype = { + __proto__: Socket, + // Although RFCs 1459 and 2812 explicitly say that \r\n is the message + // separator, some networks (euIRC) only send \n. + delimiter: /\r?\n/, + connectTimeout: 60, // Failure to connect after 1 minute + readWriteTimeout: 300, // Failure when no data for 5 minutes + _converter: null, + + sendPing() { + // Send a ping using the current timestamp as a payload prefixed with + // an underscore to signify this was an "automatic" PING (used to avoid + // socket timeouts). + this._account.sendMessage("PING", "_" + Date.now()); + }, + + _initCharsetConverter() { + try { + this._converter = new TextDecoder(this._account._encoding); + } catch (e) { + delete this._converter; + this.ERROR( + "Failed to set character set to: " + + this._account._encoding + + " for " + + this._account.name + + "." + ); + } + }, + + // Implement Section 5 of RFC 2812. + onDataReceived(aRawMessage) { + let conversionWarning = ""; + if (this._converter) { + try { + let buffer = Uint8Array.from(aRawMessage, c => c.charCodeAt(0)); + aRawMessage = this._converter.decode(buffer); + } catch (e) { + conversionWarning = + "\nThis message doesn't seem to be " + + this._account._encoding + + " encoded."; + // Unfortunately, if the unicode converter failed once, + // it will keep failing so we need to reinitialize it. + this._initCharsetConverter(); + } + } + + // We've received data and are past the authentication stage. + if (this._account.connected) { + this.resetPingTimer(); + } + + // Low level dequote: replace quote character \020 followed by 0, n, r or + // \020 with a \0, \n, \r or \020, respectively. Any other character is + // replaced with itself. + const lowDequote = { 0: "\0", n: "\n", r: "\r", "\x10": "\x10" }; + let dequotedMessage = aRawMessage.replace( + // eslint-disable-next-line no-control-regex + /\x10./g, + aStr => lowDequote[aStr[1]] || aStr[1] + ); + + try { + let message = new ircMessage( + dequotedMessage, + this._account._currentServerName + ); + this.DEBUG(JSON.stringify(message) + conversionWarning); + if (!lazy.ircHandlers.handleMessage(this._account, message)) { + // If the message was not handled, throw a warning containing + // the original quoted message. + this.WARN("Unhandled IRC message:\n" + aRawMessage); + } + } catch (e) { + // Catch the error, display it and hope the connection can continue with + // this message in error. Errors are also caught inside of handleMessage, + // but we expect to handle message parsing errors here. + this.DEBUG(aRawMessage + conversionWarning); + this.ERROR(e); + } + }, + onConnection() { + this._account._connectionRegistration(); + }, + disconnect() { + if (!this._account) { + return; + } + Socket.disconnect.call(this); + delete this._account; + }, + + // Throw errors if the socket has issues. + onConnectionClosed() { + // If the account was already disconnected, e.g. in response to + // onConnectionReset, do nothing. + if (!this._account) { + return; + } + const msg = "Connection closed by server."; + if (this._account.disconnecting) { + // The server closed the connection before we handled the ERROR + // response to QUIT. + this.LOG(msg); + this._account.gotDisconnected(); + } else { + this.WARN(msg); + this._account.gotDisconnected( + Ci.prplIAccount.ERROR_NETWORK_ERROR, + lazy._("connection.error.lost") + ); + } + }, + onConnectionReset() { + this.WARN("Connection reset."); + this._account.gotDisconnected( + Ci.prplIAccount.ERROR_NETWORK_ERROR, + lazy._("connection.error.lost") + ); + }, + onConnectionTimedOut() { + this.WARN("Connection timed out."); + this._account.gotDisconnected( + Ci.prplIAccount.ERROR_NETWORK_ERROR, + lazy._("connection.error.timeOut") + ); + }, + onConnectionSecurityError(aTLSError, aNSSErrorMessage) { + this.WARN( + "Bad certificate or SSL connection for " + + this._account.name + + ":\n" + + aNSSErrorMessage + ); + let error = this._account.handleConnectionSecurityError(this); + this._account.gotDisconnected(error, aNSSErrorMessage); + }, + + get DEBUG() { + return this._account.DEBUG; + }, + get LOG() { + return this._account.LOG; + }, + get WARN() { + return this._account.WARN; + }, + get ERROR() { + return this._account.ERROR; + }, +}; + +function ircAccountBuddy(aAccount, aBuddy, aTag, aUserName) { + this._init(aAccount, aBuddy, aTag, aUserName); +} +ircAccountBuddy.prototype = { + __proto__: GenericAccountBuddyPrototype, + + // Returns an array of prplITooltipInfo objects to be displayed when the + // user hovers over the buddy. + getTooltipInfo() { + return this._account.getBuddyInfo(this.normalizedName); + }, + + // Allow sending of messages to buddies even if they are not online since IRC + // does not always provide status information in a timely fashion. (Note that + // this is OK since the server will throw an error if the user is not online.) + get canSendMessage() { + return this.account.connected; + }, + + // Called when the user wants to chat with the buddy. + createConversation() { + return this._account.createConversation(this.userName); + }, + + remove() { + this._account.removeBuddy(this); + GenericAccountBuddyPrototype.remove.call(this); + }, +}; + +function ircRoomInfo(aName, aAccount) { + this.name = aName; + this._account = aAccount; +} +ircRoomInfo.prototype = { + __proto__: ClassInfo("prplIRoomInfo", "IRC RoomInfo Object"), + get topic() { + return this._account._channelList.get(this.name).topic; + }, + get participantCount() { + return this._account._channelList.get(this.name).participantCount; + }, + get chatRoomFieldValues() { + return this._account.getChatRoomDefaultFieldValues(this.name); + }, +}; + +export function ircAccount(aProtocol, aImAccount) { + this._init(aProtocol, aImAccount); + this.buddies = new NormalizedMap(this.normalizeNick.bind(this)); + this.conversations = new NormalizedMap(this.normalize.bind(this)); + + // Split the account name into usable parts. + const [accountNickname, server] = this.protocol.splitUsername(this.name); + this._accountNickname = accountNickname; + this._server = server; + // To avoid _currentServerName being null, initialize it to the server being + // connected to. This will also get overridden during the 001 response from + // the server. + this._currentServerName = this._server; + + this._nickname = this._accountNickname; + this._requestedNickname = this._nickname; + + // For more information, see where these are defined in the prototype below. + this.trackQueue = []; + this.pendingIsOnQueue = []; + this.whoisInformation = new NormalizedMap(this.normalizeNick.bind(this)); + this._requestedCAPs = new Set(); + this._availableCAPs = new Set(); + this._activeCAPs = new Set(); + this._queuedCAPs = []; + this._commandBuffers = new Map(); + this._roomInfoCallbacks = new Set(); +} + +ircAccount.prototype = { + __proto__: GenericAccountPrototype, + _socket: null, + _MODE_WALLOPS: 1 << 2, // mode 'w' + _MODE_INVISIBLE: 1 << 3, // mode 'i' + get _mode() { + return 0; + }, + + // The name of the server we last connected to. + _currentServerName: null, + // Whether to attempt authenticating with NickServ. + shouldAuthenticate: true, + // Whether the user has successfully authenticated with NickServ. + isAuthenticated: false, + // The current in use nickname. + _nickname: null, + // The nickname stored in the account name. + _accountNickname: null, + // The nickname that was last requested by the user. + _requestedNickname: null, + // The nickname that was last requested. This can differ from + // _requestedNickname when a new nick is automatically generated (e.g. by + // adding digits). + _sentNickname: null, + // If we don't get the desired nick on connect, we try again a bit later, + // to see if it wasn't just our nick not having timed out yet. + _nickInUseTimeout: null, + get username() { + let username; + // Use a custom username in a hidden preference. + if (this.prefs.prefHasUserValue("username")) { + username = this.getString("username"); + } + // But fallback to brandShortName if no username is provided (or is empty). + if (!username) { + username = Services.appinfo.name; + } + + return username; + }, + // The prefix minus the nick (!user@host) as returned by the server, this is + // necessary for guessing message lengths. + prefix: null, + + // Parts of the specification give max lengths, keep track of them since a + // server can overwrite them. The defaults given here are from RFC 2812. + maxNicknameLength: 9, // 1.2.1 Users + maxChannelLength: 50, // 1.3 Channels + maxMessageLength: 512, // 2.3 Messages + maxHostnameLength: 63, // 2.3.1 Message format in Augmented BNF + + // The default prefixes to modes. + userPrefixToModeMap: { "@": "o", "!": "n", "%": "h", "+": "v" }, + get userPrefixes() { + return Object.keys(this.userPrefixToModeMap); + }, + // Modes that have a nickname parameter and affect a participant. See 4.1 + // Member Status of RFC 2811. + memberStatuses: ["a", "h", "o", "O", "q", "v", "!"], + channelPrefixes: ["&", "#", "+", "!"], // 1.3 Channels + channelRestrictionToModeMap: { "@": "s", "*": "p", "=": null }, // 353 RPL_NAMREPLY + + // Handle Scandanavian lower case (optionally remove status indicators). + // See Section 2.2 of RFC 2812: the characters {}|^ are considered to be the + // lower case equivalents of the characters []\~, respectively. + normalizeExpression: /[\x41-\x5E]/g, + normalize(aStr, aPrefixes) { + let str = aStr; + + if (aPrefixes) { + while (aPrefixes.includes(str[0])) { + str = str.slice(1); + } + } + + return str.replace(this.normalizeExpression, c => + String.fromCharCode(c.charCodeAt(0) + 0x20) + ); + }, + normalizeNick(aNick) { + return this.normalize(aNick, this.userPrefixes); + }, + + isMUCName(aStr) { + return this.channelPrefixes.includes(aStr[0]); + }, + + // Tell the server about status changes. IRC is only away or not away; + // consider the away, idle and unavailable status type to be away. + isAway: false, + observe(aSubject, aTopic, aData) { + if (aTopic != "status-changed") { + return; + } + + let { statusType: type, statusText: text } = this.imAccount.statusInfo; + this.DEBUG("New status received:\ntype = " + type + "\ntext = " + text); + + // Tell the server to mark us as away. + if (type < Ci.imIStatusInfo.STATUS_AVAILABLE) { + // We have to have a string in order to set IRC as AWAY. + if (!text) { + // If no status is given, use the the default idle/away message. + const IDLE_PREF_BRANCH = "messenger.status."; + const IDLE_PREF = "defaultIdleAwayMessage"; + text = Services.prefs.getComplexValue( + IDLE_PREF_BRANCH + IDLE_PREF, + Ci.nsIPrefLocalizedString + ).data; + + if (!text) { + // Get the default value of the localized preference. + text = Services.prefs + .getDefaultBranch(IDLE_PREF_BRANCH) + .getComplexValue(IDLE_PREF, Ci.nsIPrefLocalizedString).data; + } + // The last resort, fallback to a non-localized string. + if (!text) { + text = "Away"; + } + } + this.sendMessage("AWAY", text); // Mark as away. + } else if (type == Ci.imIStatusInfo.STATUS_AVAILABLE && this.isAway) { + // Mark as back. + this.sendMessage("AWAY"); + } + }, + + // The user's user mode. + _modes: null, + _userModeReceived: false, + setUserMode(aNick, aNewModes, aSetter, aDisplayFullMode) { + if (this.normalizeNick(aNick) != this.normalizeNick(this._nickname)) { + this.WARN("Received unexpected mode for " + aNick); + return false; + } + + // Are modes being added or removed? + let addNewMode = aNewModes[0] == "+"; + if (!addNewMode && aNewModes[0] != "-") { + this.WARN("Invalid mode string: " + aNewModes); + return false; + } + _setMode.call(this, addNewMode, aNewModes.slice(1)); + + // The server informs us of the user's mode when connecting. + // We should not report this initial mode message as a mode change + // initiated by the user, but instead display the full mode + // and then remember we have done so. + this._userModeReceived = true; + + if (this._showServerTab) { + let msg; + if (aDisplayFullMode) { + msg = lazy._("message.yourmode", Array.from(this._modes).join("")); + } else { + msg = lazy._( + "message.usermode", + aNewModes, + aNick, + aSetter || this._currentServerName + ); + } + this.getConversation(this._currentServerName).writeMessage( + this._currentServerName, + msg, + { system: true } + ); + } + return true; + }, + + // Room info: maps channel names to {topic, participantCount}. + _channelList: new Map(), + _roomInfoCallbacks: new Set(), + // If true, we have sent the LIST request and are waiting for replies. + _pendingList: false, + // Callbacks receive this many channels per call while results are incoming. + _channelsPerBatch: 50, + _currentBatch: [], + _lastListTime: 0, + get isRoomInfoStale() { + return Date.now() - this._lastListTime > kListRefreshInterval; + }, + // Called by consumers that want a list of available channels, which are + // provided through the callback (prplIRoomInfoCallback instance). + requestRoomInfo(aCallback, aIsUserRequest) { + // Ignore the automaticList pref if the user explicitly requests /list. + if ( + !aIsUserRequest && + !Services.prefs.getBoolPref("chat.irc.automaticList") + ) { + // Pretend we can't return roomInfo. + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + if (this._roomInfoCallbacks.has(aCallback)) { + // Callback is not new. + return; + } + // Send a LIST request if the channel list is stale and a current request + // has not been sent. + if (this.isRoomInfoStale && !this._pendingList) { + this._channelList = new Map(); + this._currentBatch = []; + this._pendingList = true; + this._lastListTime = Date.now(); + this.sendMessage("LIST"); + } else { + // Otherwise, pass channels that have already been received to the callback. + let rooms = [...this._channelList.keys()]; + aCallback.onRoomInfoAvailable(rooms, !this._pendingList); + } + + if (this._pendingList) { + this._roomInfoCallbacks.add(aCallback); + } + }, + // Pass room info for any remaining channels to callbacks and clean up. + _sendRemainingRoomInfo() { + if (this._currentBatch.length) { + for (let callback of this._roomInfoCallbacks) { + callback.onRoomInfoAvailable(this._currentBatch, true); + } + } + this._roomInfoCallbacks.clear(); + delete this._pendingList; + delete this._currentBatch; + }, + getRoomInfo(aName) { + return new ircRoomInfo(aName, this); + }, + + // The last time a buffered command was sent. + _lastCommandSendTime: 0, + // A map from command names to the parameter buffer for that command. + // This buffer is a map from first parameter to the corresponding (optional) + // second parameter, to ensure automatic deduplication. + _commandBuffers: new Map(), + _handleCommandBuffer(aCommand) { + let buffer = this._commandBuffers.get(aCommand); + if (!buffer || !buffer.size) { + return; + } + // This short delay should usually not affect commands triggered by + // user action, but helps gather commands together which are sent + // by the prpl on connection (e.g. WHOIS sent in response to incoming + // WATCH results). + const kInterval = 1000; + let delay = kInterval - (Date.now() - this._lastCommandSendTime); + if (delay > 0) { + setTimeout(() => this._handleCommandBuffer(aCommand), delay); + return; + } + this._lastCommandSendTime = Date.now(); + + let getParams = aItems => { + // Taking the JOIN use case as an example, aItems is an array + // of [channel, key] pairs. + // To work around an inspircd bug (bug 1108596), we reorder + // the list so that entries with keys appear first. + let items = aItems.slice().sort(([c1, k1], [c2, k2]) => { + if (!k1 && k2) { + return 1; + } + if (k1 && !k2) { + return -1; + } + return 0; + }); + // To send the command, we have to group all the channels and keys + // together, i.e. grab the columns of this matrix, and build the two + // parameters of the command from that. + let channels = items.map(([channel, key]) => channel); + let keys = items.map(([channel, key]) => key).filter(key => !!key); + let params = [channels.join(",")]; + if (keys.length) { + params.push(keys.join(",")); + } + return params; + }; + let tooMany = aItems => { + let params = getParams(aItems); + let length = this.countBytes(this.buildMessage(aCommand, params)) + 2; + return this.maxMessageLength < length; + }; + let send = aItems => { + let params = getParams(aItems); + // Send the command, but don't log the keys. + this.sendMessage( + aCommand, + params, + aCommand + + " " + + params[0] + + (params.length > 1 ? " " : "") + ); + }; + + let items = []; + for (let item of buffer) { + items.push(item); + if (tooMany(items)) { + items.pop(); + send(items); + items = [item]; + } + } + send(items); + buffer.clear(); + }, + // For commands which allow an arbitrary number of parameters, we use a + // buffer to send as few commands as possible, by gathering the parameters. + // On servers which impose command penalties (e.g. inspircd) this helps + // avoid triggering fakelags by minimizing the command penalty. + // aParam is the first and aKey the optional second parameter of a command + // with the syntax *("," ) [ *("," )] + // While this code is mostly abstracted, it is currently assumed the second + // parameter is only used for JOIN. + sendBufferedCommand(aCommand, aParam, aKey = "") { + if (!this._commandBuffers.has(aCommand)) { + this._commandBuffers.set(aCommand, new Map()); + } + let buffer = this._commandBuffers.get(aCommand); + // If the buffer is empty, schedule sending the command, otherwise + // we just need to add the parameter to the buffer. + // We use executeSoon so as to not delay the sending of these + // commands when it is not necessary. + if (!buffer.size) { + executeSoon(() => this._handleCommandBuffer(aCommand)); + } + buffer.set(aParam, aKey); + }, + + // The whois information: nicks are used as keys and refer to a map of field + // to value. + whoisInformation: null, + // Request WHOIS information on a buddy when the user requests more + // information. If we already have some WHOIS information stored for this + // nick, a notification with this (potentially out-of-date) information + // is sent out immediately. It is followed by another notification when + // the current WHOIS data is returned by the server. + // If you are only interested in the current WHOIS, requestCurrentWhois + // should be used instead. + requestBuddyInfo(aBuddyName) { + if (!this.connected) { + return; + } + + // Return what we have stored immediately. + if (this.whoisInformation.has(aBuddyName)) { + this.notifyWhois(aBuddyName); + } + + // Request the current whois and update. + this.requestCurrentWhois(aBuddyName); + }, + // Request fresh WHOIS information on a nick. + requestCurrentWhois(aNick) { + if (!this.connected) { + return; + } + + this.removeBuddyInfo(aNick); + this.sendBufferedCommand("WHOIS", aNick); + }, + notifyWhois(aNick) { + Services.obs.notifyObservers( + new nsSimpleEnumerator(this.getBuddyInfo(aNick)), + "user-info-received", + this.normalizeNick(aNick) + ); + }, + // Request WHOWAS information on a buddy when the user requests more + // information. + requestOfflineBuddyInfo(aBuddyName) { + this.removeBuddyInfo(aBuddyName); + this.sendMessage("WHOWAS", aBuddyName); + }, + // Return an array of prplITooltipInfo for a given nick. + getBuddyInfo(aNick) { + if (!this.whoisInformation.has(aNick)) { + return []; + } + + let whoisInformation = this.whoisInformation.get(aNick); + if (whoisInformation.serverName && whoisInformation.serverInfo) { + whoisInformation.server = lazy._( + "tooltip.serverValue", + whoisInformation.serverName, + whoisInformation.serverInfo + ); + } + + // Sort the list of channels, ignoring the prefixes of channel and user. + let prefixes = this.userPrefixes.concat(this.channelPrefixes); + let sortWithoutPrefix = function (a, b) { + a = this.normalize(a, prefixes); + b = this.normalize(b, prefixes); + if (a < b) { + return -1; + } + return a > b ? 1 : 0; + }.bind(this); + let sortChannels = channels => + channels.trim().split(/\s+/).sort(sortWithoutPrefix).join(" "); + + // Convert booleans into a human-readable form. + let normalizeBool = aBool => lazy._(aBool ? "yes" : "no"); + + // Convert timespan in seconds into a human-readable form. + let normalizeTime = function (aTime) { + let valuesAndUnits = lazy.DownloadUtils.convertTimeUnits(aTime); + // If the time is exact to the first set of units, trim off + // the subsequent zeroes. + if (!valuesAndUnits[2]) { + valuesAndUnits.splice(2, 2); + } + return lazy._("tooltip.timespan", valuesAndUnits.join(" ")); + }; + + // List of the names of the info to actually show in the tooltip and + // optionally a transform function to apply to the value. Each field here + // maps to tooltip. in irc.properties. + // See the various RPL_WHOIS* results for the options. + const kFields = { + realname: null, + server: null, + connectedFrom: null, + registered: normalizeBool, + registeredAs: null, + secure: normalizeBool, + ircOp: normalizeBool, + bot: normalizeBool, + lastActivity: normalizeTime, + channels: sortChannels, + }; + + let tooltipInfo = []; + for (let field in kFields) { + if (whoisInformation.hasOwnProperty(field) && whoisInformation[field]) { + let value = whoisInformation[field]; + if (kFields[field]) { + value = kFields[field](value); + } + tooltipInfo.push(new TooltipInfo(lazy._("tooltip." + field), value)); + } + } + + const kSetIdleStatusAfterSeconds = 3600; + let statusType = Ci.imIStatusInfo.STATUS_AVAILABLE; + let statusText = ""; + if ("away" in whoisInformation) { + statusType = Ci.imIStatusInfo.STATUS_AWAY; + statusText = whoisInformation.away; + } else if ("offline" in whoisInformation) { + statusType = Ci.imIStatusInfo.STATUS_OFFLINE; + } else if ( + "lastActivity" in whoisInformation && + whoisInformation.lastActivity > kSetIdleStatusAfterSeconds + ) { + statusType = Ci.imIStatusInfo.STATUS_IDLE; + } + tooltipInfo.push( + new TooltipInfo(statusType, statusText, Ci.prplITooltipInfo.status) + ); + + return tooltipInfo; + }, + // Remove a WHOIS entry. + removeBuddyInfo(aNick) { + return this.whoisInformation.delete(aNick); + }, + // Copies the fields of aFields into the whois table. If the field already + // exists, that field is ignored (it is assumed that the first server response + // is the most up to date information, as is the case for 312/314). Note that + // the whois info for a nick is reset whenever whois information is requested, + // so the first response from each whois is recorded. + setWhois(aNick, aFields = {}) { + // If the nickname isn't in the list yet, add it. + if (!this.whoisInformation.has(aNick)) { + this.whoisInformation.set(aNick, {}); + } + + // Set non-normalized nickname field. + let whoisInfo = this.whoisInformation.get(aNick); + whoisInfo.nick = aNick; + + // Set the WHOIS fields, but only the first time a field is set. + for (let field in aFields) { + if (!whoisInfo.hasOwnProperty(field)) { + whoisInfo[field] = aFields[field]; + } + } + + return true; + }, + + trackBuddy(aNick) { + // Put the username as the first to be checked on the next ISON call. + this.trackQueue.unshift(aNick); + }, + untrackBuddy(aNick) { + let index = this.trackQueue.indexOf(aNick); + if (index < 0) { + this.ERROR( + "Trying to untrack a nick that was not being tracked: " + aNick + ); + return; + } + this.trackQueue.splice(index, 1); + }, + addBuddy(aTag, aName) { + let buddy = new ircAccountBuddy(this, null, aTag, aName); + this.buddies.set(buddy.normalizedName, buddy); + this.trackBuddy(buddy.userName); + + IMServices.contacts.accountBuddyAdded(buddy); + }, + removeBuddy(aBuddy) { + this.buddies.delete(aBuddy.normalizedName); + this.untrackBuddy(aBuddy.userName); + }, + // Loads a buddy from the local storage. Called for each buddy locally stored + // before connecting to the server. + loadBuddy(aBuddy, aTag) { + let buddy = new ircAccountBuddy(this, aBuddy, aTag); + this.buddies.set(buddy.normalizedName, buddy); + this.trackBuddy(buddy.userName); + + return buddy; + }, + changeBuddyNick(aOldNick, aNewNick) { + if (this.normalizeNick(aOldNick) == this.normalizeNick(this._nickname)) { + // Your nickname changed! + this._nickname = aNewNick; + this.conversations.forEach(conversation => { + // Update the nick for chats, and inform the user in every conversation. + if (conversation.isChat) { + conversation.updateNick(aOldNick, aNewNick, true); + } else { + conversation.writeMessage( + aOldNick, + lazy._conv("nickSet.you", aNewNick), + { + system: true, + } + ); + } + }); + } else { + this.conversations.forEach(conversation => { + if (conversation.isChat && conversation._participants.has(aOldNick)) { + // Update the nick in every chat conversation it is in. + conversation.updateNick(aOldNick, aNewNick, false); + } + }); + } + + // Adjust the whois table where necessary. + this.removeBuddyInfo(aOldNick); + this.setWhois(aNewNick); + + // If a private conversation is open with that user, change its title. + if (this.conversations.has(aOldNick)) { + // Get the current conversation and rename it. + let conversation = this.getConversation(aOldNick); + + // Remove the old reference to the conversation and create a new one. + this.removeConversation(aOldNick); + this.conversations.set(aNewNick, conversation); + + conversation.updateNick(aNewNick); + conversation.writeMessage( + aOldNick, + lazy._conv("nickSet", aOldNick, aNewNick), + { system: true } + ); + } + }, + + /* + * Ask the server to change the user's nick. + */ + changeNick(aNewNick) { + this._sentNickname = aNewNick; + this.sendMessage("NICK", aNewNick); // Nick message. + }, + /* + * Generate a new nick to change to if the user requested nick is already in + * use or is otherwise invalid. + * + * First try all the alternate nicks that were chosen by the user, and if none + * of them work, then generate a new nick by: + * 1. If there was not a digit at the end of the nick, append a 1. + * 2. If there was a digit, then increment the number. + * 3. Add leading 0s back on. + * 4. Ensure the nick is an appropriate length. + */ + tryNewNick(aOldNick) { + // Split the string on commas, remove whitespace around the nicks and + // remove empty nicks. + let allNicks = this.getString("alternateNicks") + .split(",") + .map(n => n.trim()) + .filter(n => !!n); + allNicks.unshift(this._accountNickname); + + // If the previously tried nick is in the array and not the last + // element, try the next nick in the array. + let oldIndex = allNicks.indexOf(aOldNick); + if (oldIndex != -1 && oldIndex < allNicks.length - 1) { + let newNick = allNicks[oldIndex + 1]; + this.LOG(aOldNick + " is already in use, trying " + newNick); + this.changeNick(newNick); + return true; + } + + // Separate the nick into the text and digits part. + let kNickPattern = /^(.+?)(\d*)$/; + let nickParts = kNickPattern.exec(aOldNick); + let newNick = nickParts[1]; + + // No nick found from the user's preferences, so just generating one. + // If there is not a digit at the end of the nick, just append 1. + let newDigits = "1"; + // If there is a digit at the end of the nick, increment it. + if (nickParts[2]) { + newDigits = (parseInt(nickParts[2], 10) + 1).toString(); + // If there are leading 0s, add them back on, after we've incremented (e.g. + // 009 --> 010). + let numLeadingZeros = nickParts[2].length - newDigits.length; + if (numLeadingZeros > 0) { + newDigits = "0".repeat(numLeadingZeros) + newDigits; + } + } + + // Servers truncate nicks that are too long, compare the previously sent + // nickname with the returned nickname and check for truncation. + if (aOldNick.length < this._sentNickname.length) { + // The nick will be too long, overwrite the end of the nick instead of + // appending. + let maxLength = aOldNick.length; + + let sentNickParts = kNickPattern.exec(this._sentNickname); + // Resend the same digits as last time, but overwrite part of the nick + // this time. + if (nickParts[2] && sentNickParts[2]) { + newDigits = sentNickParts[2]; + } + + // Handle the silly case of a single letter followed by all nines. + if (newDigits.length == this.maxNicknameLength) { + newDigits = newDigits.slice(1); + } + newNick = newNick.slice(0, maxLength - newDigits.length); + } + // Append the digits. + newNick += newDigits; + + if (this.normalize(newNick) == this.normalize(this._nickname)) { + // The nick we were about to try next is our current nick. This means + // the user attempted to change to a version of the nick with a lower or + // absent number suffix, and this failed. + let msg = lazy._("message.nick.fail", this._nickname); + this.conversations.forEach(conversation => + conversation.writeMessage(this._nickname, msg, { system: true }) + ); + return true; + } + + this.LOG(aOldNick + " is already in use, trying " + newNick); + this.changeNick(newNick); + return true; + }, + + handlePingReply(aSource, aPongTime) { + // Received PING response, display to the user. + let sentTime = new Date(parseInt(aPongTime, 10)); + + // The received timestamp is invalid. + if (isNaN(sentTime)) { + this.WARN( + aSource + " returned an invalid timestamp from a PING: " + aPongTime + ); + return false; + } + + // Find the delay in milliseconds. + let delay = Date.now() - sentTime; + + // If the delay is negative or greater than 1 minute, something is + // feeding us a crazy value. Don't display this to the user. + if (delay < 0 || 60 * 1000 < delay) { + this.WARN(aSource + " returned an invalid delay from a PING: " + delay); + return false; + } + + let msg = lazy.PluralForm.get( + delay, + lazy._("message.ping", aSource) + ).replace("#2", delay); + this.getConversation(aSource).writeMessage(aSource, msg, { system: true }); + return true; + }, + + countBytes(aStr) { + // Assume that if it's not UTF-8 then each character is 1 byte. + if (this._encoding != "UTF-8") { + return aStr.length; + } + + // Count the number of bytes in a UTF-8 encoded string. + function charCodeToByteCount(c) { + // UTF-8 stores: + // - code points below U+0080 are 1 byte, + // - code points below U+0800 are 2 bytes, + // - code points U+D800 through U+DFFF are UTF-16 surrogate halves + // (they indicate that JS has split a 4 bytes UTF-8 character + // in two halves of 2 bytes each), + // - other code points are 3 bytes. + if (c < 0x80) { + return 1; + } + if (c < 0x800 || (c >= 0xd800 && c <= 0xdfff)) { + return 2; + } + return 3; + } + let bytes = 0; + for (let i = 0; i < aStr.length; i++) { + bytes += charCodeToByteCount(aStr.charCodeAt(i)); + } + return bytes; + }, + + // To check if users are online, we need to queue multiple messages. + // An internal queue of all nicks that we wish to know the status of. + trackQueue: [], + // The nicks that were last sent to the server that we're waiting for a + // response about. + pendingIsOnQueue: [], + // The time between sending isOn messages (milliseconds). + _isOnDelay: 60 * 1000, + _isOnTimer: null, + // The number of characters that are available to be filled with nicks for + // each ISON message. + _isOnLength: null, + // Generate and send an ISON message to poll for each nick's status. + sendIsOn() { + // If no buddies, just look again after the timeout. + if (this.trackQueue.length) { + // Calculate the possible length of names we can send. + if (!this._isOnLength) { + let length = this.countBytes(this.buildMessage("ISON", " ")) + 2; + this._isOnLength = this.maxMessageLength - length + 1; + } + + // Always add the next nickname to the pending queue, this handles a silly + // case where the next nick is greater than or equal to the maximum + // message length. + this.pendingIsOnQueue = [this.trackQueue.shift()]; + + // Attempt to maximize the characters used in each message, this may mean + // that a specific user gets sent very often since they have a short name! + let buddiesLength = this.countBytes(this.pendingIsOnQueue[0]); + for (let i = 0; i < this.trackQueue.length; ++i) { + // If we can fit the nick, add it to the current buffer. + if ( + buddiesLength + this.countBytes(this.trackQueue[i]) < + this._isOnLength + ) { + // Remove the name from the list and add it to the pending queue. + let nick = this.trackQueue.splice(i--, 1)[0]; + this.pendingIsOnQueue.push(nick); + + // Keep track of the length of the string, the + 1 is for the spaces. + buddiesLength += this.countBytes(nick) + 1; + + // If we've filled up the message, stop looking for more nicks. + if (buddiesLength >= this._isOnLength) { + break; + } + } + } + + // Send the message. + this.sendMessage("ISON", this.pendingIsOnQueue.join(" ")); + + // Append the pending nicks so trackQueue contains all the nicks. + this.trackQueue = this.trackQueue.concat(this.pendingIsOnQueue); + } + + // Call this function again in _isOnDelay seconds. + // This makes the assumption that this._isOnDelay >> the response to ISON + // from the server. + this._isOnTimer = setTimeout(this.sendIsOn.bind(this), this._isOnDelay); + }, + + // The message of the day uses two fields to append messages. + _motd: null, + _motdTimer: null, + + connect() { + this.reportConnecting(); + + // Mark existing MUCs as joining if they will be rejoined. + this.conversations.forEach(conversation => { + if (conversation.isChat && conversation.chatRoomFields) { + conversation.joining = true; + } + }); + + // Load preferences. + this._port = this.getInt("port"); + this._ssl = this.getBool("ssl"); + + // Use the display name as the user's real name. + this._realname = this.imAccount.statusInfo.displayName; + this._encoding = this.getString("encoding") || "UTF-8"; + this._showServerTab = this.getBool("showServerTab"); + + // Open the socket connection. + this._socket = new ircSocket(this); + this._socket.connect(this._server, this._port, this._ssl ? ["ssl"] : []); + }, + + // Functions for keeping track of whether the Client Capabilities is done. + // If a cap is to be handled, it should be registered with addCAP, where aCAP + // is a "unique" string defining what is being handled. When the cap is done + // being handled removeCAP should be called with the same string. + _availableCAPs: new Set(), + _activeCAPs: new Set(), + _requestedCAPs: new Set(), + _negotiatedCAPs: false, + _queuedCAPs: [], + addCAP(aCAP) { + if (this.connected) { + this.ERROR("Trying to add CAP " + aCAP + " after connection."); + return; + } + + this._requestedCAPs.add(aCAP); + }, + removeCAP(aDoneCAP) { + if (!this._requestedCAPs.has(aDoneCAP)) { + this.ERROR( + "Trying to remove a CAP (" + aDoneCAP + ") which isn't added." + ); + return; + } + if (this.connected) { + this.ERROR("Trying to remove CAP " + aDoneCAP + " after connection."); + return; + } + + // Remove any reference to the given capability. + this._requestedCAPs.delete(aDoneCAP); + + // However only notify the server the first time during cap negotiation, not + // when the server exposes a new cap. + if (!this._requestedCAPs.size && !this._negotiatedCAPs) { + this.sendMessage("CAP", "END"); + this._negotiatedCAPs = true; + } + }, + + // Used to wait for a response from the server. + _quitTimer: null, + // RFC 2812 Section 3.1.7. + quit(aMessage) { + this._reportDisconnecting(Ci.prplIAccount.NO_ERROR); + this.sendMessage( + "QUIT", + aMessage || this.getString("quitmsg") || undefined + ); + }, + // When the user clicks "Disconnect" in account manager, or uses /quit. + // aMessage is an optional parameter containing the quit message. + disconnect(aMessage) { + if (this.disconnected || this.disconnecting) { + return; + } + + // If there's no socket, disconnect immediately to avoid waiting 2 seconds. + if (!this._socket || this._socket.disconnected) { + this.gotDisconnected(); + return; + } + + // Let the server know we're going to disconnect. + this.quit(aMessage); + + // Reset original nickname for the next reconnect. + this._requestedNickname = this._accountNickname; + + // Give the server 2 seconds to respond, otherwise just forcefully + // disconnect the socket. This will be cancelled if a response is heard from + // the server. + this._quitTimer = setTimeout(this.gotDisconnected.bind(this), 2 * 1000); + }, + + createConversation(aName) { + return this.getConversation(aName); + }, + + // aComponents implements prplIChatRoomFieldValues. + joinChat(aComponents) { + let channel = aComponents.getValue("channel"); + // Mildly sanitize input. + channel = channel.trimLeft().split(",")[0].split(" ")[0]; + if (!channel) { + this.ERROR("joinChat called without a valid channel name."); + return null; + } + + // A channel prefix is required. If the user didn't include one, + // we prepend # automatically to match the behavior of other + // clients. Not doing it used to cause user confusion. + if (!this.channelPrefixes.includes(channel[0])) { + channel = "#" + channel; + } + + if (this.conversations.has(channel)) { + let conv = this.getConversation(channel); + if (!conv.left) { + // No need to join a channel we are already in. + return conv; + } else if (!conv.chatRoomFields) { + // We are rejoining a channel that was parted by the user. + conv._rejoined = true; + } + } + + let key = aComponents.getValue("password"); + this.sendBufferedCommand("JOIN", channel, key); + + // Open conversation early for better responsiveness. + let conv = this.getConversation(channel); + conv.joining = true; + + // Store the prplIChatRoomFieldValues to enable later reconnections. + let defaultName = key ? channel + " " + key : channel; + conv.chatRoomFields = this.getChatRoomDefaultFieldValues(defaultName); + + return conv; + }, + + chatRoomFields: { + channel: { + get label() { + return lazy._("joinChat.channel"); + }, + required: true, + }, + password: { + get label() { + return lazy._("joinChat.password"); + }, + isPassword: true, + }, + }, + + parseDefaultChatName(aDefaultName) { + let params = aDefaultName.trim().split(/\s+/); + let chatFields = { channel: params[0] }; + if (params.length > 1) { + chatFields.password = params[1]; + } + return chatFields; + }, + + // Attributes + get canJoinChat() { + return true; + }, + + // Returns a conversation (creates it if it doesn't exist) + getConversation(aName) { + if (!this.conversations.has(aName)) { + // If the whois information has been received, we have the proper nick + // capitalization. + if (this.whoisInformation.has(aName)) { + aName = this.whoisInformation.get(aName).nick; + } + let convClass = this.isMUCName(aName) ? ircChannel : ircConversation; + this.conversations.set(aName, new convClass(this, aName, this._nickname)); + } + return this.conversations.get(aName); + }, + + removeConversation(aConversationName) { + if (this.conversations.has(aConversationName)) { + this.conversations.delete(aConversationName); + } + }, + + // This builds the message string that will be sent to the server. + buildMessage(aCommand, aParams = []) { + if (!aCommand) { + this.ERROR("IRC messages must have a command."); + return null; + } + + // Ensure a command is only characters or numbers. + if (!/^[A-Z0-9]+$/i.test(aCommand)) { + this.ERROR("IRC command invalid: " + aCommand); + return null; + } + + let message = aCommand; + // If aParams is not an array, consider it to be a single parameter and put + // it into an array. + let params = Array.isArray(aParams) ? aParams : [aParams]; + if (params.length) { + if (params.slice(0, -1).some(p => p.includes(" "))) { + this.ERROR("IRC parameters cannot have spaces: " + params.slice(0, -1)); + return null; + } + // Join the parameters with spaces. There are three cases in which the + // last parameter ("trailing" in RFC 2812) must be prepended with a colon: + // 1. If the last parameter contains a space. + // 2. If the first character of the last parameter is a colon. + // 3. If the last parameter is an empty string. + let trailing = params.slice(-1)[0]; + if ( + !trailing.length || + trailing.includes(" ") || + trailing.startsWith(":") + ) { + params.push(":" + params.pop()); + } + message += " " + params.join(" "); + } + + return message; + }, + + // Shortcut method to build & send a message at once. Use aLoggedData to log + // something different than what is actually sent. + // Returns false if the message could not be sent. + sendMessage(aCommand, aParams, aLoggedData) { + return this.sendRawMessage( + this.buildMessage(aCommand, aParams), + aLoggedData + ); + }, + + // This sends a message over the socket and catches any errors. Use + // aLoggedData to log something different than what is actually sent. + // Returns false if the message could not be sent. + sendRawMessage(aMessage, aLoggedData) { + // Low level quoting, replace \0, \n, \r or \020 with \0200, \020n, \020r or + // \020\020, respectively. + const lowQuote = { "\0": "0", "\n": "n", "\r": "r", "\x10": "\x10" }; + const lowRegex = new RegExp( + "[" + Object.keys(lowQuote).join("") + "]", + "g" + ); + aMessage = aMessage.replace(lowRegex, aChar => "\x10" + lowQuote[aChar]); + + if (!this._socket || this._socket.disconnected) { + this.gotDisconnected( + Ci.prplIAccount.ERROR_NETWORK_ERROR, + lazy._("connection.error.lost") + ); + } + + let length = this.countBytes(aMessage) + 2; + if (length > this.maxMessageLength) { + // Log if the message is too long, but try to send it anyway. + this.WARN( + "Message length too long (" + + length + + " > " + + this.maxMessageLength + + "\n" + + aMessage + ); + } + + aMessage += "\r\n"; + + try { + this._socket.sendString(aMessage, this._encoding, aLoggedData); + return true; + } catch (e) { + try { + this._socket.sendData(aMessage, aLoggedData); + this.WARN( + "Failed to convert " + + aMessage + + " from Unicode to " + + this._encoding + + "." + ); + return true; + } catch (e) { + this.ERROR("Socket error:", e); + this.gotDisconnected( + Ci.prplIAccount.ERROR_NETWORK_ERROR, + lazy._("connection.error.lost") + ); + return false; + } + } + }, + + // CTCP messages are \001 []*\001. + // Returns false if the message could not be sent. + sendCTCPMessage(aTarget, aIsNotice, aCtcpCommand, aParams = []) { + // Combine the CTCP command and parameters into the single IRC param. + let ircParam = aCtcpCommand; + // If aParams is not an array, consider it to be a single parameter and put + // it into an array. + let params = Array.isArray(aParams) ? aParams : [aParams]; + if (params.length) { + ircParam += " " + params.join(" "); + } + + // High/CTCP level quoting, replace \134 or \001 with \134\134 or \134a, + // respectively. This is only done inside the extended data message. + // eslint-disable-next-line no-control-regex + const highRegex = /\\|\x01/g; + ircParam = ircParam.replace( + highRegex, + aChar => "\\" + (aChar == "\\" ? "\\" : "a") + ); + + // Add the CTCP tagging. + ircParam = "\x01" + ircParam + "\x01"; + + // Send the IRC message as a NOTICE or PRIVMSG. + return this.sendMessage(aIsNotice ? "NOTICE" : "PRIVMSG", [ + aTarget, + ircParam, + ]); + }, + + // Implement section 3.1 of RFC 2812 + _connectionRegistration() { + // Send the Client Capabilities list command version 3.2. + this.sendMessage("CAP", ["LS", "302"]); + + if (this.prefs.prefHasUserValue("serverPassword")) { + this.sendMessage( + "PASS", + this.getString("serverPassword"), + "PASS " + ); + } + + // Send the nick message (section 3.1.2). + this.changeNick(this._requestedNickname); + + // Send the user message (section 3.1.3). + this.sendMessage("USER", [ + this.username, + this._mode.toString(), + "*", + this._realname || this._requestedNickname, + ]); + }, + + _reportDisconnecting(aErrorReason, aErrorMessage) { + this.reportDisconnecting(aErrorReason, aErrorMessage); + + // Cancel any pending buffered commands. + this._commandBuffers.clear(); + + // Mark all contacts on the account as having an unknown status. + this.buddies.forEach(aBuddy => + aBuddy.setStatus(Ci.imIStatusInfo.STATUS_UNKNOWN, "") + ); + }, + + gotDisconnected(aError = Ci.prplIAccount.NO_ERROR, aErrorMessage = "") { + if (!this.imAccount || this.disconnected) { + return; + } + + // If we are already disconnecting, this call to gotDisconnected + // is when the server acknowledges our disconnection. + // Otherwise it's because we lost the connection. + if (!this.disconnecting) { + this._reportDisconnecting(aError, aErrorMessage); + } + this._socket.disconnect(); + delete this._socket; + + // Reset cap negotiation. + this._availableCAPs.clear(); + this._activeCAPs.clear(); + this._requestedCAPs.clear(); + this._negotiatedCAPs = false; + this._queuedCAPs.length = 0; + + clearTimeout(this._isOnTimer); + delete this._isOnTimer; + + // No need to call gotDisconnected a second time. + clearTimeout(this._quitTimer); + delete this._quitTimer; + + // MOTD will be resent. + delete this._motd; + clearTimeout(this._motdTimer); + delete this._motdTimer; + + // We must authenticate if we reconnect. + delete this.isAuthenticated; + + // Clear any pending attempt to regain our nick. + clearTimeout(this._nickInUseTimeout); + delete this._nickInUseTimeout; + + // Clean up each conversation: mark as left and remove participant. + this.conversations.forEach(conversation => { + if (conversation.isChat) { + conversation.joining = false; // In case we never finished joining. + if (!conversation.left) { + // Remove the user's nick and mark the conversation as left as that's + // the final known state of the room. + conversation.removeParticipant(this._nickname); + conversation.left = true; + } + } + }); + + // If we disconnected during a pending LIST request, make sure callbacks + // receive any remaining channels. + if (this._pendingList) { + this._sendRemainingRoomInfo(); + } + + // Clear whois table. + this.whoisInformation.clear(); + + this.reportDisconnected(); + }, + + remove() { + this.conversations.forEach(conv => conv.close()); + delete this.conversations; + this.buddies.forEach(aBuddy => aBuddy.remove()); + delete this.buddies; + }, + + unInit() { + // Disconnect if we're online while this gets called. + if (this._socket) { + if (!this.disconnecting) { + this.quit(); + } + this._socket.disconnect(); + } + delete this.imAccount; + clearTimeout(this._isOnTimer); + clearTimeout(this._quitTimer); + }, +}; diff --git a/comm/chat/protocols/irc/ircBase.sys.mjs b/comm/chat/protocols/irc/ircBase.sys.mjs new file mode 100644 index 0000000000..9127dd4e24 --- /dev/null +++ b/comm/chat/protocols/irc/ircBase.sys.mjs @@ -0,0 +1,1768 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This contains the implementation for the basic Internet Relay Chat (IRC) + * protocol covered by RFCs 2810, 2811, 2812 and 2813 (which obsoletes RFC + * 1459). RFC 2812 covers the client commands and protocol. + * RFC 2810: Internet Relay Chat: Architecture + * http://tools.ietf.org/html/rfc2810 + * RFC 2811: Internet Relay Chat: Channel Management + * http://tools.ietf.org/html/rfc2811 + * RFC 2812: Internet Relay Chat: Client Protocol + * http://tools.ietf.org/html/rfc2812 + * RFC 2813: Internet Relay Chat: Server Protocol + * http://tools.ietf.org/html/rfc2813 + * RFC 1459: Internet Relay Chat Protocol + * http://tools.ietf.org/html/rfc1459 + */ +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { + l10nHelper, + nsSimpleEnumerator, +} from "resource:///modules/imXPCOMUtils.sys.mjs"; +import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs"; +import { ircHandlerPriorities } from "resource:///modules/ircHandlerPriorities.sys.mjs"; +import { + ctcpFormatToText, + conversationErrorMessage, + displayMessage, + kListRefreshInterval, +} from "resource:///modules/ircUtils.sys.mjs"; + +const lazy = {}; +XPCOMUtils.defineLazyGetter(lazy, "_", () => + l10nHelper("chrome://chat/locale/irc.properties") +); + +// Display the message and remove them from the rooms they're in. +function leftRoom(aAccount, aNicks, aChannels, aSource, aReason, aKicked) { + let msgId = "message." + (aKicked ? "kicked" : "parted"); + // If a part message was included, include it. + let reason = aReason ? lazy._(msgId + ".reason", aReason) : ""; + function __(aNick, aYou) { + // If the user is kicked, we need to say who kicked them. + let msgId2 = msgId + (aYou ? ".you" : ""); + if (aKicked) { + if (aYou) { + return lazy._(msgId2, aSource, reason); + } + return lazy._(msgId2, aNick, aSource, reason); + } + if (aYou) { + return lazy._(msgId2, reason); + } + return lazy._(msgId2, aNick, reason); + } + + for (let channelName of aChannels) { + if (!aAccount.conversations.has(channelName)) { + // Handle when we closed the window. + continue; + } + let conversation = aAccount.getConversation(channelName); + for (let nick of aNicks) { + let msg; + if (aAccount.normalize(nick) == aAccount.normalize(aAccount._nickname)) { + msg = __(nick, true); + // If the user left, mark the conversation as no longer being active. + conversation.left = true; + } else { + msg = __(nick); + } + + conversation.writeMessage(aSource, msg, { system: true }); + conversation.removeParticipant(nick); + } + } + return true; +} + +function writeMessage(aAccount, aMessage, aString, aType) { + let type = {}; + type[aType] = true; + type.tags = aMessage.tags; + aAccount + .getConversation(aMessage.origin) + .writeMessage(aMessage.origin, aString, type); + return true; +} + +// If aNoLastParam is true, the last parameter is not printed out. +function serverMessage(aAccount, aMsg, aNoLastParam) { + // If we don't want to show messages from the server, just mark it as handled. + if (!aAccount._showServerTab) { + return true; + } + + return writeMessage( + aAccount, + aMsg, + aMsg.params.slice(1, aNoLastParam ? -1 : undefined).join(" "), + "system" + ); +} + +function serverErrorMessage(aAccount, aMessage, aError) { + // If we don't want to show messages from the server, just mark it as handled. + if (!aAccount._showServerTab) { + return true; + } + + return writeMessage(aAccount, aMessage, aError, "error"); +} + +function addMotd(aAccount, aMessage) { + // If there is no current MOTD to append to, start a new one. + if (!aAccount._motd) { + aAccount._motd = []; + } + + // Traditionally, MOTD messages start with "- ", but this is not always + // true, try to handle that sanely. + let message = aMessage.params[1]; + if (message.startsWith("-")) { + message = message.slice(1).trim(); + } + // And traditionally, the initial message ends in " -", remove that. + if (message.endsWith("-")) { + message = message.slice(0, -1).trim(); + } + + // Actually add the message (if it still exists). + if (message) { + aAccount._motd.push(message); + } + + // Oh, also some servers don't send a RPL_ENDOFMOTD (e.g. irc.ppy.sh), so if + // we don't receive another MOTD message after 1 second, consider it to be + // RPL_ENDOFMOTD. + clearTimeout(aAccount._motdTimer); + aAccount._motdTimer = setTimeout( + ircBase.commands["376"].bind(aAccount), + 1000, + aMessage + ); + + return true; +} + +// See RFCs 2811 & 2812 (which obsoletes RFC 1459) for a description of these +// commands. +export var ircBase = { + // Parameters + name: "RFC 2812", // Name identifier + priority: ircHandlerPriorities.DEFAULT_PRIORITY, + isEnabled: () => true, + + // The IRC commands that can be handled. + commands: { + ERROR(aMessage) { + // ERROR + // Client connection has been terminated. + if (!this.disconnecting) { + // We received an ERROR message when we weren't expecting it, this is + // probably the server giving us a ping timeout. + this.WARN("Received unexpected ERROR response:\n" + aMessage.params[0]); + this.gotDisconnected( + Ci.prplIAccount.ERROR_NETWORK_ERROR, + lazy._("connection.error.lost") + ); + } else { + // We received an ERROR message when expecting it (i.e. we've sent a + // QUIT command). Notify account manager. + this.gotDisconnected(); + } + return true; + }, + INVITE(aMessage) { + // INVITE + let channel = aMessage.params[1]; + this.addChatRequest( + channel, + () => { + this.joinChat(this.getChatRoomDefaultFieldValues(channel)); + }, + request => { + // Inform the user when an invitation was automatically ignored. + if (!request) { + // Otherwise just notify the user. + this.getConversation(channel).writeMessage( + aMessage.origin, + lazy._("message.inviteReceived", aMessage.origin, channel), + { system: true } + ); + } + } + ); + return true; + }, + JOIN(aMessage) { + // JOIN ( *( "," ) [ *( "," ) ] ) / "0" + // Iterate over each channel. + for (let channelName of aMessage.params[0].split(",")) { + let conversation = this.getConversation(channelName); + + // Check whether we joined the channel or if someone else did. + if ( + this.normalize(aMessage.origin, this.userPrefixes) == + this.normalize(this._nickname) + ) { + // If we join, clear the participants list to avoid errors with + // repeated participants. + conversation.removeAllParticipants(); + conversation.left = false; + conversation.joining = false; + + // Update the channel name if it has improper capitalization. + if (channelName != conversation.name) { + conversation._name = channelName; + conversation.notifyObservers(null, "update-conv-title"); + } + + // If the user parted from this room earlier, confirm the rejoin. + if (conversation._rejoined) { + conversation.writeMessage( + aMessage.origin, + lazy._("message.rejoined"), + { + system: true, + } + ); + delete conversation._rejoined; + } + + // Ensure chatRoomFields information is available for reconnection. + if (!conversation.chatRoomFields) { + this.WARN( + "Opening a MUC without storing its " + + "prplIChatRoomFieldValues first." + ); + conversation.chatRoomFields = + this.getChatRoomDefaultFieldValues(channelName); + } + } else { + // Don't worry about adding ourself, RPL_NAMREPLY takes care of that + // case. + conversation.getParticipant(aMessage.origin, true); + let msg = lazy._("message.join", aMessage.origin, aMessage.source); + conversation.writeMessage(aMessage.origin, msg, { + system: true, + noLinkification: true, + }); + } + } + // If the joiner is a buddy, mark as online. + let buddy = this.buddies.get(aMessage.origin); + if (buddy) { + buddy.setStatus(Ci.imIStatusInfo.STATUS_AVAILABLE, ""); + } + return true; + }, + KICK(aMessage) { + // KICK *( "," ) *( "," ) [] + let comment = aMessage.params.length == 3 ? aMessage.params[2] : null; + // Some servers (moznet) send the kicker as the comment. + if (comment == aMessage.origin) { + comment = null; + } + return leftRoom( + this, + aMessage.params[1].split(","), + aMessage.params[0].split(","), + aMessage.origin, + comment, + true + ); + }, + MODE(aMessage) { + // MODE *( ( "+" / "-") *( "i" / "w" / "o" / "O" / "r" ) ) + // MODE *( ( "-" / "+" ) * * ) + if (this.isMUCName(aMessage.params[0])) { + // If the first parameter is a channel name, a channel/participant mode + // was updated. + this.getConversation(aMessage.params[0]).setMode( + aMessage.params[1], + aMessage.params.slice(2), + aMessage.origin + ); + + return true; + } + + // Otherwise the user's own mode is being returned to them. + return this.setUserMode( + aMessage.params[0], + aMessage.params[1], + aMessage.origin, + !this._userModeReceived + ); + }, + NICK(aMessage) { + // NICK + this.changeBuddyNick(aMessage.origin, aMessage.params[0]); + return true; + }, + NOTICE(aMessage) { + // NOTICE + // If the message is from the server, don't show it unless the user wants + // to see it. + if (!this.connected || aMessage.origin == this._currentServerName) { + return serverMessage(this, aMessage); + } + return displayMessage(this, aMessage, { notification: true }); + }, + PART(aMessage) { + // PART *( "," ) [ ] + return leftRoom( + this, + [aMessage.origin], + aMessage.params[0].split(","), + aMessage.source, + aMessage.params.length == 2 ? aMessage.params[1] : null + ); + }, + PING(aMessage) { + // PING [ ] + // Keep the connection alive. + this.sendMessage("PONG", aMessage.params[0]); + return true; + }, + PONG(aMessage) { + // PONG [ ] + let pongTime = aMessage.params[1]; + + // Ping to keep the connection alive. + if (pongTime.startsWith("_")) { + this._socket.cancelDisconnectTimer(); + return true; + } + // Otherwise, the ping was from a user command. + return this.handlePingReply(aMessage.origin, pongTime); + }, + PRIVMSG(aMessage) { + // PRIVMSG + // Display message in conversation + return displayMessage(this, aMessage); + }, + QUIT(aMessage) { + // QUIT [ < Quit Message> ] + // Some IRC servers automatically prefix a "Quit: " string. Remove the + // duplication and use a localized version. + let quitMsg = aMessage.params[0] || ""; + if (quitMsg.startsWith("Quit: ")) { + quitMsg = quitMsg.slice(6); // "Quit: ".length + } + // If a quit message was included, show it. + let nick = aMessage.origin; + let msg = lazy._( + "message.quit", + nick, + quitMsg.length ? lazy._("message.quit2", quitMsg) : "" + ); + // Loop over every conversation with the user and display that they quit. + this.conversations.forEach(conversation => { + if (conversation.isChat && conversation._participants.has(nick)) { + conversation.writeMessage(nick, msg, { system: true }); + conversation.removeParticipant(nick); + } + }); + + // Remove from the whois table. + this.removeBuddyInfo(nick); + + // If the leaver is a buddy, mark as offline. + let buddy = this.buddies.get(nick); + if (buddy) { + buddy.setStatus(Ci.imIStatusInfo.STATUS_OFFLINE, ""); + } + + // If we wanted this nickname, grab it. + if (nick == this._requestedNickname && nick != this._nickname) { + this.changeNick(this._requestedNickname); + clearTimeout(this._nickInUseTimeout); + delete this._nickInUseTimeout; + } + return true; + }, + SQUIT(aMessage) { + // + return true; + }, + TOPIC(aMessage) { + // TOPIC [ ] + // Show topic as a message. + let conversation = this.getConversation(aMessage.params[0]); + let topic = aMessage.params[1]; + // Set the topic in the conversation and update the UI. + conversation.setTopic( + topic ? ctcpFormatToText(topic) : "", + aMessage.origin + ); + return true; + }, + "001": function (aMessage) { + // RPL_WELCOME + // Welcome to the Internet Relay Network !@ + this._socket.resetPingTimer(); + // This seems a little strange, but we don't differentiate between a + // nickname and the servername since it can be ambiguous. + this._currentServerName = aMessage.origin; + + // Clear user mode. + this._modes = new Set(); + this._userModeReceived = false; + + // Check if autoUserMode is set in the account preferences. If it is set, + // then notify the server that the user wants a specific mode. + if (this.prefs.prefHasUserValue("autoUserMode")) { + this.sendMessage("MODE", [ + this._nickname, + this.getString("autoUserMode"), + ]); + } + + // Check if our nick has changed. + if (aMessage.params[0] != this._nickname) { + this.changeBuddyNick(this._nickname, aMessage.params[0]); + } + + // Request our own whois entry so we can set the prefix. + this.requestCurrentWhois(this._nickname); + + // If our status is Unavailable, tell the server. + if ( + this.imAccount.statusInfo.statusType < Ci.imIStatusInfo.STATUS_AVAILABLE + ) { + this.observe(null, "status-changed"); + } + + // Check if any of our buddies are online! + const kInitialIsOnDelay = 1000; + this._isOnTimer = setTimeout(this.sendIsOn.bind(this), kInitialIsOnDelay); + + // If we didn't handle all the CAPs we added, something is wrong. + if (this._requestedCAPs.size) { + this.ERROR( + "Connected without removing CAPs: " + [...this._requestedCAPs] + ); + } + + // Done! + this.reportConnected(); + return serverMessage(this, aMessage); + }, + "002": function (aMessage) { + // RPL_YOURHOST + // Your host is , running version + return serverMessage(this, aMessage); + }, + "003": function (aMessage) { + // RPL_CREATED + // This server was created + // TODO parse this date and keep it for some reason? Do we care? + return serverMessage(this, aMessage); + }, + "004": function (aMessage) { + // RPL_MYINFO + // + // TODO parse the available modes, let the UI respond and inform the user + return serverMessage(this, aMessage); + }, + "005": function (aMessage) { + // RPL_BOUNCE + // Try server , port + return serverMessage(this, aMessage); + }, + + /* + * Handle response to TRACE message + */ + 200(aMessage) { + // RPL_TRACELINK + // Link + // V + // + return serverMessage(this, aMessage); + }, + 201(aMessage) { + // RPL_TRACECONNECTING + // Try. + return serverMessage(this, aMessage); + }, + 202(aMessage) { + // RPL_TRACEHANDSHAKE + // H.S. + return serverMessage(this, aMessage); + }, + 203(aMessage) { + // RPL_TRACEUNKNOWN + // ???? [] + return serverMessage(this, aMessage); + }, + 204(aMessage) { + // RPL_TRACEOPERATOR + // Oper + return serverMessage(this, aMessage); + }, + 205(aMessage) { + // RPL_TRACEUSER + // User + return serverMessage(this, aMessage); + }, + 206(aMessage) { + // RPL_TRACESERVER + // Serv S C @ + // V + return serverMessage(this, aMessage); + }, + 207(aMessage) { + // RPL_TRACESERVICE + // Service + return serverMessage(this, aMessage); + }, + 208(aMessage) { + // RPL_TRACENEWTYPE + // 0 + return serverMessage(this, aMessage); + }, + 209(aMessage) { + // RPL_TRACECLASS + // Class + return serverMessage(this, aMessage); + }, + 210(aMessage) { + // RPL_TRACERECONNECTION + // Unused. + return serverMessage(this, aMessage); + }, + + /* + * Handle stats messages. + **/ + 211(aMessage) { + // RPL_STATSLINKINFO + // + //