savers_app_sdk 1.0.5
savers_app_sdk: ^1.0.5 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, OAuth session id, user 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.
On later visits, generateUrl also encrypts the persisted auth session as authSessionId next to oAuthSessionId, so the hosted app can restore login. authSessionId is omitted until a USER_SESSION_ID PostMessage (or setAuthSessionId) has stored a value in local storage.
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 setOAuthSessionId('123456789');
await setAuthSessionId('user-session-123');
await setLocationCoordinates(37.7749, -122.4194);
final id = await getDeviceId();
final apiKey = await getApiKey();
final authSessionId = await getAuthSessionId();
await clearAuthSessionId();
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 } |
OAUTH_SESSION_ID |
{ oAuthSessionId } |
USER_SESSION_ID |
{ authSessionId } or { sId } — stored under local-storage key authSessionId |
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.