Compare commits

...

310 Commits

Author SHA1 Message Date
c61ddeb338 chore(xep): Move FAST from staging into xep_0484.dart 2024-11-17 20:24:03 +01:00
e2515e25e4 fix(tests): Fix component integration test 2024-11-16 17:59:11 +01:00
09a849c6eb fix(xep): Fix failure with multiple SCRAM negotiators and SASL2 2024-11-16 17:57:29 +01:00
9eb94e5f48 fix(xep): Fix crash when the device list node is empty 2024-10-19 21:45:18 +02:00
db77790bf4 fix(meta): Fix version conflicts with moxxy 2024-09-29 18:46:04 +02:00
7ceee48d31 fix(core): Bump omemo_dart (and everything else) 2024-09-29 18:39:49 +02:00
941c3e4fd8 test(xep): Test SCRAM-SHA-1 with SASL2 2024-08-12 23:05:38 +02:00
365ff2f238 fix(xep): Fix replies in the context of Gajim 2024-06-16 14:56:07 +02:00
b3c8a6cd2f fix(docs): Update link to moxxmpp documentation 2024-04-27 00:22:45 +02:00
d4166d087e fix(core): An empty iq is okay with roster versioning
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-25 21:03:04 +02:00
ddf781daff fix(core): Remove erroneous override
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-05 20:55:41 +02:00
5973076b89 fix(core): Remove overrides file 2023-10-05 20:54:47 +02:00
72cb76d1f6 Merge pull request 'Various improvements and fixes' (#49) from fix/stanza-ordering into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/49
2023-10-05 18:50:32 +00:00
be7581e841 fix(core): Remove empty file 2023-10-04 22:42:36 +02:00
8a2435e4ad fix(example): Bump moxxmpp* versions to 0.4.0 2023-10-04 22:35:00 +02:00
97f082b6f5 feat(example): Add a very simple client example 2023-10-04 22:15:59 +02:00
f287d501ab fix(example): Comform examples to the new TCPSocketWrapper constructor 2023-10-04 22:15:24 +02:00
93e9d6ca22 feat(xep): Handle a user changing their nickname 2023-10-01 20:44:47 +02:00
007cdce53d fix(xep): Fix wrong event being triggered on join 2023-10-01 13:34:13 +02:00
6d3a5e98de fix(xep): Export XEP-0045 events 2023-10-01 13:33:58 +02:00
e97d6e6517 feat(xep): Track our own affiliation and role 2023-09-29 22:45:56 +02:00
882d20dc7a feat(core): Bump moxxmpp_socket_tcp's version 2023-09-29 21:19:28 +02:00
1f712151e4 feat(xep): Ignore the ack timer if we are receiving data 2023-09-29 21:18:43 +02:00
e7922668b1 feat(core): Add a callback for raw data events 2023-09-29 21:13:57 +02:00
87866bf3f5 fix(core): Allow disabling logging of all incoming and outgoing data 2023-09-29 20:53:29 +02:00
41b789fa28 feat(core): Stop an exception in a handler to deadlock the connection 2023-09-29 20:50:03 +02:00
0a68f09fb4 fix(style): Fix style issues 2023-09-29 20:46:14 +02:00
edf1d0b257 feat(core): Replace custom class with a record type 2023-09-29 20:33:56 +02:00
59b90307c2 fix(core): Remove the negotiation lock 2023-09-29 20:29:25 +02:00
49d3c6411b fix(tests): Fix tests 2023-09-29 20:24:58 +02:00
3a94dd9634 feat(core): Log handler executions 2023-09-29 20:01:09 +02:00
fb4b4c71e2 fix(core): Remove async_queue 2023-09-29 19:59:38 +02:00
d9fbb9e102 fix(xep,core): Ensure in-order processing of incoming stanzas 2023-09-29 19:58:43 +02:00
aba90f2e90 feat(example): Print the number of users in the MUC 2023-09-27 18:58:17 +02:00
9211963390 fix(xep): Fix ending presence processing too early if containing a photo 2023-09-27 18:57:42 +02:00
c7d58c3d3f feat(core): Add logging for when a manager ends processing early 2023-09-27 18:57:13 +02:00
6dbbf08be4 feat(xep): Add an event for when someone leaves the MUC
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-26 23:42:49 +02:00
7ca648c478 feat(xep): Add MUC events
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-26 23:34:53 +02:00
814f99436b fix(xep): Do not automatically request vCards
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-26 14:16:41 +02:00
5bd2466c54 feat(xep): Parse the room info from the extended disco info
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-26 14:00:27 +02:00
14b62cef96 fix(xep): Fix crash for messages with no id 2023-09-26 13:59:51 +02:00
c3088f9046 fix(xep): Make leaving the room non-awaitable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-25 21:08:24 +02:00
64b93b536e fix(xep): Add missing metadata
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-24 18:44:23 +02:00
c1c48d0a83 feat(xep): Provide an implementation of XEP-0392 2023-09-24 18:43:06 +02:00
4a681b9483 fix(xep): Somehow fix reconnection issues
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-23 22:19:06 +02:00
c504afc944 fix(xep): Fix joining a MUC making it stuck
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-23 14:14:23 +02:00
76a9f7be7a feat(xep): Allow adding MUCs to join later
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-22 21:43:56 +02:00
afa3927720 feat(xep): Rejoin groupchats on a new stream 2023-09-22 21:22:18 +02:00
5f36289f50 fix(all): Fix linter warnings 2023-09-22 20:57:05 +02:00
fbe3b90200 feat(xep): Implement ignoring the message reflection 2023-09-22 20:42:45 +02:00
d7c13abde6 feat(xep): Allow ignoring the discussion history
Also allow specifying the amount of stanzas of discussion history we
want.
2023-09-22 19:23:35 +02:00
d4416c8a47 fix(xep): Fix null-deference
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-17 20:19:50 +02:00
9666557655 feat(xep): Make avatar queries more explicit
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-17 20:10:53 +02:00
1625f912b0 chore(docs): Fix moxxmpp docs
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-04 21:11:00 +02:00
864cc0e747 feat(meta): Remove omemo_dart override
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-22 20:21:16 +02:00
c9e817054d fix(ci): Fix moxxmpp test pipeline
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-08-22 20:07:50 +02:00
d57bf2ef80 fix(ci): Use the pubcached pub.dev cache
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-08-22 20:02:43 +02:00
8bfdd5e54a fix(ci): Remove event restriction
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-08-19 22:49:16 +02:00
e58082bf38 feat(ci): Notify on build failure
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
2023-08-19 22:47:29 +02:00
dbb945b424 fix(docs): Remove duplicate <programming-language /> 2023-08-10 15:00:11 +02:00
2431eafa6c fix(docs): Fix broken DOAP 2023-08-10 14:58:16 +02:00
264ab130ee chore(docs): Update DOAP 2023-08-10 14:44:57 +02:00
38dba0e6b7 fix(xep): Export JingleContentThumbnail 2023-08-06 13:14:57 +02:00
94d6fe4925 Merge branch 'feat/remove-eft' 2023-08-06 13:05:55 +02:00
c8b903e5df chore(all): Fix linter issues 2023-08-06 13:04:12 +02:00
b14363319a feat(xep): Remove Extensible File Thumbnails 2023-08-06 13:01:28 +02:00
a18507cc3a chore(flake): Update flake 2023-08-06 12:23:46 +02:00
93418f0127 fix(xep): Fix missing handling of StanzaError
Fixes #48.
2023-07-29 19:27:40 +02:00
1e7279e23b Merge pull request 'Implement XEP-0045 support in moxxmpp' (#46) from ikjot-2605/moxxmpp:xep_0045 into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/46
2023-07-01 19:42:12 +00:00
ikjot-2605
b2724aba0c Merge branch 'master' into xep_0045 2023-07-01 12:01:33 +00:00
Ikjot Singh Dhody
d3742ea156 feat(xep): Small fixes - MUC Example.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-07-01 17:29:51 +05:30
b92e825bc1 fix(xep): Fix handling StanzaErrors from the DiscoManager 2023-07-01 13:19:26 +02:00
Ikjot Singh Dhody
8b00e85167 feat(xep): Add example for XEP 0045 Moxxmpp.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-07-01 09:16:51 +05:30
Ikjot Singh Dhody
04dfc6d2ac feat(xep): Replace DiscoError with StanzaError.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-30 18:33:00 +05:30
ikjot-2605
9e70e802ef Merge branch 'master' into xep_0045 2023-06-30 04:56:15 +00:00
Ikjot Singh Dhody
3ebd9b86ec feat(xep): Fix lint issues and use moxlib for result.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-30 10:25:17 +05:30
a928c5c877 feat(xep): Also potentially return "generic" stanza errors 2023-06-29 21:12:30 +02:00
77a1acb0e7 fix(moxxmpp): Somewhat fix (and break) moxxmpp_socket_tcp integration tests 2023-06-25 12:47:47 +02:00
Ikjot Singh Dhody
a873edb9ec feat(xep): Check for null nick before leaveRoom.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-21 12:20:14 +05:30
Ikjot Singh Dhody
e6bd6d05cd feat(xep): Remove NOOP cache access.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-21 00:41:51 +05:30
05e3d804a4 feat(example): Fix omemo_dart dependency 2023-06-20 16:36:49 +02:00
b5efc2dfae feat(xep): Expose new/replaced ratchets in the MessageEvent 2023-06-20 16:35:51 +02:00
Ikjot Singh Dhody
b7d53b8f47 feat(xep): Add docstings for the XEP-0045 routines
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-20 17:44:24 +05:30
Ikjot Singh Dhody
217c3ac236 feat(xep): Fix cache issue with join/leaveRoom.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-20 17:38:30 +05:30
d35b955259 fix(xep): Accidentally left getUnackedStanzas exposed 2023-06-19 23:15:29 +02:00
30dca67fb6 feat(all): Remove freezed
Fixes #43.
2023-06-19 23:11:58 +02:00
2db44e2f51 fix(xep): Fix resend behaviour leading to period disconnects
It seems that we were expecting acks "in the future" for old stanzas.
Also, this commit should prevent E2EE implementations from re-encrypting
resent stanzas.

Fixes #38.
2023-06-19 22:55:50 +02:00
Ikjot Singh Dhody
51bca6c25d feat(xep): XEP-0045 cache fixes.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-19 18:40:39 +05:30
4f9a0605c7 Merge pull request 'OMEMO Improvements' (#47) from omemo-changes into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/47
2023-06-18 19:58:05 +00:00
3621f2709a chore(docs): Update changelog 2023-06-18 21:57:53 +02:00
9da6d319a3 feat(all): Use 0.5.0 of omemo_dart 2023-06-18 21:30:35 +02:00
e3ca83670a feat(example): Implement common argument parsing 2023-06-18 21:16:47 +02:00
fbbe413148 feat(example): Improve the example code 2023-06-18 20:59:54 +02:00
Ikjot Singh Dhody
8728166a4d feat(xep): Add cache and roomstate to MUC implementation.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-18 20:49:13 +05:30
Ikjot Singh Dhody
1f1321b269 feat(xep): Small fixes - review cycle 1.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-18 20:09:06 +05:30
9fd2daabb2 feat(xep): Adjust to more omemo_dart changes 2023-06-17 23:51:52 +02:00
8252472fae feat(xep): Adjust to omemo_dart changes 2023-06-17 23:37:08 +02:00
3cb5a568ce feat(xep,core): Migrate to moxlib's Result type 2023-06-17 21:45:00 +02:00
c2f62e2967 feat(xep): Implement an OMEMO example client 2023-06-17 21:28:54 +02:00
Ikjot Singh Dhody
66195f66fa Merge branch 'master' into xep_0045
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-14 10:48:28 +05:30
Ikjot Singh Dhody
70fdfaf16d feat(xep): Fix imports for xep_0045 files.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-14 10:47:51 +05:30
Ikjot Singh Dhody
cd73f89e63 feat(xep): Remove duplicate manager string
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-14 10:47:51 +05:30
Ikjot Singh Dhody
05c41d3185 feat(xep): Refactor sendMessage to allow groupchat
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-14 10:47:50 +05:30
Ikjot Singh Dhody
64a8de6caa feat(xep): Set base for XEP 0045 implementation
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-14 10:47:07 +05:30
Ikjot Singh Dhody
68809469f6 feat(xep): Add joinRoom, leaveRoom routines.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-14 10:46:48 +05:30
Ikjot Singh Dhody
762cf1c77a feat(xep): Set base for XEP 0045 implementation
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-14 10:46:48 +05:30
29a5417b31 Merge pull request 'Add support for XEP-0421.' (#45) from ikjot-2605/moxxmpp:feat_xep_0421 into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/45
2023-06-12 10:58:32 +00:00
Ikjot Singh Dhody
255d0f88e0 feat(xep): Use cascading operation to return state
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-12 15:00:37 +05:30
fa2ce7c2d1 feat(xep): FASTSaslNegotiator now only takes a string as its token 2023-06-11 22:22:45 +02:00
Ikjot Singh Dhody
fa11a3a384 feat(xep): Checked for the occupant-id directly.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-11 23:29:17 +05:30
Ikjot Singh Dhody
ac5bb9e461 feat(xep): Implement XEP 0421 in Moxxmpp.
Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
2023-06-11 22:09:29 +05:30
aa71d3ed5d feat(xep): Allow changing the SASL2 user agent 2023-06-11 16:30:49 +02:00
f2d8c6a009 fix(xep): Fix parsing user avatar data with newlines 2023-06-10 21:06:30 +02:00
88545e3308 fix(xep): Fix the OOB fallback for SFS 2023-06-09 00:07:18 +02:00
925a46c0da feat(xep): Move to JID 2023-06-09 00:07:05 +02:00
327f695a40 Merge pull request 'Replace StanzaHandlerData with something more extensible' (#42) from feat/type-map-rework into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/42
2023-06-07 21:44:28 +00:00
8266765ff8 feat(core): Add encryption info to MessageEvent 2023-06-07 23:42:05 +02:00
e234c812ff feat(core): Add a shorthand for MessageEvent.extensions.get 2023-06-07 21:11:11 +02:00
4ff2992a03 fix(all): Last minute fixes 2023-06-07 21:04:05 +02:00
09fd5845aa feat(core): Re-fix the subscription functions 2023-06-07 18:54:05 +02:00
963f3f2cd9 feat(core): Check if we can do subscription pre-approval 2023-06-07 18:41:08 +02:00
da1d28a6d6 feat(core): Remove the PresenceReceivedEvent 2023-06-07 15:12:34 +02:00
cbd90b1163 feat(core): Add a list constructor to TypedMap 2023-06-07 14:13:46 +02:00
f0538b0447 feat(xep): Write a test for XEP-0334 2023-06-06 21:46:33 +02:00
968604b0ba fix(tests): Fix SFS parsing test 2023-06-06 21:31:14 +02:00
60279a84e0 feat(all): Restrict extensions to StanzaHandlerExtension 2023-06-06 21:19:42 +02:00
cf3287ccf4 feat(core): Remove freezed artifact 2023-06-06 20:26:35 +02:00
1ab0ed856f feat(all): Bump version to 0.4.0 2023-06-06 20:22:13 +02:00
10a5046431 feat(core): Rework [MessageEvent] 2023-06-06 16:19:16 +02:00
4d76b9f57a feat(core): Remove [MessageDetails] 2023-06-06 15:57:55 +02:00
0ec3777f44 feat(xep): Managers register the sending callback 2023-06-06 15:14:19 +02:00
6f5de9c4dc feat(xep): Implement the message sending callbacks 2023-06-06 14:12:49 +02:00
79d7e3ba64 feat(all): Move all managers to the new data system 2023-06-04 21:53:47 +02:00
8270185027 feat(core): Implement a typed map 2023-06-03 00:41:23 +02:00
9e0f38154e feat(all): Various changes
- Fix unavailable presence being sent *after* connecting
- Migrate more APIs to the JID class
- Advertise +notify for user avatar metadata
2023-06-02 22:00:44 +02:00
1475cb542f fix(xep): Just receiving <stanza-id/>s does not require disco
It is only required when we try to use them.
2023-05-25 12:42:42 +02:00
b949ec6ff5 fix(docs): Fix typo 2023-05-24 23:44:48 +02:00
c3be199cca Merge pull request 'Stanza send queue' (#40) from feat/send-queue into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/40
2023-05-24 20:50:03 +00:00
83ebe58c47 feat(tests): Test emptying stanza queue on connect 2023-05-24 22:03:17 +02:00
4db0ef6b34 feat(core): Expose StanzaDetails 2023-05-24 20:41:02 +02:00
b95e75329d feat(all): Remove StanzaAddFrom 2023-05-24 20:27:01 +02:00
3163101f82 feat(all): Migrate to the new StanzaDetails API 2023-05-24 14:28:42 +02:00
bd4e1d28ea feat(core): Implement the send queue 2023-05-24 13:34:36 +02:00
b1da6e5a53 chore(xep): Move DiscoManager from String to JID 2023-05-23 17:28:20 +02:00
c6552968d5 feat(core): Remove resumed from connection state change events 2023-05-23 15:56:38 +02:00
1d87c0ce95 feat(xep): Separate XEP-0030 and XEP-0115 support
Also, we now validate whatever we get in terms of disco#info
and capability hashes before caching.
2023-05-23 15:52:48 +02:00
da591a552d fix(xep): Fix discovery of XEP-0191 and XEP-0280 2023-05-16 19:06:50 +02:00
47b679d168 fix(xep): Do not set C2S counter to 0 after connecting 2023-05-16 13:45:52 +02:00
320f4a8d4c fix(xep): Fix issues with enhanced enums 2023-05-16 00:59:50 +02:00
1fdefacd52 feat(xep): Improve API using enhanced enums 2023-05-15 20:40:39 +02:00
09b8613c80 fix(style): Style fixes 2023-05-07 00:15:37 +02:00
f3d906e69b feat(core): Re-add xmlns for StanzaHandler 2023-05-07 00:13:25 +02:00
483cb0d7f1 fix(core): Fix stream parsing breaking after some time 2023-05-07 00:09:35 +02:00
f73daf4d1c fix(core): Fix getting stuck on invalid stream ends
Whenever the stream ends in a weird state, for example with
`<some-tag`, initiating a new stream will confuse the XML parser
and lead errors causing the connection to get stuck.

To fix this, we had to work around xml's extension methods and implement
the extensions in the new "stream parser" where we can reset the parser
(and by reset I mean replace) and thus continue with a fresh parsing state
whenever we reconnect (or connect).
2023-05-06 20:58:34 +02:00
a8693da262 chore(xep): Formatting 2023-04-09 13:17:21 +02:00
0fb66f6aca fix(xep,core): Fix some reconnection issues
- Use handleError instead of directly invoking onFailure
- Stop the ack timer only when we receive ack responses
- NoConnectionPossibleErrors are not irrecoverable
2023-04-09 13:14:53 +02:00
c9173c49bd feat(tests): Add a MUC component for testing 2023-04-09 13:14:30 +02:00
2de1126fa9 fix(example): Remove useDirectTLS 2023-04-09 13:14:06 +02:00
4d312b2100 chore(docs): Update changelog 2023-04-07 15:28:43 +02:00
c1ad646905 fix(core,xep): Fix multiple issues
- Fix a deadlock in the RandomBackoffReconnectionPolicy on failure
- Fix enablement of stream management happening too early
2023-04-07 15:26:49 +02:00
c88ab940c4 release: Version 0.3.1 2023-04-05 13:20:16 +02:00
916be1c927 fix(core): Fix components' stanza matching 2023-04-05 13:17:18 +02:00
5c47c35a46 chore(docs): Bump version of derivation 2023-04-04 16:03:42 +02:00
7f3538875b feat(core): Make all managers optional 2023-04-04 15:59:07 +02:00
f6efa20ff4 chore(meta): Bump versions 2023-04-04 15:55:52 +02:00
8443411f07 fix(docs): XEP-0144 -> XEP-0114 2023-04-04 15:53:57 +02:00
dc24b3c48a chore(core): Remove useDirectTLS 2023-04-04 15:52:56 +02:00
f6abf3d5b5 feat(xep): Implement XEP-0114 2023-04-04 15:48:26 +02:00
63c84d9479 feat(core): Allow tracking the stream id 2023-04-03 23:18:10 +02:00
3e43ac22d7 feat(core): Remove getter and setter for connectionSettings 2023-04-03 17:56:31 +02:00
47d821c02e docs(core): Add comments to NegotiationsHandler 2023-04-03 16:17:09 +02:00
355d580a9a fix(tests): Fix negotiator tests 2023-04-03 16:15:46 +02:00
03328bdf7a feat(core): Allow implementing different negotiation strategies
Fixes #27.
2023-04-03 16:05:20 +02:00
275d6e0346 feat(core): Attempt to improve the ReconnectionPolicy 2023-04-03 13:40:13 +02:00
0d9afd546c feat(core): Remove ignoreLock 2023-04-03 12:47:38 +02:00
3da334b5cf feat(core): Remove the connection lock 2023-04-03 12:46:15 +02:00
2947e2c539 Merge pull request 'SASL2 and friends' (#34) from feat/sasl2 into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/34
2023-04-02 21:36:02 +00:00
ac8433f51f chore(core): Refactor RFC6120 implementations 2023-04-02 23:33:53 +02:00
808371b271 chore(tests): Fix tests 2023-04-02 23:23:50 +02:00
7fdd83ea69 chore(xep): Clean the SASL2 implementation 2023-04-02 23:06:02 +02:00
68e2a65dcf docs(tests): Mention that we need prosody-trunk 2023-04-02 19:49:00 +02:00
d977a74446 feat(tests): Add an integration test for SASL2 2023-04-02 17:20:14 +02:00
29f0419154 feat(meta): Remove log redaction 2023-04-02 14:38:32 +02:00
b354ca8d0a feat(xep): Improve the ergonomics of Bind2 negotiators 2023-04-02 13:39:43 +02:00
ec6b5ab753 feat(xep): Allow inline enablement of carbons 2023-04-02 12:44:09 +02:00
ce1815d1f3 fix(tests): Fix namespace of <bound /> 2023-04-02 12:43:28 +02:00
fbb495dc2f feat(xep): Allow inlining CSI 2023-04-01 23:16:37 +02:00
4a6aa79e56 fix(xep): When using FAST, fallback to other SASL mechanisms on failure 2023-04-01 21:42:52 +02:00
0033d0eb6e feat(xep): Implement FAST 2023-04-01 21:10:46 +02:00
24cb05f91b feat(xep): Handle inline stream enablement with Bind2 2023-04-01 17:38:40 +02:00
91f763ac26 feat(xep): Allow negotiating SM enabling inline with Bind2 2023-04-01 17:16:29 +02:00
51edb61443 feat(xep): Implement SASL2 inline stream resumption 2023-04-01 15:50:13 +02:00
4e01d32e90 feat(xep): Allow setting a tag when using Bind2 2023-04-01 13:15:46 +02:00
f2fe06104c fix(core): Fix formatting 2023-04-01 13:13:19 +02:00
89fe8f0a9c feat(core): Make the PresenceManager optional 2023-04-01 13:09:43 +02:00
9358175925 feat(xep): Inline resource binding with Bind2 2023-04-01 13:00:35 +02:00
564a237986 feat(xep): Set the resource if SASL2 resulted in a resource 2023-04-01 12:51:26 +02:00
cf425917cf feat(core): Reset the resource if lastResource is null 2023-04-01 12:39:15 +02:00
63b7abd6f9 fix(core): Prevent resource binding if we already have a resource 2023-04-01 12:38:18 +02:00
f460e5ebe9 feat(core): Handle less resource binding in the core connection class 2023-04-01 12:28:11 +02:00
af8bc606d6 feat(xep): Guard against random data in the SASL2 result 2023-04-01 00:51:51 +02:00
30482c86f0 feat(xep): Implement inline negotiation 2023-04-01 00:47:45 +02:00
f86dbe6af8 feat(core): Verify the server signature with SASL2 2023-03-31 23:52:48 +02:00
478b5b8770 feat(core): Make SCRAM SASL2 aware 2023-03-31 21:09:16 +02:00
7ab3f4f0d9 feat(xep): Implement negotiating PLAIN via SASL2 2023-03-31 20:53:06 +02:00
2e60e9841e feat(xep): Begin work on SASL2 2023-03-31 19:02:57 +02:00
52ad9a7ddb fix(core): Reset the connection tracker when timing out 2023-03-31 15:53:11 +02:00
ac5e0c13b7 fix(xep): Do not subscribe to the data node 2023-03-31 15:52:50 +02:00
b49658784b fix(example): Adjust example to changes 2023-03-30 16:18:28 +02:00
d4a972e073 feat(core): Close the socket on an error 2023-03-30 16:17:42 +02:00
1009a2f967 feat(core): Fix typing and remove logging parameter 2023-03-30 16:17:12 +02:00
f355f01fc8 fix(tests): Fix integration tests 2023-03-30 16:15:44 +02:00
85995d51e4 feat(core): Remove SendPingEvent 2023-03-29 17:18:03 +02:00
2557a2fe5b feat(core): Make the ping manager optional 2023-03-29 15:25:17 +02:00
4321573dfb fix(core): Fix reconnections not working properly 2023-03-21 12:01:22 +01:00
70d4d6c56f feat(core): Use _testAndSetIsConnectionRunning 2023-03-18 18:41:58 +01:00
e1e492832e chore(tests): Format and lint tests 2023-03-18 16:13:45 +01:00
1950394f7d fix(meta): Add 'example' as a 'commit target' 2023-03-18 15:00:54 +01:00
308f7d93f5 chore(example): Update the example 2023-03-18 14:59:47 +01:00
de85bf848d fix(core): Fix crash when no negotiator matches
Fixes #30.

Also removes the `allowPlainAuth` attribute of `ConnectionSettings` as
users who want to disable SASL PLAIN can just not register the
negotiator or extend it.
2023-03-18 14:54:39 +01:00
7a6bf468bc test(xep): Add a test for parsing sticker packs 2023-03-16 22:04:25 +01:00
9cb6346c4d fix(style): Format and lint test helpers 2023-03-12 19:19:53 +01:00
f49eb66bb7 fix(xep): Fix usage of 'max' in publish options (#33)
This commit fixes two issues:
1. Fix an issue where [PubSubManager.publish] would always, if given
   publish options with maxItems set to 'max', use 'max' in the
   max_items publish options, even if the server indicates it does not
   support that.
2. Fix an issue with the StanzaExpectation, where it would let every
   stanza pass.
2023-03-12 19:11:55 +01:00
324ef9ca29 Merge pull request 'Merge connect and connectAwaitable' (#32) from refactor/connect into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/32
2023-03-12 16:31:21 +00:00
5b4dcc67b2 refactor(core): Remove XmppConnectionResult 2023-03-11 19:07:03 +01:00
9010218b10 refactor(core): Move connection errors into their own file 2023-03-11 19:06:28 +01:00
61144a10b3 fix(core): Minor API change 2023-03-11 19:01:55 +01:00
7a1f737c65 chore(meta): Bump version 2023-03-11 19:00:42 +01:00
546c032d43 fix(tests): Fix broken test 2023-03-11 18:59:40 +01:00
b1869be3d9 chore(docs): Update changelog 2023-03-11 18:59:22 +01:00
574fdfecaa feat(core): Merge connect and connectAwaitable 2023-03-11 18:54:36 +01:00
25c778965c feat(core): Merge connect and connectAwaitable 2023-03-11 00:10:50 +01:00
976c0040b5 chore(meta): Update JDK to 17 2023-03-11 00:10:50 +01:00
b53c62b40c Merge pull request 'fix: make the moxxmpp example work again' (#29) from bleonard252/moxxmpp-patch:master into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/29
2023-03-10 23:07:49 +00:00
Blake Leonard
2cdc56c882
chore: format corrections, comment clarifications
Signed-off-by: Blake Leonard <me@blakes.dev>
2023-03-10 13:29:30 -05:00
Blake Leonard
f5059d8008 Merge remote-tracking branch 'origin/master' into HEAD 2023-03-08 15:08:56 -05:00
Blake Leonard
792ec4d731
chore(example): format and fix lint errors
Signed-off-by: Blake Leonard <me@blakes.dev>
2023-03-08 15:07:55 -05:00
93d08188ea feat(docs): Add CONTRIBUTING.md 2023-03-08 20:51:38 +01:00
e9ad5a6c66 feat(flake): Update Flutter 2023-03-08 20:47:49 +01:00
8b0f118e2d fix(style): Format using dart format 2023-03-08 20:25:45 +01:00
Blake Leonard
60c89e28d3
chore(example): switch to connectAwaitable
That way it only acts connected when the credentials have been accepted.

Also I had to correct the value of "allowPlainAuth" which should be
true since a bug with it has been identified, and not yet fixed.

Signed-off-by: Blake Leonard <me@blakes.dev>
2023-03-08 14:09:37 -05:00
38155051f5 fix(style): Format using dart format 2023-03-08 19:32:03 +01:00
Blake Leonard
7b215d5c6e
fix: make the moxxmpp example work again
Note: to do this, I could not use the ExampleTcpSocketWrapper.
If I did, the app crashed on launch.

I also added some functionality: the header bar turns green when
connected, the FAB says what it does, and you can disconnect.

Signed-off-by: Blake Leonard <me@blakes.dev>
2023-03-08 13:02:43 -05:00
1000e0756b feat: Improve detecting new streams
Fixes #26.
2023-01-31 21:11:52 +01:00
902b497526 docs: Add links to the hosted documentation 2023-01-28 15:33:21 +01:00
039f954e70 docs: Add some doc string to the scripts 2023-01-28 15:31:47 +01:00
5dc2b127fa flake: Remove nix-dart 2023-01-28 15:31:36 +01:00
252cc44841 feat: Make moxxmpp docs buildable using flakes 2023-01-28 15:22:37 +01:00
96d9ce4761 feat: Don't attempt reconnections when the error is unrecoverable
Fixes #25.
Should fix #24.
2023-01-28 13:20:16 +01:00
7f294d6632 fix: Bump version requirement for moxxmpp 2023-01-27 22:03:20 +01:00
e17de9065b feat: Bump moxxmpp_socket_tcp to 0.2.0 2023-01-27 22:01:32 +01:00
098687de45 feat: Bump moxxmpp version to 0.2.0 2023-01-27 21:57:26 +01:00
6da3342f22 feat: Make defining managers better 2023-01-27 21:54:16 +01:00
47337540f5 feat: Factor out "multiple-waiting" into its own thing 2023-01-27 21:14:35 +01:00
7e588f01b0 feat: Add DOAP
Fixes #11.
2023-01-27 19:09:05 +01:00
c7c6c9dae4 feat: Update Message Replies to 0.2.0
Fixes #22.
2023-01-27 19:08:57 +01:00
c77cfc4dcd feat: Change namespaces 2023-01-27 18:34:13 +01:00
1bd61076ea feat: Improve the API provided by the DiscoManager
Fixes #21.
2023-01-27 16:26:01 +01:00
bff4a6f707 feat: Rework how the ReconnectionPolicy system works 2023-01-27 00:14:44 +01:00
1cc266c675 fix: Just use shouldEncryptElement for the envelope "validation" 2023-01-23 13:11:07 +01:00
72099dfde5 feat: Only add envelope elements that should be encrypted 2023-01-23 13:10:07 +01:00
c9c45baabc feat: Allow easier responding to incoming stanzas
Should fix #20.
2023-01-23 12:47:30 +01:00
a01022c217 feat: Bump omemo_dart 2023-01-22 19:25:52 +01:00
c3459e6820 feat: Always set a cancel reason on failure 2023-01-21 20:46:45 +01:00
e031e6d760 feat: Add cancelReason if recipient likely does not support OMEMO:2 2023-01-21 15:42:00 +01:00
6c63b53cf4 fix: Fix crash with direct server IQs
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-15 00:52:34 +01:00
1aa50699ad feat: Improve the stanza await system
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This fixes an issue where publishing an avatar would fail, if returned
an error where the "from" attribute is missing from the stanza.
2023-01-14 16:28:37 +01:00
b2c54ae8c0 ci: Add Woodpecker CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-14 15:00:55 +01:00
b16c9f4b30 docs: Add more doc strings 2023-01-14 15:00:02 +01:00
a8d80eaddf fix: Add the closure expectation to the event 2023-01-14 12:41:17 +01:00
9baf1ed73c fix: Attempt to fix reconnection issues
Call _reconnectionPolicy.onSuccess when negotiations are done. Never
call onFailure directly; only ever call handleError.
2023-01-14 12:39:55 +01:00
ce3ea656ad fix: Stop ack timer on connection drops
Fixes #17.
2023-01-14 12:04:52 +01:00
ed49212f5a fix: Enabling carbons crashes 2023-01-13 15:17:21 +01:00
ad1242c47d feat: Try to lock reconnections behind a flag 2023-01-13 15:16:51 +01:00
890fcfb506 feat: Do initialization inline 2023-01-13 14:44:06 +01:00
d7723615fe fix: Fix message quote generation 2023-01-13 13:39:18 +01:00
6517065a1a feat: Track stanza responses as a tuple of (to, id)
Also fixes an invalid test case in the XEP-0198 tests, where
the IQ reply sets the "to" instead of the "from".
2023-01-10 12:50:07 +01:00
9223a7d403 feat: Add docs for including a tagged version 2023-01-10 12:25:56 +01:00
7ce6703c5b tests: Fix the XEP-0198 test 2023-01-09 12:53:22 +01:00
37261cddbb fix: Run Stream Management very early 2023-01-09 12:48:30 +01:00
d8c2ef6f3b Merge pull request 'Roster Rework' (#15) from fix/roster-rework into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/15
2023-01-07 21:23:12 +00:00
98e5324409 fix: Copy the pre handler's encryption state 2023-01-07 22:18:20 +01:00
a69c2a23f2 fix: Bump omemo_dart version 2023-01-07 22:18:05 +01:00
d8de093e4d fix: Somewhat fix OMEMO 2023-01-07 21:57:56 +01:00
678564dbb3 fix: Roster request being treated as a roster push 2023-01-07 21:23:45 +01:00
09d2601e85 fix: Compare groups 2023-01-07 19:58:46 +01:00
41560682a1 fix: Handle roster items staying the same 2023-01-07 19:03:35 +01:00
473f8e4bb6 tests: Fix tests 2023-01-07 18:40:36 +01:00
67446285c1 feat: Refactor RosterPushEvent to RosterPushResult 2023-01-07 18:36:18 +01:00
e12f4688d3 feat: Trigger an event when the roster changes 2023-01-07 18:32:15 +01:00
2581bbe203 feat: Document the RosterStateManager better 2023-01-07 18:11:41 +01:00
995f2e0248 feat: Integrate the BaseRosterStateManager with the RosterManager 2023-01-07 16:26:15 +01:00
e2c8f79429 feat: Add a management class for roster state 2023-01-07 15:51:10 +01:00
763c93857d feat: Simplify the JID parser
Fixes #13.
2023-01-04 17:19:37 +01:00
55d2ef9c25 style: Remove newline 2023-01-02 17:53:06 +01:00
f37cbd1616 feat: Allow specifying XEP-0449's access model 2023-01-02 17:52:55 +01:00
2a3449d0f2 fix: Fix user avatar update being triggered for every PubSub event 2023-01-02 17:35:47 +01:00
596693c206 feat: Update to omemo_dart 0.4.1 2023-01-02 13:58:27 +01:00
22aa07c4ba feat: Propagate errors and encrypt to self if carbons are enabled 2023-01-02 13:52:06 +01:00
62001c1e29 feat: Upgrade omemo_dart to 0.4.0 2023-01-01 18:17:41 +01:00
ca85c94fe5 fix: Fix wrong XML serialisation 2023-01-01 16:38:54 +01:00
637e1e25a6 feat: Migrate to the new omemo_dart API 2023-01-01 16:19:25 +01:00
09696c1c4d fix: Fix VCard and User Avatar queries being encrypted 2022-12-25 13:05:16 +01:00
298a8342b8 docs: Add funding.yml 2022-12-23 15:18:53 +01:00
d64220426b feat: Implement XEP-0449 2022-12-19 14:14:05 +01:00
88efdc361c fix: Only add a <body> element when specified 2022-12-09 12:52:00 +01:00
cc1b371198 feat: Allow clients to read Message Processing Hints 2022-12-09 12:46:17 +01:00
d9e4a3c1d4 feat: Implement XEP-0444 2022-12-06 14:09:07 +01:00
0ae13acca0 chore(release): publish packages
- moxxmpp@0.1.6+1
 - moxxmpp_socket_tcp@0.1.2+9
2022-11-26 15:48:48 +01:00
d383fa31ae fix: Fix LMC not working 2022-11-26 15:48:29 +01:00
250 changed files with 22324 additions and 7882 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
ko_fi: papatutuwawa

3
.gitignore vendored
View File

@ -13,3 +13,6 @@ pubspec.lock
# Omit pubspec override files generated by melos
**/pubspec_overrides.yaml
# Flake results
result

14
.gitlint Normal file
View File

@ -0,0 +1,14 @@
[general]
ignore=B5,B6,B7,B8
[title-max-length]
line-length=72
[title-trailing-punctuation]
[title-hard-tab]
[title-match-regex]
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all|flake|ci)+(,(meta|tests|style|docs|xep|core|example|all|flake|ci))*\)|release): [A-Z0-9].*$
[body-trailing-whitespace]
[body-first-line-empty]

49
.woodpecker.yml Normal file
View File

@ -0,0 +1,49 @@
when:
branch: master
pipeline:
# Check moxxmpp
moxxmpp-lint:
image: dart:3.0.7
commands:
- cd packages/moxxmpp
# Proxy requests to pub.dev using pubcached
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
- dart analyze --fatal-infos --fatal-warnings
moxxmpp-test:
image: dart:3.0.7
commands:
- cd packages/moxxmpp
# Proxy requests to pub.dev using pubcached
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
- dart test
# Check moxxmpp_socket_tcp
moxxmpp_socket_tcp-lint:
image: dart:3.0.7
commands:
- cd packages/moxxmpp_socket_tcp
# Proxy requests to pub.dev using pubcached
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
- dart analyze --fatal-infos --fatal-warnings
# moxxmpp-test:
# image: dart:3.0.7
# commands:
# - cd packages/moxxmpp
# # Proxy requests to pub.dev using pubcached
# - PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
# - dart test
notify:
image: git.polynom.me/papatutuwawa/woodpecker-xmpp
settings:
xmpp_tls: 1
xmpp_is_muc: 1
xmpp_recipient: moxxy-build@muc.moxxy.org
xmpp_alias: 2Bot
secrets: [ xmpp_jid, xmpp_password, xmpp_server ]
when:
status:
- failure

19
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,19 @@
# Contribution Guide
Thanks for your interest in the moxxmpp XMPP library! This document contains guidelines and guides for working on the moxxmpp codebase.
## Contributing
If you want to fix a small issue, you can just fork, create a new branch, and start working right away. However, if you want to work
on a bigger feature, please first create an issue (if an issue does not already exist) or join the [development chat](xmpp:moxxy@muc.moxxy.org?join) (xmpp:moxxy@muc.moxxy.org?join)
to discuss the feature first.
Before creating a pull request, please make sure you checked every item on the following checklist:
- [ ] I formatted the code with the dart formatter (`dart format`) before running the linter
- [ ] I ran the linter (`dart analyze`) and introduced no new linter warnings
- [ ] I ran the tests (`dart test`) and introduced no new failing tests
- [ ] I used [gitlint](https://github.com/jorisroovers/gitlint) to ensure propper formatting of my commig messages
If you think that your code is ready for a pull request, but you are not sure if it is ready, prefix the PR's title with "WIP: ", so that discussion
can happen there. If you think your PR is ready for review, remove the "WIP: " prefix.

View File

@ -3,16 +3,22 @@
moxxmpp is a XMPP library written purely in Dart for usage in Moxxy.
## Packages
### moxxmpp
### [moxxmpp](./packages/moxxmpp)
This package contains the actual XMPP code that is platform-independent.
### moxxmpp_socket
Documentation is available [here](https://docs.moxxy.org/moxxmpp/index.html).
`moxxmpp_socket` contains the implementation of the `BaseSocketWrapper` class that
### [moxxmpp_socket_tcp](./packages/moxxmpp_socket_tcp)
`moxxmpp_socket_tcp` contains the implementation of the `BaseSocketWrapper` class that
implements the RFC6120 connection algorithm and XEP-0368 direct TLS connections,
if a DNS implementation is given, and supports StartTLS.
### moxxmpp_color
Implementation of [XEP-0392](https://xmpp.org/extensions/xep-0392.html).
## Development
To begin, use [melos](https://github.com/invertase/melos) to bootstrap the project: `melos bootstrap`. Then, the example
@ -22,6 +28,21 @@ To run the example, make sure that Flutter is correctly set up and working. If y
the development shell provided by the NixOS Flake, ensure that `ANDROID_HOME` and
`ANDROID_AVD_HOME` are pointing to the correct directories.
## Examples
This repository contains 2 types of examples:
- `example_flutter`: An example of using moxxmpp using Flutter
- `examples_dart`: A collection of pure Dart examples for showing different aspects of moxxmpp
For more information, see the respective README files.
## License
See `./LICENSE`.
## Support
If you like what I do and you want to support me, feel free to donate to me on Ko-Fi.
[<img src="https://codeberg.org/moxxy/moxxyv2/raw/branch/master/assets/repo/kofi.png" height="36" style="height: 36px; border: 0px;"></img>](https://ko-fi.com/papatutuwawa)

View File

@ -11,5 +11,3 @@ analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "test/"
- "integration_test/"

View File

@ -1,6 +0,0 @@
# melos_managed_dependency_overrides: moxxmpp,moxxmpp_socket_tcp
dependency_overrides:
moxxmpp:
path: ../packages/moxxmpp
moxxmpp_socket_tcp:
path: ../packages/moxxmpp_socket_tcp

View File

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View File

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View File

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 721 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -5,25 +5,28 @@ import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
class ExampleTcpSocketWrapper extends TCPSocketWrapper {
ExampleTcpSocketWrapper() : super(false);
ExampleTcpSocketWrapper() : super();
@override
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
final records = await MoxdnsPlugin.srvQuery(domain, false);
return records
.map((record) => MoxSrvRecord(
record.priority,
record.weight,
record.target,
record.port,
),)
.toList();
.map(
(record) => MoxSrvRecord(
record.priority,
record.weight,
record.target,
record.port,
),
)
.toList();
}
}
void main() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print('${record.level.name}: ${record.time}: ${record.message}');
});
@ -54,22 +57,32 @@ class MyHomePage extends StatefulWidget {
}
class _MyHomePageState extends State<MyHomePage> {
final logger = Logger('MyHomePage');
final XmppConnection connection = XmppConnection(
ExponentialBackoffReconnectionPolicy(),
ExampleTcpSocketWrapper(),
RandomBackoffReconnectionPolicy(1, 60),
AlwaysConnectedConnectivityManager(),
ClientToServerNegotiator(),
// The below causes the app to crash.
//ExampleTcpSocketWrapper(),
// In a production app, the below should be false.
TCPSocketWrapper(),
);
TextEditingController jidController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool connected = false;
bool loading = false;
_MyHomePageState() : super() {
connection
..registerManagers([
StreamManagementManager(),
DiscoManager(),
RosterManager(),
PingManager(),
DiscoManager([]),
RosterManager(TestingRosterStateManager("", [])),
PingManager(
const Duration(minutes: 3),
),
MessageManager(),
PresenceManager('http://moxxmpp.example'),
PresenceManager(),
])
..registerFeatureNegotiators([
ResourceBindingNegotiator(),
@ -83,17 +96,39 @@ class _MyHomePageState extends State<MyHomePage> {
SaslScramNegotiator(8, '', '', ScramHashType.sha1),
]);
}
Future<void> _buttonPressed() async {
connection.setConnectionSettings(
ConnectionSettings(
jid: JID.fromString(jidController.text),
password: passwordController.text,
useDirectTLS: true,
allowPlainAuth: false,
),
if (connected) {
await connection.disconnect();
setState(() {
connected = false;
});
return;
}
setState(() {
loading = true;
});
connection.connectionSettings = ConnectionSettings(
jid: JID.fromString(jidController.text),
password: passwordController.text,
);
await connection.connect();
final result = await connection.connect(waitUntilLogin: true);
setState(() {
connected = result.isType<bool>() && result.get<bool>();
loading = false;
});
if (result.isType<XmppError>()) {
logger.severe(result.get<XmppError>());
if (context.mounted) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Error'),
content: Text(result.get<XmppError>().toString()),
),
);
}
}
}
@override
@ -101,20 +136,24 @@ class _MyHomePageState extends State<MyHomePage> {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: connected ? Colors.green : Colors.deepPurple[800],
foregroundColor: connected ? Colors.black : Colors.white,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
enabled: !loading,
controller: jidController,
decoration: InputDecoration(
decoration: const InputDecoration(
labelText: 'JID',
),
),
TextField(
enabled: !loading,
controller: passwordController,
decoration: InputDecoration(
decoration: const InputDecoration(
labelText: 'Password',
),
obscureText: true,
@ -122,10 +161,13 @@ class _MyHomePageState extends State<MyHomePage> {
],
),
),
floatingActionButton: FloatingActionButton(
floatingActionButton: FloatingActionButton.extended(
onPressed: _buttonPressed,
label: Text(connected ? 'Disconnect' : 'Connect'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
tooltip: 'Connect',
child: const Icon(Icons.add),
icon: const Icon(Icons.power),
),
);
}

View File

@ -16,10 +16,10 @@ dependencies:
version: 0.1.4+1
moxxmpp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.1.6
version: 0.3.1
moxxmpp_socket_tcp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.1.2+8
version: 0.3.1
dev_dependencies:
flutter_test:

6
examples_dart/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build output.
build/

7
examples_dart/README.md Normal file
View File

@ -0,0 +1,7 @@
# Dart Examples
Run using `dart run bin/<example>.dart`.
## Examples
- `component.dart`: Use moxxmpp to implement a component using XEP-0114.

View File

@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,94 @@
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
class TestingTCPSocketWrapper extends TCPSocketWrapper {
TestingTCPSocketWrapper() : super(true);
@override
bool onBadCertificate(dynamic certificate, String domain) {
return true;
}
}
class EchoMessageManager extends XmppManagerBase {
EchoMessageManager() : super('org.moxxy.example.message');
@override
Future<bool> isSupported() async => true;
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: 'message',
callback: _onMessage,
priority: -100,
xmlns: null,
)
];
Future<StanzaHandlerData> _onMessage(
Stanza stanza,
StanzaHandlerData state,
) async {
final body = stanza.firstTag('body');
if (body == null) return state..done = true;
final bodyText = body.innerText();
await getAttributes().sendStanza(
StanzaDetails(
Stanza.message(
to: stanza.from,
children: [
XMLNode(
tag: 'body',
text: 'Hello, ${stanza.from}! You said "$bodyText"',
),
],
),
awaitable: false,
),
);
return state..done = true;
}
}
void main(List<String> arguments) async {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print(
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
);
});
final conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ComponentToServerNegotiator(),
TestingTCPSocketWrapper(),
)..connectionSettings = ConnectionSettings(
jid: JID.fromString('component.localhost'),
password: 'abc123',
host: '127.0.0.1',
port: 8888,
);
await conn.registerManagers([
EchoMessageManager(),
]);
final result = await conn.connect(
waitUntilLogin: true,
shouldReconnect: false,
enableReconnectOnSuccess: false,
);
if (result.isType<XmppError>()) {
print('Failed to connect as component');
return;
}
// Just block for some time to test the connection
await Future<void>.delayed(const Duration(seconds: 9999));
}

View File

@ -0,0 +1,111 @@
import 'package:cli_repl/cli_repl.dart';
import 'package:example_dart/arguments.dart';
import 'package:example_dart/socket.dart';
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
void main(List<String> args) async {
// Set up logging
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print(
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
);
});
final parser = ArgumentParser()
..parser.addOption('muc', help: 'The MUC to send messages to')
..parser.addOption('nick', help: 'The nickname with which to join the MUC');
final options = parser.handleArguments(args);
if (options == null) {
return;
}
// Connect
final muc = JID.fromString(options['muc']! as String).toBare();
final nick = options['nick']! as String;
final connection = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ClientToServerNegotiator(),
ExampleTCPSocketWrapper(parser.srvRecord),
)..connectionSettings = parser.connectionSettings;
// Register the managers and negotiators
await connection.registerManagers([
PresenceManager(),
DiscoManager([]),
PubSubManager(),
MessageManager(),
StableIdManager(),
MUCManager(),
]);
await connection.registerFeatureNegotiators([
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StartTlsNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
]);
// Connect
Logger.root.info('Connecting...');
final result =
await connection.connect(shouldReconnect: false, waitUntilLogin: true);
if (!result.isType<bool>()) {
Logger.root.severe('Authentication failed!');
return;
}
Logger.root.info('Connected.');
// Print received messages.
connection
.asBroadcastStream()
.where((event) => event is MessageEvent)
.listen((event) {
event as MessageEvent;
// Ignore messages with no <body />
final body = event.get<MessageBodyData>()?.body;
if (body == null) {
return;
}
print('=====> [${event.from}] $body');
});
// Join room
final mm = connection.getManagerById<MUCManager>(mucManager)!;
await mm.joinRoom(
muc,
nick,
maxHistoryStanzas: 0,
);
final state = (await mm.getRoomState(muc))!;
print('=====> ${state.members.length} users in room');
print('=====> ${state.members.values.map((m) => m.nick).join(", ")}');
final repl = Repl(prompt: '> ');
await for (final line in repl.runAsync()) {
await connection
.getManagerById<MessageManager>(messageManager)!
.sendMessage(
muc,
TypedMap<StanzaHandlerExtension>.fromList([
MessageBodyData(line),
StableIdData(
// NOTE: Don't do this. Use a UUID.
DateTime.now().millisecondsSinceEpoch.toString(),
null,
),
]),
type: 'groupchat');
}
// Leave room
await connection.getManagerById<MUCManager>(mucManager)!.leaveRoom(muc);
// Disconnect
await connection.disconnect();
}

View File

@ -0,0 +1,116 @@
import 'package:chalkdart/chalk.dart';
import 'package:cli_repl/cli_repl.dart';
import 'package:example_dart/arguments.dart';
import 'package:example_dart/socket.dart';
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:omemo_dart/omemo_dart.dart' as omemo;
void main(List<String> args) async {
// Set up logging
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print(
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
);
});
final parser = ArgumentParser()
..parser.addOption('to', help: 'The JID to send messages to');
final options = parser.handleArguments(args);
if (options == null) {
return;
}
// Connect
final jid = parser.jid;
final to = JID.fromString(options['to']! as String).toBare();
final connection = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ClientToServerNegotiator(),
ExampleTCPSocketWrapper(parser.srvRecord, true),
)..connectionSettings = parser.connectionSettings;
// Generate OMEMO data
omemo.OmemoManager? oom;
final moxxmppOmemo = OmemoManager(
() async => oom!,
(toJid, _) async => toJid == to,
);
oom = omemo.OmemoManager(
await omemo.OmemoDevice.generateNewDevice(jid.toString(), opkAmount: 5),
omemo.BlindTrustBeforeVerificationTrustManager(),
moxxmppOmemo.sendEmptyMessageImpl,
moxxmppOmemo.fetchDeviceList,
moxxmppOmemo.fetchDeviceBundle,
moxxmppOmemo.subscribeToDeviceListImpl,
moxxmppOmemo.publishDeviceImpl,
);
final deviceId = await oom.getDeviceId();
Logger.root.info('Our device id: $deviceId');
// Register the managers and negotiators
await connection.registerManagers([
PresenceManager(),
DiscoManager([]),
PubSubManager(),
MessageManager(),
moxxmppOmemo,
]);
await connection.registerFeatureNegotiators([
SaslPlainNegotiator(),
ResourceBindingNegotiator(),
StartTlsNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
]);
// Set up event handlers
connection.asBroadcastStream().listen((event) {
if (event is MessageEvent) {
Logger.root.info(event.id);
Logger.root.info(event.extensions.keys.toList());
final body = event.encryptionError != null
? chalk.red('Failed to decrypt message: ${event.encryptionError}')
: chalk.green(event.get<MessageBodyData>()?.body ?? '');
print('[${event.from.toString()}] $body');
}
});
// Connect
Logger.root.info('Connecting...');
final result =
await connection.connect(shouldReconnect: false, waitUntilLogin: true);
if (!result.isType<bool>()) {
Logger.root.severe('Authentication failed!');
return;
}
Logger.root.info('Connected.');
// Publish our bundle
Logger.root.info('Publishing bundle');
final device = await oom.getDevice();
final omemoResult = await moxxmppOmemo.publishBundle(await device.toBundle());
if (!omemoResult.isType<bool>()) {
Logger.root.severe(
'Failed to publish OMEMO bundle: ${omemoResult.get<OmemoError>()}');
return;
}
final repl = Repl(prompt: '> ');
await for (final line in repl.runAsync()) {
await connection
.getManagerById<MessageManager>(messageManager)!
.sendMessage(
to,
TypedMap<StanzaHandlerExtension>.fromList([
MessageBodyData(line),
]),
);
}
// Disconnect
await connection.disconnect();
}

View File

@ -0,0 +1,112 @@
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
/// The JID we want to authenticate as.
final xmppUser = JID.fromString('jane@example.com');
/// The password to authenticate with.
const xmppPass = 'secret';
/// The [xmppHost]:[xmppPort] server address to connect to.
/// In a real application, one might prefer to use [TCPSocketWrapper]
/// with a custom DNS implementation to let moxxmpp resolve the XMPP
/// server's address automatically. However, if we just provide a host
/// and a port, then [TCPSocketWrapper] will just skip the resolution and
/// immediately use the provided connection details.
const xmppHost = 'localhost';
const xmppPort = 5222;
void main(List<String> args) async {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
print('${record.level.name}|${record.time}: ${record.message}');
});
// This class manages every aspect of handling the XMPP stream.
final connection = XmppConnection(
// A reconnection policy tells the connection how to handle an error
// while or after connecting to the server. The [TestingReconnectionPolicy]
// immediately triggers a reconnection. In a real implementation, one might
// prefer to use a smarter strategy, like using an exponential backoff.
TestingReconnectionPolicy(),
// A connectivity manager tells the connection when it can connect. This is to
// ensure that we're not constantly trying to reconnect because we have no
// Internet connection. [AlwaysConnectedConnectivityManager] always says that
// we're connected. In a real application, one might prefer to use a smarter
// strategy, like using connectivity_plus to query the system's network connectivity
// state.
AlwaysConnectedConnectivityManager(),
// This kind of negotiator tells the connection how to handle the stream
// negotiations. The [ClientToServerNegotiator] allows to connect to the server
// as a regular client. Another negotiator would be the [ComponentToServerNegotiator] that
// allows for connections to the server where we're acting as a component.
ClientToServerNegotiator(),
// A wrapper around any kind of connection. In this case, we use the [TCPSocketWrapper], which
// uses a dart:io Socket/SecureSocket to connect to the server. If you want, you can also
// provide your own socket to use, for example, WebSockets or any other connection
// mechanism.
TCPSocketWrapper(false),
)..connectionSettings = ConnectionSettings(
jid: xmppUser,
password: xmppPass,
host: xmppHost,
port: xmppPort,
);
// Register a set of "managers" that provide you with implementations of various
// XEPs. Some have interdependencies, which need to be met. However, this example keeps
// it simple and just registers a [MessageManager], which has no required dependencies.
await connection.registerManagers([
// The [MessageManager] handles receiving and sending <message /> stanzas.
MessageManager(),
]);
// Feature negotiators are objects that tell the connection negotiator what stream features
// we can negotiate and enable. moxxmpp negotiators always try to enable their features.
await connection.registerFeatureNegotiators([
// This negotiator authenticates to the server using SASL PLAIN with the provided
// credentials.
SaslPlainNegotiator(),
// This negotiator attempts to bind a resource. By default, it's always a random one.
ResourceBindingNegotiator(),
// This negotiator attempts to do StartTLS before authenticating.
StartTlsNegotiator(),
]);
// Set up a stream handler for the connection's event stream. Managers and negotiators
// may trigger certain events. The [MessageManager], for example, triggers a [MessageEvent]
// whenever a message is received. If other managers are registered that parse a message's
// contents, then they can add their data to the event.
connection.asBroadcastStream().listen((event) {
if (event is! MessageEvent) {
return;
}
// The text body (contents of the <body /> element) are returned as a
// [MessageBodyData] object. However, a message does not have to contain a
// body, so it is nullable.
final body = event.extensions.get<MessageBodyData>()?.body;
print('[<-- ${event.from}] $body');
});
// Connect to the server.
final result = await connection.connect(
// This flag indicates that we want to reconnect in case something happens.
shouldReconnect: true,
// This flag indicates that we want the returned Future to only resolve
// once the stream negotiations are done and no negotiator has any feature left
// to negotiate.
waitUntilLogin: true,
);
// Check if the connection was successful. [connection.connect] can return a boolean
// to indicate success or a [XmppError] in case the connection attempt failed.
if (!result.isType<bool>()) {
print('Failed to connect to server');
return;
}
}

View File

@ -0,0 +1,84 @@
import 'package:args/args.dart';
import 'package:chalkdart/chalk.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
extension StringToInt on String {
int toInt() => int.parse(this);
}
/// A wrapper around [ArgParser] for providing convenience functions and standard parameters
/// to the examples.
class ArgumentParser {
ArgumentParser() {
parser
..addOption('jid', help: 'The JID to connect as')
..addOption('password', help: 'The password to use for authenticating')
..addOption('host',
help:
'The host address to connect to (By default uses the domain part of the JID)')
..addOption('port', help: 'The port to connect to')
..addOption('xmpps-srv',
help:
'Inject a SRV record for _xmpps-client._tcp. Format: <priority>,<weight>,<target>,<port>')
..addFlag('help',
abbr: 'h',
negatable: false,
defaultsTo: false,
help: 'Show this help text');
}
/// The [ArgParser] that handles parsing the arguments.
final ArgParser parser = ArgParser();
/// The parsed options. Only valid after calling [handleArguments].
late ArgResults options;
ArgResults? handleArguments(List<String> args) {
options = parser.parse(args);
if (options['help']!) {
print(parser.usage);
return null;
}
if (options['jid'] == null) {
print(chalk.red('No JID specified'));
print(parser.usage);
return null;
}
if (options['password'] == null) {
print(chalk.red('No password specified'));
print(parser.usage);
return null;
}
return options;
}
/// The JID to connect as.
JID get jid => JID.fromString(options['jid']!).toBare();
/// Construct connection settings from the parsed options.
ConnectionSettings get connectionSettings => ConnectionSettings(
jid: jid,
password: options['password']!,
host: options['host'],
port: (options['port'] as String?)?.toInt(),
);
/// Construct an xmpps-client SRV record for injection, if specified.
MoxSrvRecord? get srvRecord {
if (options['xmpps-srv'] == null) {
return null;
}
final parts = options['xmpps-srv']!.split(',');
return MoxSrvRecord(
int.parse(parts[0]),
int.parse(parts[1]),
parts[2],
int.parse(parts[3]),
);
}
}

View File

@ -0,0 +1,22 @@
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
/// A simple socket for examples that allows injection of SRV records (since
/// we cannot use moxdns here).
class ExampleTCPSocketWrapper extends TCPSocketWrapper {
ExampleTCPSocketWrapper(this.srvRecord, bool logData) : super(logData);
/// A potential SRV record to inject for testing.
final MoxSrvRecord? srvRecord;
@override
bool onBadCertificate(dynamic certificate, String domain) {
return true;
}
@override
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
return [
if (srvRecord != null) srvRecord!,
];
}
}

View File

@ -0,0 +1,35 @@
name: example_dart
description: A collection of samples for moxxmpp.
version: 1.0.0
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
args: 2.4.1
chalkdart: 2.0.9
cli_repl: 0.2.3
logging: ^1.0.2
moxxmpp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.4.0
moxxmpp_socket_tcp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.4.0
omemo_dart:
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
version: ^0.5.1
dependency_overrides:
moxxmpp:
path: ../packages/moxxmpp
moxxmpp_socket_tcp:
path: ../packages/moxxmpp_socket_tcp
omemo_dart:
git:
url: https://github.com/PapaTutuWawa/omemo_dart.git
rev: 49c7e114e6cf80dcde55fbbd218bba3182045862
dev_dependencies:
lints: ^2.0.0
test: ^1.16.0

View File

@ -1,12 +1,74 @@
{
"nodes": {
"flake-utils": {
"android-nixpkgs": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"lastModified": 1727554699,
"narHash": "sha256-puBCNL5PW7Pej+6Srmi2YjEgNeE015NFe33hbkkLqeQ=",
"owner": "tadfisher",
"repo": "android-nixpkgs",
"rev": "bc34ef1c71fe9eafcfb1d637b431fca83d746625",
"type": "github"
},
"original": {
"owner": "tadfisher",
"repo": "android-nixpkgs",
"type": "github"
}
},
"devshell": {
"inputs": {
"nixpkgs": [
"android-nixpkgs",
"nixpkgs"
]
},
"locked": {
"lastModified": 1722113426,
"narHash": "sha256-Yo/3loq572A8Su6aY5GP56knpuKYRvM2a1meP9oJZCw=",
"owner": "numtide",
"repo": "devshell",
"rev": "67cce7359e4cd3c45296fb4aaf6a19e2a9c757ae",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1692799911,
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
"type": "github"
},
"original": {
@ -17,11 +79,27 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1667610399,
"narHash": "sha256-XZd0f4ZWAY0QOoUSdiNWj/eFiKb4B9CJPtl9uO9SYY4=",
"lastModified": 1727348695,
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1dd8696f96db47156e1424a49578fe7dd4ce99a4",
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1727586919,
"narHash": "sha256-e/YXG0tO5GWHDS8QQauj8aj4HhXEm602q9swrrlTlKQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2dcd9c55e8914017226f5948ac22c53872a13ee2",
"type": "github"
},
"original": {
@ -33,8 +111,39 @@
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
"android-nixpkgs": "android-nixpkgs",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},

View File

@ -2,17 +2,19 @@
description = "moxxmpp";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
android-nixpkgs.url = "github:tadfisher/android-nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
outputs = { self, nixpkgs, android-nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
config = {
android_sdk.accept_license = true;
allowUnfree = true;
};
};
# Everything to make Flutter happy
android = pkgs.androidenv.composeAndroidPackages {
# TODO: Find a way to pin these
#toolsVersion = "26.1.1";
@ -29,15 +31,50 @@
useGoogleAPIs = false;
useGoogleTVAddOns = false;
};
pinnedJDK = pkgs.jdk;
lib = pkgs.lib;
pinnedJDK = pkgs.jdk17;
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
pyyaml
requests
]);
moxxmppPubCache = import ./nix/pubcache.moxxmpp.nix {
inherit (pkgs) fetchzip runCommand;
};
in {
devShell = pkgs.mkShell {
packages = {
moxxmppDartDocs = pkgs.callPackage ./nix/moxxmpp-docs.nix {
inherit (moxxmppPubCache) pubCache;
};
};
devShell = let
prosody-newer-community-modules = pkgs.prosody.overrideAttrs (old: {
communityModules = pkgs.fetchhg {
url = "https://hg.prosody.im/prosody-modules";
rev = "e3a3a6c86a9f";
sha256 = "sha256-C2x6PCv0sYuj4/SroDOJLsNPzfeNCodYKbMqmNodFrk=";
};
src = pkgs.fetchhg {
url = "https://hg.prosody.im/trunk";
rev = "8a2f75e38eb2";
sha256 = "sha256-zMNp9+wQ/hvUVyxFl76DqCVzQUPP8GkNdstiTDkG8Hw=";
};
});
prosody-sasl2 = prosody-newer-community-modules.override {
withCommunityModules = [
"sasl2" "sasl2_fast" "sasl2_sm" "sasl2_bind2"
];
};
in pkgs.mkShell {
buildInputs = with pkgs; [
flutter pinnedJDK android.platform-tools dart # Flutter/Android
flutter pinnedJDK android.platform-tools dart # Dart
gitlint # Code hygiene
ripgrep # General utilities
# Flutter dependencies for linux desktop
# Flutter dependencies for Linux desktop
atk
cairo
clang
@ -53,12 +90,38 @@
pkg-config
xorg.libX11
xorg.xorgproto
# For the scripts in ./scripts/
pythonEnv
# For integration testing against a local prosody server
prosody-sasl2
mkcert
];
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ];
ANDROID_SDK_ROOT = "${android.androidsdk}/share/android-sdk";
ANDROID_HOME = "${android.androidsdk}/share/android-sdk";
JAVA_HOME = pinnedJDK;
# Fix an issue with Flutter using an older version of aapt2, which does not know
# an used parameter.
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${android.androidsdk}/share/android-sdk/build-tools/34.0.0/aapt2";
};
apps = {
regenerateNixPackage = let
script = pkgs.writeShellScript "regenerate-nix-package.sh" ''
set -e
${pythonEnv}/bin/python ./scripts/pubspec2lock.py ./packages/moxxmpp/pubspec.lock ./nix/moxxmpp.lock
${pythonEnv}/bin/python ./scripts/lock2nix.py ./nix/moxxmpp.lock ./nix/pubcache.moxxmpp.nix moxxmpp
'';
in {
type = "app";
program = "${script}";
};
};
});
}

6
integration_tests/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build output.
build/

View File

@ -0,0 +1,5 @@
# Integration Tests
The included `./prosody.cfg.lua` config file must be used for integration testing.
Additionally, ensure that a user `testuser@localhost` with the password `abc123`
exists. Note that this currently requires prosody-trunk.

View File

@ -0,0 +1 @@
include: ../analysis_options.yaml

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEAzCCAmugAwIBAgIQd61NPnP8++X7h8a+85C6DjANBgkqhkiG9w0BAQsFADBZ
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExFzAVBgNVBAsMDmFsZXhh
bmRlckBtaWt1MR4wHAYDVQQDDBVta2NlcnQgYWxleGFuZGVyQG1pa3UwHhcNMjMw
NDAyMTM1ODIxWhcNMjUwNzAyMTM1ODIxWjBCMScwJQYDVQQKEx5ta2NlcnQgZGV2
ZWxvcG1lbnQgY2VydGlmaWNhdGUxFzAVBgNVBAsMDmFsZXhhbmRlckBtaWt1MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1DElEXPY+VDQP7cSikK0ne0K
gDgorGYPG9R7lOeuPLHyFYYry78+hB037OT0BOyA2uTu1yrog0dI/4YGicPDIqXh
IgHfjV+4kMi5SgO7ECWOBmZFqTC3bBwvbNtoW40aFjYSFaOkm/nnfp+nalEJJZ/N
kSkD4gdT3pH1ClsovlI4BlsxeIoJtyGzxMidJVXDAqMNraLatzJBwnT3OEs93xTf
7Kd1KUpQp9OZFrGi15zv/n6tCmrcC3xMOVHuYkhW0UCTFmev7ZqbghQsQ9N9s0E6
kk9rUf9xtMNH4Af6+2YRkT1DAGQ6FkXl1nQdB5H5XRgOBl+3k9s8wUrxQvQddQID
AQABo14wXDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYD
VR0jBBgwFoAU54aUZ+dytAOBTsYIdGtSnjiig/gwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MA0GCSqGSIb3DQEBCwUAA4IBgQBU8p7Ua0Cs+lXlWmtCh2j+YF9R+dvc+3Iw
dYEzCmYd375uxPctyHXW0yYjyuH9WuYn0F7OicEFEeC2+exHND+/z0J2Zv5yu34r
SfgHVfvE/Vxisn9InYrUCVtfRwLDF3HgLyIlm8FVzIyiIANhpe6vJdqjEWTsiL2X
I6hoDf1xlRgEqUx+Wxl2IFWrg+1SPPGTQzDPImiRlz8d+9ZJ9v48vaV5+aITMvDP
Gfm/bnNXXd5Gf7nGwL8zFHiwLoYQ5AUYl0IfXYwFAXJ72+LjiRT33IOidVJF0gsQ
6k9cTsc4lIrt4FOzdchalbF1Eu2prieWoZxz0apG8OuUeAhaB+t8kT6swAkwvkLW
OnlSATm9Cls9Pc4XDHTbZlbMmwF2Jmukgz/l1vlTutt4ZgZwQkSEa9Qfoi9Zym0R
iKls1CgD49zguR/cFDKK3agvfv6Afw6HdgaS/WqcI/Ros7b+RCkbAlAG5gqr6BLQ
8RGyVjZSC4Mz/ddcnMEpRAnjuFJjhGA=
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUMSURc9j5UNA/
txKKQrSd7QqAOCisZg8b1HuU5648sfIVhivLvz6EHTfs5PQE7IDa5O7XKuiDR0j/
hgaJw8MipeEiAd+NX7iQyLlKA7sQJY4GZkWpMLdsHC9s22hbjRoWNhIVo6Sb+ed+
n6dqUQkln82RKQPiB1PekfUKWyi+UjgGWzF4igm3IbPEyJ0lVcMCow2totq3MkHC
dPc4Sz3fFN/sp3UpSlCn05kWsaLXnO/+fq0KatwLfEw5Ue5iSFbRQJMWZ6/tmpuC
FCxD032zQTqST2tR/3G0w0fgB/r7ZhGRPUMAZDoWReXWdB0HkfldGA4GX7eT2zzB
SvFC9B11AgMBAAECggEAYaj4yY6LFzxVjG2i79WBsYnOonK2bZpPa9ygwEjdTXwM
0lE9SPoNONsFyVca5EVBjP1+27MY7orZkxlJWxCpeAHmmzNHg5bBqIlpliIfb3AJ
bPKXLyaH1Q8n2K8m2bQYhI6ARktZ0Jv1KrcqY2lGj3V8NEovSlFbDX4ZzJlmKCly
d4Ia6eQ7f9AjgsOwpQGeCTF7WLaVDnch6D4JfCGrW08lFeaqogiBQczsOE3hcNSd
tEul21Z0CkC7Iiw28KdkApPINquo1VYdAcOvUCOXkwJfPC1gsJwK4O2jxfi9v5NF
uU1niK0/00b396pQKvXpkfViynexwzK0MZCoo3zuQQKBgQDzaZexcniQNDyWqN3C
oMe4V3rnxs+aO/lu8Ed3mng+Jf4vuarZlxNot7WRBMGT/T+b7/UIrqRJy50CYAPY
3RRR84tLg3UMwUWhDYsPucNc2icODBG4c+QWJ300W19r+J+iT8PwS9AbH2n094Rn
LCRYFrX5aMsgIH5uwuncKzweMQKBgQDfKj2i1ptC53aOcr1tMCFYcnMGtaAZ8u6+
cKSgnzKlTw/g0EYlGcETUnCyZe0oVYWp3y859FBXU0JMDmxu84aYEZNF6BwRVlpF
feQgtUFZHyf9MepQGhjIJ5El8n7jhh1bsBY18QbDFe6/GtqPx/mQEF7vE+wPFl9h
putwdv3OhQKBgGKPyi2/BVSW4kW7IPiTM+vP+GNrnFp+mHS0dKvYb4HyzmcyzhyH
UQOhB7Mt8thivmP9GQIn/TwoZ24zxLsGYhkA/dFY7Id6pyAcpMd8V7/8Ub4dYvuG
acASw1709MF6jeEiXVuqxxyEbtoTc5h3Rkwo/gx8w2tB3RAqepl9JD2xAoGAfVL3
ci8a2iOqTKza/Cp/T3BWcHonAuuOb5xKl3lPs84GmLXd7o/cAcHWUBk1aeU9Pvx7
RQyS4bd8D8I52sUf3N5h2mxS9tmLsGLWbhfcLvR0PJh/gaRmLmEp/imEYLm8WvU0
Q+6rYXs7rE6kVwJygBjxd0m003Q49FoM9gec2RECgYEA5SLAe2UmJSLIb0DKk27o
nSfARDSdi9N40vIjDFHmDRdKTOYicED/f7KqXnxVpvFxDdCvJ7xeC4V7vkaqiiwd
/oMLQq0GjmBxG/PNd1AFIWDydyH+JcY6U4XWIzIw92OKVYC/KMvd2f9orTfmDyAU
RsGMfgV90kCzouAZKy3yPmo=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
set -ex
prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123
prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123

View File

@ -0,0 +1,62 @@
admins = { }
plugin_paths = {}
modules_enabled = {
-- Generally required
"disco"; -- Service discovery
"roster"; -- Allow users to have a roster. Recommended ;)
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
-- Not essential, but recommended
"blocklist"; -- Allow users to block communications with other users
"bookmarks"; -- Synchronise the list of open rooms between clients
"carbons"; -- Keep multiple online clients in sync
"dialback"; -- Support for verifying remote servers using DNS
"limits"; -- Enable bandwidth limiting for XMPP connections
"pep"; -- Allow users to store public and private data in their account
"private"; -- Legacy account storage mechanism (XEP-0049)
"smacks"; -- Stream management and resumption (XEP-0198)
"vcard4"; -- User profiles (stored in PEP)
"vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
-- Nice to have
"csi_simple"; -- Simple but effective traffic optimizations for mobile devices
"invites"; -- Create and manage invites
"invites_adhoc"; -- Allow admins/users to create invitations via their client
"invites_register"; -- Allows invited users to create accounts
"ping"; -- Replies to XMPP pings with pongs
"register"; -- Allow users to register on this server using a client and change passwords
"time"; -- Let others know the time here on this server
"uptime"; -- Report how long server has been running
"version"; -- Replies to server version requests
-- SASL2
"sasl2";
"sasl2_sm";
"sasl2_fast";
"sasl2_bind2";
}
s2s_secure_auth = false
-- Authentication
authentication = "internal_plain"
-- Storage
storage = "internal"
data_path = "/tmp/prosody-data/"
log = {
debug = "*console";
}
pidfile = "/tmp/prosody.pid"
component_ports = { 8888 }
component_interfaces = { '127.0.0.1' }
VirtualHost "localhost"
Component "component.localhost"
component_secret = "abc123"
Component "muc.localhost" "muc"

View File

@ -0,0 +1,18 @@
name: integration_tests
description: A sample command-line application.
version: 1.0.0
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
logging: ^1.3.0
moxxmpp:
path: ../packages/moxxmpp
moxxmpp_socket_tcp:
path: ../packages/moxxmpp_socket_tcp
dev_dependencies:
build_runner: ^2.4.13
test: ^1.25.8
very_good_analysis: ^6.0.0

View File

@ -0,0 +1,44 @@
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
import 'package:test/test.dart';
class TestingTCPSocketWrapper extends TCPSocketWrapper {
TestingTCPSocketWrapper() : super(true);
@override
bool onBadCertificate(dynamic certificate, String domain) {
return true;
}
}
void main() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print(
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
);
});
test('Test connecting to prosody as a component', () async {
final conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ComponentToServerNegotiator(),
TestingTCPSocketWrapper(),
)..connectionSettings = ConnectionSettings(
jid: JID.fromString('component.localhost'),
password: 'abc123',
host: '127.0.0.1',
port: 8888,
);
final result = await conn.connect(
waitUntilLogin: true,
shouldReconnect: false,
enableReconnectOnSuccess: false,
);
expect(result.isType<bool>(), true);
});
}

View File

@ -0,0 +1,77 @@
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
import 'package:test/test.dart';
class TestingTCPSocketWrapper extends TCPSocketWrapper {
TestingTCPSocketWrapper() : super(true);
@override
bool onBadCertificate(dynamic certificate, String domain) {
return true;
}
}
void main() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print(
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
);
});
test('Test authenticating against Prosody with SASL2, Bind2, and FAST',
() async {
final conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ClientToServerNegotiator(),
TestingTCPSocketWrapper(),
)..connectionSettings = ConnectionSettings(
jid: JID.fromString('testuser1@localhost'),
password: 'abc123',
host: '127.0.0.1',
port: 5222,
);
final csi = CSIManager();
await csi.setInactive(sendNonza: false);
await conn.registerManagers([
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
]);
await conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
SaslScramNegotiator(9, '', '', ScramHashType.sha1),
SaslScramNegotiator(10, '', '', ScramHashType.sha256),
ResourceBindingNegotiator(),
FASTSaslNegotiator(),
Bind2Negotiator(),
StartTlsNegotiator(),
Sasl2Negotiator()
..userAgent = const UserAgent(
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
software: 'moxxmpp',
device: "PapaTutuWawa's awesome device",
),
]);
final result = await conn.connect(
waitUntilLogin: true,
shouldReconnect: false,
enableReconnectOnSuccess: false,
);
expect(result.isType<bool>(), true);
expect(
conn.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!.state,
NegotiatorState.done,
);
expect(
conn
.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!
.fastToken !=
null,
true,
);
});
}

35
nix/moxxmpp-docs.nix Normal file
View File

@ -0,0 +1,35 @@
{
stdenv
, pubCache
, dart
, lib
}:
stdenv.mkDerivation {
pname = "moxxmpp-docs";
version = "0.3.1";
PUB_CACHE = "${pubCache}";
src = "${./..}/packages/moxxmpp";
buildPhase = ''
runHook preBuild
(
set -x
echo $PUB_CACHE
${dart}/bin/dart pub get --no-precompile --offline
)
runHook postBuild
'';
installPhase = ''
runHook preInstall
${dart}/bin/dart doc -o $out
runHook postInstall
'';
}

813
nix/moxxmpp.lock Normal file
View File

@ -0,0 +1,813 @@
# CREATED BY pubspec2lock.py
# DO NOT EDIT BY HAND
packages:
_fe_analyzer_shared:
archive_url: https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/61.0.0.tar.gz
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
url: https://pub.dev
sha256: 15fh9ka41dw4qsynv07msq4i243fibprcmafdygw5x88f7m55fq3
source: hosted
version: 61.0.0
analyzer:
archive_url: https://pub.dartlang.org/packages/analyzer/versions/5.13.0.tar.gz
dependency: transitive
description:
name: analyzer
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
url: https://pub.dev
sha256: 0w604zngxwfx0xqxvhbxrhdh04wgm6ad6a1lbwnyvmk57amv44np
source: hosted
version: 5.13.0
args:
archive_url: https://pub.dartlang.org/packages/args/versions/2.4.2.tar.gz
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: https://pub.dev
sha256: 01ps253280c6dbx0vncw4wga4l2qp1zx779qjj2x06xzb3744zbz
source: hosted
version: 2.4.2
async:
archive_url: https://pub.dartlang.org/packages/async/versions/2.11.0.tar.gz
dependency: transitive
description:
name: async
sha256: 947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c
url: https://pub.dev
sha256: 0hfgvjajp5c2mw68186hgrk9v5zjhhi149hlhl0fap274p2v1g3q
source: hosted
version: 2.11.0
boolean_selector:
archive_url: https://pub.dartlang.org/packages/boolean_selector/versions/2.1.1.tar.gz
dependency: transitive
description:
name: boolean_selector
sha256: 6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66
url: https://pub.dev
sha256: 0hxq8072hb89q9s91xlz9fvrjxfy7hw6jkdwkph5dp77df841kmj
source: hosted
version: 2.1.1
build:
archive_url: https://pub.dartlang.org/packages/build/versions/2.3.1.tar.gz
dependency: transitive
description:
name: build
sha256: 3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777
url: https://pub.dev
sha256: 1x6nkii6kqy6y7ck0151yfhc9lp2nvbhznnhdi2mxr8afk6jxigd
source: hosted
version: 2.3.1
build_config:
archive_url: https://pub.dartlang.org/packages/build_config/versions/1.1.1.tar.gz
dependency: transitive
description:
name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
url: https://pub.dev
sha256: 092rrbhbdy9fk50jqb1fwj1sfk415fi43irvsd0hk5w90gn8vazj
source: hosted
version: 1.1.1
build_daemon:
archive_url: https://pub.dartlang.org/packages/build_daemon/versions/3.1.1.tar.gz
dependency: transitive
description:
name: build_daemon
sha256: 757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169
url: https://pub.dev
sha256: 1wn7bq846vgdj62bkh9h25l95xdsndv0jdyw52nyr0591l3bpg3h
source: hosted
version: 3.1.1
build_resolvers:
archive_url: https://pub.dartlang.org/packages/build_resolvers/versions/2.3.1.tar.gz
dependency: transitive
description:
name: build_resolvers
sha256: a7417cc44d9edb3f2c8760000270c99dba8c72ff66d0146772b8326565780745
url: https://pub.dev
sha256: 00h9abhrfmnl0xxziyf6p68sxnbv2ww1c4dhgpnz00mzbmamnq5c
source: hosted
version: 2.3.1
build_runner:
archive_url: https://pub.dartlang.org/packages/build_runner/versions/2.3.3.tar.gz
dependency: direct dev
description:
name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
url: https://pub.dev
sha256: 0b5ha1l6k0gn2swqgqvfy2vl58klf81sxrjnmk0p7rj1wzbqjm7l
source: hosted
version: 2.3.3
build_runner_core:
archive_url: https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7%2B1.tar.gz
dependency: transitive
description:
name: build_runner_core
sha256: 0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e
url: https://pub.dev
sha256: 07r1kfy6ylm4i4xrb24ns8l26h4h1lgcskmnf8wvq2rd5d5hq790
source: hosted
version: 7.2.7+1
built_collection:
archive_url: https://pub.dartlang.org/packages/built_collection/versions/5.1.1.tar.gz
dependency: transitive
description:
name: built_collection
sha256: 376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100
url: https://pub.dev
sha256: 0bqjahxr42q84w91nhv3n4cr580l3s3ffx3vgzyyypgqnrck0hv3
source: hosted
version: 5.1.1
built_value:
archive_url: https://pub.dartlang.org/packages/built_value/versions/8.6.2.tar.gz
dependency: transitive
description:
name: built_value
sha256: ff627b645b28fb8bdb69e645f910c2458fd6b65f6585c3a53e0626024897dedf
url: https://pub.dev
sha256: 1y84imf9xqqy3gnd5zz9bcln6mycy7qx35r70b0izm31ismlbzkv
source: hosted
version: 8.6.2
checked_yaml:
archive_url: https://pub.dartlang.org/packages/checked_yaml/versions/2.0.3.tar.gz
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: https://pub.dev
sha256: 1sn01yrmj0pkijn08g3v45c3zmyvdygk9svigkkzybgicdwlkpqs
source: hosted
version: 2.0.3
code_builder:
archive_url: https://pub.dartlang.org/packages/code_builder/versions/4.6.0.tar.gz
dependency: transitive
description:
name: code_builder
sha256: 315a598c7fbe77f22de1c9da7cfd6fd21816312f16ffa124453b4fc679e540f1
url: https://pub.dev
sha256: 1shgl7mxiyv0hhw326yqj2b9jxi1h74qxmsnxf1d1xc6yz766p9a
source: hosted
version: 4.6.0
collection:
archive_url: https://pub.dartlang.org/packages/collection/versions/1.18.0.tar.gz
dependency: direct main
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: https://pub.dev
sha256: 1mr8j0078c4z9hhckiq8m735rggsazwfprm0w9gisil51vh7j2mk
source: hosted
version: 1.18.0
convert:
archive_url: https://pub.dartlang.org/packages/convert/versions/3.1.1.tar.gz
dependency: transitive
description:
name: convert
sha256: 0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592
url: https://pub.dev
sha256: 0adsigjk3l1c31i6k91p28dqyjlgwiqrs4lky5djrm2scf8k6cri
source: hosted
version: 3.1.1
coverage:
archive_url: https://pub.dartlang.org/packages/coverage/versions/1.6.3.tar.gz
dependency: transitive
description:
name: coverage
sha256: 2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097
url: https://pub.dev
sha256: 1yy9bgkax5b6kk7qa07p452v82fyj4rl1j03fn366ywyvhfrh6lp
source: hosted
version: 1.6.3
crypto:
archive_url: https://pub.dartlang.org/packages/crypto/versions/3.0.3.tar.gz
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: https://pub.dev
sha256: 100ai8qa4p3dyvvd60c4xa9p0gm06yh0d68xgcfm3giraad8xmqj
source: hosted
version: 3.0.3
cryptography:
archive_url: https://pub.dartlang.org/packages/cryptography/versions/2.5.0.tar.gz
dependency: direct main
description:
name: cryptography
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
url: https://pub.dev
sha256: 1yxn9slqq93ri81fbr2nbsinz0mpk9wk39ny076ja8q31d4i8v3f
source: hosted
version: 2.5.0
dart_style:
archive_url: https://pub.dartlang.org/packages/dart_style/versions/2.3.2.tar.gz
dependency: transitive
description:
name: dart_style
sha256: 1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55
url: https://pub.dev
sha256: 0cjhrb1hs8iw9smmfd0fgnxq3nm0w8sz17l6q6svyz6kif19wk9k
source: hosted
version: 2.3.2
file:
archive_url: https://pub.dartlang.org/packages/file/versions/6.1.4.tar.gz
dependency: transitive
description:
name: file
sha256: 1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d
url: https://pub.dev
sha256: 0ajcfblf8d4dicp1sgzkbrhd0b0v0d8wl70jsnf5drjck3p3ppk7
source: hosted
version: 6.1.4
fixnum:
archive_url: https://pub.dartlang.org/packages/fixnum/versions/1.1.0.tar.gz
dependency: transitive
description:
name: fixnum
sha256: 25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1
url: https://pub.dev
sha256: 0nqrzj41ys8dpxf1x70r0kfj1avj0f2j2b7498k8kvc0i9c5asz7
source: hosted
version: 1.1.0
frontend_server_client:
archive_url: https://pub.dartlang.org/packages/frontend_server_client/versions/3.2.0.tar.gz
dependency: transitive
description:
name: frontend_server_client
sha256: 408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612
url: https://pub.dev
sha256: 096v7ycix5hgnk750s1qgykyghl2mymhdkg39jrlk3kbj6xygq5b
source: hosted
version: 3.2.0
glob:
archive_url: https://pub.dartlang.org/packages/glob/versions/2.1.2.tar.gz
dependency: transitive
description:
name: glob
sha256: 0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63
url: https://pub.dev
sha256: 0ffab3azx8zkma36mk6wnig8bn8g5g0vjrq2gl21y77rxgw9iqxj
source: hosted
version: 2.1.2
graphs:
archive_url: https://pub.dartlang.org/packages/graphs/versions/2.3.1.tar.gz
dependency: transitive
description:
name: graphs
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: https://pub.dev
sha256: 0fda0j8y6sq1rc9zpzglrzysl5h49y2ji1wq2lq0wx2c609dxm7f
source: hosted
version: 2.3.1
hex:
archive_url: https://pub.dartlang.org/packages/hex/versions/0.2.0.tar.gz
dependency: direct main
description:
name: hex
sha256: 4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a
url: https://pub.dev
sha256: 19w3f90mdiy06a6kf8hlwc4jn4cxixkj106kc3g3bis27ar7smkh
source: hosted
version: 0.2.0
http_multi_server:
archive_url: https://pub.dartlang.org/packages/http_multi_server/versions/3.2.1.tar.gz
dependency: transitive
description:
name: http_multi_server
sha256: 97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b
url: https://pub.dev
sha256: 1zdcm04z85jahb2hs7qs85rh974kw49hffhy9cn1gfda3077dvql
source: hosted
version: 3.2.1
http_parser:
archive_url: https://pub.dartlang.org/packages/http_parser/versions/4.0.2.tar.gz
dependency: transitive
description:
name: http_parser
sha256: 2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b
url: https://pub.dev
sha256: 027c4sjkhkkx3sk1aqs6s4djb87syi9h521qpm1bf21bq3gga5jd
source: hosted
version: 4.0.2
io:
archive_url: https://pub.dartlang.org/packages/io/versions/1.0.4.tar.gz
dependency: transitive
description:
name: io
sha256: 2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e
url: https://pub.dev
sha256: 101kd0rw26vglmr1m5p130kbrp3k7dk4p5nr77wsbwgg53w8c0d4
source: hosted
version: 1.0.4
js:
archive_url: https://pub.dartlang.org/packages/js/versions/0.6.7.tar.gz
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: https://pub.dev
sha256: 124a9yqrjdw3p4nnirab9hm9ziwraldlw4q5cb3sr0dcrli74qpw
source: hosted
version: 0.6.7
json_annotation:
archive_url: https://pub.dartlang.org/packages/json_annotation/versions/4.8.1.tar.gz
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: https://pub.dev
sha256: 1jjw7p8qyqajgdq4jqvxipq5w0qrq9dpi1qmia70pk995akryh6m
source: hosted
version: 4.8.1
json_serializable:
archive_url: https://pub.dartlang.org/packages/json_serializable/versions/6.6.2.tar.gz
dependency: direct main
description:
name: json_serializable
sha256: 43793352f90efa5d8b251893a63d767b2f7c833120e3cc02adad55eefec04dc7
url: https://pub.dev
sha256: 1pmidql9x6s2pbhdx9x20pwqwvwpfkvrz0h0cm1f8pqis76c90hb
source: hosted
version: 6.6.2
logging:
archive_url: https://pub.dartlang.org/packages/logging/versions/1.2.0.tar.gz
dependency: direct main
description:
name: logging
sha256: 623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340
url: https://pub.dev
sha256: 124hfjs66r30p92ndfmy5fymgy66yk9in97h8sq6fi7r78pqyc7g
source: hosted
version: 1.2.0
matcher:
archive_url: https://pub.dartlang.org/packages/matcher/versions/0.12.16.tar.gz
dependency: transitive
description:
name: matcher
sha256: 1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e
url: https://pub.dev
sha256: 0inznqkrxqnq09lcbwvda3xd07qfm1k3aa6dv1wy39gvci8hybss
source: hosted
version: 0.12.16
meta:
archive_url: https://pub.dartlang.org/packages/meta/versions/1.9.1.tar.gz
dependency: direct main
description:
name: meta
sha256: 3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3
url: https://pub.dev
sha256: 1l3zaz6q2s9mnm7s674xshsfqspy79p5kdbbnc99rf2l76avv4h3
source: hosted
version: 1.9.1
mime:
archive_url: https://pub.dartlang.org/packages/mime/versions/1.0.4.tar.gz
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: https://pub.dev
sha256: 1dha9z64bsz8xhi0p62vmlyikr8xwbdlrw90hxghmm3rdgd9h25w
source: hosted
version: 1.0.4
moxlib:
archive_url: https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.2.0.tar.gz
dependency: direct main
description:
name: moxlib
sha256: 2a76a632d23ea73906964cee4463352995e40199036162217ea323a6c3846e73
url: https://git.polynom.me/api/packages/Moxxy/pub/
sha256: 1qaacmcqhq33grn2nq8sn23ki62dcmw0fqy589xm1zv6w0pzfmsk
source: hosted
version: 0.2.0
node_preamble:
archive_url: https://pub.dartlang.org/packages/node_preamble/versions/2.0.2.tar.gz
dependency: transitive
description:
name: node_preamble
sha256: 6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db
url: https://pub.dev
sha256: 12ajg76r9aqmqkavvlxbnb3sszg1szcq3f30badkd0xc25mnhyh8
source: hosted
version: 2.0.2
omemo_dart:
archive_url: https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.5.1.tar.gz
dependency: direct main
description:
name: omemo_dart
sha256: 61f4695f105bc72663bd804350fc9cdb14c81e95fc190137a8344280e70fbc00
url: https://git.polynom.me/api/packages/PapaTutuWawa/pub/
sha256: 0fhf89ic5mdyld25l6rfb37a1fk1f0f2b4d72xi4r7pvr0ddjhz8
source: hosted
version: 0.5.1
package_config:
archive_url: https://pub.dartlang.org/packages/package_config/versions/2.1.0.tar.gz
dependency: transitive
description:
name: package_config
sha256: 1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd
url: https://pub.dev
sha256: 1d4l0i4cby344zj45f5shrg2pkw1i1jn03kx0qqh0l7gh1ha7bpc
source: hosted
version: 2.1.0
path:
archive_url: https://pub.dartlang.org/packages/path/versions/1.8.3.tar.gz
dependency: transitive
description:
name: path
sha256: 8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917
url: https://pub.dev
sha256: 1mjdhq2fsz6i9krhp2mnaks2bcw34sa4p7mg0v6njk8dgx2754iv
source: hosted
version: 1.8.3
pedantic:
archive_url: https://pub.dartlang.org/packages/pedantic/versions/1.11.1.tar.gz
dependency: transitive
description:
name: pedantic
sha256: 67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602
url: https://pub.dev
sha256: 10ch0h3hi6cfwiz2ihfkh6m36m75c0m7fd0wwqaqggffsj2dn8ad
source: hosted
version: 1.11.1
petitparser:
archive_url: https://pub.dartlang.org/packages/petitparser/versions/5.4.0.tar.gz
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: https://pub.dev
sha256: 19zqrpb1z77aw1k2s8rsxdfxczzv9934g2rdfj2jyiv3pqgdq8gh
source: hosted
version: 5.4.0
pinenacl:
archive_url: https://pub.dartlang.org/packages/pinenacl/versions/0.5.1.tar.gz
dependency: transitive
description:
name: pinenacl
sha256: 3a5503637587d635647c93ea9a8fecf48a420cc7deebe6f1fc85c2a5637ab327
url: https://pub.dev
sha256: 0didjgva658z90hbcmhd0y8w1b8v86dp6gabfhylnw1aixl47cxg
source: hosted
version: 0.5.1
pool:
archive_url: https://pub.dartlang.org/packages/pool/versions/1.5.1.tar.gz
dependency: transitive
description:
name: pool
sha256: 20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a
url: https://pub.dev
sha256: 0wmzs46hjszv3ayhr1p5l7xza7q9rkg2q9z4swmhdqmhlz3c50x4
source: hosted
version: 1.5.1
protobuf:
archive_url: https://pub.dartlang.org/packages/protobuf/versions/2.1.0.tar.gz
dependency: transitive
description:
name: protobuf
sha256: 01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08
url: https://pub.dev
sha256: 1jriyisf8bnvq5ygjk93mn2yzdlnii7xrhy6aabz54xr3y4dcy9x
source: hosted
version: 2.1.0
protoc_plugin:
archive_url: https://pub.dartlang.org/packages/protoc_plugin/versions/20.0.1.tar.gz
dependency: transitive
description:
name: protoc_plugin
sha256: e2be5014ba145dc0f8de20ac425afa2a513aff64fe350d338e481d40de0573df
url: https://pub.dev
sha256: 0hjjd1xkv4s4g1d5n2aza0kdwlbfl2aivq99230m3yml7irn00jk
source: hosted
version: 20.0.1
pub_semver:
archive_url: https://pub.dartlang.org/packages/pub_semver/versions/2.1.4.tar.gz
dependency: transitive
description:
name: pub_semver
sha256: 40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c
url: https://pub.dev
sha256: 0wpcfz1crxipbjm18m71pl4vl2ra8vw1n93ff8snr54mmlyfb9z1
source: hosted
version: 2.1.4
pubspec_parse:
archive_url: https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.3.tar.gz
dependency: transitive
description:
name: pubspec_parse
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: https://pub.dev
sha256: 0dj8sf1w61g7vh1ly3sl690z0nwllzjzbapxswmgsglr0ndcyrs1
source: hosted
version: 1.2.3
random_string:
archive_url: https://pub.dartlang.org/packages/random_string/versions/2.3.1.tar.gz
dependency: direct main
description:
name: random_string
sha256: 03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02
url: https://pub.dev
sha256: 11cjiv75sgldvk3x7w6j77lgi08r6737wm94m3ylabylsr6zdyff
source: hosted
version: 2.3.1
saslprep:
archive_url: https://pub.dartlang.org/packages/saslprep/versions/1.0.2.tar.gz
dependency: direct main
description:
name: saslprep
sha256: 79c9e163a82f55da542feaf0f7a59031e74493299c92008b2b404cd88d639bb4
url: https://pub.dev
sha256: 04lss0xvm6p801p8306jdxg7k0b28kr6n65dz2f57dkca237kcw7
source: hosted
version: 1.0.2
shelf:
archive_url: https://pub.dartlang.org/packages/shelf/versions/1.4.1.tar.gz
dependency: transitive
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: https://pub.dev
sha256: 10yk98nadrgj5d3r3241kdaywjjs1j10mg8gacv80kg1mhcfdrxp
source: hosted
version: 1.4.1
shelf_packages_handler:
archive_url: https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.2.tar.gz
dependency: transitive
description:
name: shelf_packages_handler
sha256: 89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e
url: https://pub.dev
sha256: 1h8s42nff9ar0xn7yb42m64lpvmqzq8wranqrkkixdnp7w3pmv1x
source: hosted
version: 3.0.2
shelf_static:
archive_url: https://pub.dartlang.org/packages/shelf_static/versions/1.1.2.tar.gz
dependency: transitive
description:
name: shelf_static
sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
url: https://pub.dev
sha256: 1bcqynn2z2syrigmrclxgg8hjhd1x9742938i62cicbaga6vclaz
source: hosted
version: 1.1.2
shelf_web_socket:
archive_url: https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.4.tar.gz
dependency: transitive
description:
name: shelf_web_socket
sha256: 9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1
url: https://pub.dev
sha256: 110b5hrqwpnmq16shxxzjmcih5yfs5kh80dn8avfv0xj5iv7n94c
source: hosted
version: 1.0.4
source_gen:
archive_url: https://pub.dartlang.org/packages/source_gen/versions/1.3.2.tar.gz
dependency: transitive
description:
name: source_gen
sha256: 373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33
url: https://pub.dev
sha256: 1jql5zccv4vnbbvwcpyyvz8l27pg1rviqbp4vrks5313nf4b0kjg
source: hosted
version: 1.3.2
source_helper:
archive_url: https://pub.dartlang.org/packages/source_helper/versions/1.3.4.tar.gz
dependency: transitive
description:
name: source_helper
sha256: 6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd
url: https://pub.dev
sha256: 0mdd02vhcdcv9n58gzbx2q0bphwj0alz312ca1a8xpkf8jx3y8v4
source: hosted
version: 1.3.4
source_map_stack_trace:
archive_url: https://pub.dartlang.org/packages/source_map_stack_trace/versions/2.1.1.tar.gz
dependency: transitive
description:
name: source_map_stack_trace
sha256: 84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae
url: https://pub.dev
sha256: 0b5d4c5n5qd3j8n10gp1khhr508wfl3819bhk6xnl34qxz8n032k
source: hosted
version: 2.1.1
source_maps:
archive_url: https://pub.dartlang.org/packages/source_maps/versions/0.10.12.tar.gz
dependency: transitive
description:
name: source_maps
sha256: 708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703
url: https://pub.dev
sha256: 004lcfka01agxjdw7zjhrffdkisvgx5s61b5gsl8qqk2jd1rswa7
source: hosted
version: 0.10.12
source_span:
archive_url: https://pub.dartlang.org/packages/source_span/versions/1.10.0.tar.gz
dependency: transitive
description:
name: source_span
sha256: 53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c
url: https://pub.dev
sha256: 1nybnf7l5chslp4fczhqnrgrhymy844lw7qrj6y08i626dshrd46
source: hosted
version: 1.10.0
stack_trace:
archive_url: https://pub.dartlang.org/packages/stack_trace/versions/1.11.1.tar.gz
dependency: transitive
description:
name: stack_trace
sha256: 73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b
url: https://pub.dev
sha256: 0xpk2cvmgdh46iwip9jsb54fqx13jnina8pk03akxkmsxvag5izb
source: hosted
version: 1.11.1
stream_channel:
archive_url: https://pub.dartlang.org/packages/stream_channel/versions/2.1.2.tar.gz
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: https://pub.dev
sha256: 0nrlw6zcscgnn6818krkbgs9qiv3f7q8pa7ljw1bqkrsb7xabm8s
source: hosted
version: 2.1.2
stream_transform:
archive_url: https://pub.dartlang.org/packages/stream_transform/versions/2.1.0.tar.gz
dependency: transitive
description:
name: stream_transform
sha256: 14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f
url: https://pub.dev
sha256: 0jq6767v9ds17i2nd6mdd9i0f7nvsgg3dz74d0v54x66axjgr0gp
source: hosted
version: 2.1.0
string_scanner:
archive_url: https://pub.dartlang.org/packages/string_scanner/versions/1.2.0.tar.gz
dependency: transitive
description:
name: string_scanner
sha256: 556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde
url: https://pub.dev
sha256: 0p1r0v2923avwfg03rk0pmc6f21m0zxpcx6i57xygd25k6hdfi00
source: hosted
version: 1.2.0
synchronized:
archive_url: https://pub.dartlang.org/packages/synchronized/versions/3.1.0.tar.gz
dependency: direct main
description:
name: synchronized
sha256: 5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60
url: https://pub.dev
sha256: 1fx1z1p5qkn4qnq24riw5s86vmq645ppg8f74iyv2fc9rvr301ar
source: hosted
version: 3.1.0
term_glyph:
archive_url: https://pub.dartlang.org/packages/term_glyph/versions/1.2.1.tar.gz
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: https://pub.dev
sha256: 1x8nspxaccls0sxjamp703yp55yxdvhj6wg21lzwd296i9rwlxh9
source: hosted
version: 1.2.1
test:
archive_url: https://pub.dartlang.org/packages/test/versions/1.24.3.tar.gz
dependency: direct dev
description:
name: test
sha256: 13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46
url: https://pub.dev
sha256: 002phlj2pg6nll5hv449izxbqk29zwmwc77d0jx2iimz18dgy2r5
source: hosted
version: 1.24.3
test_api:
archive_url: https://pub.dartlang.org/packages/test_api/versions/0.6.0.tar.gz
dependency: transitive
description:
name: test_api
sha256: 75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8
url: https://pub.dev
sha256: 0as1xcywjrd2zax3cm56qmnac12shf8c1ynnzzjwnggm23f61dxb
source: hosted
version: 0.6.0
test_core:
archive_url: https://pub.dartlang.org/packages/test_core/versions/0.5.3.tar.gz
dependency: transitive
description:
name: test_core
sha256: 99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e
url: https://pub.dev
sha256: 1cx2rmz1xzk5z5yh8fpbsrsz4mgjanrw4xvnp0qzdnm2d7vhaq0y
source: hosted
version: 0.5.3
timing:
archive_url: https://pub.dartlang.org/packages/timing/versions/1.0.1.tar.gz
dependency: transitive
description:
name: timing
sha256: 70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32
url: https://pub.dev
sha256: 15jvxsw7v0gwbdlykma60l1qlhlzb3brh6m0sg2bgbfir4l5s9gw
source: hosted
version: 1.0.1
typed_data:
archive_url: https://pub.dartlang.org/packages/typed_data/versions/1.3.2.tar.gz
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: https://pub.dev
sha256: 0q6ggc52vfpr8kqaq69h757wy942hvgshhnsr2pjdinb4zk2sxl1
source: hosted
version: 1.3.2
unorm_dart:
archive_url: https://pub.dartlang.org/packages/unorm_dart/versions/0.2.0.tar.gz
dependency: transitive
description:
name: unorm_dart
sha256: 5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b
url: https://pub.dev
sha256: 05kyk2764yz14pzgx00i7h5b1lzh8kjqnxspfzyf8z920bcgbz0v
source: hosted
version: 0.2.0
uuid:
archive_url: https://pub.dartlang.org/packages/uuid/versions/3.0.7.tar.gz
dependency: direct main
description:
name: uuid
sha256: 648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313
url: https://pub.dev
sha256: 1nh1hxfr6bhyadqqcxrpwrphmm75f1iq4rzfjdwa2486xwlh7vx3
source: hosted
version: 3.0.7
very_good_analysis:
archive_url: https://pub.dartlang.org/packages/very_good_analysis/versions/3.1.0.tar.gz
dependency: direct dev
description:
name: very_good_analysis
sha256: 4815adc7ded57657038d2bb2a7f332c50e3c8152f7d3c6acf8f6b7c0cc81e5e2
url: https://pub.dev
sha256: 1p2dh8aahbqyyqfzbsxswafgxnmxgisjq2xfp008skyh7imk6sz4
source: hosted
version: 3.1.0
vm_service:
archive_url: https://pub.dartlang.org/packages/vm_service/versions/11.10.0.tar.gz
dependency: transitive
description:
name: vm_service
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
url: https://pub.dev
sha256: 15ail7rbaq9ksg73cc0mw2k5imbiidl95yfd4v49k81gp5xmj92w
source: hosted
version: 11.10.0
watcher:
archive_url: https://pub.dartlang.org/packages/watcher/versions/1.0.2.tar.gz
dependency: transitive
description:
name: watcher
sha256: 6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0
url: https://pub.dev
sha256: 1sk7gvwa7s0h4l652qrgbh7l8wyqc6nr6lki8m4rj55720p0fnyg
source: hosted
version: 1.0.2
web_socket_channel:
archive_url: https://pub.dartlang.org/packages/web_socket_channel/versions/2.4.0.tar.gz
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: https://pub.dev
sha256: 0f9441c4zifb5qadpjg319dcilimpkdhfacnkl543802bf8qjn4w
source: hosted
version: 2.4.0
webkit_inspection_protocol:
archive_url: https://pub.dartlang.org/packages/webkit_inspection_protocol/versions/1.2.0.tar.gz
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: 67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d
url: https://pub.dev
sha256: 0z400dzw7gf68a3wm95xi2mf461iigkyq6x69xgi7qs3fvpmn3hx
source: hosted
version: 1.2.0
xml:
archive_url: https://pub.dartlang.org/packages/xml/versions/6.3.0.tar.gz
dependency: direct main
description:
name: xml
sha256: 5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84
url: https://pub.dev
sha256: 120azx71gazvrrn07vd83vrffzrhsqnmf9rdjxl73rra9py8ixiy
source: hosted
version: 6.3.0
yaml:
archive_url: https://pub.dartlang.org/packages/yaml/versions/3.1.2.tar.gz
dependency: transitive
description:
name: yaml
sha256: 75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5
url: https://pub.dev
sha256: 0awh9dynbhrlq8zgszaiyxbyyn9b6wyps1zww4z2lx62nbma0pda
source: hosted
version: 3.1.2

816
nix/pubcache.moxxmpp.nix Normal file
View File

@ -0,0 +1,816 @@
# GENERATED BY LOCK2NIX.py
# DO NOT EDIT BY HAND
{fetchzip, runCommand} : rec {
_fe_analyzer_shared = fetchzip {
sha256 = "15fh9ka41dw4qsynv07msq4i243fibprcmafdygw5x88f7m55fq3";
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/61.0.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
analyzer = fetchzip {
sha256 = "0w604zngxwfx0xqxvhbxrhdh04wgm6ad6a1lbwnyvmk57amv44np";
url = "https://pub.dartlang.org/packages/analyzer/versions/5.13.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
args = fetchzip {
sha256 = "01ps253280c6dbx0vncw4wga4l2qp1zx779qjj2x06xzb3744zbz";
url = "https://pub.dartlang.org/packages/args/versions/2.4.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
async = fetchzip {
sha256 = "0hfgvjajp5c2mw68186hgrk9v5zjhhi149hlhl0fap274p2v1g3q";
url = "https://pub.dartlang.org/packages/async/versions/2.11.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
boolean_selector = fetchzip {
sha256 = "0hxq8072hb89q9s91xlz9fvrjxfy7hw6jkdwkph5dp77df841kmj";
url = "https://pub.dartlang.org/packages/boolean_selector/versions/2.1.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
build = fetchzip {
sha256 = "1x6nkii6kqy6y7ck0151yfhc9lp2nvbhznnhdi2mxr8afk6jxigd";
url = "https://pub.dartlang.org/packages/build/versions/2.3.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
build_config = fetchzip {
sha256 = "092rrbhbdy9fk50jqb1fwj1sfk415fi43irvsd0hk5w90gn8vazj";
url = "https://pub.dartlang.org/packages/build_config/versions/1.1.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
build_daemon = fetchzip {
sha256 = "1wn7bq846vgdj62bkh9h25l95xdsndv0jdyw52nyr0591l3bpg3h";
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
build_resolvers = fetchzip {
sha256 = "00h9abhrfmnl0xxziyf6p68sxnbv2ww1c4dhgpnz00mzbmamnq5c";
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.3.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
build_runner = fetchzip {
sha256 = "0b5ha1l6k0gn2swqgqvfy2vl58klf81sxrjnmk0p7rj1wzbqjm7l";
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
build_runner_core = fetchzip {
sha256 = "07r1kfy6ylm4i4xrb24ns8l26h4h1lgcskmnf8wvq2rd5d5hq790";
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7%2B1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
built_collection = fetchzip {
sha256 = "0bqjahxr42q84w91nhv3n4cr580l3s3ffx3vgzyyypgqnrck0hv3";
url = "https://pub.dartlang.org/packages/built_collection/versions/5.1.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
built_value = fetchzip {
sha256 = "1y84imf9xqqy3gnd5zz9bcln6mycy7qx35r70b0izm31ismlbzkv";
url = "https://pub.dartlang.org/packages/built_value/versions/8.6.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
checked_yaml = fetchzip {
sha256 = "1sn01yrmj0pkijn08g3v45c3zmyvdygk9svigkkzybgicdwlkpqs";
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
code_builder = fetchzip {
sha256 = "1shgl7mxiyv0hhw326yqj2b9jxi1h74qxmsnxf1d1xc6yz766p9a";
url = "https://pub.dartlang.org/packages/code_builder/versions/4.6.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
collection = fetchzip {
sha256 = "1mr8j0078c4z9hhckiq8m735rggsazwfprm0w9gisil51vh7j2mk";
url = "https://pub.dartlang.org/packages/collection/versions/1.18.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
convert = fetchzip {
sha256 = "0adsigjk3l1c31i6k91p28dqyjlgwiqrs4lky5djrm2scf8k6cri";
url = "https://pub.dartlang.org/packages/convert/versions/3.1.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
coverage = fetchzip {
sha256 = "1yy9bgkax5b6kk7qa07p452v82fyj4rl1j03fn366ywyvhfrh6lp";
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
crypto = fetchzip {
sha256 = "100ai8qa4p3dyvvd60c4xa9p0gm06yh0d68xgcfm3giraad8xmqj";
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
cryptography = fetchzip {
sha256 = "1yxn9slqq93ri81fbr2nbsinz0mpk9wk39ny076ja8q31d4i8v3f";
url = "https://pub.dartlang.org/packages/cryptography/versions/2.5.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
dart_style = fetchzip {
sha256 = "0cjhrb1hs8iw9smmfd0fgnxq3nm0w8sz17l6q6svyz6kif19wk9k";
url = "https://pub.dartlang.org/packages/dart_style/versions/2.3.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
file = fetchzip {
sha256 = "0ajcfblf8d4dicp1sgzkbrhd0b0v0d8wl70jsnf5drjck3p3ppk7";
url = "https://pub.dartlang.org/packages/file/versions/6.1.4.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
fixnum = fetchzip {
sha256 = "0nqrzj41ys8dpxf1x70r0kfj1avj0f2j2b7498k8kvc0i9c5asz7";
url = "https://pub.dartlang.org/packages/fixnum/versions/1.1.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
frontend_server_client = fetchzip {
sha256 = "096v7ycix5hgnk750s1qgykyghl2mymhdkg39jrlk3kbj6xygq5b";
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.2.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
glob = fetchzip {
sha256 = "0ffab3azx8zkma36mk6wnig8bn8g5g0vjrq2gl21y77rxgw9iqxj";
url = "https://pub.dartlang.org/packages/glob/versions/2.1.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
graphs = fetchzip {
sha256 = "0fda0j8y6sq1rc9zpzglrzysl5h49y2ji1wq2lq0wx2c609dxm7f";
url = "https://pub.dartlang.org/packages/graphs/versions/2.3.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
hex = fetchzip {
sha256 = "19w3f90mdiy06a6kf8hlwc4jn4cxixkj106kc3g3bis27ar7smkh";
url = "https://pub.dartlang.org/packages/hex/versions/0.2.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
http_multi_server = fetchzip {
sha256 = "1zdcm04z85jahb2hs7qs85rh974kw49hffhy9cn1gfda3077dvql";
url = "https://pub.dartlang.org/packages/http_multi_server/versions/3.2.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
http_parser = fetchzip {
sha256 = "027c4sjkhkkx3sk1aqs6s4djb87syi9h521qpm1bf21bq3gga5jd";
url = "https://pub.dartlang.org/packages/http_parser/versions/4.0.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
io = fetchzip {
sha256 = "101kd0rw26vglmr1m5p130kbrp3k7dk4p5nr77wsbwgg53w8c0d4";
url = "https://pub.dartlang.org/packages/io/versions/1.0.4.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
js = fetchzip {
sha256 = "124a9yqrjdw3p4nnirab9hm9ziwraldlw4q5cb3sr0dcrli74qpw";
url = "https://pub.dartlang.org/packages/js/versions/0.6.7.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
json_annotation = fetchzip {
sha256 = "1jjw7p8qyqajgdq4jqvxipq5w0qrq9dpi1qmia70pk995akryh6m";
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.8.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
json_serializable = fetchzip {
sha256 = "1pmidql9x6s2pbhdx9x20pwqwvwpfkvrz0h0cm1f8pqis76c90hb";
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.6.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
logging = fetchzip {
sha256 = "124hfjs66r30p92ndfmy5fymgy66yk9in97h8sq6fi7r78pqyc7g";
url = "https://pub.dartlang.org/packages/logging/versions/1.2.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
matcher = fetchzip {
sha256 = "0inznqkrxqnq09lcbwvda3xd07qfm1k3aa6dv1wy39gvci8hybss";
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.16.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
meta = fetchzip {
sha256 = "1l3zaz6q2s9mnm7s674xshsfqspy79p5kdbbnc99rf2l76avv4h3";
url = "https://pub.dartlang.org/packages/meta/versions/1.9.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
mime = fetchzip {
sha256 = "1dha9z64bsz8xhi0p62vmlyikr8xwbdlrw90hxghmm3rdgd9h25w";
url = "https://pub.dartlang.org/packages/mime/versions/1.0.4.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
moxlib = fetchzip {
sha256 = "1qaacmcqhq33grn2nq8sn23ki62dcmw0fqy589xm1zv6w0pzfmsk";
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.2.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
node_preamble = fetchzip {
sha256 = "12ajg76r9aqmqkavvlxbnb3sszg1szcq3f30badkd0xc25mnhyh8";
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
omemo_dart = fetchzip {
sha256 = "0fhf89ic5mdyld25l6rfb37a1fk1f0f2b4d72xi4r7pvr0ddjhz8";
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.5.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
package_config = fetchzip {
sha256 = "1d4l0i4cby344zj45f5shrg2pkw1i1jn03kx0qqh0l7gh1ha7bpc";
url = "https://pub.dartlang.org/packages/package_config/versions/2.1.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
path = fetchzip {
sha256 = "1mjdhq2fsz6i9krhp2mnaks2bcw34sa4p7mg0v6njk8dgx2754iv";
url = "https://pub.dartlang.org/packages/path/versions/1.8.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
pedantic = fetchzip {
sha256 = "10ch0h3hi6cfwiz2ihfkh6m36m75c0m7fd0wwqaqggffsj2dn8ad";
url = "https://pub.dartlang.org/packages/pedantic/versions/1.11.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
petitparser = fetchzip {
sha256 = "19zqrpb1z77aw1k2s8rsxdfxczzv9934g2rdfj2jyiv3pqgdq8gh";
url = "https://pub.dartlang.org/packages/petitparser/versions/5.4.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
pinenacl = fetchzip {
sha256 = "0didjgva658z90hbcmhd0y8w1b8v86dp6gabfhylnw1aixl47cxg";
url = "https://pub.dartlang.org/packages/pinenacl/versions/0.5.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
pool = fetchzip {
sha256 = "0wmzs46hjszv3ayhr1p5l7xza7q9rkg2q9z4swmhdqmhlz3c50x4";
url = "https://pub.dartlang.org/packages/pool/versions/1.5.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
protobuf = fetchzip {
sha256 = "1jriyisf8bnvq5ygjk93mn2yzdlnii7xrhy6aabz54xr3y4dcy9x";
url = "https://pub.dartlang.org/packages/protobuf/versions/2.1.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
protoc_plugin = fetchzip {
sha256 = "0hjjd1xkv4s4g1d5n2aza0kdwlbfl2aivq99230m3yml7irn00jk";
url = "https://pub.dartlang.org/packages/protoc_plugin/versions/20.0.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
pub_semver = fetchzip {
sha256 = "0wpcfz1crxipbjm18m71pl4vl2ra8vw1n93ff8snr54mmlyfb9z1";
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.4.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
pubspec_parse = fetchzip {
sha256 = "0dj8sf1w61g7vh1ly3sl690z0nwllzjzbapxswmgsglr0ndcyrs1";
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
random_string = fetchzip {
sha256 = "11cjiv75sgldvk3x7w6j77lgi08r6737wm94m3ylabylsr6zdyff";
url = "https://pub.dartlang.org/packages/random_string/versions/2.3.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
saslprep = fetchzip {
sha256 = "04lss0xvm6p801p8306jdxg7k0b28kr6n65dz2f57dkca237kcw7";
url = "https://pub.dartlang.org/packages/saslprep/versions/1.0.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
shelf = fetchzip {
sha256 = "10yk98nadrgj5d3r3241kdaywjjs1j10mg8gacv80kg1mhcfdrxp";
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
shelf_packages_handler = fetchzip {
sha256 = "1h8s42nff9ar0xn7yb42m64lpvmqzq8wranqrkkixdnp7w3pmv1x";
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
shelf_static = fetchzip {
sha256 = "1bcqynn2z2syrigmrclxgg8hjhd1x9742938i62cicbaga6vclaz";
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
shelf_web_socket = fetchzip {
sha256 = "110b5hrqwpnmq16shxxzjmcih5yfs5kh80dn8avfv0xj5iv7n94c";
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.4.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
source_gen = fetchzip {
sha256 = "1jql5zccv4vnbbvwcpyyvz8l27pg1rviqbp4vrks5313nf4b0kjg";
url = "https://pub.dartlang.org/packages/source_gen/versions/1.3.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
source_helper = fetchzip {
sha256 = "0mdd02vhcdcv9n58gzbx2q0bphwj0alz312ca1a8xpkf8jx3y8v4";
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.4.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
source_map_stack_trace = fetchzip {
sha256 = "0b5d4c5n5qd3j8n10gp1khhr508wfl3819bhk6xnl34qxz8n032k";
url = "https://pub.dartlang.org/packages/source_map_stack_trace/versions/2.1.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
source_maps = fetchzip {
sha256 = "004lcfka01agxjdw7zjhrffdkisvgx5s61b5gsl8qqk2jd1rswa7";
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.12.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
source_span = fetchzip {
sha256 = "1nybnf7l5chslp4fczhqnrgrhymy844lw7qrj6y08i626dshrd46";
url = "https://pub.dartlang.org/packages/source_span/versions/1.10.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
stack_trace = fetchzip {
sha256 = "0xpk2cvmgdh46iwip9jsb54fqx13jnina8pk03akxkmsxvag5izb";
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
stream_channel = fetchzip {
sha256 = "0nrlw6zcscgnn6818krkbgs9qiv3f7q8pa7ljw1bqkrsb7xabm8s";
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
stream_transform = fetchzip {
sha256 = "0jq6767v9ds17i2nd6mdd9i0f7nvsgg3dz74d0v54x66axjgr0gp";
url = "https://pub.dartlang.org/packages/stream_transform/versions/2.1.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
string_scanner = fetchzip {
sha256 = "0p1r0v2923avwfg03rk0pmc6f21m0zxpcx6i57xygd25k6hdfi00";
url = "https://pub.dartlang.org/packages/string_scanner/versions/1.2.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
synchronized = fetchzip {
sha256 = "1fx1z1p5qkn4qnq24riw5s86vmq645ppg8f74iyv2fc9rvr301ar";
url = "https://pub.dartlang.org/packages/synchronized/versions/3.1.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
term_glyph = fetchzip {
sha256 = "1x8nspxaccls0sxjamp703yp55yxdvhj6wg21lzwd296i9rwlxh9";
url = "https://pub.dartlang.org/packages/term_glyph/versions/1.2.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
test = fetchzip {
sha256 = "002phlj2pg6nll5hv449izxbqk29zwmwc77d0jx2iimz18dgy2r5";
url = "https://pub.dartlang.org/packages/test/versions/1.24.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
test_api = fetchzip {
sha256 = "0as1xcywjrd2zax3cm56qmnac12shf8c1ynnzzjwnggm23f61dxb";
url = "https://pub.dartlang.org/packages/test_api/versions/0.6.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
test_core = fetchzip {
sha256 = "1cx2rmz1xzk5z5yh8fpbsrsz4mgjanrw4xvnp0qzdnm2d7vhaq0y";
url = "https://pub.dartlang.org/packages/test_core/versions/0.5.3.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
timing = fetchzip {
sha256 = "15jvxsw7v0gwbdlykma60l1qlhlzb3brh6m0sg2bgbfir4l5s9gw";
url = "https://pub.dartlang.org/packages/timing/versions/1.0.1.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
typed_data = fetchzip {
sha256 = "0q6ggc52vfpr8kqaq69h757wy942hvgshhnsr2pjdinb4zk2sxl1";
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
unorm_dart = fetchzip {
sha256 = "05kyk2764yz14pzgx00i7h5b1lzh8kjqnxspfzyf8z920bcgbz0v";
url = "https://pub.dartlang.org/packages/unorm_dart/versions/0.2.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
uuid = fetchzip {
sha256 = "1nh1hxfr6bhyadqqcxrpwrphmm75f1iq4rzfjdwa2486xwlh7vx3";
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.7.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
very_good_analysis = fetchzip {
sha256 = "1p2dh8aahbqyyqfzbsxswafgxnmxgisjq2xfp008skyh7imk6sz4";
url = "https://pub.dartlang.org/packages/very_good_analysis/versions/3.1.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
vm_service = fetchzip {
sha256 = "15ail7rbaq9ksg73cc0mw2k5imbiidl95yfd4v49k81gp5xmj92w";
url = "https://pub.dartlang.org/packages/vm_service/versions/11.10.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
watcher = fetchzip {
sha256 = "1sk7gvwa7s0h4l652qrgbh7l8wyqc6nr6lki8m4rj55720p0fnyg";
url = "https://pub.dartlang.org/packages/watcher/versions/1.0.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
web_socket_channel = fetchzip {
sha256 = "0f9441c4zifb5qadpjg319dcilimpkdhfacnkl543802bf8qjn4w";
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.4.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
webkit_inspection_protocol = fetchzip {
sha256 = "0z400dzw7gf68a3wm95xi2mf461iigkyq6x69xgi7qs3fvpmn3hx";
url = "https://pub.dartlang.org/packages/webkit_inspection_protocol/versions/1.2.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
xml = fetchzip {
sha256 = "120azx71gazvrrn07vd83vrffzrhsqnmf9rdjxl73rra9py8ixiy";
url = "https://pub.dartlang.org/packages/xml/versions/6.3.0.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
yaml = fetchzip {
sha256 = "0awh9dynbhrlq8zgszaiyxbyyn9b6wyps1zww4z2lx62nbma0pda";
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.2.tar.gz";
stripRoot = false;
extension = "tar.gz";
};
pubCache = runCommand "moxxmpp-pub-cache" {} ''
mkdir -p $out/hosted/pub.dev
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dev/_fe_analyzer_shared-61.0.0
mkdir -p $out/hosted/pub.dev
ln -s ${analyzer} $out/hosted/pub.dev/analyzer-5.13.0
mkdir -p $out/hosted/pub.dev
ln -s ${args} $out/hosted/pub.dev/args-2.4.2
mkdir -p $out/hosted/pub.dev
ln -s ${async} $out/hosted/pub.dev/async-2.11.0
mkdir -p $out/hosted/pub.dev
ln -s ${boolean_selector} $out/hosted/pub.dev/boolean_selector-2.1.1
mkdir -p $out/hosted/pub.dev
ln -s ${build} $out/hosted/pub.dev/build-2.3.1
mkdir -p $out/hosted/pub.dev
ln -s ${build_config} $out/hosted/pub.dev/build_config-1.1.1
mkdir -p $out/hosted/pub.dev
ln -s ${build_daemon} $out/hosted/pub.dev/build_daemon-3.1.1
mkdir -p $out/hosted/pub.dev
ln -s ${build_resolvers} $out/hosted/pub.dev/build_resolvers-2.3.1
mkdir -p $out/hosted/pub.dev
ln -s ${build_runner} $out/hosted/pub.dev/build_runner-2.3.3
mkdir -p $out/hosted/pub.dev
ln -s ${build_runner_core} $out/hosted/pub.dev/build_runner_core-7.2.7+1
mkdir -p $out/hosted/pub.dev
ln -s ${built_collection} $out/hosted/pub.dev/built_collection-5.1.1
mkdir -p $out/hosted/pub.dev
ln -s ${built_value} $out/hosted/pub.dev/built_value-8.6.2
mkdir -p $out/hosted/pub.dev
ln -s ${checked_yaml} $out/hosted/pub.dev/checked_yaml-2.0.3
mkdir -p $out/hosted/pub.dev
ln -s ${code_builder} $out/hosted/pub.dev/code_builder-4.6.0
mkdir -p $out/hosted/pub.dev
ln -s ${collection} $out/hosted/pub.dev/collection-1.18.0
mkdir -p $out/hosted/pub.dev
ln -s ${convert} $out/hosted/pub.dev/convert-3.1.1
mkdir -p $out/hosted/pub.dev
ln -s ${coverage} $out/hosted/pub.dev/coverage-1.6.3
mkdir -p $out/hosted/pub.dev
ln -s ${crypto} $out/hosted/pub.dev/crypto-3.0.3
mkdir -p $out/hosted/pub.dev
ln -s ${cryptography} $out/hosted/pub.dev/cryptography-2.5.0
mkdir -p $out/hosted/pub.dev
ln -s ${dart_style} $out/hosted/pub.dev/dart_style-2.3.2
mkdir -p $out/hosted/pub.dev
ln -s ${file} $out/hosted/pub.dev/file-6.1.4
mkdir -p $out/hosted/pub.dev
ln -s ${fixnum} $out/hosted/pub.dev/fixnum-1.1.0
mkdir -p $out/hosted/pub.dev
ln -s ${frontend_server_client} $out/hosted/pub.dev/frontend_server_client-3.2.0
mkdir -p $out/hosted/pub.dev
ln -s ${glob} $out/hosted/pub.dev/glob-2.1.2
mkdir -p $out/hosted/pub.dev
ln -s ${graphs} $out/hosted/pub.dev/graphs-2.3.1
mkdir -p $out/hosted/pub.dev
ln -s ${hex} $out/hosted/pub.dev/hex-0.2.0
mkdir -p $out/hosted/pub.dev
ln -s ${http_multi_server} $out/hosted/pub.dev/http_multi_server-3.2.1
mkdir -p $out/hosted/pub.dev
ln -s ${http_parser} $out/hosted/pub.dev/http_parser-4.0.2
mkdir -p $out/hosted/pub.dev
ln -s ${io} $out/hosted/pub.dev/io-1.0.4
mkdir -p $out/hosted/pub.dev
ln -s ${js} $out/hosted/pub.dev/js-0.6.7
mkdir -p $out/hosted/pub.dev
ln -s ${json_annotation} $out/hosted/pub.dev/json_annotation-4.8.1
mkdir -p $out/hosted/pub.dev
ln -s ${json_serializable} $out/hosted/pub.dev/json_serializable-6.6.2
mkdir -p $out/hosted/pub.dev
ln -s ${logging} $out/hosted/pub.dev/logging-1.2.0
mkdir -p $out/hosted/pub.dev
ln -s ${matcher} $out/hosted/pub.dev/matcher-0.12.16
mkdir -p $out/hosted/pub.dev
ln -s ${meta} $out/hosted/pub.dev/meta-1.9.1
mkdir -p $out/hosted/pub.dev
ln -s ${mime} $out/hosted/pub.dev/mime-1.0.4
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47
ln -s ${moxlib} $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxlib-0.2.0
mkdir -p $out/hosted/pub.dev
ln -s ${node_preamble} $out/hosted/pub.dev/node_preamble-2.0.2
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47
ln -s ${omemo_dart} $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47/omemo_dart-0.5.1
mkdir -p $out/hosted/pub.dev
ln -s ${package_config} $out/hosted/pub.dev/package_config-2.1.0
mkdir -p $out/hosted/pub.dev
ln -s ${path} $out/hosted/pub.dev/path-1.8.3
mkdir -p $out/hosted/pub.dev
ln -s ${pedantic} $out/hosted/pub.dev/pedantic-1.11.1
mkdir -p $out/hosted/pub.dev
ln -s ${petitparser} $out/hosted/pub.dev/petitparser-5.4.0
mkdir -p $out/hosted/pub.dev
ln -s ${pinenacl} $out/hosted/pub.dev/pinenacl-0.5.1
mkdir -p $out/hosted/pub.dev
ln -s ${pool} $out/hosted/pub.dev/pool-1.5.1
mkdir -p $out/hosted/pub.dev
ln -s ${protobuf} $out/hosted/pub.dev/protobuf-2.1.0
mkdir -p $out/hosted/pub.dev
ln -s ${protoc_plugin} $out/hosted/pub.dev/protoc_plugin-20.0.1
mkdir -p $out/hosted/pub.dev
ln -s ${pub_semver} $out/hosted/pub.dev/pub_semver-2.1.4
mkdir -p $out/hosted/pub.dev
ln -s ${pubspec_parse} $out/hosted/pub.dev/pubspec_parse-1.2.3
mkdir -p $out/hosted/pub.dev
ln -s ${random_string} $out/hosted/pub.dev/random_string-2.3.1
mkdir -p $out/hosted/pub.dev
ln -s ${saslprep} $out/hosted/pub.dev/saslprep-1.0.2
mkdir -p $out/hosted/pub.dev
ln -s ${shelf} $out/hosted/pub.dev/shelf-1.4.1
mkdir -p $out/hosted/pub.dev
ln -s ${shelf_packages_handler} $out/hosted/pub.dev/shelf_packages_handler-3.0.2
mkdir -p $out/hosted/pub.dev
ln -s ${shelf_static} $out/hosted/pub.dev/shelf_static-1.1.2
mkdir -p $out/hosted/pub.dev
ln -s ${shelf_web_socket} $out/hosted/pub.dev/shelf_web_socket-1.0.4
mkdir -p $out/hosted/pub.dev
ln -s ${source_gen} $out/hosted/pub.dev/source_gen-1.3.2
mkdir -p $out/hosted/pub.dev
ln -s ${source_helper} $out/hosted/pub.dev/source_helper-1.3.4
mkdir -p $out/hosted/pub.dev
ln -s ${source_map_stack_trace} $out/hosted/pub.dev/source_map_stack_trace-2.1.1
mkdir -p $out/hosted/pub.dev
ln -s ${source_maps} $out/hosted/pub.dev/source_maps-0.10.12
mkdir -p $out/hosted/pub.dev
ln -s ${source_span} $out/hosted/pub.dev/source_span-1.10.0
mkdir -p $out/hosted/pub.dev
ln -s ${stack_trace} $out/hosted/pub.dev/stack_trace-1.11.1
mkdir -p $out/hosted/pub.dev
ln -s ${stream_channel} $out/hosted/pub.dev/stream_channel-2.1.2
mkdir -p $out/hosted/pub.dev
ln -s ${stream_transform} $out/hosted/pub.dev/stream_transform-2.1.0
mkdir -p $out/hosted/pub.dev
ln -s ${string_scanner} $out/hosted/pub.dev/string_scanner-1.2.0
mkdir -p $out/hosted/pub.dev
ln -s ${synchronized} $out/hosted/pub.dev/synchronized-3.1.0
mkdir -p $out/hosted/pub.dev
ln -s ${term_glyph} $out/hosted/pub.dev/term_glyph-1.2.1
mkdir -p $out/hosted/pub.dev
ln -s ${test} $out/hosted/pub.dev/test-1.24.3
mkdir -p $out/hosted/pub.dev
ln -s ${test_api} $out/hosted/pub.dev/test_api-0.6.0
mkdir -p $out/hosted/pub.dev
ln -s ${test_core} $out/hosted/pub.dev/test_core-0.5.3
mkdir -p $out/hosted/pub.dev
ln -s ${timing} $out/hosted/pub.dev/timing-1.0.1
mkdir -p $out/hosted/pub.dev
ln -s ${typed_data} $out/hosted/pub.dev/typed_data-1.3.2
mkdir -p $out/hosted/pub.dev
ln -s ${unorm_dart} $out/hosted/pub.dev/unorm_dart-0.2.0
mkdir -p $out/hosted/pub.dev
ln -s ${uuid} $out/hosted/pub.dev/uuid-3.0.7
mkdir -p $out/hosted/pub.dev
ln -s ${very_good_analysis} $out/hosted/pub.dev/very_good_analysis-3.1.0
mkdir -p $out/hosted/pub.dev
ln -s ${vm_service} $out/hosted/pub.dev/vm_service-11.10.0
mkdir -p $out/hosted/pub.dev
ln -s ${watcher} $out/hosted/pub.dev/watcher-1.0.2
mkdir -p $out/hosted/pub.dev
ln -s ${web_socket_channel} $out/hosted/pub.dev/web_socket_channel-2.4.0
mkdir -p $out/hosted/pub.dev
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dev/webkit_inspection_protocol-1.2.0
mkdir -p $out/hosted/pub.dev
ln -s ${xml} $out/hosted/pub.dev/xml-6.3.0
mkdir -p $out/hosted/pub.dev
ln -s ${yaml} $out/hosted/pub.dev/yaml-3.1.2
'';
}

View File

@ -1,3 +1,55 @@
## 0.4.1
- Moved FAST from staging to xep_0484.dart
## 0.4.0
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
- **BREAKING**: Changed order of parameters of `CryptographicHashManager.hashFromData`
- **BREAKING**: Removed support for XEP-0414, as the (supported) hash computations are already implemented by `CryptographicHashManager.hashFromData`.
- The `DiscoManager` now only handled entity capabilities if a `EntityCapabilityManager` is registered.
- The `EntityCapabilityManager` now verifies and validates its data before caching.
- **BREAKING**: Added the `resumed` parameter to `StreamNegotiationsDoneEvent`. Use this to check if the current stream is new or resumed instead of using the `ConnectionStateChangedEvent`.
- **BREAKING**: Remove `DiscoManager.discoInfoCapHashQuery`.
- **BREAKING**: The entity argument of `DiscoManager.discoInfoQuery` and `DiscoManager.discoItemsQuery` are now `JID` instead of `String`.
- **BREAKING**: `PubSubManager` and `UserAvatarManager` now use `JID` instead of `String`.
- **BREAKING**: `XmppConnection.sendStanza` not only takes a `StanzaDetails` argument.
- Sent stanzas are now kept in a queue until sent.
- **BREAKING**: `MessageManager.sendMessage` does not use `MessageDetails` anymore. Instead, use `TypedMap`.
- `MessageManager` now allows registering callbacks for adding data whenever a message is sent.
- **BREAKING**: `MessageEvent` now makes use of `TypedMap`.
- **BREAKING**: Removed `PresenceReceivedEvent`. Use a manager registering handlers with priority greater than `[PresenceManager.presenceHandlerPriority]` instead.
- **BREAKING**: `ChatState.toString()` is now `ChatState.toName()`
- **BREAKING**: Overriding `BaseOmemoManager` is no longer required. `OmemoManager` now takes callback methods instead.
- Removed `ErrorResponseDiscoError` from the possible XEP-0030 errors.
- **BREAKING**: Removed "Extensible File Thumbnails" (The `Thumbnail` type).
- *BREAKING*: Rename `UserAvatarManager`'s `getUserAvatar` to `getUserAvatarData`. It now also requires the id of the avatar to fetch
- *BREAKING*: `UserAvatarManager`'s `getAvatarId` with `getLatestMetadata`.
- The `PubSubManager` now supports PubSub's `max_items` in `getItems`.
- *BREAKING*: `vCardManager`'s `VCardAvatarUpdatedEvent` no longer automatically requests the newest VCard avatar.
- *BREAKING*: `XmppConnection` now tries to ensure that incoming data is processed in-order. The only exception are awaited stanzas as they are allowed to bypass the queue.
- *BREAKING*: If a stanza handler causes an exception, the handler is simply skipped while processing.
- Add better logging around what stanza handler is running and if they end processing early.
## 0.3.1
- Fix some issues with running moxxmpp as a component
## 0.3.0
- **BREAKING**: Removed `connectAwaitable` and merged it with `connect`.
- **BREAKING**: Removed `allowPlainAuth` from `ConnectionSettings`. If you don't want to use SASL PLAIN, don't register the negotiator. If you want to only conditionally use SASL PLAIN, extend the `SaslPlainNegotiator` and override its `matchesFeature` method to only call the super method when SASL PLAIN should be used.
- **BREAKING**: The user avatar's `subscribe` and `unsubscribe` no longer subscribe to the `:data` PubSub nodes
- Renamed `ResourceBindingSuccessEvent` to `ResourceBoundEvent`
- **BREAKING**: Removed `isFeatureSupported` from the manager attributes. The managers now all have a method `isFeatureSupported` that works the same
- The `PresenceManager` is now optional
- **BREAKING**: Removed `setConnectionSettings` and `getConnectionSettings`. Just directly acces the `connectionSettings` field.
- Implement XEP-0114 for implementing components
- **BREAKING**: Remove `useDirectTLS` from `ConnectionSettings`
## 0.1.6+1
- **FIX**: Fix LMC not working.
## 0.1.6
- **FEAT**: Implement XEP-0308.

View File

@ -2,6 +2,24 @@
A pure-Dart XMPP library written for Moxxy.
## Usage
Include the following as a dependency in your pubspec file:
```
moxxmpp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.3.1
```
You can find the documentation [here](https://moxxy.org/developers/docs/moxxmpp/).
## License
See `./LICENSE`.
## Support
If you like what I do and you want to support me, feel free to donate to me on Ko-Fi.
[<img src="https://codeberg.org/moxxy/moxxyv2/raw/branch/master/assets/repo/kofi.png" height="36" style="height: 36px; border: 0px;"></img>](https://ko-fi.com/papatutuwawa)

View File

@ -1,98 +0,0 @@
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
import 'package:test/test.dart';
void main() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
final log = Logger('FailureReconnectionTest');
test('Failing an awaited connection with TestingSleepReconnectionPolicy', () async {
var errors = 0;
final connection = XmppConnection(
TestingSleepReconnectionPolicy(10),
TCPSocketWrapper(false),
);
connection.registerFeatureNegotiators([
StartTlsNegotiator(),
]);
connection.registerManagers([
DiscoManager(),
RosterManager(),
PingManager(),
MessageManager(),
PresenceManager('http://moxxmpp.example'),
]);
connection.asBroadcastStream().listen((event) {
if (event is ConnectionStateChangedEvent) {
if (event.state == XmppConnectionState.error) {
errors++;
}
}
});
connection.setConnectionSettings(
ConnectionSettings(
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
password: 'abc123',
useDirectTLS: true,
allowPlainAuth: true,
),
);
final result = await connection.connectAwaitable();
log.info('Connection failed as expected');
expect(result.success, false);
expect(errors, 1);
log.info('Waiting 20 seconds for unexpected reconnections');
await Future.delayed(const Duration(seconds: 20));
expect(errors, 1);
}, timeout: Timeout.factor(2));
test('Failing an awaited connection with ExponentialBackoffReconnectionPolicy', () async {
var errors = 0;
final connection = XmppConnection(
ExponentialBackoffReconnectionPolicy(1),
TCPSocketWrapper(false),
);
connection.registerFeatureNegotiators([
StartTlsNegotiator(),
]);
connection.registerManagers([
DiscoManager(),
RosterManager(),
PingManager(),
MessageManager(),
PresenceManager('http://moxxmpp.example'),
]);
connection.asBroadcastStream().listen((event) {
if (event is ConnectionStateChangedEvent) {
if (event.state == XmppConnectionState.error) {
errors++;
}
}
});
connection.setConnectionSettings(
ConnectionSettings(
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
password: 'abc123',
useDirectTLS: true,
allowPlainAuth: true,
),
);
final result = await connection.connectAwaitable();
log.info('Connection failed as expected');
expect(result.success, false);
expect(errors, 1);
log.info('Waiting 20 seconds for unexpected reconnections');
await Future.delayed(const Duration(seconds: 20));
expect(errors, 1);
}, timeout: Timeout.factor(2));
}

View File

@ -1,8 +1,13 @@
library moxxmpp;
export 'package:moxxmpp/src/connection.dart';
export 'package:moxxmpp/src/connection_errors.dart';
export 'package:moxxmpp/src/connectivity.dart';
export 'package:moxxmpp/src/errors.dart';
export 'package:moxxmpp/src/events.dart';
export 'package:moxxmpp/src/handlers/base.dart';
export 'package:moxxmpp/src/handlers/client.dart';
export 'package:moxxmpp/src/handlers/component.dart';
export 'package:moxxmpp/src/iq.dart';
export 'package:moxxmpp/src/jid.dart';
export 'package:moxxmpp/src/managers/attributes.dart';
@ -13,34 +18,37 @@ export 'package:moxxmpp/src/managers/namespaces.dart';
export 'package:moxxmpp/src/managers/priorities.dart';
export 'package:moxxmpp/src/message.dart';
export 'package:moxxmpp/src/namespaces.dart';
export 'package:moxxmpp/src/negotiators/manager.dart';
export 'package:moxxmpp/src/negotiators/namespaces.dart';
export 'package:moxxmpp/src/negotiators/negotiator.dart';
export 'package:moxxmpp/src/negotiators/resource_binding.dart';
export 'package:moxxmpp/src/negotiators/sasl/errors.dart';
export 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
export 'package:moxxmpp/src/negotiators/sasl/plain.dart';
export 'package:moxxmpp/src/negotiators/sasl/scram.dart';
export 'package:moxxmpp/src/negotiators/starttls.dart';
export 'package:moxxmpp/src/ping.dart';
export 'package:moxxmpp/src/presence.dart';
export 'package:moxxmpp/src/reconnect.dart';
export 'package:moxxmpp/src/rfcs/rfc_2782.dart';
export 'package:moxxmpp/src/rfcs/rfc_4790.dart';
export 'package:moxxmpp/src/rfcs/rfc_6120/resource_binding.dart';
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/negotiator.dart';
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/plain.dart';
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/scram.dart';
export 'package:moxxmpp/src/rfcs/rfc_6120/starttls.dart';
export 'package:moxxmpp/src/roster/errors.dart';
export 'package:moxxmpp/src/roster/roster.dart';
export 'package:moxxmpp/src/roster/state.dart';
export 'package:moxxmpp/src/settings.dart';
export 'package:moxxmpp/src/socket.dart';
export 'package:moxxmpp/src/stanza.dart';
export 'package:moxxmpp/src/stringxml.dart';
export 'package:moxxmpp/src/types/result.dart';
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
export 'package:moxxmpp/src/util/typed_map.dart';
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
export 'package:moxxmpp/src/xeps/xep_0004.dart';
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
export 'package:moxxmpp/src/xeps/xep_0030/helpers.dart';
export 'package:moxxmpp/src/xeps/xep_0030/types.dart';
export 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
export 'package:moxxmpp/src/xeps/xep_0045/errors.dart';
export 'package:moxxmpp/src/xeps/xep_0045/events.dart';
export 'package:moxxmpp/src/xeps/xep_0045/types.dart';
export 'package:moxxmpp/src/xeps/xep_0045/xep_0045.dart';
export 'package:moxxmpp/src/xeps/xep_0054.dart';
export 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
export 'package:moxxmpp/src/xeps/xep_0060/helpers.dart';
@ -56,9 +64,11 @@ export 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
export 'package:moxxmpp/src/xeps/xep_0198/state.dart';
export 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
export 'package:moxxmpp/src/xeps/xep_0203.dart';
export 'package:moxxmpp/src/xeps/xep_0264.dart';
export 'package:moxxmpp/src/xeps/xep_0280.dart';
export 'package:moxxmpp/src/xeps/xep_0297.dart';
export 'package:moxxmpp/src/xeps/xep_0300.dart';
export 'package:moxxmpp/src/xeps/xep_0308.dart';
export 'package:moxxmpp/src/xeps/xep_0333.dart';
export 'package:moxxmpp/src/xeps/xep_0334.dart';
export 'package:moxxmpp/src/xeps/xep_0352.dart';
@ -72,9 +82,17 @@ export 'package:moxxmpp/src/xeps/xep_0384/helpers.dart';
export 'package:moxxmpp/src/xeps/xep_0384/types.dart';
export 'package:moxxmpp/src/xeps/xep_0384/xep_0384.dart';
export 'package:moxxmpp/src/xeps/xep_0385.dart';
export 'package:moxxmpp/src/xeps/xep_0414.dart';
export 'package:moxxmpp/src/xeps/xep_0386.dart';
export 'package:moxxmpp/src/xeps/xep_0388/errors.dart';
export 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
export 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart';
export 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
export 'package:moxxmpp/src/xeps/xep_0421.dart';
export 'package:moxxmpp/src/xeps/xep_0424.dart';
export 'package:moxxmpp/src/xeps/xep_0444.dart';
export 'package:moxxmpp/src/xeps/xep_0446.dart';
export 'package:moxxmpp/src/xeps/xep_0447.dart';
export 'package:moxxmpp/src/xeps/xep_0448.dart';
export 'package:moxxmpp/src/xeps/xep_0449.dart';
export 'package:moxxmpp/src/xeps/xep_0461.dart';
export 'package:moxxmpp/src/xeps/xep_0484.dart';

View File

@ -0,0 +1,94 @@
import 'dart:async';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:synchronized/synchronized.dart';
/// (JID we sent a stanza to, the id of the sent stanza, the tag of the sent stanza).
// ignore: avoid_private_typedef_functions
typedef _StanzaCompositeKey = (String?, String, String);
/// Callback function that returns the bare JID of the connection as a String.
typedef GetBareJidCallback = String Function();
/// This class handles the await semantics for stanzas. Stanzas are given a "unique"
/// key equal to the tuple (to, id, tag) with which their response is identified.
///
/// That means that when sending ```<iq to="example@some.server.example" id="abc123" />```,
/// the response stanza must be from "example@some.server.example", have id "abc123" and
/// be an iq stanza.
///
/// This class also handles some "edge cases" of RFC 6120, like an empty "from" attribute.
class StanzaAwaiter {
StanzaAwaiter(this._bareJidCallback);
final GetBareJidCallback _bareJidCallback;
/// The pending stanzas, identified by their surrogate key.
final Map<_StanzaCompositeKey, Completer<XMLNode>> _pending = {};
/// The critical section for accessing [StanzaAwaiter._pending].
final Lock _lock = Lock();
/// Register a stanza as pending.
/// [to] is the value of the stanza's "to" attribute.
/// [id] is the value of the stanza's "id" attribute.
/// [tag] is the stanza's tag name.
///
/// Returns a future that might resolve to the response to the stanza.
Future<Future<XMLNode>> addPending(String? to, String id, String tag) async {
// Check if we want to send a stanza to our bare JID and replace it with null.
final processedTo = to != null && to == _bareJidCallback() ? null : to;
final completer = await _lock.synchronized(() {
final completer = Completer<XMLNode>();
_pending[(processedTo, id, tag)] = completer;
return completer;
});
return completer.future;
}
/// Checks if the stanza [stanza] is being awaited.
/// If [stanza] is awaited, resolves the future and returns true. If not, returns
/// false.
Future<bool> onData(XMLNode stanza) async {
final id = stanza.attributes['id'] as String?;
if (id == null) return false;
// Check if we want to send a stanza to our bare JID and replace it with null.
final from = stanza.attributes['from'] as String?;
final processedFrom =
from != null && from == _bareJidCallback() ? null : from;
final key = (
processedFrom,
id,
stanza.tag,
);
return _lock.synchronized(() {
final completer = _pending[key];
if (completer != null) {
_pending.remove(key);
completer.complete(stanza);
return true;
}
return false;
});
}
/// Checks if [stanza] represents a stanza that is awaited. Returns true, if [stanza]
/// is awaited. False, if not.
Future<bool> isAwaited(XMLNode stanza) async {
final id = stanza.attributes['id'] as String?;
if (id == null) return false;
final key = (
stanza.attributes['from'] as String?,
id,
stanza.tag,
);
return _lock.synchronized(() => _pending.containsKey(key));
}
}

View File

@ -1,27 +0,0 @@
import 'dart:async';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:xml/xml.dart';
import 'package:xml/xml_events.dart';
class XmlStreamBuffer extends StreamTransformerBase<String, XMLNode> {
XmlStreamBuffer() : _streamController = StreamController(), _decoder = const XmlNodeDecoder();
final StreamController<XMLNode> _streamController;
final XmlNodeDecoder _decoder;
@override
Stream<XMLNode> bind(Stream<String> stream) {
stream.toXmlEvents().selectSubtreeEvents((event) {
return event.qualifiedName != 'stream:stream';
}).transform(_decoder).listen((nodes) {
for (final node in nodes) {
if (node.nodeType == XmlNodeType.ELEMENT) {
_streamController.add(XMLNode.fromXmlElement(node as XmlElement));
}
}
});
return _streamController.stream;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
import 'package:moxxmpp/src/errors.dart';
import 'package:moxxmpp/src/negotiators/negotiator.dart';
/// The reason a call to `XmppConnection.connect` failed.
abstract class XmppConnectionError extends XmppError {}
/// Returned by `XmppConnection.connect` when a negotiator returned an unrecoverable
/// error. Only returned when waitUntilLogin is true.
class NegotiatorReturnedError extends XmppConnectionError {
NegotiatorReturnedError(this.error);
@override
bool isRecoverable() => error.isRecoverable();
/// The error returned by the negotiator.
final NegotiatorError error;
}
class StreamFailureError extends XmppConnectionError {
StreamFailureError(this.error);
@override
bool isRecoverable() => error.isRecoverable();
/// The error that causes a connection failure.
final XmppError error;
}
/// Returned by `XmppConnection.connect` when no connection could
/// be established.
class NoConnectionPossibleError extends XmppConnectionError {
@override
bool isRecoverable() => true;
}
/// Returned if no matching authentication mechanism has been presented
class NoMatchingAuthenticationMechanismAvailableError
extends XmppConnectionError {
@override
bool isRecoverable() => false;
}
/// Returned if no negotiator was picked, even though negotiations are not done
/// yet.
class NoAuthenticatorAvailableError extends XmppConnectionError {
@override
bool isRecoverable() => false;
}
/// Returned by the negotiation handler if unexpected data has been received
class UnexpectedDataError extends XmppConnectionError {
@override
bool isRecoverable() => false;
}
/// Returned by the ComponentToServerNegotiator if the handshake is not successful.
class InvalidHandshakeCredentialsError extends XmppConnectionError {
@override
bool isRecoverable() => false;
}

View File

@ -0,0 +1,18 @@
/// This manager class is responsible to tell the moxxmpp XmppConnection
/// when a connection can be established or not, regarding the network availability.
abstract class ConnectivityManager {
/// Returns true if a network connection is available. If not, returns false.
Future<bool> hasConnection();
/// Returns a future that resolves once we have a network connection.
Future<void> waitForConnection();
}
/// An implementation of [ConnectivityManager] that is always connected.
class AlwaysConnectedConnectivityManager extends ConnectivityManager {
@override
Future<bool> hasConnection() async => true;
@override
Future<void> waitForConnection() async {}
}

View File

@ -1,20 +1,30 @@
import 'package:moxxmpp/src/socket.dart';
/// An internal error class
abstract class XmppError {}
/// Returned if we could not establish a TCP connection
/// to the server.
class NoConnectionError extends XmppError {}
// ignore: one_member_abstracts
abstract class XmppError {
/// Return true if we can recover from the error by attempting a reconnection.
bool isRecoverable();
}
/// Returned if a socket error occured
class SocketError extends XmppError {
SocketError(this.event);
final XmppSocketErrorEvent event;
@override
bool isRecoverable() => true;
}
/// Returned if we time out
class TimeoutError extends XmppError {}
class TimeoutError extends XmppError {
@override
bool isRecoverable() => true;
}
/// Returned if we received a stream error
class StreamError extends XmppError {}
class StreamError extends XmppError {
// TODO(PapaTutuWawa): Be more precise
@override
bool isRecoverable() => true;
}

View File

@ -1,32 +1,34 @@
import 'package:moxxmpp/src/connection.dart';
import 'package:moxxmpp/src/errors.dart';
import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/roster/roster.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
import 'package:moxxmpp/src/xeps/xep_0066.dart';
import 'package:moxxmpp/src/xeps/xep_0085.dart';
import 'package:moxxmpp/src/xeps/xep_0359.dart';
import 'package:moxxmpp/src/xeps/xep_0385.dart';
import 'package:moxxmpp/src/xeps/xep_0424.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart';
import 'package:moxxmpp/src/xeps/xep_0447.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart';
import 'package:moxxmpp/src/xeps/xep_0084.dart';
import 'package:moxxmpp/src/xeps/xep_0333.dart';
abstract class XmppEvent {}
/// Triggered when the connection state of the XmppConnection has
/// changed.
class ConnectionStateChangedEvent extends XmppEvent {
ConnectionStateChangedEvent(this.state, this.before, this.resumed);
ConnectionStateChangedEvent(this.state, this.before);
final XmppConnectionState before;
final XmppConnectionState state;
final bool resumed;
/// Indicates whether the connection state switched from a not connected state to a
/// connected state.
bool get connectionEstablished =>
before != XmppConnectionState.connected &&
state == XmppConnectionState.connected;
}
/// Triggered when we encounter a stream error.
class StreamErrorEvent extends XmppEvent {
StreamErrorEvent({ required this.error });
StreamErrorEvent({required this.error});
final String error;
}
@ -39,91 +41,100 @@ class AuthenticationFailedEvent extends XmppEvent {
/// Triggered after the SASL authentication has succeeded.
class AuthenticationSuccessEvent extends XmppEvent {}
/// Triggered when we want to ping the connection open
class SendPingEvent extends XmppEvent {}
/// Triggered when the stream resumption was successful
class StreamResumedEvent extends XmppEvent {
StreamResumedEvent({ required this.h });
StreamResumedEvent({required this.h});
final int h;
}
/// Triggered when stream resumption failed
class StreamResumeFailedEvent extends XmppEvent {}
/// Triggered when the roster has been modified
class RosterUpdatedEvent extends XmppEvent {
RosterUpdatedEvent(this.removed, this.modified, this.added);
/// A list of bare JIDs that are removed from the roster
final List<String> removed;
/// A list of XmppRosterItems that are modified. Can be correlated with one's cache
/// using the jid attribute.
final List<XmppRosterItem> modified;
/// A list of XmppRosterItems that are added to the roster.
final List<XmppRosterItem> added;
}
/// Triggered when a message is received
class MessageEvent extends XmppEvent {
MessageEvent({
required this.body,
required this.fromJid,
required this.toJid,
required this.sid,
required this.stanzaId,
required this.isCarbon,
required this.deliveryReceiptRequested,
required this.isMarkable,
required this.encrypted,
required this.other,
this.error,
MessageEvent(
this.from,
this.to,
this.encrypted,
this.extensions, {
this.id,
this.type,
this.oob,
this.sfs,
this.sims,
this.reply,
this.chatState,
this.fun,
this.funReplacement,
this.funCancellation,
this.messageRetraction,
this.messageCorrectionId,
this.error,
this.encryptionError,
});
final StanzaError? error;
final String body;
final JID fromJid;
final JID toJid;
final String sid;
/// The from attribute of the message.
final JID from;
/// The to attribute of the message.
final JID to;
/// The id attribute of the message.
final String? id;
/// The type attribute of the message.
final String? type;
final StableStanzaId stanzaId;
final bool isCarbon;
final bool deliveryReceiptRequested;
final bool isMarkable;
final OOBData? oob;
final StatelessFileSharingData? sfs;
final StatelessMediaSharingData? sims;
final ReplyData? reply;
final ChatState? chatState;
final FileMetadataData? fun;
final String? funReplacement;
final String? funCancellation;
final StanzaError? error;
/// Flag indicating whether the message was encrypted.
final bool encrypted;
final MessageRetractionData? messageRetraction;
final String? messageCorrectionId;
final Map<String, dynamic> other;
/// The error in case an encryption error occurred.
final Object? encryptionError;
/// Data added by other handlers.
final TypedMap<StanzaHandlerExtension> extensions;
/// Shorthand for extensions.get<T>().
T? get<T>() => extensions.get<T>();
}
/// Triggered when a client responds to our delivery receipt request
class DeliveryReceiptReceivedEvent extends XmppEvent {
DeliveryReceiptReceivedEvent({ required this.from, required this.id });
DeliveryReceiptReceivedEvent({required this.from, required this.id});
final JID from;
final String id;
}
class ChatMarkerEvent extends XmppEvent {
ChatMarkerEvent({
required this.type,
required this.from,
required this.id,
});
ChatMarkerEvent(
this.from,
this.type,
this.id,
);
/// The entity that sent the chat marker.
final JID from;
final String type;
/// The type of chat marker that was sent.
final ChatMarker type;
/// The id of the message that the marker applies to.
final String id;
}
// Triggered when we received a Stream resumption ID
class StreamManagementEnabledEvent extends XmppEvent {
StreamManagementEnabledEvent({
required this.resource,
this.id,
this.location,
required this.resource,
this.id,
this.location,
});
final String resource;
final String? id;
@ -131,16 +142,11 @@ class StreamManagementEnabledEvent extends XmppEvent {
}
/// Triggered when we bound a resource
class ResourceBindingSuccessEvent extends XmppEvent {
ResourceBindingSuccessEvent({ required this.resource });
final String resource;
}
class ResourceBoundEvent extends XmppEvent {
ResourceBoundEvent(this.resource);
/// Triggered when we receive presence
class PresenceReceivedEvent extends XmppEvent {
PresenceReceivedEvent(this.jid, this.presence);
final JID jid;
final Stanza presence;
/// The resource that was just bound.
final String resource;
}
/// Triggered when we are starting an connection attempt
@ -156,21 +162,41 @@ class ServerItemDiscoEvent extends XmppEvent {
/// Triggered when we receive a subscription request
class SubscriptionRequestReceivedEvent extends XmppEvent {
SubscriptionRequestReceivedEvent({ required this.from });
SubscriptionRequestReceivedEvent({required this.from});
final JID from;
}
/// Triggered when we receive a new or updated avatar
class AvatarUpdatedEvent extends XmppEvent {
AvatarUpdatedEvent({ required this.jid, required this.base64, required this.hash });
final String jid;
final String base64;
/// Triggered when we receive a new or updated avatar via XEP-0084
class UserAvatarUpdatedEvent extends XmppEvent {
UserAvatarUpdatedEvent(
this.jid,
this.metadata,
);
/// The JID of the user updating their avatar.
final JID jid;
/// The metadata of the avatar.
final List<UserAvatarMetadata> metadata;
}
/// Triggered when we receive a new or updated avatar via XEP-0054
class VCardAvatarUpdatedEvent extends XmppEvent {
VCardAvatarUpdatedEvent(
this.jid,
this.hash,
);
/// The JID of the entity that updated their avatar.
final JID jid;
/// The SHA-1 hash of the avatar.
final String hash;
}
/// Triggered when a PubSub notification has been received
class PubSubNotificationEvent extends XmppEvent {
PubSubNotificationEvent({ required this.item, required this.from });
PubSubNotificationEvent({required this.item, required this.from});
final PubSubItem item;
final String from;
}
@ -183,13 +209,13 @@ class StanzaAckedEvent extends XmppEvent {
/// Triggered when receiving a push of the blocklist
class BlocklistBlockPushEvent extends XmppEvent {
BlocklistBlockPushEvent({ required this.items });
BlocklistBlockPushEvent({required this.items});
final List<String> items;
}
/// Triggered when receiving a push of the blocklist
class BlocklistUnblockPushEvent extends XmppEvent {
BlocklistUnblockPushEvent({ required this.items });
BlocklistUnblockPushEvent({required this.items});
final List<String> items;
}
@ -211,3 +237,21 @@ class OmemoDeviceListUpdatedEvent extends XmppEvent {
final JID jid;
final List<int> deviceList;
}
/// Triggered when a reconnection is not performed due to a non-recoverable
/// error.
class NonRecoverableErrorEvent extends XmppEvent {
NonRecoverableErrorEvent(this.error);
/// The error in question.
final XmppError error;
}
/// Triggered when the stream negotiations are done.
class StreamNegotiationsDoneEvent extends XmppEvent {
StreamNegotiationsDoneEvent(this.resumed);
/// Flag indicating whether we resumed a previous stream (true) or are in a completely
/// new stream (false).
final bool resumed;
}

View File

@ -0,0 +1,132 @@
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:moxxmpp/src/errors.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/negotiators/negotiator.dart';
import 'package:moxxmpp/src/parser.dart';
import 'package:moxxmpp/src/settings.dart';
import 'package:moxxmpp/src/stringxml.dart';
/// A callback for when the [NegotiationsHandler] is done.
typedef NegotiationsDoneCallback = Future<void> Function();
/// A callback for the case that an error occurs while negotiating.
typedef ErrorCallback = Future<void> Function(XmppError);
/// Return true if the current connection is authenticated. If not, return false.
typedef IsAuthenticatedFunction = bool Function();
/// Send a nonza on the stream
typedef SendNonzaFunction = void Function(XMLNode);
/// Returns the connection settings.
typedef GetConnectionSettingsFunction = ConnectionSettings Function();
/// Resets the stream parser's state.
typedef ResetStreamParserFunction = void Function();
/// This class implements the stream feature negotiation for XmppConnection.
abstract class NegotiationsHandler {
@protected
late final Logger log;
/// Map of all negotiators registered against the handler.
@protected
final Map<String, XmppFeatureNegotiatorBase> negotiators = {};
/// Function that is called once the negotiator is done with its stream negotiations.
@protected
late final NegotiationsDoneCallback onNegotiationsDone;
/// XmppConnection's handleError method.
@protected
late final ErrorCallback handleError;
/// Returns true if the connection is authenticated. If not, returns false.
@protected
late final IsAuthenticatedFunction isAuthenticated;
/// Send a nonza over the stream.
@protected
late final SendNonzaFunction sendNonza;
/// Get the connection's settings.
@protected
late final GetConnectionSettingsFunction getConnectionSettings;
@protected
late final ResetStreamParserFunction resetStreamParser;
/// The id included in the last stream header.
@protected
String? streamId;
/// Set the id of the last stream header.
void setStreamHeaderId(String? id) {
streamId = id;
}
/// Returns, if registered, a negotiator with id [id].
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) =>
negotiators[id] as T?;
/// Register the parameters as the corresponding methods in this class. Also
/// initializes the logger.
void register(
NegotiationsDoneCallback onNegotiationsDone,
ErrorCallback handleError,
IsAuthenticatedFunction isAuthenticated,
SendNonzaFunction sendNonza,
GetConnectionSettingsFunction getConnectionSettings,
ResetStreamParserFunction resetStreamParser,
) {
this.onNegotiationsDone = onNegotiationsDone;
this.handleError = handleError;
this.isAuthenticated = isAuthenticated;
this.sendNonza = sendNonza;
this.getConnectionSettings = getConnectionSettings;
this.resetStreamParser = resetStreamParser;
log = Logger(toString());
}
/// Returns the xmlns attribute that stanzas should have.
String getStanzaNamespace();
/// Registers the negotiator [negotiator] against this negotiations handler.
void registerNegotiator(XmppFeatureNegotiatorBase negotiator);
/// Sends the stream header.
void sendStreamHeader();
/// Runs the post-register callback of all negotiators.
Future<void> runPostRegisterCallback() async {
for (final negotiator in negotiators.values) {
await negotiator.postRegisterCallback();
}
}
Future<void> sendEventToNegotiators(XmppEvent event) async {
for (final negotiator in negotiators.values) {
await negotiator.onXmppEvent(event);
}
}
/// Remove [feature] from the stream features we are currently negotiating.
void removeNegotiatingFeature(String feature) {}
/// Resets all registered negotiators and the negotiation handler.
@mustCallSuper
void reset() {
streamId = null;
for (final negotiator in negotiators.values) {
negotiator.reset();
}
}
/// Called whenever the stream buffer outputs a new event [event].
Future<void> negotiate(XMPPStreamObject event) async {
if (event is XMPPStreamHeader) {
streamId = event.attributes['id'];
}
}
}

View File

@ -0,0 +1,227 @@
import 'package:meta/meta.dart';
import 'package:moxxmpp/src/connection_errors.dart';
import 'package:moxxmpp/src/handlers/base.dart';
import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/negotiators/negotiator.dart';
import 'package:moxxmpp/src/parser.dart';
import 'package:moxxmpp/src/stringxml.dart';
/// "Nonza" describing the XMPP stream header of a client-to-server connection.
class ClientStreamHeaderNonza extends XMLNode {
ClientStreamHeaderNonza(JID jid)
: super(
tag: 'stream:stream',
attributes: <String, String>{
'xmlns': stanzaXmlns,
'version': '1.0',
'xmlns:stream': streamXmlns,
'to': jid.domain,
'from': jid.toBare().toString(),
'xml:lang': 'en',
},
closeTag: false,
);
}
/// This class implements the stream feature negotiation for usage in client to server
/// connections.
class ClientToServerNegotiator extends NegotiationsHandler {
ClientToServerNegotiator() : super();
/// Cached list of stream features.
final List<XMLNode> _streamFeatures = List.empty(growable: true);
/// The currently active negotiator.
XmppFeatureNegotiatorBase? _currentNegotiator;
@override
String getStanzaNamespace() => stanzaXmlns;
@override
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {
negotiators[negotiator.id] = negotiator;
}
@override
void reset() {
super.reset();
// Prevent leaking the last active negotiator
_currentNegotiator = null;
}
@override
void removeNegotiatingFeature(String feature) {
_streamFeatures.removeWhere((node) {
return node.attributes['xmlns'] == feature;
});
}
@override
void sendStreamHeader() {
resetStreamParser();
sendNonza(
XMLNode(
tag: 'xml',
attributes: {'version': '1.0'},
closeTag: false,
isDeclaration: true,
children: [
ClientStreamHeaderNonza(getConnectionSettings().jid),
],
),
);
}
/// Returns true if all mandatory features in [features] have been negotiated.
/// Otherwise returns false.
bool _isMandatoryNegotiationDone(List<XMLNode> features) {
return features.every((XMLNode feature) {
return feature.firstTag('required') == null &&
feature.tag != 'mechanisms';
});
}
/// Returns true if we can still negotiate. Returns false if no negotiator is
/// matching and ready.
bool _isNegotiationPossible(List<XMLNode> features) {
return getNextNegotiator(features, log: false) != null;
}
/// Returns the next negotiator that matches [features]. Returns null if none can be
/// picked. If [log] is true, then the list of matching negotiators will be logged.
@visibleForTesting
XmppFeatureNegotiatorBase? getNextNegotiator(
List<XMLNode> features, {
bool log = true,
}) {
final matchingNegotiators =
negotiators.values.where((XmppFeatureNegotiatorBase negotiator) {
return negotiator.state == NegotiatorState.ready &&
negotiator.matchesFeature(features);
}).toList()
..sort((a, b) => b.priority.compareTo(a.priority));
if (log) {
this.log.finest(
'List of matching negotiators: ${matchingNegotiators.map((a) => a.id)}',
);
}
if (matchingNegotiators.isEmpty) return null;
return matchingNegotiators.first;
}
Future<void> _executeCurrentNegotiator(XMLNode nonza) async {
// If we don't have a negotiator, get one
_currentNegotiator ??= getNextNegotiator(_streamFeatures);
if (_currentNegotiator == null &&
_isMandatoryNegotiationDone(_streamFeatures) &&
!_isNegotiationPossible(_streamFeatures)) {
log.finest('Negotiations done!');
await onNegotiationsDone();
return;
}
// If we don't have a next negotiator, we have to bail
if (_currentNegotiator == null &&
!_isMandatoryNegotiationDone(_streamFeatures) &&
!_isNegotiationPossible(_streamFeatures)) {
// We failed before authenticating
if (!isAuthenticated()) {
log.severe('No negotiator could be picked while unauthenticated');
await handleError(NoMatchingAuthenticationMechanismAvailableError());
return;
} else {
log.severe(
'No negotiator could be picked while negotiations are not done',
);
await handleError(NoAuthenticatorAvailableError());
return;
}
}
final result = await _currentNegotiator!.negotiate(nonza);
if (result.isType<NegotiatorError>()) {
log.severe('Negotiator returned an error');
await handleError(result.get<NegotiatorError>());
return;
}
final state = result.get<NegotiatorState>();
_currentNegotiator!.state = state;
switch (state) {
case NegotiatorState.ready:
return;
case NegotiatorState.done:
if (_currentNegotiator!.sendStreamHeaderWhenDone) {
_currentNegotiator = null;
_streamFeatures.clear();
sendStreamHeader();
} else {
removeNegotiatingFeature(_currentNegotiator!.negotiatingXmlns);
_currentNegotiator = null;
if (_isMandatoryNegotiationDone(_streamFeatures) &&
!_isNegotiationPossible(_streamFeatures)) {
log.finest('Negotiations done!');
await onNegotiationsDone();
} else {
_currentNegotiator = getNextNegotiator(_streamFeatures);
log.finest('Chose ${_currentNegotiator!.id} as next negotiator');
final fakeStanza = XMLNode(
tag: 'stream:features',
children: _streamFeatures,
);
await _executeCurrentNegotiator(fakeStanza);
}
}
break;
case NegotiatorState.retryLater:
log.finest('Negotiator wants to continue later. Picking new one...');
_currentNegotiator!.state = NegotiatorState.ready;
if (_isMandatoryNegotiationDone(_streamFeatures) &&
!_isNegotiationPossible(_streamFeatures)) {
log.finest('Negotiations done!');
await onNegotiationsDone();
} else {
log.finest('Picking new negotiator...');
_currentNegotiator = getNextNegotiator(_streamFeatures);
log.finest('Chose $_currentNegotiator as next negotiator');
final fakeStanza = XMLNode(
tag: 'stream:features',
children: _streamFeatures,
);
await _executeCurrentNegotiator(fakeStanza);
}
break;
case NegotiatorState.skipRest:
log.finest(
'Negotiator wants to skip the remaining negotiation... Negotiations (assumed) done!',
);
await onNegotiationsDone();
break;
}
}
@override
Future<void> negotiate(XMPPStreamObject event) async {
if (event is XMPPStreamElement) {
if (event.node.tag == 'stream:features') {
// Store the received stream features
_streamFeatures
..clear()
..addAll(event.node.children);
}
await _executeCurrentNegotiator(event.node);
}
}
}

View File

@ -0,0 +1,121 @@
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
import 'package:hex/hex.dart';
import 'package:moxxmpp/src/connection_errors.dart';
import 'package:moxxmpp/src/handlers/base.dart';
import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/negotiators/negotiator.dart';
import 'package:moxxmpp/src/parser.dart';
import 'package:moxxmpp/src/stringxml.dart';
/// Nonza describing the XMPP stream header.
class ComponentStreamHeaderNonza extends XMLNode {
ComponentStreamHeaderNonza(JID jid)
: assert(jid.isBare(), 'Component JID must be bare'),
super(
tag: 'stream:stream',
attributes: <String, String>{
'xmlns': componentAcceptXmlns,
'xmlns:stream': streamXmlns,
'to': jid.domain,
},
closeTag: false,
);
}
/// The states the ComponentToServerNegotiator can be in.
enum ComponentToServerState {
/// No data has been sent or received yet
idle,
/// Handshake has been sent
handshakeSent,
}
/// The ComponentToServerNegotiator is a NegotiationsHandler that allows writing
/// components that adhere to XEP-0114.
class ComponentToServerNegotiator extends NegotiationsHandler {
ComponentToServerNegotiator();
/// The state the negotiation handler is currently in
ComponentToServerState _state = ComponentToServerState.idle;
@override
String getStanzaNamespace() => componentAcceptXmlns;
@override
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {}
@override
void sendStreamHeader() {
resetStreamParser();
sendNonza(
XMLNode(
tag: 'xml',
attributes: {'version': '1.0'},
closeTag: false,
isDeclaration: true,
children: [
ComponentStreamHeaderNonza(getConnectionSettings().jid),
],
),
);
}
Future<String> _computeHandshake(String id) async {
final secret = getConnectionSettings().password;
return HEX.encode(
(await Sha1().hash(utf8.encode('$streamId$secret'))).bytes,
);
}
@override
Future<void> negotiate(XMPPStreamObject event) async {
switch (_state) {
case ComponentToServerState.idle:
if (event is XMPPStreamHeader) {
streamId = event.attributes['id'];
assert(
streamId != null,
'The server must respond with a stream header that contains an id',
);
_state = ComponentToServerState.handshakeSent;
sendNonza(
XMLNode(
tag: 'handshake',
text: await _computeHandshake(streamId!),
),
);
} else {
log.severe('Unexpected data received');
await handleError(UnexpectedDataError());
}
break;
case ComponentToServerState.handshakeSent:
if (event is XMPPStreamElement) {
if (event.node.tag == 'handshake' &&
event.node.children.isEmpty &&
event.node.attributes.isEmpty) {
log.info('Successfully authenticated as component');
await onNegotiationsDone();
} else {
log.warning('Handshake failed');
await handleError(InvalidHandshakeCredentialsError());
}
} else {
log.severe('Unexpected data received');
await handleError(UnexpectedDataError());
}
break;
}
}
@override
void reset() {
_state = ComponentToServerState.idle;
super.reset();
}
}

View File

@ -1,10 +1,33 @@
import 'package:moxxmpp/src/connection.dart';
import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/stanza.dart';
bool handleUnhandledStanza(XmppConnection conn, Stanza stanza) {
if (stanza.type != 'error' && stanza.type != 'result') {
conn.sendStanza(stanza.errorReply('cancel', 'feature-not-implemented'));
}
/// Bounce a stanza if it was not handled by any manager. [conn] is the connection object
/// to use for sending the stanza. [data] is the StanzaHandlerData of the unhandled
/// stanza.
Future<void> handleUnhandledStanza(
XmppConnection conn,
StanzaHandlerData data,
) async {
if (data.stanza.type != 'error' && data.stanza.type != 'result') {
final stanza = data.stanza.copyWith(
to: data.stanza.from,
from: data.stanza.to,
type: 'error',
children: [
buildErrorElement(
'cancel',
'feature-not-implemented',
),
],
);
return true;
await conn.sendStanza(
StanzaDetails(
stanza,
awaitable: false,
forceEncryption: data.encrypted,
),
);
}
}

View File

@ -1,83 +1,82 @@
import 'package:meta/meta.dart';
/// Represents a Jabber ID in parsed form.
@immutable
class JID {
const JID(this.local, this.domain, this.resource);
/// Parses the string [jid] into a JID instance.
factory JID.fromString(String jid) {
// 0: Parsing either the local or domain part
// 1: Parsing the domain part
// 2: Parsing the resource
var state = 0;
var buffer = '';
var local_ = '';
var domain_ = '';
var resource_ = '';
for (var i = 0; i < jid.length; i++) {
final c = jid[i];
final eol = i == jid.length - 1;
switch (state) {
case 0: {
if (c == '@') {
local_ = buffer;
buffer = '';
state = 1;
} else if (c == '/') {
domain_ = buffer;
buffer = '';
state = 2;
} else if (eol) {
domain_ = buffer + c;
} else {
buffer += c;
}
}
break;
case 1: {
if (c == '/') {
domain_ = buffer;
buffer = '';
state = 2;
} else if (eol) {
domain_ = buffer;
// Algorithm taken from here: https://blog.samwhited.com/2021/02/xmpp-addresses/
var localPart = '';
var domainPart = '';
var resourcePart = '';
if (c != ' ') {
domain_ = domain_ + c;
}
} else if (c != ' ') {
buffer += c;
}
}
break;
case 2: {
if (eol) {
resource_ = buffer;
final slashParts = jid.split('/');
if (slashParts.length == 1) {
resourcePart = '';
} else {
resourcePart = slashParts.sublist(1).join('/');
if (c != ' ') {
resource_ = resource_ + c;
}
} else if (c != ''){
buffer += c;
}
}
}
assert(
resourcePart.isNotEmpty,
'Resource part cannot be there and empty',
);
}
return JID(local_, domain_, resource_);
final atParts = slashParts.first.split('@');
if (atParts.length == 1) {
localPart = '';
domainPart = atParts.first;
} else {
localPart = atParts.first;
domainPart = atParts.sublist(1).join('@');
assert(localPart.isNotEmpty, 'Local part cannot be there and empty');
}
return JID(
localPart,
domainPart.endsWith('.')
? domainPart.substring(0, domainPart.length - 1)
: domainPart,
resourcePart,
);
}
final String local;
final String domain;
final String resource;
/// Returns true if the JID is bare.
bool isBare() => resource.isEmpty;
/// Returns true if the JID is full.
bool isFull() => resource.isNotEmpty;
JID toBare() => JID(local, domain, '');
/// Converts the JID into a bare JID.
JID toBare() {
return JID(local, domain, '');
}
/// Converts the JID into one with a resource part of [resource].
JID withResource(String resource) => JID(local, domain, resource);
/// Convert the JID into the JID of the domain. For example, converts alice@example.org/abc123 to example.org.
JID toDomain() {
return JID('', domain, '');
}
/// Compares the JID with [other]. This function assumes that JID and [other]
/// are bare, i.e. only the domain- and localparts are compared. If [ensureBare]
/// is optionally set to true, then [other] MUST be bare. Otherwise, false is returned.
bool bareCompare(JID other, {bool ensureBare = false}) {
if (ensureBare && !other.isBare()) return false;
return local == other.local && domain == other.domain;
}
/// Converts to JID instance into its string representation of
/// localpart@domainpart/resource.
@override
String toString() {
var result = '';
@ -97,7 +96,9 @@ class JID {
@override
bool operator ==(Object other) {
if (other is JID) {
return other.local == local && other.domain == domain && other.resource == resource;
return other.local == local &&
other.domain == domain &&
other.resource == resource;
}
return false;

View File

@ -1,5 +1,4 @@
import 'dart:async';
import 'package:moxxmpp/src/connection.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/jid.dart';
@ -11,21 +10,20 @@ import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
class XmppManagerAttributes {
XmppManagerAttributes({
required this.sendStanza,
required this.sendNonza,
required this.getManagerById,
required this.sendEvent,
required this.getConnectionSettings,
required this.isFeatureSupported,
required this.getFullJID,
required this.getSocket,
required this.getConnection,
required this.getNegotiatorById,
});
/// Send a stanza whose response can be awaited.
final Future<XMLNode> Function(Stanza stanza, { StanzaFromType addFrom, bool addId, bool awaitable, bool encrypted}) sendStanza;
final Future<XMLNode?> Function(StanzaDetails) sendStanza;
/// Send a nonza.
final void Function(XMLNode) sendNonza;
@ -39,9 +37,6 @@ class XmppManagerAttributes {
/// (Maybe) Get a Manager attached to the connection by its Id.
final T? Function<T extends XmppManagerBase>(String) getManagerById;
/// Returns true if a server feature is supported
final bool Function(String) isFeatureSupported;
/// Returns the full JID of the current account
final JID Function() getFullJID;
@ -51,5 +46,6 @@ class XmppManagerAttributes {
/// Return the [XmppConnection] the manager is registered against.
final XmppConnection Function() getConnection;
final T? Function<T extends XmppFeatureNegotiatorBase>(String) getNegotiatorById;
final T? Function<T extends XmppFeatureNegotiatorBase>(String)
getNegotiatorById;
}

View File

@ -1,72 +1,178 @@
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/managers/attributes.dart';
import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/managers/handlers.dart';
import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
abstract class XmppManagerBase {
XmppManagerBase(this.id);
late final XmppManagerAttributes _managerAttributes;
late final Logger _log;
/// Flag indicating that the post registration callback has been called once.
bool initialized = false;
/// Registers the callbacks from XmppConnection with the manager
void register(XmppManagerAttributes attributes) {
_managerAttributes = attributes;
_log = Logger(getName());
_log = Logger(name);
}
/// Returns the attributes that are registered with the manager.
/// Must only be called after register has been called on it.
XmppManagerAttributes getAttributes() {
return _managerAttributes;
}
/// Resolves to true when the server supports the disco feature [xmlns]. Resolves
/// to false when either the disco request fails or the server does not
/// support [xmlns].
/// Note that this function requires a registered DiscoManager.
@protected
Future<bool> isFeatureSupported(String xmlns) async {
final dm = _managerAttributes.getManagerById<DiscoManager>(discoManager);
assert(
dm != null,
'The DiscoManager must be registered for isFeatureSupported to work',
);
final result = await dm!.discoInfoQuery(
_managerAttributes.getConnectionSettings().jid.toDomain(),
);
if (result.isType<DiscoError>()) {
return false;
}
return result.get<DiscoInfo>().features.contains(xmlns);
}
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
/// send. These are run before the stanza is sent.
/// send. These are run before the stanza is sent. The higher the value of the
/// handler's priority, the earlier it is run.
List<StanzaHandler> getOutgoingPreStanzaHandlers() => [];
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
/// send. These are run after the stanza is sent.
/// send. These are run after the stanza is sent. The higher the value of the
/// handler's priority, the earlier it is run.
List<StanzaHandler> getOutgoingPostStanzaHandlers() => [];
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
/// receive.
/// receive. The higher the value of the
/// handler's priority, the earlier it is run.
List<StanzaHandler> getIncomingStanzaHandlers() => [];
/// Return the NonzaHandlers associated with this manager.
/// Return the StanzaHandlers associated with this manager that deal with stanza handlers
/// that have to run before the main ones run. This is useful, for example, for OMEMO
/// as we have to decrypt the stanza before we do anything else. The higher the value
/// of the handler's priority, the earlier it is run.
List<StanzaHandler> getIncomingPreStanzaHandlers() => [];
/// Return the NonzaHandlers associated with this manager. The higher the value of the
/// handler's priority, the earlier it is run.
List<NonzaHandler> getNonzaHandlers() => [];
/// Whenever the socket receives data, this method is called, if it is non-null.
Future<void> onData() async {}
/// Return a list of features that should be included in a disco response.
List<String> getDiscoFeatures() => [];
/// Return the Id (akin to xmlns) of this manager.
String getId();
/// Return a name that will be used for logging.
String getName();
/// Return a list of identities that should be included in a disco response.
List<Identity> getDiscoIdentities() => [];
/// Return the Id (akin to xmlns) of this manager.
final String id;
/// The name of the manager.
String get name => toString();
/// Return the logger for this manager.
Logger get logger => _log;
/// Called when XmppConnection triggers an event
Future<void> onXmppEvent(XmppEvent event) async {}
/// Returns true if the XEP is supported on the server. If not, returns false
Future<bool> isSupported();
/// Called after the registration of all managers against the XmppConnection is done.
/// This method is only called once during the entire lifetime of it.
@mustCallSuper
Future<void> postRegisterCallback() async {
initialized = true;
final disco = getAttributes().getManagerById<DiscoManager>(discoManager);
if (disco != null) {
if (getDiscoFeatures().isNotEmpty) {
disco.addFeatures(getDiscoFeatures());
}
if (getDiscoIdentities().isNotEmpty) {
disco.addIdentities(getDiscoIdentities());
}
}
}
/// Runs all NonzaHandlers of this Manager which match the nonza. Resolves to true if
/// the nonza has been handled by one of the handlers. Resolves to false otherwise.
Future<bool> runNonzaHandlers(XMLNode nonza) async {
var handled = false;
await Future.forEach(
getNonzaHandlers(),
(NonzaHandler handler) async {
if (handler.matches(nonza)) {
handled = true;
await handler.callback(nonza);
}
await Future.forEach(getNonzaHandlers(), (NonzaHandler handler) async {
if (handler.matches(nonza)) {
handled = true;
await handler.callback(nonza);
}
);
});
return handled;
}
/// Returns true, if the current stream negotiations resulted in a new stream. Useful
/// for plugins to reset their cache in case of a new stream.
/// The value only makes sense after receiving a StreamNegotiationsDoneEvent.
Future<bool> isNewStream() async {
final sm =
getAttributes().getManagerById<StreamManagementManager>(smManager);
return sm?.streamResumed == false;
}
/// Sends a reply of the stanza in [data] with [type]. Replaces the original stanza's
/// children with [children].
///
/// Note that this function currently only accepts IQ stanzas.
Future<void> reply(
StanzaHandlerData data,
String type,
List<XMLNode> children,
) async {
assert(
data.stanza.tag == 'iq',
'Reply makes little sense for non-IQ stanzas',
);
final stanza = data.stanza.copyWith(
to: data.stanza.from,
from: data.stanza.to,
type: type,
children: children,
);
await getAttributes().sendStanza(
StanzaDetails(
stanza,
awaitable: false,
forceEncryption: data.encrypted,
),
);
}
}

View File

@ -1,66 +1,59 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/xeps/xep_0066.dart';
import 'package:moxxmpp/src/xeps/xep_0085.dart';
import 'package:moxxmpp/src/xeps/xep_0203.dart';
import 'package:moxxmpp/src/xeps/xep_0359.dart';
import 'package:moxxmpp/src/xeps/xep_0380.dart';
import 'package:moxxmpp/src/xeps/xep_0385.dart';
import 'package:moxxmpp/src/xeps/xep_0424.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart';
import 'package:moxxmpp/src/xeps/xep_0447.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart';
import 'package:moxxmpp/src/util/typed_map.dart';
part 'data.freezed.dart';
abstract class StanzaHandlerExtension {}
@freezed
class StanzaHandlerData with _$StanzaHandlerData {
factory StanzaHandlerData(
// Indicates to the runner that processing is now done. This means that all
// pre-processing is done and no other handlers should be consulted.
bool done,
// Indicates to the runner that processing is to be cancelled and no further handlers
// should run. The stanza also will not be sent.
bool cancel,
// The reason why we cancelled the processing and sending
dynamic cancelReason,
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
// necessary, e.g. with Message Carbons or OMEMO
Stanza stanza,
{
// Whether the stanza is retransmitted. Only useful in the context of outgoing
// stanza handlers. MUST NOT be overwritten.
@Default(false) bool retransmitted,
StatelessMediaSharingData? sims,
StatelessFileSharingData? sfs,
OOBData? oob,
StableStanzaId? stableId,
ReplyData? reply,
ChatState? chatState,
@Default(false) bool isCarbon,
@Default(false) bool deliveryReceiptRequested,
@Default(false) bool isMarkable,
// File Upload Notifications
// A notification
FileMetadataData? fun,
// The stanza id this replaces
String? funReplacement,
// The stanza id this cancels
String? funCancellation,
// Whether the stanza was received encrypted
@Default(false) bool encrypted,
// The stated type of encryption used, if any was used
ExplicitEncryptionType? encryptionType,
// Delayed Delivery
DelayedDelivery? delayedDelivery,
// This is for stanza handlers that are not part of the XMPP library but still need
// pass data around.
@Default(<String, dynamic>{}) Map<String, dynamic> other,
// If non-null, then it indicates the origin Id of the message that should be
// retracted
MessageRetractionData? messageRetraction,
// If non-null, then the message is a correction for the specified stanza Id
String? lastMessageCorrectionSid,
}
) = _StanzaHandlerData;
class StanzaHandlerData {
StanzaHandlerData(
this.done,
this.cancel,
this.stanza,
this.extensions, {
this.cancelReason,
this.encryptionError,
this.encrypted = false,
this.forceEncryption = false,
this.shouldEncrypt = true,
this.skip = false,
});
/// Indicates to the runner that processing is now done. This means that all
/// pre-processing is done and no other handlers should be consulted.
bool done;
/// Only useful in combination with [done] = true: When [skip] is set to true and
/// this [StanzaHandlerData] object is returned from a IncomingPreStanzaHandler, then
/// moxxmpp will skip checking whether the stanza was awaited and will not run any actual
/// IncomingStanzaHandler callbacks.
bool skip;
/// Indicates to the runner that processing is to be cancelled and no further handlers
/// should run. The stanza also will not be sent.
bool cancel;
/// The reason why we cancelled the processing and sending.
Object? cancelReason;
/// The reason why an encryption or decryption failed.
Object? encryptionError;
/// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
Stanza stanza;
/// Whether the stanza is already encrypted
bool encrypted;
// If true, forces the encryption manager to encrypt to the JID, even if it
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
// but forceEncryption is true, then the OMEMO manager will try to encrypt
// to the JID anyway.
bool forceEncryption;
/// Flag indicating whether a E2EE implementation should encrypt the stanza (true)
/// or not (false).
bool shouldEncrypt;
/// Additional data from other managers.
final TypedMap<StanzaHandlerExtension> extensions;
}

View File

@ -1,665 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'data.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$StanzaHandlerData {
// Indicates to the runner that processing is now done. This means that all
// pre-processing is done and no other handlers should be consulted.
bool get done =>
throw _privateConstructorUsedError; // Indicates to the runner that processing is to be cancelled and no further handlers
// should run. The stanza also will not be sent.
bool get cancel =>
throw _privateConstructorUsedError; // The reason why we cancelled the processing and sending
dynamic get cancelReason =>
throw _privateConstructorUsedError; // The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
// necessary, e.g. with Message Carbons or OMEMO
Stanza get stanza =>
throw _privateConstructorUsedError; // Whether the stanza is retransmitted. Only useful in the context of outgoing
// stanza handlers. MUST NOT be overwritten.
bool get retransmitted => throw _privateConstructorUsedError;
StatelessMediaSharingData? get sims => throw _privateConstructorUsedError;
StatelessFileSharingData? get sfs => throw _privateConstructorUsedError;
OOBData? get oob => throw _privateConstructorUsedError;
StableStanzaId? get stableId => throw _privateConstructorUsedError;
ReplyData? get reply => throw _privateConstructorUsedError;
ChatState? get chatState => throw _privateConstructorUsedError;
bool get isCarbon => throw _privateConstructorUsedError;
bool get deliveryReceiptRequested => throw _privateConstructorUsedError;
bool get isMarkable =>
throw _privateConstructorUsedError; // File Upload Notifications
// A notification
FileMetadataData? get fun =>
throw _privateConstructorUsedError; // The stanza id this replaces
String? get funReplacement =>
throw _privateConstructorUsedError; // The stanza id this cancels
String? get funCancellation =>
throw _privateConstructorUsedError; // Whether the stanza was received encrypted
bool get encrypted =>
throw _privateConstructorUsedError; // The stated type of encryption used, if any was used
ExplicitEncryptionType? get encryptionType =>
throw _privateConstructorUsedError; // Delayed Delivery
DelayedDelivery? get delayedDelivery =>
throw _privateConstructorUsedError; // This is for stanza handlers that are not part of the XMPP library but still need
// pass data around.
Map<String, dynamic> get other =>
throw _privateConstructorUsedError; // If non-null, then it indicates the origin Id of the message that should be
// retracted
MessageRetractionData? get messageRetraction =>
throw _privateConstructorUsedError; // If non-null, then the message is a correction for the specified stanza Id
String? get lastMessageCorrectionSid => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$StanzaHandlerDataCopyWith<StanzaHandlerData> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $StanzaHandlerDataCopyWith<$Res> {
factory $StanzaHandlerDataCopyWith(
StanzaHandlerData value, $Res Function(StanzaHandlerData) then) =
_$StanzaHandlerDataCopyWithImpl<$Res>;
$Res call(
{bool done,
bool cancel,
dynamic cancelReason,
Stanza stanza,
bool retransmitted,
StatelessMediaSharingData? sims,
StatelessFileSharingData? sfs,
OOBData? oob,
StableStanzaId? stableId,
ReplyData? reply,
ChatState? chatState,
bool isCarbon,
bool deliveryReceiptRequested,
bool isMarkable,
FileMetadataData? fun,
String? funReplacement,
String? funCancellation,
bool encrypted,
ExplicitEncryptionType? encryptionType,
DelayedDelivery? delayedDelivery,
Map<String, dynamic> other,
MessageRetractionData? messageRetraction,
String? lastMessageCorrectionSid});
}
/// @nodoc
class _$StanzaHandlerDataCopyWithImpl<$Res>
implements $StanzaHandlerDataCopyWith<$Res> {
_$StanzaHandlerDataCopyWithImpl(this._value, this._then);
final StanzaHandlerData _value;
// ignore: unused_field
final $Res Function(StanzaHandlerData) _then;
@override
$Res call({
Object? done = freezed,
Object? cancel = freezed,
Object? cancelReason = freezed,
Object? stanza = freezed,
Object? retransmitted = freezed,
Object? sims = freezed,
Object? sfs = freezed,
Object? oob = freezed,
Object? stableId = freezed,
Object? reply = freezed,
Object? chatState = freezed,
Object? isCarbon = freezed,
Object? deliveryReceiptRequested = freezed,
Object? isMarkable = freezed,
Object? fun = freezed,
Object? funReplacement = freezed,
Object? funCancellation = freezed,
Object? encrypted = freezed,
Object? encryptionType = freezed,
Object? delayedDelivery = freezed,
Object? other = freezed,
Object? messageRetraction = freezed,
Object? lastMessageCorrectionSid = freezed,
}) {
return _then(_value.copyWith(
done: done == freezed
? _value.done
: done // ignore: cast_nullable_to_non_nullable
as bool,
cancel: cancel == freezed
? _value.cancel
: cancel // ignore: cast_nullable_to_non_nullable
as bool,
cancelReason: cancelReason == freezed
? _value.cancelReason
: cancelReason // ignore: cast_nullable_to_non_nullable
as dynamic,
stanza: stanza == freezed
? _value.stanza
: stanza // ignore: cast_nullable_to_non_nullable
as Stanza,
retransmitted: retransmitted == freezed
? _value.retransmitted
: retransmitted // ignore: cast_nullable_to_non_nullable
as bool,
sims: sims == freezed
? _value.sims
: sims // ignore: cast_nullable_to_non_nullable
as StatelessMediaSharingData?,
sfs: sfs == freezed
? _value.sfs
: sfs // ignore: cast_nullable_to_non_nullable
as StatelessFileSharingData?,
oob: oob == freezed
? _value.oob
: oob // ignore: cast_nullable_to_non_nullable
as OOBData?,
stableId: stableId == freezed
? _value.stableId
: stableId // ignore: cast_nullable_to_non_nullable
as StableStanzaId?,
reply: reply == freezed
? _value.reply
: reply // ignore: cast_nullable_to_non_nullable
as ReplyData?,
chatState: chatState == freezed
? _value.chatState
: chatState // ignore: cast_nullable_to_non_nullable
as ChatState?,
isCarbon: isCarbon == freezed
? _value.isCarbon
: isCarbon // ignore: cast_nullable_to_non_nullable
as bool,
deliveryReceiptRequested: deliveryReceiptRequested == freezed
? _value.deliveryReceiptRequested
: deliveryReceiptRequested // ignore: cast_nullable_to_non_nullable
as bool,
isMarkable: isMarkable == freezed
? _value.isMarkable
: isMarkable // ignore: cast_nullable_to_non_nullable
as bool,
fun: fun == freezed
? _value.fun
: fun // ignore: cast_nullable_to_non_nullable
as FileMetadataData?,
funReplacement: funReplacement == freezed
? _value.funReplacement
: funReplacement // ignore: cast_nullable_to_non_nullable
as String?,
funCancellation: funCancellation == freezed
? _value.funCancellation
: funCancellation // ignore: cast_nullable_to_non_nullable
as String?,
encrypted: encrypted == freezed
? _value.encrypted
: encrypted // ignore: cast_nullable_to_non_nullable
as bool,
encryptionType: encryptionType == freezed
? _value.encryptionType
: encryptionType // ignore: cast_nullable_to_non_nullable
as ExplicitEncryptionType?,
delayedDelivery: delayedDelivery == freezed
? _value.delayedDelivery
: delayedDelivery // ignore: cast_nullable_to_non_nullable
as DelayedDelivery?,
other: other == freezed
? _value.other
: other // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
messageRetraction: messageRetraction == freezed
? _value.messageRetraction
: messageRetraction // ignore: cast_nullable_to_non_nullable
as MessageRetractionData?,
lastMessageCorrectionSid: lastMessageCorrectionSid == freezed
? _value.lastMessageCorrectionSid
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
abstract class _$$_StanzaHandlerDataCopyWith<$Res>
implements $StanzaHandlerDataCopyWith<$Res> {
factory _$$_StanzaHandlerDataCopyWith(_$_StanzaHandlerData value,
$Res Function(_$_StanzaHandlerData) then) =
__$$_StanzaHandlerDataCopyWithImpl<$Res>;
@override
$Res call(
{bool done,
bool cancel,
dynamic cancelReason,
Stanza stanza,
bool retransmitted,
StatelessMediaSharingData? sims,
StatelessFileSharingData? sfs,
OOBData? oob,
StableStanzaId? stableId,
ReplyData? reply,
ChatState? chatState,
bool isCarbon,
bool deliveryReceiptRequested,
bool isMarkable,
FileMetadataData? fun,
String? funReplacement,
String? funCancellation,
bool encrypted,
ExplicitEncryptionType? encryptionType,
DelayedDelivery? delayedDelivery,
Map<String, dynamic> other,
MessageRetractionData? messageRetraction,
String? lastMessageCorrectionSid});
}
/// @nodoc
class __$$_StanzaHandlerDataCopyWithImpl<$Res>
extends _$StanzaHandlerDataCopyWithImpl<$Res>
implements _$$_StanzaHandlerDataCopyWith<$Res> {
__$$_StanzaHandlerDataCopyWithImpl(
_$_StanzaHandlerData _value, $Res Function(_$_StanzaHandlerData) _then)
: super(_value, (v) => _then(v as _$_StanzaHandlerData));
@override
_$_StanzaHandlerData get _value => super._value as _$_StanzaHandlerData;
@override
$Res call({
Object? done = freezed,
Object? cancel = freezed,
Object? cancelReason = freezed,
Object? stanza = freezed,
Object? retransmitted = freezed,
Object? sims = freezed,
Object? sfs = freezed,
Object? oob = freezed,
Object? stableId = freezed,
Object? reply = freezed,
Object? chatState = freezed,
Object? isCarbon = freezed,
Object? deliveryReceiptRequested = freezed,
Object? isMarkable = freezed,
Object? fun = freezed,
Object? funReplacement = freezed,
Object? funCancellation = freezed,
Object? encrypted = freezed,
Object? encryptionType = freezed,
Object? delayedDelivery = freezed,
Object? other = freezed,
Object? messageRetraction = freezed,
Object? lastMessageCorrectionSid = freezed,
}) {
return _then(_$_StanzaHandlerData(
done == freezed
? _value.done
: done // ignore: cast_nullable_to_non_nullable
as bool,
cancel == freezed
? _value.cancel
: cancel // ignore: cast_nullable_to_non_nullable
as bool,
cancelReason == freezed
? _value.cancelReason
: cancelReason // ignore: cast_nullable_to_non_nullable
as dynamic,
stanza == freezed
? _value.stanza
: stanza // ignore: cast_nullable_to_non_nullable
as Stanza,
retransmitted: retransmitted == freezed
? _value.retransmitted
: retransmitted // ignore: cast_nullable_to_non_nullable
as bool,
sims: sims == freezed
? _value.sims
: sims // ignore: cast_nullable_to_non_nullable
as StatelessMediaSharingData?,
sfs: sfs == freezed
? _value.sfs
: sfs // ignore: cast_nullable_to_non_nullable
as StatelessFileSharingData?,
oob: oob == freezed
? _value.oob
: oob // ignore: cast_nullable_to_non_nullable
as OOBData?,
stableId: stableId == freezed
? _value.stableId
: stableId // ignore: cast_nullable_to_non_nullable
as StableStanzaId?,
reply: reply == freezed
? _value.reply
: reply // ignore: cast_nullable_to_non_nullable
as ReplyData?,
chatState: chatState == freezed
? _value.chatState
: chatState // ignore: cast_nullable_to_non_nullable
as ChatState?,
isCarbon: isCarbon == freezed
? _value.isCarbon
: isCarbon // ignore: cast_nullable_to_non_nullable
as bool,
deliveryReceiptRequested: deliveryReceiptRequested == freezed
? _value.deliveryReceiptRequested
: deliveryReceiptRequested // ignore: cast_nullable_to_non_nullable
as bool,
isMarkable: isMarkable == freezed
? _value.isMarkable
: isMarkable // ignore: cast_nullable_to_non_nullable
as bool,
fun: fun == freezed
? _value.fun
: fun // ignore: cast_nullable_to_non_nullable
as FileMetadataData?,
funReplacement: funReplacement == freezed
? _value.funReplacement
: funReplacement // ignore: cast_nullable_to_non_nullable
as String?,
funCancellation: funCancellation == freezed
? _value.funCancellation
: funCancellation // ignore: cast_nullable_to_non_nullable
as String?,
encrypted: encrypted == freezed
? _value.encrypted
: encrypted // ignore: cast_nullable_to_non_nullable
as bool,
encryptionType: encryptionType == freezed
? _value.encryptionType
: encryptionType // ignore: cast_nullable_to_non_nullable
as ExplicitEncryptionType?,
delayedDelivery: delayedDelivery == freezed
? _value.delayedDelivery
: delayedDelivery // ignore: cast_nullable_to_non_nullable
as DelayedDelivery?,
other: other == freezed
? _value._other
: other // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,
messageRetraction: messageRetraction == freezed
? _value.messageRetraction
: messageRetraction // ignore: cast_nullable_to_non_nullable
as MessageRetractionData?,
lastMessageCorrectionSid: lastMessageCorrectionSid == freezed
? _value.lastMessageCorrectionSid
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class _$_StanzaHandlerData implements _StanzaHandlerData {
_$_StanzaHandlerData(this.done, this.cancel, this.cancelReason, this.stanza,
{this.retransmitted = false,
this.sims,
this.sfs,
this.oob,
this.stableId,
this.reply,
this.chatState,
this.isCarbon = false,
this.deliveryReceiptRequested = false,
this.isMarkable = false,
this.fun,
this.funReplacement,
this.funCancellation,
this.encrypted = false,
this.encryptionType,
this.delayedDelivery,
final Map<String, dynamic> other = const <String, dynamic>{},
this.messageRetraction,
this.lastMessageCorrectionSid})
: _other = other;
// Indicates to the runner that processing is now done. This means that all
// pre-processing is done and no other handlers should be consulted.
@override
final bool done;
// Indicates to the runner that processing is to be cancelled and no further handlers
// should run. The stanza also will not be sent.
@override
final bool cancel;
// The reason why we cancelled the processing and sending
@override
final dynamic cancelReason;
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
// necessary, e.g. with Message Carbons or OMEMO
@override
final Stanza stanza;
// Whether the stanza is retransmitted. Only useful in the context of outgoing
// stanza handlers. MUST NOT be overwritten.
@override
@JsonKey()
final bool retransmitted;
@override
final StatelessMediaSharingData? sims;
@override
final StatelessFileSharingData? sfs;
@override
final OOBData? oob;
@override
final StableStanzaId? stableId;
@override
final ReplyData? reply;
@override
final ChatState? chatState;
@override
@JsonKey()
final bool isCarbon;
@override
@JsonKey()
final bool deliveryReceiptRequested;
@override
@JsonKey()
final bool isMarkable;
// File Upload Notifications
// A notification
@override
final FileMetadataData? fun;
// The stanza id this replaces
@override
final String? funReplacement;
// The stanza id this cancels
@override
final String? funCancellation;
// Whether the stanza was received encrypted
@override
@JsonKey()
final bool encrypted;
// The stated type of encryption used, if any was used
@override
final ExplicitEncryptionType? encryptionType;
// Delayed Delivery
@override
final DelayedDelivery? delayedDelivery;
// This is for stanza handlers that are not part of the XMPP library but still need
// pass data around.
final Map<String, dynamic> _other;
// This is for stanza handlers that are not part of the XMPP library but still need
// pass data around.
@override
@JsonKey()
Map<String, dynamic> get other {
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_other);
}
// If non-null, then it indicates the origin Id of the message that should be
// retracted
@override
final MessageRetractionData? messageRetraction;
// If non-null, then the message is a correction for the specified stanza Id
@override
final String? lastMessageCorrectionSid;
@override
String toString() {
return 'StanzaHandlerData(done: $done, cancel: $cancel, cancelReason: $cancelReason, stanza: $stanza, retransmitted: $retransmitted, sims: $sims, sfs: $sfs, oob: $oob, stableId: $stableId, reply: $reply, chatState: $chatState, isCarbon: $isCarbon, deliveryReceiptRequested: $deliveryReceiptRequested, isMarkable: $isMarkable, fun: $fun, funReplacement: $funReplacement, funCancellation: $funCancellation, encrypted: $encrypted, encryptionType: $encryptionType, delayedDelivery: $delayedDelivery, other: $other, messageRetraction: $messageRetraction, lastMessageCorrectionSid: $lastMessageCorrectionSid)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_StanzaHandlerData &&
const DeepCollectionEquality().equals(other.done, done) &&
const DeepCollectionEquality().equals(other.cancel, cancel) &&
const DeepCollectionEquality()
.equals(other.cancelReason, cancelReason) &&
const DeepCollectionEquality().equals(other.stanza, stanza) &&
const DeepCollectionEquality()
.equals(other.retransmitted, retransmitted) &&
const DeepCollectionEquality().equals(other.sims, sims) &&
const DeepCollectionEquality().equals(other.sfs, sfs) &&
const DeepCollectionEquality().equals(other.oob, oob) &&
const DeepCollectionEquality().equals(other.stableId, stableId) &&
const DeepCollectionEquality().equals(other.reply, reply) &&
const DeepCollectionEquality().equals(other.chatState, chatState) &&
const DeepCollectionEquality().equals(other.isCarbon, isCarbon) &&
const DeepCollectionEquality().equals(
other.deliveryReceiptRequested, deliveryReceiptRequested) &&
const DeepCollectionEquality()
.equals(other.isMarkable, isMarkable) &&
const DeepCollectionEquality().equals(other.fun, fun) &&
const DeepCollectionEquality()
.equals(other.funReplacement, funReplacement) &&
const DeepCollectionEquality()
.equals(other.funCancellation, funCancellation) &&
const DeepCollectionEquality().equals(other.encrypted, encrypted) &&
const DeepCollectionEquality()
.equals(other.encryptionType, encryptionType) &&
const DeepCollectionEquality()
.equals(other.delayedDelivery, delayedDelivery) &&
const DeepCollectionEquality().equals(other._other, this._other) &&
const DeepCollectionEquality()
.equals(other.messageRetraction, messageRetraction) &&
const DeepCollectionEquality().equals(
other.lastMessageCorrectionSid, lastMessageCorrectionSid));
}
@override
int get hashCode => Object.hashAll([
runtimeType,
const DeepCollectionEquality().hash(done),
const DeepCollectionEquality().hash(cancel),
const DeepCollectionEquality().hash(cancelReason),
const DeepCollectionEquality().hash(stanza),
const DeepCollectionEquality().hash(retransmitted),
const DeepCollectionEquality().hash(sims),
const DeepCollectionEquality().hash(sfs),
const DeepCollectionEquality().hash(oob),
const DeepCollectionEquality().hash(stableId),
const DeepCollectionEquality().hash(reply),
const DeepCollectionEquality().hash(chatState),
const DeepCollectionEquality().hash(isCarbon),
const DeepCollectionEquality().hash(deliveryReceiptRequested),
const DeepCollectionEquality().hash(isMarkable),
const DeepCollectionEquality().hash(fun),
const DeepCollectionEquality().hash(funReplacement),
const DeepCollectionEquality().hash(funCancellation),
const DeepCollectionEquality().hash(encrypted),
const DeepCollectionEquality().hash(encryptionType),
const DeepCollectionEquality().hash(delayedDelivery),
const DeepCollectionEquality().hash(_other),
const DeepCollectionEquality().hash(messageRetraction),
const DeepCollectionEquality().hash(lastMessageCorrectionSid)
]);
@JsonKey(ignore: true)
@override
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
__$$_StanzaHandlerDataCopyWithImpl<_$_StanzaHandlerData>(
this, _$identity);
}
abstract class _StanzaHandlerData implements StanzaHandlerData {
factory _StanzaHandlerData(final bool done, final bool cancel,
final dynamic cancelReason, final Stanza stanza,
{final bool retransmitted,
final StatelessMediaSharingData? sims,
final StatelessFileSharingData? sfs,
final OOBData? oob,
final StableStanzaId? stableId,
final ReplyData? reply,
final ChatState? chatState,
final bool isCarbon,
final bool deliveryReceiptRequested,
final bool isMarkable,
final FileMetadataData? fun,
final String? funReplacement,
final String? funCancellation,
final bool encrypted,
final ExplicitEncryptionType? encryptionType,
final DelayedDelivery? delayedDelivery,
final Map<String, dynamic> other,
final MessageRetractionData? messageRetraction,
final String? lastMessageCorrectionSid}) = _$_StanzaHandlerData;
@override // Indicates to the runner that processing is now done. This means that all
// pre-processing is done and no other handlers should be consulted.
bool get done;
@override // Indicates to the runner that processing is to be cancelled and no further handlers
// should run. The stanza also will not be sent.
bool get cancel;
@override // The reason why we cancelled the processing and sending
dynamic get cancelReason;
@override // The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
// necessary, e.g. with Message Carbons or OMEMO
Stanza get stanza;
@override // Whether the stanza is retransmitted. Only useful in the context of outgoing
// stanza handlers. MUST NOT be overwritten.
bool get retransmitted;
@override
StatelessMediaSharingData? get sims;
@override
StatelessFileSharingData? get sfs;
@override
OOBData? get oob;
@override
StableStanzaId? get stableId;
@override
ReplyData? get reply;
@override
ChatState? get chatState;
@override
bool get isCarbon;
@override
bool get deliveryReceiptRequested;
@override
bool get isMarkable;
@override // File Upload Notifications
// A notification
FileMetadataData? get fun;
@override // The stanza id this replaces
String? get funReplacement;
@override // The stanza id this cancels
String? get funCancellation;
@override // Whether the stanza was received encrypted
bool get encrypted;
@override // The stated type of encryption used, if any was used
ExplicitEncryptionType? get encryptionType;
@override // Delayed Delivery
DelayedDelivery? get delayedDelivery;
@override // This is for stanza handlers that are not part of the XMPP library but still need
// pass data around.
Map<String, dynamic> get other;
@override // If non-null, then it indicates the origin Id of the message that should be
// retracted
MessageRetractionData? get messageRetraction;
@override // If non-null, then the message is a correction for the specified stanza Id
String? get lastMessageCorrectionSid;
@override
@JsonKey(ignore: true)
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,93 +1,114 @@
import 'package:moxlib/moxlib.dart';
import 'package:collection/collection.dart';
import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
/// A Handler is responsible for matching any kind of toplevel item in the XML stream
/// (stanzas and Nonzas). For that, its [matches] method is called. What happens
/// next depends on the subclass.
// ignore: one_member_abstracts
abstract class Handler {
const Handler(this.matchStanzas, { this.nonzaTag, this.nonzaXmlns });
final String? nonzaTag;
final String? nonzaXmlns;
final bool matchStanzas;
/// Returns true if the node matches the description provided by this [Handler].
bool matches(XMLNode node) {
var matches = false;
if (nonzaTag == null && nonzaXmlns == null) {
matches = true;
}
if (nonzaXmlns != null && nonzaTag != null) {
matches = (node.attributes['xmlns'] ?? '') == nonzaXmlns! && node.tag == nonzaTag!;
}
if (matchStanzas && nonzaTag == null) {
matches = [ 'iq', 'presence', 'message' ].contains(node.tag);
}
return matches;
}
bool matches(XMLNode node);
}
/// A Handler that specialises in matching Nonzas (and stanzas).
class NonzaHandler extends Handler {
NonzaHandler({
required this.callback,
String? nonzaTag,
String? nonzaXmlns,
}) : super(
false,
nonzaTag: nonzaTag,
nonzaXmlns: nonzaXmlns,
);
required this.callback,
this.nonzaTag,
this.nonzaXmlns,
});
/// The function to call when a nonza matches the description.
final Future<bool> Function(XMLNode) callback;
}
class StanzaHandler extends Handler {
/// The expected tag of a matching nonza.
final String? nonzaTag;
// The expected xmlns attribute of a matching nonza.
final String? nonzaXmlns;
StanzaHandler({
required this.callback,
this.tagXmlns,
this.tagName,
this.priority = 0,
String? stanzaTag,
}) : super(
true,
nonzaTag: stanzaTag,
nonzaXmlns: stanzaXmlns,
);
final String? tagName;
final String? tagXmlns;
final int priority;
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
@override
bool matches(XMLNode node) {
var matches = super.matches(node);
if (matches == false) {
return false;
}
if (tagName != null) {
final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
matches = firstTag != null;
} else if (tagXmlns != null) {
return listContains(
node.children,
(XMLNode node_) => node_.attributes.containsKey('xmlns') && node_.attributes['xmlns'] == tagXmlns,
);
var matches = true;
if (nonzaTag == null && nonzaXmlns == null) {
return true;
} else {
if (nonzaXmlns != null) {
matches &= node.attributes['xmlns'] == nonzaXmlns;
}
if (nonzaTag != null) {
matches &= node.tag == nonzaTag;
}
}
if (tagName == null && tagXmlns == null) {
matches = true;
}
return matches;
}
}
int stanzaHandlerSortComparator(StanzaHandler a, StanzaHandler b) => b.priority.compareTo(a.priority);
/// A Handler that only matches stanzas.
class StanzaHandler extends Handler {
StanzaHandler({
required this.callback,
this.tagXmlns,
this.tagName,
this.priority = 0,
this.stanzaTag,
this.xmlns,
});
/// If specified, then the stanza must contain a direct child with a tag equal to
/// [tagName].
final String? tagName;
/// If specified, then the stanza must contain a direct child with a xmlns attribute
/// equal to [tagXmlns]. If [tagName] is also non-null, then the element must also
/// have a tag equal to [tagName].
final String? tagXmlns;
/// If specified, the matching stanza must have a tag equal to [stanzaTag].
final String? stanzaTag;
/// If specified, then the stanza must have a xmlns attribute equal to [xmlns].
/// This defaults to [stanzaXmlns], but can be set to any other value or null. This
/// is useful, for example, for components.
final String? xmlns;
/// The priority after which [StanzaHandler]s are sorted.
final int priority;
/// The function to call when a stanza matches the description.
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
@override
bool matches(XMLNode node) {
var matches = ['iq', 'message', 'presence'].contains(node.tag);
if (stanzaTag != null) {
matches &= node.tag == stanzaTag;
}
if (xmlns != null) {
matches &= node.xmlns == xmlns;
}
if (tagName != null) {
final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
matches &= firstTag != null;
if (tagXmlns != null) {
matches &= firstTag?.xmlns == tagXmlns;
}
} else if (tagXmlns != null) {
matches &= node.children.firstWhereOrNull(
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
) !=
null;
}
return matches;
}
}
int stanzaHandlerSortComparator(StanzaHandler a, StanzaHandler b) =>
b.priority.compareTo(a.priority);

View File

@ -1,28 +1,36 @@
const smManager = 'im.moxxmpp.streammangementmanager';
const discoManager = 'im.moxxmpp.discomanager';
const messageManager = 'im.moxxmpp.messagemanager';
const rosterManager = 'im.moxxmpp.rostermanager';
const presenceManager = 'im.moxxmpp.presencemanager';
const csiManager = 'im.moxxmpp.csimanager';
const carbonsManager = 'im.moxxmpp.carbonsmanager';
const vcardManager = 'im.moxxmpp.vcardmanager';
const pubsubManager = 'im.moxxmpp.pubsubmanager';
const userAvatarManager = 'im.moxxmpp.useravatarmanager';
const stableIdManager = 'im.moxxmpp.stableidmanager';
const simsManager = 'im.moxxmpp.simsmanager';
const messageDeliveryReceiptManager = 'im.moxxmpp.messagedeliveryreceiptmanager';
const chatMarkerManager = 'im.moxxmpp.chatmarkermanager';
const oobManager = 'im.moxxmpp.oobmanager';
const sfsManager = 'im.moxxmpp.sfsmanager';
const messageRepliesManager = 'im.moxxmpp.messagerepliesmanager';
const blockingManager = 'im.moxxmpp.blockingmanager';
const httpFileUploadManager = 'im.moxxmpp.httpfileuploadmanager';
const chatStateManager = 'im.moxxmpp.chatstatemanager';
const pingManager = 'im.moxxmpp.ping';
const fileUploadNotificationManager = 'im.moxxmpp.fileuploadnotificationmanager';
const smManager = 'org.moxxmpp.streammangementmanager';
const discoManager = 'org.moxxmpp.discomanager';
const messageManager = 'org.moxxmpp.messagemanager';
const rosterManager = 'org.moxxmpp.rostermanager';
const presenceManager = 'org.moxxmpp.presencemanager';
const csiManager = 'org.moxxmpp.csimanager';
const carbonsManager = 'org.moxxmpp.carbonsmanager';
const vcardManager = 'org.moxxmpp.vcardmanager';
const pubsubManager = 'org.moxxmpp.pubsubmanager';
const userAvatarManager = 'org.moxxmpp.useravatarmanager';
const stableIdManager = 'org.moxxmpp.stableidmanager';
const simsManager = 'org.moxxmpp.simsmanager';
const messageDeliveryReceiptManager =
'org.moxxmpp.messagedeliveryreceiptmanager';
const chatMarkerManager = 'org.moxxmpp.chatmarkermanager';
const oobManager = 'org.moxxmpp.oobmanager';
const sfsManager = 'org.moxxmpp.sfsmanager';
const messageRepliesManager = 'org.moxxmpp.messagerepliesmanager';
const blockingManager = 'org.moxxmpp.blockingmanager';
const httpFileUploadManager = 'org.moxxmpp.httpfileuploadmanager';
const chatStateManager = 'org.moxxmpp.chatstatemanager';
const pingManager = 'org.moxxmpp.ping';
const fileUploadNotificationManager =
'org.moxxmpp.fileuploadnotificationmanager';
const omemoManager = 'org.moxxmpp.omemomanager';
const emeManager = 'org.moxxmpp.ememanager';
const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager';
const delayedDeliveryManager = 'org.moxxmpp.delayeddeliverymanager';
const messageRetractionManager = 'org.moxxmpp.messageretractionmanager';
const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager';
const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
const stickersManager = 'org.moxxmpp.stickersmanager';
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
const messageProcessingHintManager = 'org.moxxmpp.messageprocessinghint';
const occupantIdManager = 'org.moxxmpp.occupantidmanager';
const mucManager = 'org.moxxmpp.mucmanager';

View File

@ -1,267 +1,153 @@
import 'package:collection/collection.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/managers/base.dart';
import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/managers/handlers.dart';
import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:moxxmpp/src/xeps/xep_0066.dart';
import 'package:moxxmpp/src/xeps/xep_0085.dart';
import 'package:moxxmpp/src/xeps/xep_0184.dart';
import 'package:moxxmpp/src/xeps/xep_0308.dart';
import 'package:moxxmpp/src/xeps/xep_0333.dart';
import 'package:moxxmpp/src/xeps/xep_0359.dart';
import 'package:moxxmpp/src/xeps/xep_0424.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart';
import 'package:moxxmpp/src/xeps/xep_0447.dart';
import 'package:moxxmpp/src/xeps/xep_0448.dart';
import 'package:moxxmpp/src/xeps/xep_0449.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart';
class MessageDetails {
const MessageDetails({
required this.to,
this.body,
this.requestDeliveryReceipt = false,
this.requestChatMarkers = true,
this.id,
this.originId,
this.quoteBody,
this.quoteId,
this.quoteFrom,
this.chatState,
this.sfs,
this.fun,
this.funReplacement,
this.funCancellation,
this.shouldEncrypt = false,
this.messageRetraction,
this.lastMessageCorrectionId,
});
final String to;
/// A callback that is called whenever a message is sent using
/// [MessageManager.sendMessage]. The input the typed map that is passed to
/// sendMessage.
typedef MessageSendingCallback = List<XMLNode> Function(
TypedMap<StanzaHandlerExtension>,
);
/// The raw content of the <body /> element.
class MessageBodyData implements StanzaHandlerExtension {
const MessageBodyData(this.body);
/// The content of the <body /> element.
final String? body;
final bool requestDeliveryReceipt;
final bool requestChatMarkers;
final String? id;
final String? originId;
final String? quoteBody;
final String? quoteId;
final String? quoteFrom;
final ChatState? chatState;
final StatelessFileSharingData? sfs;
final FileMetadataData? fun;
final String? funReplacement;
final String? funCancellation;
final bool shouldEncrypt;
final MessageRetractionData? messageRetraction;
final String? lastMessageCorrectionId;
XMLNode toXML() {
return XMLNode(
tag: 'body',
text: body,
);
}
}
/// The id attribute of the message stanza.
class MessageIdData implements StanzaHandlerExtension {
const MessageIdData(this.id);
/// The id attribute of the stanza.
final String id;
}
class MessageManager extends XmppManagerBase {
@override
String getId() => messageManager;
MessageManager() : super(messageManager);
@override
String getName() => 'MessageManager';
/// The priority of the message handler. If a handler should run before this one,
/// which emits the [MessageEvent] event and terminates processing, make sure it
/// has a priority greater than [messageHandlerPriority].
static int messageHandlerPriority = -100;
/// A list of callbacks that are called when a message is sent in order to add
/// appropriate child elements.
final List<MessageSendingCallback> _messageSendingCallbacks =
List<MessageSendingCallback>.empty(growable: true);
void registerMessageSendingCallback(MessageSendingCallback callback) {
_messageSendingCallbacks.add(callback);
}
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: 'message',
callback: _onMessage,
priority: -100,
)
];
StanzaHandler(
stanzaTag: 'message',
callback: _onMessage,
priority: messageHandlerPriority,
),
];
@override
Future<bool> isSupported() async => true;
Future<StanzaHandlerData> _onMessage(Stanza _, StanzaHandlerData state) async {
final message = state.stanza;
final body = message.firstTag('body');
getAttributes().sendEvent(MessageEvent(
body: body != null ? body.innerText() : '',
fromJid: JID.fromString(message.attributes['from']! as String),
toJid: JID.fromString(message.attributes['to']! as String),
sid: message.attributes['id']! as String,
stanzaId: state.stableId ?? const StableStanzaId(),
isCarbon: state.isCarbon,
deliveryReceiptRequested: state.deliveryReceiptRequested,
isMarkable: state.isMarkable,
type: message.attributes['type'] as String?,
oob: state.oob,
sfs: state.sfs,
sims: state.sims,
reply: state.reply,
chatState: state.chatState,
fun: state.fun,
funReplacement: state.funReplacement,
funCancellation: state.funCancellation,
encrypted: state.encrypted,
messageRetraction: state.messageRetraction,
messageCorrectionId: state.lastMessageCorrectionSid,
other: state.other,
error: StanzaError.fromStanza(message),
),);
Future<StanzaHandlerData> _onMessage(
Stanza stanza,
StanzaHandlerData state,
) async {
final body = stanza.firstTag('body');
if (body != null) {
state.extensions.set(
MessageBodyData(body.innerText()),
);
}
return state.copyWith(done: true);
}
/// Send a message to to with the content body. If deliveryRequest is true, then
/// the message will also request a delivery receipt from the receiver.
/// If id is non-null, then it will be the id of the message stanza.
/// element to this id. If originId is non-null, then it will create an "origin-id"
/// child in the message stanza and set its id to originId.
void sendMessage(MessageDetails details) {
final stanza = Stanza.message(
to: details.to,
type: 'chat',
id: details.id,
children: [],
getAttributes().sendEvent(
MessageEvent(
JID.fromString(state.stanza.attributes['from']! as String),
JID.fromString(state.stanza.attributes['to']! as String),
state.encrypted,
state.extensions,
id: state.stanza.attributes['id'] as String?,
type: state.stanza.attributes['type'] as String?,
error: StanzaError.fromStanza(state.stanza),
encryptionError: state.encryptionError,
),
);
if (details.quoteBody != null) {
final fallback = '&gt; ${details.quoteBody!}';
return state..done = true;
}
stanza
..addChild(
XMLNode(tag: 'body', text: '$fallback\n${details.body}'),
)
..addChild(
XMLNode.xmlns(
tag: 'reply',
xmlns: replyXmlns,
attributes: {
'to': details.quoteFrom!,
'id': details.quoteId!
},
),
)
..addChild(
XMLNode.xmlns(
tag: 'fallback',
xmlns: fallbackXmlns,
attributes: {
'for': replyXmlns
},
children: [
XMLNode(
tag: 'body',
attributes: <String, String>{
'start': '0',
'end': '${fallback.length}'
},
)
],
),
);
} else {
var body = details.body;
if (details.sfs != null) {
// TODO(Unknown): Maybe find a better solution
final firstSource = details.sfs!.sources.first;
if (firstSource is StatelessFileSharingUrlSource) {
body = firstSource.url;
} else if (firstSource is StatelessFileSharingEncryptedSource) {
body = firstSource.source.url;
}
} else if (details.messageRetraction?.fallback != null) {
body = details.messageRetraction!.fallback;
}
stanza.addChild(
XMLNode(tag: 'body', text: body),
);
}
if (details.requestDeliveryReceipt) {
stanza.addChild(makeMessageDeliveryRequest());
}
if (details.requestChatMarkers) {
stanza.addChild(makeChatMarkerMarkable());
}
if (details.originId != null) {
stanza.addChild(makeOriginIdElement(details.originId!));
}
if (details.sfs != null) {
stanza.addChild(details.sfs!.toXML());
final source = details.sfs!.sources.first;
if (source is StatelessFileSharingUrlSource) {
// SFS recommends OOB as a fallback
stanza.addChild(constructOOBNode(OOBData(url: source.url)));
}
}
if (details.chatState != null) {
stanza.addChild(
// TODO(Unknown): Move this into xep_0085.dart
XMLNode.xmlns(tag: chatStateToString(details.chatState!), xmlns: chatStateXmlns),
);
}
if (details.fun != null) {
stanza.addChild(
XMLNode.xmlns(
tag: 'file-upload',
xmlns: fileUploadNotificationXmlns,
children: [
details.fun!.toXML(),
],
/// Send an unawaitable message to [to]. [extensions] is a typed map that contains
/// data for building the message.
Future<void> sendMessage(
JID to,
TypedMap<StanzaHandlerExtension> extensions, {
String type = 'chat',
}) async {
await getAttributes().sendStanza(
StanzaDetails(
Stanza.message(
to: to.toString(),
id: extensions.get<MessageIdData>()?.id,
type: type,
children: _messageSendingCallbacks
.map((c) => c(extensions))
.flattened
.toList(),
),
);
extensions: extensions,
awaitable: false,
),
);
}
List<XMLNode> _messageSendingCallback(
TypedMap<StanzaHandlerExtension> extensions,
) {
if (extensions.get<ReplyData>() != null) {
return [];
}
if (extensions.get<StickersData>() != null) {
return [];
}
if (extensions.get<StatelessFileSharingData>() != null) {
return [];
}
if (extensions.get<OOBData>() != null) {
return [];
}
if (details.funReplacement != null) {
stanza.addChild(
XMLNode.xmlns(
tag: 'replaces',
xmlns: fileUploadNotificationXmlns,
attributes: <String, String>{
'id': details.funReplacement!,
},
),
);
}
final data = extensions.get<MessageBodyData>();
return data != null ? [data.toXML()] : [];
}
if (details.messageRetraction != null) {
stanza.addChild(
XMLNode.xmlns(
tag: 'apply-to',
xmlns: fasteningXmlns,
attributes: <String, String>{
'id': details.messageRetraction!.id,
},
children: [
XMLNode.xmlns(
tag: 'retract',
xmlns: messageRetractionXmlns,
),
],
),
);
@override
Future<void> postRegisterCallback() async {
await super.postRegisterCallback();
if (details.messageRetraction!.fallback != null) {
stanza.addChild(
XMLNode.xmlns(
tag: 'fallback',
xmlns: fallbackIndicationXmlns,
),
);
}
}
if (details.lastMessageCorrectionId != null) {
stanza.addChild(
makeLastMessageCorrectionEdit(
details.lastMessageCorrectionId!,
),
);
}
getAttributes().sendStanza(stanza, awaitable: false);
// Register the sending callback
registerMessageSendingCallback(_messageSendingCallback);
}
}

View File

@ -9,9 +9,11 @@ const fullStanzaXmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas';
// RFC 6121
const rosterXmlns = 'jabber:iq:roster';
const rosterVersioningXmlns = 'urn:xmpp:features:rosterver';
const subscriptionPreApprovalXmlns = 'urn:xmpp:features:pre-approval';
// XEP-0004
const dataFormsXmlns = 'jabber:x:data';
const formVarFormType = 'FORM_TYPE';
// XEP-0030
const discoInfoXmlns = 'http://jabber.org/protocol/disco#info';
@ -20,6 +22,11 @@ const discoItemsXmlns = 'http://jabber.org/protocol/disco#items';
// XEP-0033
const extendedAddressingXmlns = 'http://jabber.org/protocol/address';
// XEP-0045
const mucXmlns = 'http://jabber.org/protocol/muc';
const mucUserXmlns = 'http://jabber.org/protocol/muc#user';
const roomInfoFormType = 'http://jabber.org/protocol/muc#roominfo';
// XEP-0054
const vCardTempXmlns = 'vcard-temp';
const vCardTempUpdate = 'vcard-temp:x:update';
@ -28,9 +35,11 @@ const vCardTempUpdate = 'vcard-temp:x:update';
const pubsubXmlns = 'http://jabber.org/protocol/pubsub';
const pubsubEventXmlns = 'http://jabber.org/protocol/pubsub#event';
const pubsubOwnerXmlns = 'http://jabber.org/protocol/pubsub#owner';
const pubsubPublishOptionsXmlns = 'http://jabber.org/protocol/pubsub#publish-options';
const pubsubPublishOptionsXmlns =
'http://jabber.org/protocol/pubsub#publish-options';
const pubsubNodeConfigMax = 'http://jabber.org/protocol/pubsub#config-node-max';
const pubsubNodeConfigMultiItems = 'http://jabber.org/protocol/pubsub#multi-items';
const pubsubNodeConfigMultiItems =
'http://jabber.org/protocol/pubsub#multi-items';
// XEP-0066
const oobDataXmlns = 'jabber:x:oob';
@ -42,6 +51,9 @@ const userAvatarMetadataXmlns = 'urn:xmpp:avatar:metadata';
// XEP-0085
const chatStateXmlns = 'http://jabber.org/protocol/chatstates';
// XEP-0114
const componentAcceptXmlns = 'jabber:component:accept';
// XEP-0115
const capsXmlns = 'http://jabber.org/protocol/caps';
@ -60,6 +72,9 @@ const delayedDeliveryXmlns = 'urn:xmpp:delay';
// XEP-0234
const jingleFileTransferXmlns = 'urn:xmpp:jingle:apps:file-transfer:5';
// XEP-0264
const jingleContentThumbnailXmlns = 'urn:xmpp:thumbs:1';
// XEP-0280
const carbonsXmlns = 'urn:xmpp:carbons:2';
@ -69,12 +84,6 @@ const forwardedXmlns = 'urn:xmpp:forward:0';
// XEP-0300
const hashXmlns = 'urn:xmpp:hashes:2';
const hashFunctionNameBaseXmlns = 'urn:xmpp:hash-function-text-names';
const hashSha256 = 'sha-256';
const hashSha512 = 'sha-512';
const hashSha3256 = 'sha3-256';
const hashSha3512 = 'sha3-512';
const hashBlake2b256 = 'blake2b-256';
const hashBlake2b512 = 'blake2b-512';
// XEP-0308
const lmcXmlns = 'urn:xmpp:message-correct:0';
@ -97,7 +106,7 @@ const httpFileUploadXmlns = 'urn:xmpp:http:upload:0';
// XEP-0372
const referenceXmlns = 'urn:xmpp:reference:0';
// XEP-380
// XEP-0380
const emeXmlns = 'urn:xmpp:eme:0';
const emeOtr = 'urn:xmpp:otr:0';
const emeLegacyOpenPGP = 'jabber:x:encrypted';
@ -114,18 +123,30 @@ const omemoBundlesXmlns = 'urn:xmpp:omemo:2:bundles';
// XEP-0385
const simsXmlns = 'urn:xmpp:sims:1';
// XEP-0386
const bind2Xmlns = 'urn:xmpp:bind:0';
// XEP-0388
const sasl2Xmlns = 'urn:xmpp:sasl:2';
// XEP-0420
const sceXmlns = 'urn:xmpp:sce:1';
// XEP-0421
const occupantIdXmlns = 'urn:xmpp:occupant-id:0';
// XEP-0422
const fasteningXmlns = 'urn:xmpp:fasten:0';
// XEP-0424
const messageRetractionXmlns = 'urn:xmpp:message-retract:0';
// XEp-0428
// XEP-0428
const fallbackIndicationXmlns = 'urn:xmpp:fallback:0';
// XEP-0444
const messageReactionsXmlns = 'urn:xmpp:reactions:0';
// XEP-0446
const fileMetadataXmlns = 'urn:xmpp:file:metadata:0';
@ -134,13 +155,20 @@ const sfsXmlns = 'urn:xmpp:sfs:0';
// XEP-0448
const sfsEncryptionXmlns = 'urn:xmpp:esfs:0';
const sfsEncryptionAes128GcmNoPaddingXmlns = 'urn:xmpp:ciphers:aes-128-gcm-nopadding:0';
const sfsEncryptionAes256GcmNoPaddingXmlns = 'urn:xmpp:ciphers:aes-256-gcm-nopadding:0';
const sfsEncryptionAes128GcmNoPaddingXmlns =
'urn:xmpp:ciphers:aes-128-gcm-nopadding:0';
const sfsEncryptionAes256GcmNoPaddingXmlns =
'urn:xmpp:ciphers:aes-256-gcm-nopadding:0';
const sfsEncryptionAes256CbcPkcs7Xmlns = 'urn:xmpp:ciphers:aes-256-cbc-pkcs7:0';
// XEP-0449
const stickersXmlns = 'urn:xmpp:stickers:0';
// XEP-0461
const replyXmlns = 'urn:xmpp:reply:0';
const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
// ???
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
// XEP-XXXX
const fastXmlns = 'urn:xmpp:fast:0';

View File

@ -7,3 +7,8 @@ const rosterNegotiator = 'im.moxxmpp.core.roster';
const resourceBindingNegotiator = 'im.moxxmpp.core.resource';
const streamManagementNegotiator = 'im.moxxmpp.xeps.sm';
const startTlsNegotiator = 'im.moxxmpp.core.starttls';
const sasl2Negotiator = 'org.moxxmpp.sasl.sasl2';
const bind2Negotiator = 'org.moxxmpp.bind2';
const saslFASTNegotiator = 'org.moxxmpp.sasl.fast';
const carbonsNegotiator = 'org.moxxmpp.bind2.carbons';
const presenceNegotiator = 'org.moxxmpp.core.presence';

View File

@ -1,4 +1,7 @@
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:moxlib/moxlib.dart';
import 'package:moxxmpp/src/connection.dart';
import 'package:moxxmpp/src/errors.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/jid.dart';
@ -6,7 +9,6 @@ import 'package:moxxmpp/src/managers/base.dart';
import 'package:moxxmpp/src/settings.dart';
import 'package:moxxmpp/src/socket.dart';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/types/result.dart';
/// The state a negotiator is currently in
enum NegotiatorState {
@ -27,6 +29,7 @@ abstract class NegotiatorError extends XmppError {}
class NegotiatorAttributes {
const NegotiatorAttributes(
this.sendNonza,
this.getConnection,
this.getConnectionSettings,
this.sendEvent,
this.getNegotiatorById,
@ -34,29 +37,59 @@ class NegotiatorAttributes {
this.getFullJID,
this.getSocket,
this.isAuthenticated,
this.setAuthenticated,
this.setResource,
this.removeNegotiatingFeature,
);
/// Sends the nonza nonza and optionally redacts it in logs if redact is not null.
final void Function(XMLNode nonza, {String? redact}) sendNonza;
final void Function(XMLNode nonza) sendNonza;
/// Returns the connection settings.
final ConnectionSettings Function() getConnectionSettings;
/// Send an event event to the connection's event bus
/// Returns the connection object.
final XmppConnection Function() getConnection;
/// Send an event event to the connection's event bus.
final Future<void> Function(XmppEvent event) sendEvent;
/// Returns the negotiator with id id of the connection or null.
final T? Function<T extends XmppFeatureNegotiatorBase>(String) getNegotiatorById;
final T? Function<T extends XmppFeatureNegotiatorBase>(String)
getNegotiatorById;
/// Returns the manager with id id of the connection or null.
final T? Function<T extends XmppManagerBase>(String) getManagerById;
/// Returns the full JID of the current account
final JID Function() getFullJID;
/// Returns the socket the negotiator is attached to
final BaseSocketWrapper Function() getSocket;
/// Returns true if the stream is authenticated. Returns false if not.
final bool Function() isAuthenticated;
/// Sets the resource of the connection. If triggerEvent is true, then a
/// [ResourceBoundEvent] is triggered.
final void Function(String, {bool triggerEvent}) setResource;
/// Sets the authentication state of the connection to true.
final void Function() setAuthenticated;
/// Remove a stream feature from our internal cache. This is useful for when you
/// negotiated a feature for another negotiator, like SASL2.
final void Function(String) removeNegotiatingFeature;
}
abstract class XmppFeatureNegotiatorBase {
XmppFeatureNegotiatorBase(
this.priority,
this.sendStreamHeaderWhenDone,
this.negotiatingXmlns,
this.id,
) : state = NegotiatorState.ready;
XmppFeatureNegotiatorBase(this.priority, this.sendStreamHeaderWhenDone, this.negotiatingXmlns, this.id)
: state = NegotiatorState.ready;
/// The priority regarding other negotiators. The higher, the earlier will the
/// negotiator be used
final int priority;
@ -70,26 +103,29 @@ abstract class XmppFeatureNegotiatorBase {
/// The Id of the negotiator
final String id;
/// The state the negotiator is currently in
NegotiatorState state;
late NegotiatorAttributes _attributes;
/// Register the negotiator against a connection class by means of [attributes].
void register(NegotiatorAttributes attributes) {
_attributes = attributes;
}
/// Returns true if a feature in [features], which are the children of the
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
bool matchesFeature(List<XMLNode> features) {
return firstWhereOrNull(
features,
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
) != null;
return features.firstWhereOrNull(
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
) !=
null;
}
/// Called when an event is triggered in the [XmppConnection].
Future<void> onXmppEvent(XmppEvent event) async {}
/// Called with the currently received nonza [nonza] when the negotiator is active.
/// If the negotiator is just elected to be the next one, then [nonza] is equal to
/// the <stream:features /> nonza.
@ -105,6 +141,11 @@ abstract class XmppFeatureNegotiatorBase {
void reset() {
state = NegotiatorState.ready;
}
@protected
NegotiatorAttributes get attributes => _attributes;
/// Run after all negotiators are registered. Useful for registering callbacks against
/// other negotiators. By default this function does nothing.
Future<void> postRegisterCallback() async {}
}

View File

@ -1,3 +0,0 @@
import 'package:moxxmpp/src/negotiators/negotiator.dart';
class SaslFailedError extends NegotiatorError {}

View File

@ -1,46 +0,0 @@
enum ParserState {
variableName,
variableValue
}
/// Parse a string like "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL" into
/// { "n": "user", "r": "fyko+d2lbbFgONRv9qkxdawL"}.
Map<String, String> parseKeyValue(String keyValueString) {
var state = ParserState.variableName;
var name = '';
var value = '';
final values = <String, String>{};
for (var i = 0; i < keyValueString.length; i++) {
final char = keyValueString[i];
switch (state) {
case ParserState.variableName: {
if (char == '=') {
state = ParserState.variableValue;
} else if (char == ',') {
name = '';
} else {
name += char;
}
}
break;
case ParserState.variableValue: {
if (char == ',' || i == keyValueString.length - 1) {
if (char != ',') {
value += char;
}
values[name] = value;
value = '';
name = '';
state = ParserState.variableName;
} else {
value += char;
}
}
break;
}
}
return values;
}

View File

@ -1,13 +0,0 @@
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stringxml.dart';
class SaslAuthNonza extends XMLNode {
SaslAuthNonza(String mechanism, String body) : super(
tag: 'auth',
attributes: <String, String>{
'xmlns': saslXmlns,
'mechanism': mechanism ,
},
text: body,
);
}

View File

@ -1,73 +0,0 @@
import 'dart:convert';
import 'package:logging/logging.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/negotiators/namespaces.dart';
import 'package:moxxmpp/src/negotiators/negotiator.dart';
import 'package:moxxmpp/src/negotiators/sasl/errors.dart';
import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
import 'package:moxxmpp/src/negotiators/sasl/nonza.dart';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/types/result.dart';
class SaslPlainAuthNonza extends SaslAuthNonza {
SaslPlainAuthNonza(String username, String password) : super(
'PLAIN', base64.encode(utf8.encode('\u0000$username\u0000$password')),
);
}
class SaslPlainNegotiator extends SaslNegotiator {
SaslPlainNegotiator()
: _authSent = false,
_log = Logger('SaslPlainNegotiator'),
super(0, saslPlainNegotiator, 'PLAIN');
bool _authSent;
final Logger _log;
@override
bool matchesFeature(List<XMLNode> features) {
if (!attributes.getConnectionSettings().allowPlainAuth) return false;
if (super.matchesFeature(features)) {
if (!attributes.getSocket().isSecure()) {
_log.warning('Refusing to match SASL feature due to unsecured connection');
return false;
}
return true;
}
return false;
}
@override
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async {
if (!_authSent) {
final settings = attributes.getConnectionSettings();
attributes.sendNonza(
SaslPlainAuthNonza(settings.jid.local, settings.password),
redact: SaslPlainAuthNonza('******', '******').toXml(),
);
_authSent = true;
return const Result(NegotiatorState.ready);
} else {
final tag = nonza.tag;
if (tag == 'success') {
await attributes.sendEvent(AuthenticationSuccessEvent());
return const Result(NegotiatorState.done);
} else {
// We assume it's a <failure/>
final error = nonza.children.first.tag;
await attributes.sendEvent(AuthenticationFailedEvent(error));
return Result(SaslFailedError());
}
}
}
@override
void reset() {
_authSent = false;
super.reset();
}
}

View File

@ -1,265 +0,0 @@
import 'dart:convert';
import 'dart:math' show Random;
import 'package:cryptography/cryptography.dart';
import 'package:logging/logging.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/negotiators/namespaces.dart';
import 'package:moxxmpp/src/negotiators/negotiator.dart';
import 'package:moxxmpp/src/negotiators/sasl/errors.dart';
import 'package:moxxmpp/src/negotiators/sasl/kv.dart';
import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
import 'package:moxxmpp/src/negotiators/sasl/nonza.dart';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/types/result.dart';
import 'package:random_string/random_string.dart';
import 'package:saslprep/saslprep.dart';
// NOTE: Inspired by https://github.com/vukoye/xmpp_dart/blob/3b1a0588562b9e591488c99d834088391840911d/lib/src/features/sasl/ScramSaslHandler.dart
enum ScramHashType {
sha1,
sha256,
sha512
}
HashAlgorithm hashFromType(ScramHashType type) {
switch (type) {
case ScramHashType.sha1: return Sha1();
case ScramHashType.sha256: return Sha256();
case ScramHashType.sha512: return Sha512();
}
}
int pbkdfBitsFromHash(ScramHashType type) {
switch (type) {
// NOTE: SHA1 is 20 octets long => 20 octets * 8 bits/octet
case ScramHashType.sha1: return 160;
// NOTE: SHA256 is 32 octets long => 32 octets * 8 bits/octet
case ScramHashType.sha256: return 256;
// NOTE: SHA512 is 64 octets long => 64 octets * 8 bits/octet
case ScramHashType.sha512: return 512;
}
}
const scramSha1Mechanism = 'SCRAM-SHA-1';
const scramSha256Mechanism = 'SCRAM-SHA-256';
const scramSha512Mechanism = 'SCRAM-SHA-512';
String mechanismNameFromType(ScramHashType type) {
switch (type) {
case ScramHashType.sha1: return scramSha1Mechanism;
case ScramHashType.sha256: return scramSha256Mechanism;
case ScramHashType.sha512: return scramSha512Mechanism;
}
}
String namespaceFromType(ScramHashType type) {
switch (type) {
case ScramHashType.sha1: return saslScramSha1Negotiator;
case ScramHashType.sha256: return saslScramSha256Negotiator;
case ScramHashType.sha512: return saslScramSha512Negotiator;
}
}
class SaslScramAuthNonza extends SaslAuthNonza {
// This subclassing makes less sense here, but this is since the auth nonza here
// requires knowledge of the inner state of the Negotiator.
SaslScramAuthNonza({ required ScramHashType type, required String body }) : super(
mechanismNameFromType(type), body,
);
}
class SaslScramResponseNonza extends XMLNode {
SaslScramResponseNonza({ required String body }) : super(
tag: 'response',
attributes: <String, String>{
'xmlns': saslXmlns,
},
text: body,
);
}
enum ScramState {
preSent,
initialMessageSent,
challengeResponseSent,
error
}
const gs2Header = 'n,,';
class SaslScramNegotiator extends SaslNegotiator {
// NOTE: NEVER, and I mean, NEVER set clientNonce or initalMessageNoGS2. They are just there for testing
SaslScramNegotiator(
int priority,
this.initialMessageNoGS2,
this.clientNonce,
this.hashType,
) :
_hash = hashFromType(hashType),
_serverSignature = '',
_scramState = ScramState.preSent,
_log = Logger('SaslScramNegotiator(${mechanismNameFromType(hashType)})'),
super(priority, namespaceFromType(hashType), mechanismNameFromType(hashType));
String? clientNonce;
String initialMessageNoGS2;
final ScramHashType hashType;
final HashAlgorithm _hash;
String _serverSignature;
// The internal state for performing the negotiation
ScramState _scramState;
final Logger _log;
Future<List<int>> calculateSaltedPassword(String salt, int iterations) async {
final pbkdf2 = Pbkdf2(
macAlgorithm: Hmac(_hash),
iterations: iterations,
bits: pbkdfBitsFromHash(hashType),
);
final saltedPasswordRaw = await pbkdf2.deriveKey(
secretKey: SecretKey(
utf8.encode(Saslprep.saslprep(attributes.getConnectionSettings().password)),
),
nonce: base64.decode(salt),
);
return saltedPasswordRaw.extractBytes();
}
Future<List<int>> calculateClientKey(List<int> saltedPassword) async {
return (await Hmac(_hash).calculateMac(
utf8.encode('Client Key'), secretKey: SecretKey(saltedPassword),
)).bytes;
}
Future<List<int>> calculateClientSignature(String authMessage, List<int> storedKey) async {
return (await Hmac(_hash).calculateMac(
utf8.encode(authMessage),
secretKey: SecretKey(storedKey),
)).bytes;
}
Future<List<int>> calculateServerKey(List<int> saltedPassword) async {
return (await Hmac(_hash).calculateMac(
utf8.encode('Server Key'),
secretKey: SecretKey(saltedPassword),
)).bytes;
}
Future<List<int>> calculateServerSignature(String authMessage, List<int> serverKey) async {
return (await Hmac(_hash).calculateMac(
utf8.encode(authMessage),
secretKey: SecretKey(serverKey),
)).bytes;
}
List<int> calculateClientProof(List<int> clientKey, List<int> clientSignature) {
final clientProof = List<int>.filled(clientKey.length, 0);
for (var i = 0; i < clientKey.length; i++) {
clientProof[i] = clientKey[i] ^ clientSignature[i];
}
return clientProof;
}
Future<String> calculateChallengeResponse(String base64Challenge) async {
final challengeString = utf8.decode(base64.decode(base64Challenge));
final challenge = parseKeyValue(challengeString);
final clientFinalMessageBare = 'c=biws,r=${challenge['r']!}';
final saltedPassword = await calculateSaltedPassword(challenge['s']!, int.parse(challenge['i']!));
final clientKey = await calculateClientKey(saltedPassword);
final storedKey = (await _hash.hash(clientKey)).bytes;
final authMessage = '$initialMessageNoGS2,$challengeString,$clientFinalMessageBare';
final clientSignature = await calculateClientSignature(authMessage, storedKey);
final clientProof = calculateClientProof(clientKey, clientSignature);
final serverKey = await calculateServerKey(saltedPassword);
_serverSignature = base64.encode(await calculateServerSignature(authMessage, serverKey));
return '$clientFinalMessageBare,p=${base64.encode(clientProof)}';
}
@override
bool matchesFeature(List<XMLNode> features) {
if (super.matchesFeature(features)) {
if (!attributes.getSocket().isSecure()) {
_log.warning('Refusing to match SASL feature due to unsecured connection');
return false;
}
return true;
}
return false;
}
@override
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async {
switch (_scramState) {
case ScramState.preSent:
if (clientNonce == null || clientNonce == '') {
clientNonce = randomAlphaNumeric(40, provider: CoreRandomProvider.from(Random.secure()));
}
initialMessageNoGS2 = 'n=${attributes.getConnectionSettings().jid.local},r=$clientNonce';
_scramState = ScramState.initialMessageSent;
attributes.sendNonza(
SaslScramAuthNonza(body: base64.encode(utf8.encode(gs2Header + initialMessageNoGS2)), type: hashType),
redact: SaslScramAuthNonza(body: '******', type: hashType).toXml(),
);
return const Result(NegotiatorState.ready);
case ScramState.initialMessageSent:
if (nonza.tag != 'challenge') {
final error = nonza.children.first.tag;
await attributes.sendEvent(AuthenticationFailedEvent(error));
_scramState = ScramState.error;
return Result(SaslFailedError());
}
final challengeBase64 = nonza.innerText();
final response = await calculateChallengeResponse(challengeBase64);
final responseBase64 = base64.encode(utf8.encode(response));
_scramState = ScramState.challengeResponseSent;
attributes.sendNonza(
SaslScramResponseNonza(body: responseBase64),
redact: SaslScramResponseNonza(body: '******').toXml(),
);
return const Result(NegotiatorState.ready);
case ScramState.challengeResponseSent:
if (nonza.tag != 'success') {
// We assume it's a <failure />
final error = nonza.children.first.tag;
await attributes.sendEvent(AuthenticationFailedEvent(error));
_scramState = ScramState.error;
return Result(SaslFailedError());
}
// NOTE: This assumes that the string is always "v=..." and contains no other parameters
final signature = parseKeyValue(utf8.decode(base64.decode(nonza.innerText())));
if (signature['v']! != _serverSignature) {
// TODO(Unknown): Notify of a signature mismatch
//final error = nonza.children.first.tag;
//attributes.sendEvent(AuthenticationFailedEvent(error));
_scramState = ScramState.error;
return Result(SaslFailedError());
}
await attributes.sendEvent(AuthenticationSuccessEvent());
return const Result(NegotiatorState.done);
case ScramState.error:
return Result(SaslFailedError());
}
}
@override
void reset() {
_scramState = ScramState.preSent;
super.reset();
}
}

Some files were not shown because too many files have changed in this diff Show More