@player-ui/metrics-plugin 0.0.1-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,365 @@
1
+ import { SyncBailHook, SyncHook } from 'tapable';
2
+ import { BeaconPlugin } from '@player-ui/beacon-plugin';
3
+
4
+ const MetricsCorePluginSymbol = Symbol.for("MetricsCorePlugin");
5
+ const MetricsViewBeaconPluginContextSymbol = Symbol.for("MetricsViewBeaconPluginContext");
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __defProps = Object.defineProperties;
9
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
10
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
13
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
14
+ var __spreadValues = (a, b) => {
15
+ for (var prop in b || (b = {}))
16
+ if (__hasOwnProp.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ if (__getOwnPropSymbols)
19
+ for (var prop of __getOwnPropSymbols(b)) {
20
+ if (__propIsEnum.call(b, prop))
21
+ __defNormalProp(a, prop, b[prop]);
22
+ }
23
+ return a;
24
+ };
25
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
26
+ var __async = (__this, __arguments, generator) => {
27
+ return new Promise((resolve, reject) => {
28
+ var fulfilled = (value) => {
29
+ try {
30
+ step(generator.next(value));
31
+ } catch (e) {
32
+ reject(e);
33
+ }
34
+ };
35
+ var rejected = (value) => {
36
+ try {
37
+ step(generator.throw(value));
38
+ } catch (e) {
39
+ reject(e);
40
+ }
41
+ };
42
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
43
+ step((generator = generator.apply(__this, __arguments)).next());
44
+ });
45
+ };
46
+ const defaultGetTime = typeof performance === "undefined" ? () => Date.now() : () => performance.now();
47
+ const callbacks = [
48
+ "onFlowBegin",
49
+ "onFlowEnd",
50
+ "onInteractive",
51
+ "onNodeStart",
52
+ "onNodeEnd",
53
+ "onRenderStart",
54
+ "onRenderEnd",
55
+ "onUpdateStart",
56
+ "onUpdateEnd",
57
+ "onUpdate"
58
+ ];
59
+ const _MetricsViewBeaconPlugin = class {
60
+ constructor(metricsPlugin) {
61
+ this.symbol = _MetricsViewBeaconPlugin.Symbol;
62
+ this.metricsPlugin = metricsPlugin;
63
+ this.metricsPlugin.hooks.onRenderEnd.tap("MetricsViewBeaconPlugin", (timing) => {
64
+ if (timing.completed && this.resolvePendingRenderTime) {
65
+ this.resolvePendingRenderTime(timing.duration);
66
+ this.resolvePendingRenderTime = void 0;
67
+ }
68
+ });
69
+ }
70
+ apply(beaconPlugin) {
71
+ beaconPlugin.hooks.buildBeacon.intercept({
72
+ context: true,
73
+ call: (context, beacon) => {
74
+ if (context && beacon.action === "viewed") {
75
+ context[this.symbol] = this.buildContext();
76
+ }
77
+ }
78
+ });
79
+ }
80
+ buildContext() {
81
+ return __async(this, null, function* () {
82
+ return {
83
+ renderTime: yield this.getRenderTime(),
84
+ requestTime: this.getRequestTime()
85
+ };
86
+ });
87
+ }
88
+ getRenderTime() {
89
+ return __async(this, null, function* () {
90
+ const { flow } = this.metricsPlugin.getMetrics();
91
+ if (flow) {
92
+ const lastItem = flow.timeline[flow.timeline.length - 1];
93
+ if ("render" in lastItem && lastItem.render.completed) {
94
+ return lastItem.render.duration;
95
+ }
96
+ }
97
+ return new Promise((resolve) => {
98
+ this.resolvePendingRenderTime = resolve;
99
+ });
100
+ });
101
+ }
102
+ getRequestTime() {
103
+ const { flow } = this.metricsPlugin.getMetrics();
104
+ return flow == null ? void 0 : flow.requestTime;
105
+ }
106
+ };
107
+ let MetricsViewBeaconPlugin = _MetricsViewBeaconPlugin;
108
+ MetricsViewBeaconPlugin.Symbol = MetricsViewBeaconPluginContextSymbol;
109
+ class RequestTimeWebPlugin {
110
+ constructor(getRequestTime) {
111
+ this.name = "RequestTimeWebPlugin";
112
+ this.getRequestTime = getRequestTime;
113
+ }
114
+ apply(metricsCorePlugin) {
115
+ metricsCorePlugin.hooks.resolveRequestTime.tap(this.name, () => {
116
+ return this.getRequestTime();
117
+ });
118
+ }
119
+ }
120
+ class MetricsCorePlugin {
121
+ constructor(options) {
122
+ this.name = "metrics";
123
+ this.symbol = MetricsCorePluginSymbol;
124
+ this.hooks = {
125
+ resolveRequestTime: new SyncBailHook(["requestTime"]),
126
+ onFlowBegin: new SyncHook(["update"]),
127
+ onFlowEnd: new SyncHook(["update"]),
128
+ onInteractive: new SyncHook([
129
+ "timing",
130
+ "update"
131
+ ]),
132
+ onNodeStart: new SyncHook([
133
+ "nodeMetrics",
134
+ "update"
135
+ ]),
136
+ onNodeEnd: new SyncHook([
137
+ "nodeMetrics",
138
+ "update"
139
+ ]),
140
+ onRenderStart: new SyncHook([
141
+ "timing",
142
+ "nodeMetrics",
143
+ "update"
144
+ ]),
145
+ onRenderEnd: new SyncHook([
146
+ "timing",
147
+ "nodeMetrics",
148
+ "update"
149
+ ]),
150
+ onUpdateStart: new SyncHook([
151
+ "timing",
152
+ "nodeMetrics",
153
+ "update"
154
+ ]),
155
+ onUpdateEnd: new SyncHook([
156
+ "timing",
157
+ "nodeMetrics",
158
+ "update"
159
+ ]),
160
+ onUpdate: new SyncHook(["update"])
161
+ };
162
+ this.metrics = {};
163
+ var _a, _b, _c;
164
+ this.trackRender = (_a = options == null ? void 0 : options.trackRenderTime) != null ? _a : false;
165
+ this.trackUpdate = (_b = options == null ? void 0 : options.trackUpdateTime) != null ? _b : false;
166
+ this.getTime = (_c = options == null ? void 0 : options.getTime) != null ? _c : defaultGetTime;
167
+ const callOnUpdate = () => {
168
+ this.hooks.onUpdate.call(this.metrics);
169
+ };
170
+ this.hooks.onFlowBegin.tap(this.name, callOnUpdate);
171
+ this.hooks.onFlowEnd.tap(this.name, callOnUpdate);
172
+ this.hooks.onInteractive.tap(this.name, callOnUpdate);
173
+ this.hooks.onNodeStart.tap(this.name, callOnUpdate);
174
+ this.hooks.onNodeEnd.tap(this.name, callOnUpdate);
175
+ this.hooks.onRenderStart.tap(this.name, callOnUpdate);
176
+ this.hooks.onRenderEnd.tap(this.name, callOnUpdate);
177
+ this.hooks.onUpdateStart.tap(this.name, callOnUpdate);
178
+ this.hooks.onUpdateEnd.tap(this.name, callOnUpdate);
179
+ callbacks.forEach((hookName) => {
180
+ if ((options == null ? void 0 : options[hookName]) !== void 0) {
181
+ this.hooks[hookName].tap("options", options == null ? void 0 : options[hookName]);
182
+ }
183
+ });
184
+ }
185
+ getMetrics() {
186
+ return this.metrics;
187
+ }
188
+ renderStart() {
189
+ var _a;
190
+ const timeline = (_a = this.metrics.flow) == null ? void 0 : _a.timeline;
191
+ if (!timeline || timeline.length === 0) {
192
+ return;
193
+ }
194
+ const lastItem = timeline[timeline.length - 1];
195
+ if ("updates" in lastItem) {
196
+ if (lastItem.updates.length > 0) {
197
+ const lastUpdate = lastItem.updates[lastItem.updates.length - 1];
198
+ if (lastUpdate.completed === false) {
199
+ return;
200
+ }
201
+ }
202
+ if (!lastItem.render.completed) {
203
+ return;
204
+ }
205
+ const update = {
206
+ completed: false,
207
+ startTime: defaultGetTime()
208
+ };
209
+ lastItem.updates.push(update);
210
+ this.hooks.onUpdateStart.call(update, lastItem, this.metrics);
211
+ } else {
212
+ const renderInfo = __spreadProps(__spreadValues({}, lastItem), {
213
+ render: {
214
+ completed: false,
215
+ startTime: defaultGetTime()
216
+ },
217
+ updates: []
218
+ });
219
+ timeline[timeline.length - 1] = renderInfo;
220
+ this.hooks.onRenderStart.call(renderInfo.render, renderInfo, this.metrics);
221
+ }
222
+ }
223
+ renderEnd() {
224
+ if (!this.trackRender) {
225
+ throw new Error("Must start the metrics-plugin with render tracking enabled");
226
+ }
227
+ const { flow } = this.metrics;
228
+ if (!flow) {
229
+ return;
230
+ }
231
+ const { timeline, interactive } = flow;
232
+ if (!timeline || !interactive || timeline.length === 0) {
233
+ return;
234
+ }
235
+ const lastItem = timeline[timeline.length - 1];
236
+ if (!("render" in lastItem)) {
237
+ return;
238
+ }
239
+ const endTime = defaultGetTime();
240
+ if (lastItem.render.completed) {
241
+ if (lastItem.updates.length === 0) {
242
+ return;
243
+ }
244
+ const lastUpdate = lastItem.updates[lastItem.updates.length - 1];
245
+ if (lastUpdate.completed === true) {
246
+ return;
247
+ }
248
+ const update = __spreadProps(__spreadValues({}, lastUpdate), {
249
+ completed: true,
250
+ endTime,
251
+ duration: endTime - lastUpdate.startTime
252
+ });
253
+ lastItem.updates[lastItem.updates.length - 1] = update;
254
+ this.hooks.onUpdateEnd.call(update, lastItem, this.metrics);
255
+ } else {
256
+ lastItem.render = __spreadProps(__spreadValues({}, lastItem.render), {
257
+ completed: true,
258
+ endTime,
259
+ duration: endTime - lastItem.startTime
260
+ });
261
+ this.hooks.onRenderEnd.call(lastItem.render, lastItem, this.metrics);
262
+ if (!interactive.completed) {
263
+ flow.interactive = __spreadProps(__spreadValues({}, interactive), {
264
+ completed: true,
265
+ duration: endTime - interactive.startTime,
266
+ endTime
267
+ });
268
+ this.hooks.onInteractive.call(flow.interactive, this.metrics);
269
+ }
270
+ }
271
+ }
272
+ apply(player) {
273
+ player.hooks.onStart.tap(this.name, (flow) => {
274
+ const requestTime = this.hooks.resolveRequestTime.call();
275
+ const startTime = defaultGetTime();
276
+ this.metrics = {
277
+ flow: {
278
+ id: flow.id,
279
+ requestTime,
280
+ timeline: [],
281
+ startTime,
282
+ completed: false,
283
+ interactive: {
284
+ completed: false,
285
+ startTime
286
+ }
287
+ }
288
+ };
289
+ this.hooks.onFlowBegin.call(this.metrics);
290
+ });
291
+ player.hooks.state.tap(this.name, (state) => {
292
+ if (state.status === "completed" || state.status === "error") {
293
+ const endTime = defaultGetTime();
294
+ const { flow } = this.metrics;
295
+ if (flow === void 0 || (flow == null ? void 0 : flow.completed) === true) {
296
+ return;
297
+ }
298
+ this.metrics = {
299
+ flow: __spreadProps(__spreadValues({}, flow), {
300
+ completed: true,
301
+ endTime,
302
+ duration: endTime - flow.startTime
303
+ })
304
+ };
305
+ const lastUpdate = flow.timeline[flow.timeline.length - 1];
306
+ if (lastUpdate && !lastUpdate.completed) {
307
+ this.metrics.flow.timeline[flow.timeline.length - 1] = __spreadProps(__spreadValues({}, lastUpdate), {
308
+ completed: true,
309
+ endTime,
310
+ duration: endTime - lastUpdate.startTime
311
+ });
312
+ }
313
+ this.hooks.onFlowEnd.call(this.metrics);
314
+ }
315
+ });
316
+ player.hooks.flowController.tap(this.name, (fc) => {
317
+ fc.hooks.flow.tap(this.name, (f) => {
318
+ f.hooks.transition.tap(this.name, (from, to) => {
319
+ const time = defaultGetTime();
320
+ const { flow } = this.metrics;
321
+ if (!flow) {
322
+ return;
323
+ }
324
+ const { timeline } = flow;
325
+ if (timeline.length > 0) {
326
+ const prev = timeline[timeline.length - 1];
327
+ if (prev.completed) {
328
+ throw new Error("Completing a state that's already done.");
329
+ }
330
+ timeline[timeline.length - 1] = __spreadProps(__spreadValues({}, prev), {
331
+ completed: true,
332
+ endTime: time,
333
+ duration: time - prev.startTime
334
+ });
335
+ this.hooks.onNodeEnd.call(timeline[timeline.length - 1]);
336
+ }
337
+ const nodeMetrics = {
338
+ completed: false,
339
+ startTime: time,
340
+ stateName: to.name,
341
+ stateType: to.value.state_type
342
+ };
343
+ timeline.push(nodeMetrics);
344
+ this.hooks.onNodeStart.call(nodeMetrics);
345
+ });
346
+ });
347
+ });
348
+ if (this.trackRender) {
349
+ player.hooks.view.tap(this.name, (v) => {
350
+ if (this.trackUpdate) {
351
+ v.hooks.onUpdate.tap(this.name, () => {
352
+ this.renderStart();
353
+ });
354
+ } else {
355
+ this.renderStart();
356
+ }
357
+ });
358
+ player.applyTo(BeaconPlugin.Symbol, (beaconPlugin) => new MetricsViewBeaconPlugin(this).apply(beaconPlugin));
359
+ }
360
+ }
361
+ }
362
+ MetricsCorePlugin.Symbol = MetricsCorePluginSymbol;
363
+
364
+ export { MetricsCorePlugin, MetricsCorePluginSymbol, MetricsViewBeaconPlugin, MetricsViewBeaconPluginContextSymbol, RequestTimeWebPlugin, defaultGetTime };
365
+ //# sourceMappingURL=index.esm.js.map
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@player-ui/metrics-plugin",
3
+ "version": "0.0.1-next.1",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org"
7
+ },
8
+ "peerDependencies": {
9
+ "@player-ui/binding-grammar": "0.0.1-next.1"
10
+ },
11
+ "dependencies": {
12
+ "tapable": "1.1.3",
13
+ "@types/tapable": "^1.0.5",
14
+ "@babel/runtime": "7.15.4"
15
+ },
16
+ "main": "dist/index.cjs.js",
17
+ "module": "dist/index.esm.js",
18
+ "typings": "dist/index.d.ts"
19
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './metrics';
2
+ export * from './symbols';