Compare commits
2 Commits
9223a7d403
...
d7723615fe
Author | SHA1 | Date | |
---|---|---|---|
d7723615fe | |||
6517065a1a |
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/buffer.dart';
|
||||
import 'package:moxxmpp/src/errors.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
@ -72,6 +73,23 @@ class XmppConnectionResult {
|
||||
final XmppError? error;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class _StanzaAwaitableData {
|
||||
const _StanzaAwaitableData(this.sentTo, this.id);
|
||||
final String sentTo;
|
||||
final String id;
|
||||
|
||||
@override
|
||||
int get hashCode => sentTo.hashCode ^ id.hashCode;
|
||||
|
||||
@override
|
||||
bool operator==(Object other) {
|
||||
return other is _StanzaAwaitableData &&
|
||||
other.sentTo == sentTo &&
|
||||
other.id == id;
|
||||
}
|
||||
}
|
||||
|
||||
class XmppConnection {
|
||||
/// [_socket] is for debugging purposes.
|
||||
/// [connectionPingDuration] is the duration after which a ping will be sent to keep
|
||||
@ -128,7 +146,7 @@ class XmppConnection {
|
||||
/// A policy on how to reconnect
|
||||
final ReconnectionPolicy _reconnectionPolicy;
|
||||
/// A list of stanzas we are tracking with its corresponding critical section
|
||||
final Map<String, Completer<XMLNode>> _awaitingResponse;
|
||||
final Map<_StanzaAwaitableData, Completer<XMLNode>> _awaitingResponse;
|
||||
final Lock _awaitingResponseLock;
|
||||
|
||||
/// Helpers
|
||||
@ -428,9 +446,10 @@ class XmppConnection {
|
||||
/// none.
|
||||
// TODO(Unknown): if addId = false, the function crashes.
|
||||
Future<XMLNode> sendStanza(Stanza stanza, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool awaitable = true, bool encrypted = false }) async {
|
||||
var stanza_ = stanza;
|
||||
assert(implies(addId == false && stanza.id == null, !awaitable), 'Cannot await a stanza with no id');
|
||||
|
||||
// Add extra data in case it was not set
|
||||
var stanza_ = stanza;
|
||||
if (addId && (stanza_.id == null || stanza_.id == '')) {
|
||||
stanza_ = stanza.copyWith(id: generateId());
|
||||
}
|
||||
@ -448,8 +467,6 @@ class XmppConnection {
|
||||
}
|
||||
}
|
||||
|
||||
final id = stanza_.id!;
|
||||
|
||||
_log.fine('Running pre stanza handlers..');
|
||||
final data = await _runOutgoingPreStanzaHandlers(
|
||||
stanza_,
|
||||
@ -487,42 +504,45 @@ class XmppConnection {
|
||||
final stanzaString = data.stanza.toXml();
|
||||
|
||||
// ignore: cascade_invocations
|
||||
_log.fine('Attempting to acquire lock for $id...');
|
||||
_log.fine('Attempting to acquire lock for ${data.stanza.id}...');
|
||||
// TODO(PapaTutuWawa): Handle this much more graceful
|
||||
var future = Future.value(XMLNode(tag: 'not-used'));
|
||||
await _awaitingResponseLock.synchronized(() async {
|
||||
_log.fine('Lock acquired for $id');
|
||||
if (awaitable) {
|
||||
_awaitingResponse[id] = Completer();
|
||||
}
|
||||
_log.fine('Lock acquired for ${data.stanza.id}');
|
||||
|
||||
// This uses the StreamManager to behave like a send queue
|
||||
if (await _canSendData()) {
|
||||
_socket.write(stanzaString);
|
||||
_StanzaAwaitableData? key;
|
||||
if (awaitable) {
|
||||
key = _StanzaAwaitableData(data.stanza.to!, data.stanza.id!);
|
||||
_awaitingResponse[key] = Completer();
|
||||
}
|
||||
|
||||
// Try to ack every stanza
|
||||
// NOTE: Here we have send an Ack request nonza. This is now done by StreamManagementManager when receiving the StanzaSentEvent
|
||||
} else {
|
||||
_log.fine('_canSendData() returned false.');
|
||||
}
|
||||
// This uses the StreamManager to behave like a send queue
|
||||
if (await _canSendData()) {
|
||||
_socket.write(stanzaString);
|
||||
|
||||
_log.fine('Running post stanza handlers..');
|
||||
await _runOutgoingPostStanzaHandlers(
|
||||
// Try to ack every stanza
|
||||
// NOTE: Here we have send an Ack request nonza. This is now done by StreamManagementManager when receiving the StanzaSentEvent
|
||||
} else {
|
||||
_log.fine('_canSendData() returned false.');
|
||||
}
|
||||
|
||||
_log.fine('Running post stanza handlers..');
|
||||
await _runOutgoingPostStanzaHandlers(
|
||||
stanza_,
|
||||
initial: StanzaHandlerData(
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
stanza_,
|
||||
initial: StanzaHandlerData(
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
stanza_,
|
||||
),
|
||||
);
|
||||
_log.fine('Done');
|
||||
),
|
||||
);
|
||||
_log.fine('Done');
|
||||
|
||||
if (awaitable) {
|
||||
future = _awaitingResponse[id]!.future;
|
||||
}
|
||||
if (awaitable) {
|
||||
future = _awaitingResponse[key]!.future;
|
||||
}
|
||||
|
||||
_log.fine('Releasing lock for $id');
|
||||
_log.fine('Releasing lock for ${data.stanza.id}');
|
||||
});
|
||||
|
||||
return future;
|
||||
@ -691,10 +711,14 @@ class XmppConnection {
|
||||
final id = incomingPreHandlers.stanza.attributes['id'] as String?;
|
||||
var awaited = false;
|
||||
await _awaitingResponseLock.synchronized(() async {
|
||||
if (id != null && _awaitingResponse.containsKey(id)) {
|
||||
_awaitingResponse[id]!.complete(incomingPreHandlers.stanza);
|
||||
_awaitingResponse.remove(id);
|
||||
awaited = true;
|
||||
if (id != null && incomingPreHandlers.stanza.from != null) {
|
||||
final key = _StanzaAwaitableData(incomingPreHandlers.stanza.from!, id);
|
||||
final comp = _awaitingResponse[key];
|
||||
if (comp != null) {
|
||||
comp.complete(incomingPreHandlers.stanza);
|
||||
_awaitingResponse.remove(key);
|
||||
awaited = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@ -20,6 +21,7 @@ import 'package:moxxmpp/src/xeps/xep_0444.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_0461.dart';
|
||||
|
||||
/// Data used to build a message stanza.
|
||||
///
|
||||
@ -140,6 +142,11 @@ class MessageManager extends XmppManagerBase {
|
||||
/// 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) {
|
||||
assert(
|
||||
implies(details.quoteBody != null, details.quoteFrom != null && details.quoteId != null),
|
||||
'When quoting a message, then quoteFrom and quoteId must also be non-null',
|
||||
);
|
||||
|
||||
final stanza = Stanza.message(
|
||||
to: details.to,
|
||||
type: 'chat',
|
||||
@ -148,11 +155,11 @@ class MessageManager extends XmppManagerBase {
|
||||
);
|
||||
|
||||
if (details.quoteBody != null) {
|
||||
final fallback = '> ${details.quoteBody!}';
|
||||
final quote = QuoteData.fromBodies(details.quoteBody!, details.body!);
|
||||
|
||||
stanza
|
||||
..addChild(
|
||||
XMLNode(tag: 'body', text: '$fallback\n${details.body}'),
|
||||
XMLNode(tag: 'body', text: quote.body),
|
||||
)
|
||||
..addChild(
|
||||
XMLNode.xmlns(
|
||||
@ -176,7 +183,7 @@ class MessageManager extends XmppManagerBase {
|
||||
tag: 'body',
|
||||
attributes: <String, String>{
|
||||
'start': '0',
|
||||
'end': '${fallback.length}'
|
||||
'end': '${quote.fallbackLength}',
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||
@ -5,6 +6,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
|
||||
/// Data summarizing the XEP-0461 data.
|
||||
class ReplyData {
|
||||
const ReplyData({
|
||||
required this.to,
|
||||
@ -12,12 +14,57 @@ class ReplyData {
|
||||
this.start,
|
||||
this.end,
|
||||
});
|
||||
|
||||
/// The bare JID to whom the reply applies to
|
||||
final String to;
|
||||
|
||||
/// The stanza ID of the message that is replied to
|
||||
final String id;
|
||||
|
||||
/// The start of the fallback body (inclusive)
|
||||
final int? start;
|
||||
|
||||
/// The end of the fallback body (exclusive)
|
||||
final int? end;
|
||||
|
||||
/// Applies the metadata to the received body [body] in order to remove the fallback.
|
||||
/// If either [ReplyData.start] or [ReplyData.end] are null, then body is returned as
|
||||
/// is.
|
||||
String removeFallback(String body) {
|
||||
if (start == null || end == null) return body;
|
||||
|
||||
return body.replaceRange(start!, end, '');
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal class describing how to build a message with a quote fallback body.
|
||||
@visibleForTesting
|
||||
class QuoteData {
|
||||
const QuoteData(this.body, this.fallbackLength);
|
||||
|
||||
/// Takes the body of the message we want to quote [quoteBody] and the content of
|
||||
/// the reply [body] and computes the fallback body and its length.
|
||||
factory QuoteData.fromBodies(String quoteBody, String body) {
|
||||
final fallback = quoteBody
|
||||
.split('\n')
|
||||
.map((line) => '> $line\n')
|
||||
.join();
|
||||
|
||||
return QuoteData(
|
||||
'$fallback$body',
|
||||
fallback.length,
|
||||
);
|
||||
}
|
||||
|
||||
/// The new body with fallback data at the beginning
|
||||
final String body;
|
||||
|
||||
/// The length of the fallback data
|
||||
final int fallbackLength;
|
||||
}
|
||||
|
||||
/// A manager implementing support for parsing XEP-0461 metadata. The
|
||||
/// MessageRepliesManager itself does not modify the body of the message.
|
||||
class MessageRepliesManager extends XmppManagerBase {
|
||||
@override
|
||||
String getName() => 'MessageRepliesManager';
|
||||
|
@ -348,7 +348,7 @@ void main() {
|
||||
),
|
||||
StanzaExpectation(
|
||||
"<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
|
||||
"<iq to='user@example.com' type='result' id='a' />",
|
||||
"<iq from='user@example.com' type='result' id='a' />",
|
||||
ignoreId: true,
|
||||
adjustId: true,
|
||||
),
|
||||
|
44
packages/moxxmpp/test/xeps/xep_0461_test.dart
Normal file
44
packages/moxxmpp/test/xeps/xep_0461_test.dart
Normal file
@ -0,0 +1,44 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Test building a singleline quote', () {
|
||||
final quote = QuoteData.fromBodies('Hallo Welt', 'Hello Earth!');
|
||||
|
||||
expect(quote.body, '> Hallo Welt\nHello Earth!');
|
||||
expect(quote.fallbackLength, 13);
|
||||
});
|
||||
|
||||
test('Test building a multiline quote', () {
|
||||
final quote = QuoteData.fromBodies('Hallo Welt\nHallo Erde', 'How are you?');
|
||||
|
||||
expect(quote.body, '> Hallo Welt\n> Hallo Erde\nHow are you?');
|
||||
expect(quote.fallbackLength, 26);
|
||||
});
|
||||
|
||||
test('Applying a singleline quote', () {
|
||||
final body = '> Hallo Welt\nHello right back!';
|
||||
final reply = ReplyData(
|
||||
to: '',
|
||||
id: '',
|
||||
start: 0,
|
||||
end: 13,
|
||||
);
|
||||
|
||||
final bodyWithoutFallback = reply.removeFallback(body);
|
||||
expect(bodyWithoutFallback, 'Hello right back!');
|
||||
});
|
||||
|
||||
test('Applying a multiline quote', () {
|
||||
final body = "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!";
|
||||
final reply = ReplyData(
|
||||
to: '',
|
||||
id: '',
|
||||
start: 0,
|
||||
end: 28,
|
||||
);
|
||||
|
||||
final bodyWithoutFallback = reply.removeFallback(body);
|
||||
expect(bodyWithoutFallback, "I'm fine.\nThank you!");
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user