Skip to content
Open
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
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Customer.io SDK
def cioVersion = "4.12.0"
def cioVersion = "4.13.0"
implementation "io.customer.android:datapipelines:$cioVersion"
implementation "io.customer.android:messaging-push-fcm:$cioVersion"
implementation "io.customer.android:messaging-in-app:$cioVersion"
Expand Down
10 changes: 9 additions & 1 deletion apps/amiapp_flutter/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ packages:
path: "../.."
relative: true
source: path
version: "2.8.0"
version: "2.9.0"
dbus:
dependency: transitive
description:
Expand Down Expand Up @@ -295,6 +295,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.3.0"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
Expand Down
3 changes: 2 additions & 1 deletion customerio-flutter.api
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public final class CustomerIO {
public final class CustomerIOConfig {
public fun new(
required String cdpApiKey,
String? jsKey,
String? migrationSiteId,
Region? region,
CioLogLevel? logLevel,
Expand All @@ -63,6 +64,7 @@ public final class CustomerIOConfig {
public val cdnHost: String?;
public val cdpApiKey: String;
public val flushAt: int?;
public val jsKey: String?;
public val flushInterval: int?;
public val inAppConfig: InAppConfig?;
public val logLevel: CioLogLevel?;
Expand Down Expand Up @@ -234,4 +236,3 @@ public final class ScreenView {
static public val values: List<ScreenView>;
}


10 changes: 9 additions & 1 deletion lib/config/customer_io_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class CustomerIOConfig {
final String version = plugin_info.version;

final String cdpApiKey;
final String? jsKey;
final String? migrationSiteId;
final Region? region;
final CioLogLevel? logLevel;
Expand All @@ -23,6 +24,7 @@ class CustomerIOConfig {

CustomerIOConfig({
required this.cdpApiKey,
this.jsKey,
this.migrationSiteId,
this.region,
this.logLevel,
Expand All @@ -38,7 +40,7 @@ class CustomerIOConfig {
}) : pushConfig = pushConfig ?? PushConfig();

Map<String, dynamic> toMap() {
return {
final map = {
'cdpApiKey': cdpApiKey,
'migrationSiteId': migrationSiteId,
'region': region?.name,
Expand All @@ -55,5 +57,11 @@ class CustomerIOConfig {
'version': version,
'source': source
};

if (jsKey != null) {
map['jsKey'] = jsKey;
}

return map;
}
}
211 changes: 211 additions & 0 deletions lib/customer_io_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:js/js.dart';

import 'package:customer_io/customer_io_config.dart';
import 'package:customer_io/customer_io_enums.dart';
import 'package:customer_io/data_pipelines/customer_io_platform_interface.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

@JS('window')
external JSObject get _window;

@JS('Function')
external Object _jsFunctionConstructor(String code);

class CustomerIOWebPlugin extends CustomerIOPlatform {
String? _currentUserId;
late final CustomerIOConfig _config;

static void registerWith(Registrar registrar) {
CustomerIOPlatform.instance = CustomerIOWebPlugin();
}

void _injectAnalyticsSnippet(String key) {
try {
final has = _window.has('cioanalytics');
if (has) return;
} catch (_) {}

final snippet = '''
!(function () {
var i = "cioanalytics",
analytics = (window[i] = window[i] || []);
if (!analytics.initialize)
if (analytics.invoked)
window.console &&
console.error &&
console.error("Snippet included twice.");
else {
analytics.invoked = !0;
analytics.methods = [
"trackSubmit",
"trackClick",
"trackLink",
"trackForm",
"pageview",
"identify",
"reset",
"group",
"track",
"ready",
"alias",
"debug",
"page",
"once",
"off",
"on",
"addSourceMiddleware",
"addIntegrationMiddleware",
"setAnonymousId",
"addDestinationMiddleware",
];
analytics.factory = function (e) {
return function () {
var t = Array.prototype.slice.call(arguments);
t.unshift(e);
analytics.push(t);
return analytics;
};
};
for (var e = 0; e < analytics.methods.length; e++) {
var key = analytics.methods[e];
analytics[key] = analytics.factory(key);
}
analytics.load = function (key, e) {
var t = document.createElement("script");
t.type = "text/javascript";
t.async = !0;
t.setAttribute("data-global-customerio-analytics-key", i);
t.src =
"https://cdp.customer.io/v1/analytics-js/snippet/" +
key +
"/analytics.min.js";
var n = document.getElementsByTagName("script")[0];
n.parentNode.insertBefore(t, n);
analytics._writeKey = key;
analytics._loadOptions = e;
};
analytics.SNIPPET_VERSION = "4.15.3";
analytics.load("$key");
}
})();
''';

try {
final fn = _jsFunctionConstructor(snippet) as Function;
fn();
} catch (_) {
print('CustomerIO web: Failed to inject analytics snippet.');
}
}

JSObject get _cio {
if (!_window.has('cioanalytics')) {
_window['cioanalytics'] = <JSAny?>[].toJS;
}
return _window['cioanalytics'] as JSObject;
}

JSAny? _toJS(dynamic value) {
if (value == null) return null;
if (value is String) return value.toJS;
if (value is num) return value.toJS;
if (value is bool) return value.toJS;
if (value is List) {
return value.map(_toJS).toList().toJS;
}
if (value is Map<String, dynamic>) {
final jsObject = JSObject();
value.forEach((key, val) {
jsObject[key] = _toJS(val);
});
return jsObject;
}
return value.toString().toJS;
}

void _callCio(String method, [List<dynamic>? args]) {
final List<JSAny?> payload = [method.toJS];
if (args != null && args.isNotEmpty) {
payload.addAll(args.map(_toJS));
}

_cio.callMethod('push'.toJS, payload.toJS);
}

@override
Future<void> initialize({required CustomerIOConfig config}) async {
_config = config;
final key = _config.jsKey ?? '';
if (key.isEmpty) {
print(
'CustomerIO web: JSKey is empty. Do NOT use cdpApiKey in client-side code.');
}
_injectAnalyticsSnippet(key);
}

@override
void identify(
{required String userId, Map<String, dynamic> traits = const {}}) {
_currentUserId = userId;

if (traits.isEmpty) {
_callCio('identify', [userId]);
} else {
_callCio('identify', [userId, traits]);
}
}

@override
void clearIdentify() {
_currentUserId = null;
_callCio('reset', const []);
}

@override
void track(
{required String name, Map<String, dynamic> properties = const {}}) {
if (properties.isEmpty) {
_callCio('track', [name]);
} else {
_callCio('track', [name, properties]);
}
}

@override
void screen(
{required String title, Map<String, dynamic> properties = const {}}) {
if (properties.isEmpty) {
_callCio('page', [title]);
} else {
_callCio('page', [title, properties]);
}
}

@override
void trackMetric(
{required String deliveryID,
required String deviceToken,
required MetricEvent event}) {}

@override
void deleteDeviceToken() {}

@override
void registerDeviceToken({required String deviceToken}) {}

@override
void setDeviceAttributes({required Map<String, dynamic> attributes}) {
if (_currentUserId != null) {
identify(userId: _currentUserId!, traits: attributes);
}
}

@override
void setProfileAttributes({required Map<String, dynamic> attributes}) {
if (_currentUserId != null) {
identify(userId: _currentUserId!, traits: attributes);
}
}
}
8 changes: 7 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ dependencies:
flutter:
sdk: flutter
plugin_platform_interface: ^2.0.2
flutter_web_plugins:
sdk: flutter
js: ^0.6.5

dev_dependencies:
flutter_test:
Expand Down Expand Up @@ -43,4 +46,7 @@ flutter:
pluginClass: CustomerIOPlugin
ios:
pluginClass: CustomerIOPlugin
native_sdk_version: 3.14.0
native_sdk_version: 4.0.0
web:
pluginClass: CustomerIOWebPlugin
fileName: customer_io_web.dart
12 changes: 12 additions & 0 deletions test/customer_io_config_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ void main() {
final config = CustomerIOConfig(cdpApiKey: 'testApiKey');

expect(config.cdpApiKey, 'testApiKey');
expect(config.jsKey, isNull);
expect(config.migrationSiteId, isNull);
expect(config.region, isNull);
expect(config.logLevel, isNull);
Expand Down Expand Up @@ -43,6 +44,7 @@ void main() {

final config = CustomerIOConfig(
cdpApiKey: 'testApiKey',
jsKey: 'webKey',
migrationSiteId: 'testMigrationSiteId',
region: Region.us,
logLevel: CioLogLevel.debug,
Expand All @@ -57,6 +59,7 @@ void main() {
);

expect(config.cdpApiKey, 'testApiKey');
expect(config.jsKey, 'webKey');
expect(config.migrationSiteId, 'testMigrationSiteId');
expect(config.region, Region.us);
expect(config.logLevel, CioLogLevel.debug);
Expand All @@ -81,6 +84,7 @@ void main() {

final config = CustomerIOConfig(
cdpApiKey: 'testApiKey',
jsKey: 'webKey',
migrationSiteId: 'testMigrationSiteId',
region: Region.eu,
logLevel: CioLogLevel.info,
Expand All @@ -98,6 +102,7 @@ void main() {
final expectedMap = {
'cdpApiKey': 'testApiKey',
'migrationSiteId': 'testMigrationSiteId',
'jsKey': 'webKey',
'region': 'eu',
'logLevel': 'info',
'autoTrackDeviceAttributes': false,
Expand All @@ -122,6 +127,13 @@ void main() {
expect(config.pushConfig.pushConfigAndroid.pushClickBehavior,
PushClickBehaviorAndroid.activityPreventRestart);
});

test('toMap() omits jsKey when not provided', () {
final config = CustomerIOConfig(cdpApiKey: 'testApiKey');

final map = config.toMap();
expect(map.containsKey('jsKey'), isFalse);
});
});

group('CustomerIOConfig with Region', () {
Expand Down