Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dart/moq/lib/src/aliases.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ typedef Client = MoqClient;
/// The credentials a [Client] presents, replaceable while it runs.
typedef Tokens = MoqTokens;

/// A request token a session presents, which a route or [Claim] names with
/// `setAuth`. Await `answered()` for the peer's verdict; `cancel()` withdraws it
/// unless a route or claim still names it.
typedef Credential = MoqCredential;

/// A live pub/sub session with a relay, exposing publish and consume origins.
typedef Session = MoqSession;

Expand Down
8 changes: 8 additions & 0 deletions dart/moq/lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ final class Moq {
/// `ConfigMoqException`. Await [Tokens.answered] for the relay's verdict.
Tokens tokens() => session.tokens()!;

/// Present a request token, for a route or [Claim] to carry with `setAuth`.
///
/// On moq-transport it rides each naming route's announcing request; on
/// moq-lite it needs AUTH, where it earns a grant. The session presents it
/// again on every reconnect. Await [Credential.answered] for the peer's
/// verdict. An empty token throws `ConfigMoqException`.
Credential credential(String token) => session.credential(token: token);

/// Drain within one second, then stop the client.
///
/// `moq-lite-05` and newer wait for the peer to receive finished tails; older
Expand Down
70 changes: 70 additions & 0 deletions dart/moq/test/moq_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,76 @@ void main() {
server.cancel();
});

test(
'a credential is answered, names a route and a claim, and cancels',
() async {
const version = ['moq-lite-07-wip'];
final server = MoqServer(
config: MoqServerConfig(
bind: '127.0.0.1:0',
versions: version,
tls: MoqServerTls(generate: ['localhost']),
quic: MoqQuicConfig(),
),
);
final address = await server.listen().timeout(timeout);
final accepted = () async {
final request = await server.accept().timeout(timeout);
if (request == null) throw StateError('server closed before accept');
return request.accept().timeout(timeout);
}();

final client = await Moq.connect(
'https://$address',
options: ConnectOptions(
tls: ClientTls(insecure: true),
bind: '127.0.0.1:0',
versions: version,
once: true,
),
).timeout(timeout);
final serverSession = await accepted;

expect(() => client.credential(''), throwsA(isA<ConfigMoqException>()));
final Credential credential = client.credential('extra');
// The server verifies no tokens of its own, so it refuses this one.
await expectLater(
credential.answered().timeout(timeout),
throwsA(isA<UnsupportedMoqException>()),
);

final route = client.route('room/x', options: RouteOptions(hidden: true));
final claim = client.session.publish().claim(
prefix: 'live',
options: RouteOptions(hidden: true),
);
route.setAuth(credential: credential);
claim.setAuth(credential: credential);
route.setAuth(credential: null);
claim.setAuth(credential: null);

credential.cancel();
await expectLater(
credential.answered().timeout(timeout),
throwsA(isA<CancelledMoqException>()),
);
expect(
() => route.setAuth(credential: credential),
throwsA(isA<CancelledMoqException>()),
);
expect(
() => claim.setAuth(credential: credential),
throwsA(isA<CancelledMoqException>()),
);
route.cancel();
claim.cancel();

await client.close();
serverSession.cancel(code: 0);
server.cancel();
},
);

test('an announced instance pins request and follow', () async {
// moq-lite-06 carries no instance, so the session mints one for the route.
const version = ['moq-lite-06'];
Expand Down
Loading