Skip to content
This repository was archived by the owner on Jun 13, 2023. It is now read-only.

Commit 81a475d

Browse files
authored
add support for Flutter Web (#50)
1 parent 96e6e57 commit 81a475d

17 files changed

+570
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Unreleased
22

3+
## 1.3.0
4+
5+
* Add support for Flutter web.
6+
37
## 1.2.1
48

59
* Add environment configuration on Android (#42)

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Community implementation of native bindings for Datadog's SDK. **This is not an
2222
2323
:warning: Your Podfile must have `use_frameworks!` (Flutter includes this by default) and your minimum iOS target must be >= 11. This is a requirement [from the Datadog SDK](https://github.com/DataDog/dd-sdk-ios/blob/master/DatadogSDKObjc.podspec#L17).
2424
25+
### Flutter Web Caveats
26+
27+
The Datadog scripts need to be inserted in your `index.html` document. Please add both the [logging script](https://docs.datadoghq.com/logs/log_collection/javascript/#cdn-async) and the [RUM script](https://docs.datadoghq.com/real_user_monitoring/browser/#cdn-async) to [their own](example/web/index.html) `<script>` tags. **DO NOT** add the `.init` on `.onReady` code.
28+
2529
## Logging
2630
2731
In its default implementation, log data will only be transmitted to Datadog through [`Logger`](https://pub.dev/packages/logging) records. `print` statements are not guaranteed to be captured.
@@ -43,6 +47,10 @@ ddLogger.log('time to cook pizza', Level.FINE, attributes: {
4347
});
4448
```
4549

50+
### Flutter Web Caveats
51+
52+
* `addTag` and `removeTag` are not invoked and resolve silently when using Flutter web. This is a missing feature in Datadog's JS SDK.
53+
4654
## Real User Monitoring
4755

4856
RUM adds support for error, event, and screen tracking. The integration requires additional configuration for each service.
@@ -55,6 +63,7 @@ RUM adds support for error, event, and screen tracking. The integration requires
5563
environment: 'production',
5664
iosRumApplicationId: myiOSRumApplicationId,
5765
androidRumApplicationId: myAndroidRumApplicationId,
66+
webRumApplicationId: myWebRumApplicationId,
5867
)
5968
```
6069
1. Acknowledge `TrackingConsent` at initialization or later within your application. **Events will not be logged until `trackingConsent` is `.granted`**. This value can be updated via `DatadogFlutter.updateTrackingConsent`.
@@ -110,6 +119,15 @@ RUM adds support for error, event, and screen tracking. The integration requires
110119
)
111120
```
112121
122+
### Flutter Web Caveats
123+
124+
* `addUserAction` ignores the `action` when using Flutter web.
125+
* `updateTrackingConsent` is not invoked and fails silently when using Flutter web. This is a missing feature in Datadog's JS SDK.
126+
* `stopView` is not invoked and fails silently when using Flutter web. This is a missing feature in Datadog's JS SDK.
127+
* `startUserAction` and `stopStopUserAction` are not invoked and fail silently when using Flutter web. This is a missing feature in Datadog's JS SDK.
128+
* `startResourceLoading` and `stopResourceLoading` are not invoked and resolve silently when using Flutter web. This is a missing feature in Datadog's JS SDK.
129+
* `setUserInfo` does not support custom attributes when using Flutter web. This is due to Dart's method of strongly typing JS. Only `name`, `id`, and `email` are supported.
130+
113131
## Tracing
114132
115133
Associate your HTTP requests with your backend service. Be sure to setup (usually immediately after `DatadogFlutter.initialize`):

example/lib/main.dart

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ void main() async {
1717
await DatadogFlutter.initialize(
1818
clientToken: DATADOG_CLIENT_TOKEN,
1919
environment: 'production',
20-
androidRumApplicationId: 'YOUR_IOS_RUM_APPLICATION_ID',
20+
androidRumApplicationId: 'YOUR_ANDROID_RUM_APPLICATION_ID',
2121
iosRumApplicationId: 'YOUR_IOS_RUM_APPLICATION_ID',
22+
webRumApplicationId: 'YOUR_WEB_RUM_APPLICATION_ID',
2223
serviceName: 'my-cool-app',
2324
trackingConsent: TrackingConsent.granted,
2425
);
@@ -57,11 +58,26 @@ class MyApp extends StatelessWidget {
5758
appBar: AppBar(
5859
title: Text('My Flutter Homepage'),
5960
),
60-
body: Center(
61-
child: TextButton(
62-
onPressed: () => otherLogger.fine('hello datadog'),
63-
child: Text('Log to Datadog'),
64-
),
61+
body: ListView(
62+
children: [
63+
TextButton(
64+
onPressed: () => otherLogger.fine('FINE log from Flutter'),
65+
child: Text('FINE Log to Datadog'),
66+
),
67+
TextButton(
68+
onPressed: () => otherLogger.warning('WARN log from Flutter'),
69+
child: Text('WARN Log to Datadog'),
70+
),
71+
TextButton(
72+
onPressed: () =>
73+
DatadogRum.instance.addUserAction('Send Event Button'),
74+
child: Text('Send Event To RUM'),
75+
),
76+
TextButton(
77+
onPressed: () => throw StateError('State Error from Flutter'),
78+
child: Text('Report Zoned Error To RUM'),
79+
),
80+
],
6581
),
6682
),
6783
navigatorObservers: [

example/web/favicon.png

917 Bytes
Loading

example/web/index.html

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<!--
5+
If you are serving your web app in a path other than the root, change the
6+
href value below to reflect the base path you are serving from.
7+
8+
The path provided below has to start and end with a slash "/" in order for
9+
it to work correctly.
10+
11+
For more details:
12+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
13+
-->
14+
<base href="/">
15+
16+
<meta charset="UTF-8">
17+
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
18+
<meta name="description" content="A new Flutter project.">
19+
20+
<!-- iOS meta tags & icons -->
21+
<meta name="apple-mobile-web-app-capable" content="yes">
22+
<meta name="apple-mobile-web-app-status-bar-style" content="black">
23+
<meta name="apple-mobile-web-app-title" content="example">
24+
<link rel="apple-touch-icon" href="icons/Icon-192.png">
25+
26+
<title>example</title>
27+
<link rel="manifest" href="manifest.json">
28+
<script>
29+
(function(h,o,u,n,d) {
30+
h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
31+
d=o.createElement(u);d.async=1;d.src=n
32+
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
33+
})(window,document,'script','https://www.datadoghq-browser-agent.com/datadog-logs-v3.js','DD_LOGS')
34+
</script>
35+
36+
<script>
37+
(function(h,o,u,n,d) {
38+
h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
39+
d=o.createElement(u);d.async=1;d.src=n
40+
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
41+
})(window,document,'script','https://www.datadoghq-browser-agent.com/datadog-rum-v3.js','DD_RUM')
42+
</script>
43+
</head>
44+
<body>
45+
<!-- This script installs service_worker.js to provide PWA functionality to
46+
application. For more information, see:
47+
https://developers.google.com/web/fundamentals/primers/service-workers -->
48+
<script>
49+
var serviceWorkerVersion = null;
50+
var scriptLoaded = false;
51+
function loadMainDartJs() {
52+
if (scriptLoaded) {
53+
return;
54+
}
55+
scriptLoaded = true;
56+
var scriptTag = document.createElement('script');
57+
scriptTag.src = 'main.dart.js';
58+
scriptTag.type = 'application/javascript';
59+
document.body.append(scriptTag);
60+
}
61+
62+
if ('serviceWorker' in navigator) {
63+
// Service workers are supported. Use them.
64+
window.addEventListener('load', function () {
65+
// Wait for registration to finish before dropping the <script> tag.
66+
// Otherwise, the browser will load the script multiple times,
67+
// potentially different versions.
68+
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
69+
navigator.serviceWorker.register(serviceWorkerUrl)
70+
.then((reg) => {
71+
function waitForActivation(serviceWorker) {
72+
serviceWorker.addEventListener('statechange', () => {
73+
if (serviceWorker.state == 'activated') {
74+
console.log('Installed new service worker.');
75+
loadMainDartJs();
76+
}
77+
});
78+
}
79+
if (!reg.active && (reg.installing || reg.waiting)) {
80+
// No active web worker and we have installed or are installing
81+
// one for the first time. Simply wait for it to activate.
82+
waitForActivation(reg.installing ?? reg.waiting);
83+
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
84+
// When the app updates the serviceWorkerVersion changes, so we
85+
// need to ask the service worker to update.
86+
console.log('New service worker available.');
87+
reg.update();
88+
waitForActivation(reg.installing);
89+
} else {
90+
// Existing service worker is still good.
91+
console.log('Loading app from service worker.');
92+
loadMainDartJs();
93+
}
94+
});
95+
96+
// If service worker doesn't succeed in a reasonable amount of time,
97+
// fallback to plaint <script> tag.
98+
setTimeout(() => {
99+
if (!scriptLoaded) {
100+
console.warn(
101+
'Failed to load app from service worker. Falling back to plain <script> tag.',
102+
);
103+
loadMainDartJs();
104+
}
105+
}, 4000);
106+
});
107+
} else {
108+
// Service workers not supported. Just drop the <script> tag.
109+
loadMainDartJs();
110+
}
111+
</script>
112+
</body>
113+
</html>

example/web/manifest.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "example",
3+
"short_name": "example",
4+
"start_url": ".",
5+
"display": "standalone",
6+
"background_color": "#0175C2",
7+
"theme_color": "#0175C2",
8+
"description": "A new Flutter project.",
9+
"orientation": "portrait-primary",
10+
"prefer_related_applications": false
11+
}

lib/datadog_flutter.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class DatadogFlutter {
2323
String flavorName = '',
2424
String? iosRumApplicationId,
2525
bool useEUEndpoints = false,
26+
String? webRumApplicationId,
2627
}) async {
2728
await channel.invokeMethod('initWithClientToken', {
2829
'androidRumApplicationId': androidRumApplicationId,
@@ -33,6 +34,7 @@ class DatadogFlutter {
3334
'serviceName': serviceName,
3435
'trackingConsent': trackingConsent.index,
3536
'useEUEndpoints': useEUEndpoints,
37+
'webRumApplicationId': webRumApplicationId,
3638
});
3739
}
3840

@@ -57,6 +59,8 @@ class DatadogFlutter {
5759
/// and it is changed to `.granted`, the SDK will send all current and
5860
/// future data to Datadog; if changed to `.notGranted`, the SDK will
5961
/// wipe all current data and will not collect any future data.
62+
///
63+
/// This is not invoked and resolves silently when using Flutter web.
6064
static Future<void> updateTrackingConsent(
6165
TrackingConsent trackingConsent,
6266
) async {

lib/datadog_logger.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class DatadogLogger {
2424
if (bindOnRecord) Logger.root.onRecord.listen(onRecordCallback);
2525
}
2626

27+
/// Adds an attribute to all future messages from this logger.
2728
Future<void> addAttribute(String attributeName, String value) async {
2829
return await channel.invokeMethod('loggerAddAttribute', {
2930
'identifier': hashCode.toString(),
@@ -32,6 +33,9 @@ class DatadogLogger {
3233
});
3334
}
3435

36+
/// Adds a tag to all future messages from this logger.
37+
///
38+
/// This is not invoked and resolves silently when using Flutter web.
3539
Future<void> addTag(String tagName, String value) async {
3640
return await channel.invokeMethod('loggerAddTag', {
3741
'identifier': hashCode.toString(),
@@ -48,13 +52,17 @@ class DatadogLogger {
4852
attributes: {'loggerName': record.loggerName},
4953
);
5054

55+
/// Removes a previously-added attribute from all future messages from this logger.
5156
Future<void> removeAttribute(String attributeName) async {
5257
return await channel.invokeMethod('loggerRemoveAttribute', {
5358
'identifier': hashCode.toString(),
5459
'key': attributeName,
5560
});
5661
}
5762

63+
/// Removes a previously-added tag from all future messages from this logger.
64+
///
65+
/// This is not invoked and resolves silently when using Flutter web.
5866
Future<void> removeTag(String tagName) async {
5967
return await channel.invokeMethod('loggerRemoveTag', {
6068
'identifier': hashCode.toString(),

lib/datadog_rum.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class DatadogRum {
5656
}
5757

5858
/// Manually track a user event.
59+
///
60+
/// [action] is ignored when using Flutter web.
5961
Future<void> addUserAction(
6062
String name, {
6163
RUMAction action = RUMAction.tap,
@@ -81,6 +83,8 @@ class DatadogRum {
8183
/// should be used by [stopLoading] when ready.
8284
///
8385
/// [method] should be an uppercase HTTP method.
86+
///
87+
/// This is not invoked and resolves silently when using Flutter web.
8488
static Future<void> startResourceLoading(
8589
String key, {
8690
required String url,
@@ -105,6 +109,8 @@ class DatadogRum {
105109
/// This is used to track long running user actions (e.g. "scroll").
106110
/// Such an User Action must be stopped with [stopUserAction], and
107111
/// will be stopped automatically if it lasts for more than 10 seconds.
112+
///
113+
/// This is not invoked and resolves silently when using Flutter web.
108114
Future<void> startUserAction(
109115
String name, {
110116
RUMAction action = RUMAction.tap,
@@ -126,6 +132,8 @@ class DatadogRum {
126132
///
127133
/// [attributes] will not be reported if [errorMessage] is present
128134
/// on Android.
135+
///
136+
/// This is not invoked and resolves silently when using Flutter web.
129137
static Future<void> stopResourceLoading(
130138
String key, {
131139
int? statusCode,
@@ -146,6 +154,8 @@ class DatadogRum {
146154
///
147155
/// This is used to stop tracking long running user actions (e.g. "scroll"),
148156
/// started with [startUserAction].
157+
///
158+
/// This is not invoked and resolves silently when using Flutter web.
149159
Future<void> stopUserAction(
150160
String name, {
151161
RUMAction action = RUMAction.tap,
@@ -157,6 +167,8 @@ class DatadogRum {
157167
}
158168

159169
/// Manually track exit from a screen. See [DatadogObserver].
170+
///
171+
/// This is not invoked and resolves silently when using Flutter web.
160172
Future<void> stopView(String screenName) async {
161173
return await channel.invokeMethod('rumStopView', {'key': screenName});
162174
}

lib/datadog_web.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'dart:async';
2+
3+
import 'package:datadog_flutter/src/web/datadog_web_logger.dart';
4+
import 'package:datadog_flutter/src/web/datadog_web_rum.dart';
5+
6+
import 'package:flutter/services.dart';
7+
import 'package:datadog_flutter/src/channel.dart';
8+
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
9+
10+
class DatadogFlutterPlugin {
11+
final logger = DatadogWebLogger();
12+
final rum = DatadogWebRum();
13+
14+
static void registerWith(Registrar registrar) {
15+
final _channel = MethodChannel(
16+
channel.name,
17+
const StandardMethodCodec(),
18+
registrar,
19+
);
20+
final instance = DatadogFlutterPlugin();
21+
_channel.setMethodCallHandler(instance.handleMethodCall);
22+
}
23+
24+
Future<dynamic> handleMethodCall(MethodCall call) async {
25+
switch (call.method) {
26+
case 'initWithClientToken':
27+
logger.init(call);
28+
if (call.arguments['webRumApplicationId'] != null) {
29+
rum.init(call);
30+
}
31+
32+
return true;
33+
// tracing is handled natively on the browser
34+
case 'tracingCreateHeadersForRequest':
35+
return {};
36+
case 'tracingInitialize':
37+
return false;
38+
case 'tracingFinishSpan':
39+
return true;
40+
// trackingConsent is handled by `site:` on the browser
41+
case 'updateTrackingConsent':
42+
return false;
43+
44+
default:
45+
final result =
46+
logger.handleMethodCall(call) ?? rum.handleMethodCall(call);
47+
if (result != null) return result;
48+
throw PlatformException(
49+
code: 'Unimplemented',
50+
details:
51+
"The datadog_flutter plugin for web doesn't implement '${call.method}'",
52+
);
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)