mirror of
https://github.com/hanskokx/arcane_implementations.git
synced 2026-06-09 15:16:06 +02:00
@@ -0,0 +1,280 @@
|
||||
import "package:amplify_auth_cognito/amplify_auth_cognito.dart";
|
||||
import "package:amplify_flutter/amplify_flutter.dart";
|
||||
import "package:arcane_framework/arcane_framework.dart";
|
||||
import "package:flutter/widgets.dart";
|
||||
|
||||
class AmplifyInterface implements ArcaneAuthInterface {
|
||||
AmplifyInterface._internal();
|
||||
|
||||
static bool _mocked = false;
|
||||
|
||||
static final ArcaneAuthInterface _instance = AmplifyInterface._internal();
|
||||
static ArcaneAuthInterface get I => _instance;
|
||||
|
||||
AmplifyAuthCognito get _cognito =>
|
||||
Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey);
|
||||
|
||||
Future<CognitoAuthSession?> get _session async {
|
||||
try {
|
||||
return await _cognito.fetchAuthSession();
|
||||
} on AuthException catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> get isSignedIn =>
|
||||
_session.then((value) => value?.isSignedIn == true);
|
||||
|
||||
@override
|
||||
Future<String?> get accessToken => isSignedIn.then(
|
||||
(loggedIn) => loggedIn
|
||||
? _session.then(
|
||||
(value) => value?.userPoolTokensResult.value.accessToken.raw,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<String?> get refreshToken => isSignedIn.then(
|
||||
(loggedIn) => loggedIn
|
||||
? _session.then(
|
||||
(value) => value?.userPoolTokensResult.value.refreshToken,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Result<void, String>> logout() async {
|
||||
final result = await _cognito.signOut();
|
||||
|
||||
if (result is CognitoFailedSignOut) {
|
||||
return Result.error(result.exception.message);
|
||||
}
|
||||
|
||||
return Result.ok(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<void, String>> loginWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
final bool alreadyLoggedIn = await isSignedIn;
|
||||
|
||||
if (alreadyLoggedIn) return Result.ok(null);
|
||||
|
||||
try {
|
||||
final CognitoSignInResult result = await _cognito.signIn(
|
||||
username: email,
|
||||
password: password,
|
||||
);
|
||||
return await _handleSignInResult(result, email);
|
||||
} on AuthException catch (e) {
|
||||
return Result.error("Error signing in: ${e.message}");
|
||||
} catch (e) {
|
||||
return Result.error("Error signing in: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<void, String>> _handleSignInResult(
|
||||
SignInResult result,
|
||||
String email,
|
||||
) async {
|
||||
switch (result.nextStep.signInStep) {
|
||||
case AuthSignInStep.confirmSignInWithSmsMfaCode:
|
||||
final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!;
|
||||
return Result.error(_handleCodeDelivery(codeDeliveryDetails));
|
||||
case AuthSignInStep.confirmSignInWithNewPassword:
|
||||
return Result.error("Enter a new password to continue signing in");
|
||||
case AuthSignInStep.confirmSignInWithCustomChallenge:
|
||||
final parameters = result.nextStep.additionalInfo;
|
||||
final prompt = parameters["prompt"]!;
|
||||
return Result.error(prompt);
|
||||
case AuthSignInStep.resetPassword:
|
||||
final resetResult = await _cognito.resetPassword(
|
||||
username: email,
|
||||
);
|
||||
return Result.error("Reset password result: $resetResult");
|
||||
case AuthSignInStep.confirmSignUp:
|
||||
// Resend the sign up code to the registered device.
|
||||
final resendResult = await _cognito.resendSignUpCode(
|
||||
username: email,
|
||||
);
|
||||
return Result.error(
|
||||
_handleCodeDelivery(resendResult.codeDeliveryDetails),
|
||||
);
|
||||
case AuthSignInStep.done:
|
||||
return Result.ok(null);
|
||||
default:
|
||||
Arcane.log(
|
||||
"Sign-in failed",
|
||||
level: Level.warning,
|
||||
metadata: {
|
||||
"result": result.toString(),
|
||||
},
|
||||
);
|
||||
return Result.error(
|
||||
"Unexpected sign-in result: ${result.nextStep.signInStep}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) {
|
||||
// TODO(any): localize this
|
||||
return "A confirmation code has been sent to ${codeDeliveryDetails.destination}. "
|
||||
"Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<String, String>> resendVerificationCode(String email) async {
|
||||
try {
|
||||
final result = await _cognito.resendSignUpCode(
|
||||
username: email.toLowerCase(),
|
||||
);
|
||||
final codeDeliveryDetails = result.codeDeliveryDetails;
|
||||
final String returnValue = _handleCodeDelivery(codeDeliveryDetails);
|
||||
return Result.ok(returnValue);
|
||||
} on AuthException catch (e) {
|
||||
return Result.error("Error resending verification code: ${e.message}");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<SignUpStep, String>> signup({
|
||||
required String password,
|
||||
required String email,
|
||||
}) async {
|
||||
try {
|
||||
final String accountEmail = email.toLowerCase();
|
||||
final userAttributes = {
|
||||
AuthUserAttributeKey.email: accountEmail,
|
||||
};
|
||||
final SignUpResult result = await _cognito.signUp(
|
||||
username: accountEmail,
|
||||
password: password,
|
||||
options: SignUpOptions(
|
||||
userAttributes: userAttributes,
|
||||
),
|
||||
);
|
||||
|
||||
if (result.nextStep.signUpStep == AuthSignUpStep.confirmSignUp) {
|
||||
return Result.ok(SignUpStep.confirmSignUp);
|
||||
}
|
||||
|
||||
return Result.ok(SignUpStep.done);
|
||||
} on AuthException catch (e) {
|
||||
return Result.error("Error signing up user: ${e.message}");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<bool, String>> confirmSignup({
|
||||
required String username,
|
||||
required String confirmationCode,
|
||||
}) async {
|
||||
try {
|
||||
final CognitoSignUpResult result = await _cognito.confirmSignUp(
|
||||
username: username.toLowerCase(),
|
||||
confirmationCode: confirmationCode,
|
||||
);
|
||||
|
||||
return Result.ok(result.isSignUpComplete);
|
||||
} on AuthException catch (e) {
|
||||
return Result.error("Error confirming user: ${e.message}");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<bool, String>> resetPassword({
|
||||
required String email,
|
||||
String? newPassword,
|
||||
String? code,
|
||||
}) async {
|
||||
try {
|
||||
late ResetPasswordResult result;
|
||||
if (newPassword != null && code != null) {
|
||||
result = await _cognito.confirmResetPassword(
|
||||
username: email,
|
||||
newPassword: newPassword,
|
||||
confirmationCode: code,
|
||||
);
|
||||
}
|
||||
|
||||
if (newPassword == null && code == null) {
|
||||
result = await _cognito.resetPassword(
|
||||
username: email,
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(result.isPasswordReset);
|
||||
} on AuthException catch (e) {
|
||||
return Result.error("Error resetting the password: ${e.message}");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
if (_mocked) return;
|
||||
|
||||
if (Amplify.isConfigured) return;
|
||||
|
||||
final plugin = AmplifyAuthCognito();
|
||||
|
||||
await Amplify.addPlugin(plugin);
|
||||
await Amplify.configure(_amplifyconfig);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static void setMocked() {
|
||||
_mocked = true;
|
||||
}
|
||||
|
||||
static final String _amplifyconfig = '''
|
||||
{
|
||||
"UserAgent": "aws-amplify-cli/2.0",
|
||||
"Version": "1.0",
|
||||
"auth": {
|
||||
"plugins": {
|
||||
"awsCognitoAuthPlugin": {
|
||||
"IdentityManager": {
|
||||
"Default": {}
|
||||
},
|
||||
"CredentialsProvider": {
|
||||
"CognitoIdentity": {
|
||||
"Default": {
|
||||
"PoolId": "${EnvVar.cognitoPoolId.value}",
|
||||
"Region": "${EnvVar.cognitoRegion.value}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CognitoUserPool": {
|
||||
"Default": {
|
||||
"PoolId": "${EnvVar.cognitoPoolId.value}",
|
||||
"AppClientId": "${EnvVar.cognitoClientId.value}",
|
||||
"Region": "${EnvVar.cognitoRegion.value}"
|
||||
}
|
||||
},
|
||||
"Auth": {
|
||||
"Default": {
|
||||
"authenticationFlowType": "USER_SRP_AUTH",
|
||||
"OAuth": {
|
||||
"WebDomain": "${EnvVar.authUrl.value}",
|
||||
"AppClientId": "${EnvVar.cognitoClientId.value}",
|
||||
"SignInRedirectURI": "${EnvVar.redirectUri.value}",
|
||||
"SignOutRedirectURI": "${EnvVar.redirectUri.value}",
|
||||
"Scopes": [
|
||||
"phone",
|
||||
"email",
|
||||
"openid",
|
||||
"profile",
|
||||
"aws.cognito.signin.user.admin"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}''';
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import "dart:convert";
|
||||
import "dart:io" show Platform;
|
||||
|
||||
import "package:arcane_framework/arcane_framework.dart";
|
||||
import "package:arcane_helper_utils/arcane_helper_utils.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:logger/logger.dart" as l;
|
||||
|
||||
class DebugConsole implements LoggingInterface {
|
||||
static final DebugConsole _instance = DebugConsole._internal();
|
||||
static DebugConsole get I => _instance;
|
||||
|
||||
final bool _initialized = true;
|
||||
|
||||
@override
|
||||
bool get initialized => I._initialized;
|
||||
|
||||
DebugConsole._internal();
|
||||
|
||||
@visibleForTesting
|
||||
void setMocked() => _mocked = true;
|
||||
bool _mocked = false;
|
||||
|
||||
@override
|
||||
void log(
|
||||
String message, {
|
||||
Map<String, dynamic>? metadata,
|
||||
Level? level,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
if (Feature.logging.disabled) return;
|
||||
if (Feature.debugConsoleLogging.disabled) return;
|
||||
if (!kDebugMode) return;
|
||||
if (!initialized) init();
|
||||
|
||||
const Level cutoff = AppConfig.debugLoggingThreshold;
|
||||
|
||||
if ((level?.value ?? Level.debug.value) < cutoff.value) return;
|
||||
|
||||
final l.Level logLevel = l.Level.values
|
||||
.firstWhere((value) => value.name == (level ?? Level.debug).name);
|
||||
|
||||
final Map<String, dynamic> localMetadata = metadata ?? {};
|
||||
|
||||
final String? module = localMetadata["module"] as String?;
|
||||
final String? method = localMetadata["method"] as String?;
|
||||
|
||||
const JsonEncoder encoder = JsonEncoder.withIndent(" ");
|
||||
final String? prettyprint =
|
||||
(localMetadata.isNotEmpty) ? encoder.convert(localMetadata) : null;
|
||||
|
||||
final l.Logger logger = l.Logger(
|
||||
level: logLevel,
|
||||
printer: l.PrettyPrinter(
|
||||
methodCount: 2,
|
||||
errorMethodCount: kDebugMode &&
|
||||
!(level == Level.error ||
|
||||
level == Level.warning ||
|
||||
level == Level.trace ||
|
||||
level == Level.fatal)
|
||||
? 4
|
||||
: 8,
|
||||
stackTraceBeginIndex: 1,
|
||||
lineLength: 120,
|
||||
colors: !Platform.isIOS,
|
||||
printEmojis: kDebugMode,
|
||||
dateTimeFormat: l.DateTimeFormat.none,
|
||||
),
|
||||
);
|
||||
|
||||
// Print the message to the debug console
|
||||
String prefix = "";
|
||||
if (module != null) prefix += "[$module]";
|
||||
if (method != null) prefix += "[$method]";
|
||||
if (prefix.isNotEmpty) prefix += " ";
|
||||
message = "$prefix$message";
|
||||
|
||||
if (prettyprint.isNotNullOrEmpty) message += "\n\n$prettyprint";
|
||||
|
||||
localMetadata.removeWhere((key, value) => key == "module");
|
||||
localMetadata.removeWhere((key, value) => key == "method");
|
||||
localMetadata.removeWhere((key, value) => key == "filenameAndLineNumber");
|
||||
|
||||
logger.log(
|
||||
logLevel,
|
||||
message,
|
||||
error: localMetadata["error"] ?? "",
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoggingInterface?> init() async {
|
||||
if (_mocked) return null;
|
||||
|
||||
return I;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import "package:arcane_framework/arcane_framework.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
|
||||
class DebugPrint implements LoggingInterface {
|
||||
DebugPrint._internal();
|
||||
static final DebugPrint _instance = DebugPrint._internal();
|
||||
static DebugPrint get I => _instance;
|
||||
|
||||
@override
|
||||
bool get initialized => true;
|
||||
|
||||
@override
|
||||
void log(
|
||||
String message, {
|
||||
Map<String, dynamic>? metadata,
|
||||
Level? level = Level.debug,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
debugPrint("[${level!.name}] $message ($metadata)");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoggingInterface?> init() async => I;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import "dart:io" show Platform;
|
||||
|
||||
import "package:arcane_framework/arcane_framework.dart";
|
||||
import "package:arcane_helper_utils/arcane_helper_utils.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:newrelic_mobile/config.dart";
|
||||
import "package:newrelic_mobile/newrelic_mobile.dart";
|
||||
|
||||
class NewRelic implements LoggingInterface {
|
||||
static final NewRelic _instance = NewRelic._internal();
|
||||
static NewRelic get I => _instance;
|
||||
|
||||
bool _initialized = false;
|
||||
@override
|
||||
bool get initialized => I._initialized;
|
||||
|
||||
NewRelic._internal();
|
||||
|
||||
@visibleForTesting
|
||||
void setMocked() => _mocked = true;
|
||||
bool _mocked = false;
|
||||
|
||||
@override
|
||||
void log(
|
||||
String message, {
|
||||
Map<String, dynamic>? metadata,
|
||||
Level? level,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
if (Feature.logging.disabled) return;
|
||||
if (Feature.newRelic.disabled) return;
|
||||
if (!initialized) return;
|
||||
|
||||
final Map<String, dynamic> metadataToSend = metadata ?? {};
|
||||
|
||||
// New Relic strips this out, anyway, so let's not cause additional logs.
|
||||
metadataToSend.removeWhere((key, _) => key == "timestamp");
|
||||
|
||||
// Add the logging level to the metadata
|
||||
metadataToSend.putIfAbsent(
|
||||
"level",
|
||||
() => (level?.name ?? "debug").capitalize,
|
||||
);
|
||||
|
||||
NewrelicMobile.instance.recordCustomEvent(
|
||||
"App",
|
||||
eventName: message,
|
||||
eventAttributes: metadataToSend,
|
||||
);
|
||||
|
||||
if (stackTrace != null) {
|
||||
NewrelicMobile.instance.recordError(message, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<NewRelic?> init() async {
|
||||
if (_mocked) return null;
|
||||
if (Feature.newRelic.disabled) return null;
|
||||
if (initialized) return I;
|
||||
|
||||
final String appToken = AppEnv.valueOf(
|
||||
Platform.isAndroid
|
||||
? EnvVar.newRelicAppTokenAndroid
|
||||
: EnvVar.newRelicAppTokenIos,
|
||||
);
|
||||
|
||||
final Config config = Config(
|
||||
accessToken: appToken,
|
||||
|
||||
//Android Specific
|
||||
// Optional: Enable or disable collection of event data.
|
||||
analyticsEventEnabled: true,
|
||||
// Optional: Enable or disable reporting successful HTTP requests to the MobileRequest event type.
|
||||
networkErrorRequestEnabled: true,
|
||||
// Optional: Enable or disable reporting network and HTTP request errors to the MobileRequestError event type.
|
||||
networkRequestEnabled: true,
|
||||
// Optional: Enable or disable crash reporting.
|
||||
crashReportingEnabled: true,
|
||||
// Optional: Enable or disable interaction tracing. Trace instrumentation still occurs, but no traces are harvested. This will disable default and custom interactions.
|
||||
interactionTracingEnabled: true,
|
||||
// Optional: Enable or disable capture of HTTP response bodies for HTTP error traces and MobileRequestError events.
|
||||
httpResponseBodyCaptureEnabled: true,
|
||||
// Optional: Enable or disable agent logging.
|
||||
loggingEnabled: true,
|
||||
// iOS specific
|
||||
// Optional: Enable or disable automatic instrumentation of WebViews
|
||||
webViewInstrumentation: false,
|
||||
//Optional: Enable or disable Print Statements as Analytics Events
|
||||
printStatementAsEventsEnabled: false,
|
||||
// Optional: Enable or disable automatic instrumentation of HTTP Request
|
||||
httpInstrumentationEnabled: true,
|
||||
// Optional: Enable or disable reporting data using different endpoints for US government clients
|
||||
fedRampEnabled: false,
|
||||
// Optional: Enable or disable offline data storage when no internet connection is available.
|
||||
offlineStorageEnabled: true,
|
||||
// iOS Specific
|
||||
// Optional: Enable or disable background reporting functionality.
|
||||
backgroundReportingEnabled: false,
|
||||
// iOS Specific
|
||||
// Optional: Enable or disable to use our new, more stable, event system for iOS agent.
|
||||
newEventSystemEnabled: true,
|
||||
|
||||
// Optional: Enable or disable distributed tracing.
|
||||
distributedTracingEnabled: true,
|
||||
);
|
||||
|
||||
await NewrelicMobile.instance.startAgent(config);
|
||||
|
||||
// Initialization complete.
|
||||
I._initialized = true;
|
||||
|
||||
return I;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user