savers_app_sdk 1.0.4
savers_app_sdk: ^1.0.4 copied to clipboard
Flutter SDK for the Savers hosted app: WebView bridge, native maps/dialer/browser, device/session helpers, and encrypted URL generation.
Savers App SDK (Flutter) #
Flutter SDK that bridges host-app features (maps, dialer, browser, device/session, encrypted URL generation) and hosts the Savers merchant web app in a dual-WebView UI (HostedAppComponent).
Package name: savers_app_sdk
Features #
SaversAppSDK.initialized— store API key, AES-256 key, program ref code, auth mode, optional hosted environmentgenerateUrl— AES-GCM encryptedqPpayload fortestm.saversapp.com(sandbox) orm.saversapp.com(prod)HostedAppComponent— header + two WebViews (hosted Hub, then Travel portal)- WebView HOST actions: maps, dial pad, external browser, session id, end session
- Device ID, session, and location helpers
- Navigation close via global
navigationKey(END_SESSION)
Getting Started #
dependencies:
savers_app_sdk:
path: ../SaversFlutterLibrary # or your published package name
The SDK depends on webview_flutter. After adding the package, run flutter pub get.
Platform Setup #
Android AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
iOS Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location access is used to show nearby offers.</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>tel</string>
<string>telprompt</string>
</array>
Quick Start #
1. Initialize the SDK #
initialized is unchanged except for an optional environment. Existing callers keep working; omitted environment defaults to production (https://m.saversapp.com/).
import 'package:savers_app_sdk/savers_app_sdk.dart';
await SaversAppSDK.initialized(
apiKey: 'YOUR_API_KEY',
encryptionKey: 'BASE64_256_BIT_ENCRYPTION_KEY', // base64 of 32 bytes
pRefCode: 'PROGRAM_REF_CODE',
authMode: 'EMAIL', // 'EMAIL' | 'PHONE'
environment: SaversSdkHostedEnvironment.sandbox, // optional: sandbox | prod
);
environment |
generateUrl host |
|---|---|
SaversSdkHostedEnvironment.sandbox |
https://testm.saversapp.com/ |
SaversSdkHostedEnvironment.prod (or omitted) |
https://m.saversapp.com/ |
Wire navigationKey on your MaterialApp so END_SESSION can pop the current route:
MaterialApp(
navigatorKey: navigationKey,
home: const HomeScreen(),
);
2. Generate URL #
final url = await generateUrl(
UrlInput(
profile: Profile(
userId: 'USER_ID',
firstname: 'Jane',
lastname: 'Doe',
email: 'jane@example.com',
phone: '+15555550100', // required when authType is phone
pv: '1',
ev: '1',
),
authType: AuthType.email, // phone | email | username
screen: Screen(name: 'Explore'),
),
);
// https://testm.saversapp.com/?pRefCode=...&qP=... (sandbox)
Profile rules:
userIdandemailare mandatoryphoneis mandatory whenauthTypeisAuthType.phoneusernameis mandatory whenauthTypeisAuthType.usernamepv/evare optional ('0'or'1')- When
screen.nameisOfrDetails,screen.attributesmust be non-empty
Nonce is fetched automatically from profile.userId + pRefCode. Coordinates from setLocationCoordinates are included in deviceInfo.location when set.
qP encryption matches React Native AesGcmCrypto / hosted web decrypt:
- AES-256-GCM
- inner
hex(iv):base64(content):hex(tag) - outer standard base64 (so the page can
atob(qP))
3. HostedAppComponent (dual WebView) #
Do not change initialized / generateUrl for this UI. Pass the generated URL into HostedAppComponent.
HostedAppComponent(
saversAppUrl: generatedUrl,
travelHeaderTopInset: TravelPortalHeaderInsets.safeArea,
onSaversSdkMessage: (raw, postBack) {
try {
final msg = jsonDecode(raw);
if (msg is Map && msg['action'] == 'END_SESSION') {
Navigator.of(context).maybePop();
return;
}
} catch (_) {}
handleWebMessage(raw, postBack: postBack);
},
);
Flow:
- Load: WebView 1 opens
saversAppUrl. Header and WebView 2 stay hidden. - Hub → Travel tile: hosted app posts an envelope with the travel URL.
- SDK processes
open_travel, hides WebView 1, shows header + WebView 2 with that URL. - Header Back (native, no close Post Message required): hide WebView 2, show WebView 1 (still loaded), inject Hub navigation
{ type: 'NAVIGATE', route: 'Hub' }.
Travel Post Message (from Hub):
{
"target": "SDK",
"from": "savers",
"action": "open_travel",
"payload": { "url": "https://sandbox.travelercashback.com/sso" }
}
Optional: prepare_travel (show header early), close_travel (same as native Back), relay between surfaces. Payload may include branding (logo, backIconColor, loaderColor).
HostedAppController.closeSession() asks the Savers WebView to run Cognito closeSession.
Native helpers #
await openMap(37.7749, -122.4194, 'San Francisco');
await openDialPad('+1234567890');
await openBrowser('https://www.example.com');
await setSessionId('123456789');
await setLocationCoordinates(37.7749, -122.4194);
final id = await getDeviceId();
final apiKey = await getApiKey();
WebView HOST messages #
Use handleWebMessage for HOST actions that are not dual-WebView envelopes.
handleWebMessage(raw, postBack: (data) { /* optional reply to the page */ });
Supported action values:
| Action | Payload |
|---|---|
OPEN_MAP |
{ lat, lng, label? } |
SHOW_DIAL_PAD |
{ number? } |
MERCHANT_PORTAL_REDIRECT |
{ url } |
SESSION_ID |
{ sessionId } |
END_SESSION |
none — pops via navigationKey |
From a page inside HostedAppComponent, post JSON the same way as React Native (window.ReactNativeWebView.postMessage). The Flutter WebView injects a shim onto the SaversPostMessage channel.
Example App #
cd example
flutter pub get
flutter run
Demo notes:
- Init uses sandbox credentials and
SaversSdkHostedEnvironment.sandbox. - Open in Browser / Open in WebView sit on the Generated URL card. WebView opens
HostedAppComponent. - After encryption or init changes, use hot restart (not only hot reload).
Notes #
- Location permissions are required on Android and iOS for coordinate enrichment.
encryptionKeymust be base64 of exactly 32 bytes (AES-256).pRefCodeis required beforegenerateUrl.