|
| 1 | +import 'dart:js_interop'; |
| 2 | +import 'dart:js_interop_unsafe'; |
| 3 | +import 'package:js/js.dart'; |
| 4 | + |
| 5 | +import 'package:customer_io/customer_io_config.dart'; |
| 6 | +import 'package:customer_io/customer_io_enums.dart'; |
| 7 | +import 'package:customer_io/data_pipelines/customer_io_platform_interface.dart'; |
| 8 | +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; |
| 9 | + |
| 10 | +@JS('window') |
| 11 | +external JSObject get _window; |
| 12 | + |
| 13 | +@JS('Function') |
| 14 | +external Object _jsFunctionConstructor(String code); |
| 15 | + |
| 16 | +class CustomerIOWebPlugin extends CustomerIOPlatform { |
| 17 | + String? _currentUserId; |
| 18 | + late final CustomerIOConfig _config; |
| 19 | + |
| 20 | + static void registerWith(Registrar registrar) { |
| 21 | + CustomerIOPlatform.instance = CustomerIOWebPlugin(); |
| 22 | + } |
| 23 | + |
| 24 | + void _injectAnalyticsSnippet(String key) { |
| 25 | + try { |
| 26 | + final has = _window.has('cioanalytics'); |
| 27 | + if (has) return; |
| 28 | + } catch (_) {} |
| 29 | + |
| 30 | + final snippet = ''' |
| 31 | + !(function () { |
| 32 | + var i = "cioanalytics", |
| 33 | + analytics = (window[i] = window[i] || []); |
| 34 | + if (!analytics.initialize) |
| 35 | + if (analytics.invoked) |
| 36 | + window.console && |
| 37 | + console.error && |
| 38 | + console.error("Snippet included twice."); |
| 39 | + else { |
| 40 | + analytics.invoked = !0; |
| 41 | + analytics.methods = [ |
| 42 | + "trackSubmit", |
| 43 | + "trackClick", |
| 44 | + "trackLink", |
| 45 | + "trackForm", |
| 46 | + "pageview", |
| 47 | + "identify", |
| 48 | + "reset", |
| 49 | + "group", |
| 50 | + "track", |
| 51 | + "ready", |
| 52 | + "alias", |
| 53 | + "debug", |
| 54 | + "page", |
| 55 | + "once", |
| 56 | + "off", |
| 57 | + "on", |
| 58 | + "addSourceMiddleware", |
| 59 | + "addIntegrationMiddleware", |
| 60 | + "setAnonymousId", |
| 61 | + "addDestinationMiddleware", |
| 62 | + ]; |
| 63 | + analytics.factory = function (e) { |
| 64 | + return function () { |
| 65 | + var t = Array.prototype.slice.call(arguments); |
| 66 | + t.unshift(e); |
| 67 | + analytics.push(t); |
| 68 | + return analytics; |
| 69 | + }; |
| 70 | + }; |
| 71 | + for (var e = 0; e < analytics.methods.length; e++) { |
| 72 | + var key = analytics.methods[e]; |
| 73 | + analytics[key] = analytics.factory(key); |
| 74 | + } |
| 75 | + analytics.load = function (key, e) { |
| 76 | + var t = document.createElement("script"); |
| 77 | + t.type = "text/javascript"; |
| 78 | + t.async = !0; |
| 79 | + t.setAttribute("data-global-customerio-analytics-key", i); |
| 80 | + t.src = |
| 81 | + "https://cdp.customer.io/v1/analytics-js/snippet/" + |
| 82 | + key + |
| 83 | + "/analytics.min.js"; |
| 84 | + var n = document.getElementsByTagName("script")[0]; |
| 85 | + n.parentNode.insertBefore(t, n); |
| 86 | + analytics._writeKey = key; |
| 87 | + analytics._loadOptions = e; |
| 88 | + }; |
| 89 | + analytics.SNIPPET_VERSION = "4.15.3"; |
| 90 | + analytics.load("$key"); |
| 91 | + } |
| 92 | + })(); |
| 93 | + '''; |
| 94 | + |
| 95 | + try { |
| 96 | + final fn = _jsFunctionConstructor(snippet) as Function; |
| 97 | + fn(); |
| 98 | + } catch (_) { |
| 99 | + print('CustomerIO web: Failed to inject analytics snippet.'); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + JSObject get _cio { |
| 104 | + if (!_window.has('cioanalytics')) { |
| 105 | + _window['cioanalytics'] = <JSAny?>[].toJS; |
| 106 | + } |
| 107 | + return _window['cioanalytics'] as JSObject; |
| 108 | + } |
| 109 | + |
| 110 | + JSAny? _toJS(dynamic value) { |
| 111 | + if (value == null) return null; |
| 112 | + if (value is String) return value.toJS; |
| 113 | + if (value is num) return value.toJS; |
| 114 | + if (value is bool) return value.toJS; |
| 115 | + if (value is List) { |
| 116 | + return value.map(_toJS).toList().toJS; |
| 117 | + } |
| 118 | + if (value is Map<String, dynamic>) { |
| 119 | + final jsObject = JSObject(); |
| 120 | + value.forEach((key, val) { |
| 121 | + jsObject[key] = _toJS(val); |
| 122 | + }); |
| 123 | + return jsObject; |
| 124 | + } |
| 125 | + return value.toString().toJS; |
| 126 | + } |
| 127 | + |
| 128 | + void _callCio(String method, [List<dynamic>? args]) { |
| 129 | + final List<JSAny?> payload = [method.toJS]; |
| 130 | + if (args != null && args.isNotEmpty) { |
| 131 | + payload.addAll(args.map(_toJS)); |
| 132 | + } |
| 133 | + |
| 134 | + _cio.callMethod('push'.toJS, payload.toJS); |
| 135 | + } |
| 136 | + |
| 137 | + @override |
| 138 | + Future<void> initialize({required CustomerIOConfig config}) async { |
| 139 | + _config = config; |
| 140 | + final key = _config.jsKey ?? ''; |
| 141 | + if (key.isEmpty) { |
| 142 | + print( |
| 143 | + 'CustomerIO web: JSKey is empty. Do NOT use cdpApiKey in client-side code.'); |
| 144 | + } |
| 145 | + _injectAnalyticsSnippet(key); |
| 146 | + } |
| 147 | + |
| 148 | + @override |
| 149 | + void identify( |
| 150 | + {required String userId, Map<String, dynamic> traits = const {}}) { |
| 151 | + _currentUserId = userId; |
| 152 | + |
| 153 | + if (traits.isEmpty) { |
| 154 | + _callCio('identify', [userId]); |
| 155 | + } else { |
| 156 | + _callCio('identify', [userId, traits]); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + @override |
| 161 | + void clearIdentify() { |
| 162 | + _currentUserId = null; |
| 163 | + _callCio('reset', const []); |
| 164 | + } |
| 165 | + |
| 166 | + @override |
| 167 | + void track( |
| 168 | + {required String name, Map<String, dynamic> properties = const {}}) { |
| 169 | + if (properties.isEmpty) { |
| 170 | + _callCio('track', [name]); |
| 171 | + } else { |
| 172 | + _callCio('track', [name, properties]); |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + @override |
| 177 | + void screen( |
| 178 | + {required String title, Map<String, dynamic> properties = const {}}) { |
| 179 | + if (properties.isEmpty) { |
| 180 | + _callCio('page', [title]); |
| 181 | + } else { |
| 182 | + _callCio('page', [title, properties]); |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + @override |
| 187 | + void trackMetric( |
| 188 | + {required String deliveryID, |
| 189 | + required String deviceToken, |
| 190 | + required MetricEvent event}) {} |
| 191 | + |
| 192 | + @override |
| 193 | + void deleteDeviceToken() {} |
| 194 | + |
| 195 | + @override |
| 196 | + void registerDeviceToken({required String deviceToken}) {} |
| 197 | + |
| 198 | + @override |
| 199 | + void setDeviceAttributes({required Map<String, dynamic> attributes}) { |
| 200 | + if (_currentUserId != null) { |
| 201 | + identify(userId: _currentUserId!, traits: attributes); |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + @override |
| 206 | + void setProfileAttributes({required Map<String, dynamic> attributes}) { |
| 207 | + if (_currentUserId != null) { |
| 208 | + identify(userId: _currentUserId!, traits: attributes); |
| 209 | + } |
| 210 | + } |
| 211 | +} |
0 commit comments