start static method

Future<Session> start(
  1. String? realm,
  2. AbstractTransport transport, {
  3. String? authId,
  4. String? authRole,
  5. Map<String, dynamic>? authExtra,
  6. List<AbstractAuthentication>? authMethods,
  7. Duration? reconnect,
  8. WampE2eeProvider? e2eeProvider,
  9. SessionE2eeProviderResolver? e2eeProviderResolver,
})

Starting the session will also start the authentication process.

Implementation

static Future<Session> start(
  String? realm,
  AbstractTransport transport, {
  String? authId,
  String? authRole,
  Map<String, dynamic>? authExtra,
  List<AbstractAuthentication>? authMethods,
  Duration? reconnect,
  WampE2eeProvider? e2eeProvider,
  SessionE2eeProviderResolver? e2eeProviderResolver,
}) async {
  final session = Session(
    realm,
    transport,
    e2eeProvider: e2eeProvider,
    e2eeProviderResolver: e2eeProviderResolver,
  );

  final hello = Hello(realm, Details.forHello());
  if (authId != null) {
    hello.details.authid = authId;
  }
  if (authRole != null) {
    hello.details.authrole = authRole;
  }
  if (authExtra != null) {
    hello.details.authextra = authExtra;
  }

  if (authMethods != null && authMethods.isNotEmpty) {
    await authMethods[0].hello(realm, hello.details);
    hello.details.authmethods = authMethods
        .map<String>((authMethod) => authMethod.getName())
        .toList();
  }

  final welcomeCompleter = Completer<Session>();
  AbstractAuthentication? challengedAuthMethod;
  Future<void> cancelAuthentication() async {
    if (authMethods == null) return;
    await Future.wait(
      authMethods.map((method) async {
        try {
          await method.cancelPendingChallenge();
        } catch (_) {
          // Authentication teardown is best-effort and must reach every method.
        }
      }),
    );
  }

  session
      ._transportStreamSubscription = session._receiveSessionMessages().listen(
    (message) {
      final materialized = session._materializeTransportMessage(message);
      if (materialized is Challenge) {
        AbstractAuthentication? foundAuthMethod;
        for (final authenticationMethod
            in authMethods ?? const <AbstractAuthentication>[]) {
          if (authenticationMethod.getName() == materialized.authMethod) {
            foundAuthMethod = authenticationMethod;
            break;
          }
        }
        if (foundAuthMethod != null) {
          challengedAuthMethod = foundAuthMethod;
          try {
            foundAuthMethod
                .challenge(materialized.extra)
                .then(
                  (authenticate) => session.authenticate(authenticate),
                  onError: (error) {
                    unawaited(cancelAuthentication());
                    if (!welcomeCompleter.isCompleted) {
                      welcomeCompleter.completeError(
                        Abort(
                          Error.authorizationFailed,
                          message: error.toString(),
                        ),
                      );
                    }
                    session._transport.send(
                      Abort(
                        Error.authorizationFailed,
                        message: error.toString(),
                      ),
                    );
                    session._transport.close();
                  },
                );
          } catch (exception) {
            unawaited(cancelAuthentication());
            try {
              transport.close();
            } catch (_) {
              /* transport may already be closed */
            }
            welcomeCompleter.completeError(
              Abort(Error.authorizationFailed, message: exception.toString()),
            );
          }
          return;
        }
        final goodbye = Goodbye(
          GoodbyeMessage('Authmethod $foundAuthMethod not supported'),
          Goodbye.reasonGoodbyeAndOut,
        );
        session._transport.send(goodbye);
        welcomeCompleter.completeError(goodbye);
        return;
      }

      if (materialized is Welcome) {
        if ((session.realm ?? materialized.details.realm) == null) {
          welcomeCompleter.completeError(
            Abort(
              Error.authorizationFailed,
              message:
                  'No realm specified! Neither by the client nor by the router',
            ),
          );
          return;
        }
        session._transportStreamSubscription.pause();
        unawaited(
          Future<void>(() async {
            await challengedAuthMethod?.verifyFinal(
              authId: materialized.details.authid,
              authMethod: materialized.details.authmethod,
              authExtra: materialized.details.authextra,
            );
            session.id = materialized.sessionId;
            if (materialized.details.realm == null) {
              if (_logger.level <= Level.INFO) {
                _logger.info('Warning! No realm returned by the router');
              }
            } else {
              session.realm = materialized.details.realm;
            }
            session.authId = materialized.details.authid;
            session.authRole = materialized.details.authrole;
            session.authMethod = materialized.details.authmethod;
            session.authProvider = materialized.details.authprovider;
            session.authExtra = materialized.details.authextra;
            await session._initializeSessionE2eeProvider();
          }).then(
            (_) {
              if (welcomeCompleter.isCompleted) {
                unawaited(session._transportStreamSubscription.cancel());
                return;
              }
              session._transportStreamSubscription.onData(
                session._handleTransportMessage,
              );
              session._transportStreamSubscription.onDone(() {
                unawaited(session._handleTransportClosed());
              });
              welcomeCompleter.complete(session);
              session._transportStreamSubscription.resume();
            },
            onError: (error, stackTrace) {
              unawaited(cancelAuthentication());
              unawaited(session._transportStreamSubscription.cancel());
              try {
                transport.close();
              } catch (_) {
                /* transport may already be closed */
              }
              if (welcomeCompleter.isCompleted) {
                return;
              }
              welcomeCompleter.completeError(error, stackTrace);
            },
          ),
        );
        return;
      }

      if (materialized is Abort) {
        unawaited(cancelAuthentication());
        try {
          transport.close();
        } catch (_) {
          /* transport may already be closed */
        }
        welcomeCompleter.completeError(materialized);
        return;
      }

      if (materialized is Goodbye) {
        unawaited(cancelAuthentication());
        if (!welcomeCompleter.isCompleted) {
          welcomeCompleter.completeError(materialized);
        }
        session._handleGoodbye(materialized);
        return;
      }
    },
    cancelOnError: true,
    onError: (error, stackTrace) {
      unawaited(cancelAuthentication());
      _logger.warning(error);
      if (!welcomeCompleter.isCompleted) {
        welcomeCompleter.completeError(error, stackTrace);
      }
      unawaited(session._handleTransportClosed(error, stackTrace));
      transport.close(error: error);
    },
    onDone: () {
      unawaited(cancelAuthentication());
      if (!welcomeCompleter.isCompleted) {
        welcomeCompleter.completeError(
          StateError('Transport closed before session welcome'),
        );
      }
      unawaited(session._handleTransportClosed());
      transport.close();
    },
  );
  if (!transport.isReady) {
    await transport.onReady;
  }
  transport.send(hello);
  return welcomeCompleter.future;
}