hina-cloud-js-sdk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,610 @@
1
+ import { addProps } from "./property";
2
+ import { Log, _ } from "./utils";
3
+
4
+ let { location } = window;
5
+
6
+ class SiteLinker {
7
+ constructor() {
8
+ this.ctx = {};
9
+ this.option = {};
10
+ this.isInited = false;
11
+ }
12
+
13
+ init(ctx, option) {
14
+ this.ctx = ctx;
15
+ this.option = option;
16
+ if (this.isInited) return;
17
+
18
+ this.isInited = true;
19
+
20
+ if (
21
+ _.check.isObject(option) &&
22
+ _.check.isArray(option.linker) &&
23
+ option.linker.length > 0
24
+ ) {
25
+ this.setRefferId(option);
26
+ this.addClickListen();
27
+ } else {
28
+ Log.log("siteLinker plugin: Please configure the linker parameter");
29
+ return;
30
+ }
31
+ function resolveOption(option) {
32
+ let l = option.length,
33
+ arr = [];
34
+ for (let i = 0; i < l; i++) {
35
+ if (
36
+ /[A-Za-z0-9]+\./.test(option[i].part_url) &&
37
+ _.check.isBoolean(option[i].after_hash)
38
+ ) {
39
+ arr.push(option[i]);
40
+ } else {
41
+ Log.log(
42
+ "The configuration of linker " +
43
+ (i + 1) +
44
+ " is not supported.Please check format"
45
+ );
46
+ }
47
+ }
48
+ return arr;
49
+ }
50
+ this.option = resolveOption(option.linker);
51
+ }
52
+
53
+ getPartUrl(part) {
54
+ let l = this.option.length;
55
+ if (l) {
56
+ for (let i = 0; i < l; i++) {
57
+ if (part.indexOf(this.option[i]["part_url"]) > -1) {
58
+ return true;
59
+ }
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+
65
+ getPartHash(part) {
66
+ let l = this.option.length;
67
+ if (l) {
68
+ for (let i = 0; i < l; i++) {
69
+ if (part.indexOf(this.option[i]["part_url"]) > -1) {
70
+ return this.option[i]["after_hash"];
71
+ }
72
+ }
73
+ }
74
+ return false;
75
+ }
76
+
77
+ getCurrentId() {
78
+ let accountId = this.ctx.store.getAccountId() || "";
79
+ let firstId = this.ctx.store.getFirstId() || "";
80
+ let urlId = firstId ? "f" + accountId : "d" + accountId;
81
+ return _.encodeURIComponent(urlId);
82
+ }
83
+
84
+ rewriteUrl(url, target) {
85
+ let reg = /([^?#]+)(\?[^#]*)?(#.*)?/;
86
+ let arr = reg.exec(url),
87
+ nurl = "";
88
+ if (!arr) return;
89
+
90
+ let host = arr[1] || "";
91
+ let search = arr[2] || "";
92
+ let hash = arr[3] || "";
93
+
94
+ let idIndex;
95
+ let hnId = "_hnsdk=" + this.getCurrentId();
96
+
97
+ function changeHnId(str) {
98
+ let arr = str.split("&");
99
+ let res = [];
100
+ _.each(arr, function (val) {
101
+ if (val.indexOf("_hnsdk=") > -1) {
102
+ res.push(hnId);
103
+ } else {
104
+ res.push(val);
105
+ }
106
+ });
107
+ return res.join("&");
108
+ }
109
+
110
+ if (this.getPartHash(url)) {
111
+ idIndex = hash.indexOf("_hnsdk");
112
+ let queryIndex = hash.indexOf("?");
113
+ if (queryIndex > -1) {
114
+ if (idIndex > -1) {
115
+ nurl =
116
+ host +
117
+ search +
118
+ "#" +
119
+ hash.substring(1, idIndex) +
120
+ changeHnId(hash.substring(idIndex, hash.length));
121
+ } else {
122
+ nurl = host + search + "#" + hash.substring(1) + "&" + hnId;
123
+ }
124
+ } else {
125
+ nurl = host + search + "#" + hash.substring(1) + "?" + hnId;
126
+ }
127
+ } else {
128
+ idIndex = search.indexOf("_hnsdk");
129
+ let hasQuery = /^\?(\w)+/.test(search);
130
+ if (hasQuery) {
131
+ if (idIndex > -1) {
132
+ nurl = host + "?" + changeHnId(search.substring(1)) + hash;
133
+ } else {
134
+ nurl = host + "?" + search.substring(1) + "&" + hnId + hash;
135
+ }
136
+ } else {
137
+ nurl = host + "?" + search.substring(1) + hnId + hash;
138
+ }
139
+ }
140
+
141
+ if (target) {
142
+ target.href = nurl;
143
+ }
144
+ return nurl;
145
+ }
146
+
147
+ getUrlId() {
148
+ let hnId = location.href.match(/_hnsdk=([aufd][^\?\#\&\=]+)/);
149
+ if (_.check.isArray(hnId) && hnId[1]) {
150
+ let uid = _.decodeURIComponent(hnId[1]);
151
+ return uid;
152
+ } else {
153
+ return "";
154
+ }
155
+ }
156
+
157
+ setRefferId(option) {
158
+ let accountId = this.ctx.store.getAccountId();
159
+ let urlId = this.getUrlId();
160
+ if (urlId === "") return false;
161
+ let isAnonymousId = urlId.substring(0, 1) === "d";
162
+ urlId = urlId.substring(1);
163
+
164
+ if (urlId === accountId) return;
165
+
166
+ if (isAnonymousId) {
167
+ this.ctx.setDeviceUId(urlId, true);
168
+ let firstId = this.ctx.store.getFirstId();
169
+ if (firstId) {
170
+ this.ctx.sendRequest({
171
+ anonymous_id: urlId,
172
+ account_id: accountId,
173
+ type: "track_signup",
174
+ event: "H_signUp",
175
+ properties: {},
176
+ });
177
+ }
178
+ } else if (!this.ctx.store.getFirstId() || option.re_login) {
179
+ this.ctx.setUserUId(urlId);
180
+ }
181
+ }
182
+
183
+ addClickListen() {
184
+ let clickFn = (event) => {
185
+ let target = event.target;
186
+ let nodeName = target.tagName.toLowerCase();
187
+ let parentTarget = target.parentNode;
188
+ let parentNodeName = parentTarget?.tagName?.toLowerCase();
189
+ let sdkUrl, sdkTarget;
190
+ if (
191
+ (nodeName === "a" && target.href) ||
192
+ (parentNodeName === "a" && parentTarget.href)
193
+ ) {
194
+ if (nodeName === "a" && target.href) {
195
+ sdkUrl = target.href;
196
+ sdkTarget = target;
197
+ } else {
198
+ sdkUrl = parentTarget.href;
199
+ sdkTarget = parentTarget;
200
+ }
201
+ let location = _.URL(sdkUrl);
202
+ let protocol = location.protocol;
203
+ if (protocol === "http:" || protocol === "https:") {
204
+ if (this.getPartUrl(sdkUrl)) {
205
+ this.rewriteUrl(sdkUrl, sdkTarget);
206
+ }
207
+ }
208
+ }
209
+ };
210
+
211
+ _.addEvent(document, "mosedown", clickFn);
212
+ if (
213
+ !!window.PointerEvent &&
214
+ "maxTouchPoints" in window.navigator &&
215
+ window.navigator.maxTouchPoints >= 0
216
+ ) {
217
+ _.addEvent(document, "pointerdown", clickFn);
218
+ }
219
+ }
220
+ }
221
+
222
+ class PageLoad {
223
+ constructor() {
224
+ this.ctx = {};
225
+ this.option = {};
226
+ this.isInited = false;
227
+ }
228
+
229
+ init(ctx, option) {
230
+ this.ctx = ctx;
231
+ this.option = option;
232
+ let observe = () => {
233
+ let duration = 0;
234
+ let p =
235
+ window.performance ||
236
+ window.webkitPerformance ||
237
+ window.msPerformance ||
238
+ window.mozPerformance;
239
+ let { location } = window;
240
+ let prop = {
241
+ H_url: location.href,
242
+ H_title: document.title,
243
+ H_url_path: location.pathname,
244
+ H_referrer: _.getReferrer(null, true),
245
+ };
246
+ if (!p) {
247
+ Log.log("your browser not support performance API");
248
+ return;
249
+ } else {
250
+ duration = this.getDuration(p) || this.getDurationCompatible(p);
251
+ this.getPageSize(p, prop);
252
+ }
253
+
254
+ if (duration > 0) {
255
+ let maxDuration = 1800;
256
+ if (_.check.isObject(this.option) && this.option.max_duration) {
257
+ maxDuration = this.option.max_duration;
258
+ }
259
+ duration = Number((duration / 1000).toFixed(3));
260
+ if (
261
+ !_.check.isNumber(maxDuration) ||
262
+ maxDuration <= 0 ||
263
+ duration <= maxDuration
264
+ ) {
265
+ prop.event_duration = duration;
266
+ }
267
+ }
268
+
269
+ if (!this.isInited) {
270
+ this.ctx.track("H_WebPageLoad", prop);
271
+ this.isInited = true;
272
+ }
273
+
274
+ if (window.removeEventListener) {
275
+ window.removeEventListener("load", observe);
276
+ }
277
+ };
278
+ if (document.readyState === "complete") {
279
+ observe();
280
+ } else if (window.addEventListener) {
281
+ window.addEventListener("load", observe);
282
+ }
283
+ }
284
+
285
+ getPageSize(p, prop) {
286
+ if (p.getEntries && _.check.isFunction(p.getEntries)) {
287
+ let entries = p.getEntries();
288
+
289
+ let totalSize = 0;
290
+ for (let i = 0; i < entries.length; i++) {
291
+ if ("transferSize" in entries[i]) {
292
+ totalSize += entries[i].transferSize;
293
+ }
294
+ }
295
+
296
+ if (
297
+ _.check.isNumber(totalSize) &&
298
+ totalSize >= 0 &&
299
+ totalSize < 10737418240
300
+ ) {
301
+ prop.H_page_resource_size = Number((totalSize / 1024).toFixed(3));
302
+ }
303
+ }
304
+ }
305
+
306
+ getDurationCompatible(p) {
307
+ let duration = 0;
308
+ if (p.timing) {
309
+ let t = p.timing;
310
+ if (
311
+ t.fetchStart === 0 ||
312
+ !_.check.isNumber(t.fetchStart) ||
313
+ t.domContentLoadedEventEnd === 0 ||
314
+ !_.check.isNumber(t.domContentLoadedEventEnd)
315
+ ) {
316
+ Log.log("performance data parsing exception");
317
+ } else {
318
+ duration = t.domContentLoadedEventEnd - t.fetchStart;
319
+ }
320
+ }
321
+ return duration;
322
+ }
323
+
324
+ getDuration(p) {
325
+ let duration = 0;
326
+ if (_.check.isFunction(p.getEntriesByType)) {
327
+ let entries = p.getEntriesByType("navigation") || [{}];
328
+ duration = (entries[0] || {}).domContentLoadedEventEnd || 0;
329
+ }
330
+ return duration;
331
+ }
332
+ }
333
+
334
+ class PageLeave {
335
+ constructor() {
336
+ this.isInited = false;
337
+
338
+ this.startTime = _.now();
339
+ this.currentPageUrl = document.referrer;
340
+ this.url = location.href;
341
+ this.title = document.title || "";
342
+ this.pageShowStatus = true;
343
+ this.pageHiddenStatus = false;
344
+
345
+ this.timer = null;
346
+ this.heartbeatIntervalTime = 5000;
347
+ this.heartbeatIntervalTimer = null;
348
+ this.pageId = null;
349
+ this.maxDuration = 432000;
350
+ this.storageName = "hinasdk_pageleave_";
351
+ }
352
+
353
+ init(ctx, option) {
354
+ this.ctx = ctx;
355
+ this.option = option;
356
+ if (this.isInited) return;
357
+ this.isInited = true;
358
+
359
+ if (this.option) {
360
+ this.option = option;
361
+
362
+ let heartbeatIntervalTime = option.heartbeat_interval_time;
363
+ if (
364
+ heartbeatIntervalTime &&
365
+ _.check.isNumber(heartbeatIntervalTime * 1) &&
366
+ heartbeatIntervalTime * 1 > 0
367
+ ) {
368
+ this.heartbeatIntervalTime = heartbeatIntervalTime * 1000;
369
+ }
370
+
371
+ let maxDuration = option.max_duration;
372
+ if (
373
+ maxDuration &&
374
+ _.check.isNumber(maxDuration * 1) &&
375
+ maxDuration * 1 > 0
376
+ ) {
377
+ this.maxDuration = maxDuration;
378
+ }
379
+ }
380
+
381
+ this.pageId = Number(
382
+ String(_.getRandom()).slice(2, 5) +
383
+ String(_.getRandom()).slice(2, 4) +
384
+ String(_.now()).slice(-4)
385
+ );
386
+ this.addPageLeaveEventListener();
387
+ if (document.hidden) {
388
+ this.pageShowStatus = false;
389
+ } else {
390
+ this.addHeartBeatInterval();
391
+ }
392
+ // Log.log("PageLeave has initial");
393
+ }
394
+
395
+ refreshPageEndTimer() {
396
+ if (this.timer) {
397
+ clearTimeout(this.timer);
398
+ this.timer = null;
399
+ }
400
+ this.timer = setTimeout(() => {
401
+ this.pageHiddenStatus = false;
402
+ }, 5000);
403
+ }
404
+
405
+ hiddenStatusHandler() {
406
+ clearTimeout(this.timer);
407
+ this.timer = null;
408
+ this.pageHiddenStatus = false;
409
+ }
410
+
411
+ pageStartHandler() {
412
+ this.startTime = _.now();
413
+
414
+ if (document.hidden === true) {
415
+ this.pageShowStatus = false;
416
+ } else {
417
+ this.pageShowStatus = true;
418
+ }
419
+ this.url = location.href;
420
+ this.title = document.title;
421
+ }
422
+
423
+ pageEndHandler() {
424
+ if (this.pageHiddenStatus) return;
425
+
426
+ let data = this.getPageLeaveProperties();
427
+ if (!this.pageShowStatus) delete data.event_duration;
428
+
429
+ this.pageShowStatus = false;
430
+ this.pageHiddenStatus = true;
431
+ if (this.isCollectUrl(this.url)) {
432
+ this.ctx.track("H_WebPageLeave", data);
433
+ }
434
+
435
+ this.refreshPageEndTimer();
436
+ this.delHeartBeatData();
437
+ }
438
+
439
+ addPageLeaveEventListener() {
440
+ this.addPageStartListener();
441
+ this.addPageSwitchListener();
442
+ this.addSinglePageListener();
443
+ this.addPageEndListener();
444
+ }
445
+
446
+ addPageStartListener() {
447
+ if ("onpageshow" in window) {
448
+ _.addEvent(window, "pageshow", () => {
449
+ this.pageStartHandler();
450
+ this.hiddenStatusHandler();
451
+ });
452
+ }
453
+ }
454
+
455
+ addPageSwitchListener() {
456
+ _.mitt.prepend("urlChange", (url) => {
457
+ if (url !== location.href) {
458
+ this.url = url;
459
+ this.pageEndHandler();
460
+ this.stopHeartBeatInterval();
461
+ this.currentPageUrl = url;
462
+ this.pageStartHandler();
463
+ this.hiddenStatusHandler();
464
+ this.addHeartBeatInterval();
465
+ }
466
+ });
467
+ }
468
+
469
+ addSinglePageListener() {}
470
+
471
+ addPageEndListener() {
472
+ _.each(["pagehide", "beforeunload", "unload"], (key) => {
473
+ if ("on" + key in window) {
474
+ _.addEvent(window, key, () => {
475
+ this.pageEndHandler();
476
+ this.stopHeartBeatInterval();
477
+ });
478
+ }
479
+ });
480
+ }
481
+
482
+ addHeartBeatInterval() {
483
+ if (!_.localStorage.isSupport()) return;
484
+ this.startHeartBeatInterval();
485
+ }
486
+
487
+ startHeartBeatInterval() {
488
+ if (this.heartbeatIntervalTimer) {
489
+ this.stopHeartBeatInterval();
490
+ }
491
+ let collectUrlStatus = true;
492
+ if (!this.isCollectUrl(this.url)) {
493
+ collectUrlStatus = false;
494
+ }
495
+
496
+ if (collectUrlStatus) {
497
+ this.heartbeatIntervalTimer = setInterval(() => {
498
+ this.saveHeartBeatData();
499
+ }, this.heartbeatIntervalTime);
500
+ this.saveHeartBeatData("first");
501
+ }
502
+ this.reissueHeartBeatData();
503
+ }
504
+
505
+ reissueHeartBeatData() {
506
+ let storageLen = _.localStorage.length;
507
+ for (let i = storageLen - 1; i >= 0; i--) {
508
+ let itemKey = _.localStorage.key(i);
509
+ if (
510
+ itemKey &&
511
+ itemKey !== this.storageName + this.pageId &&
512
+ itemKey.indexOf(this.storageName) > -1
513
+ ) {
514
+ let itemValue = _.readObjectVal(itemKey);
515
+ if (
516
+ _.check.isObject(itemValue) &&
517
+ _.now() - itemValue.time > itemValue.heartbeat_interval_time + 5000
518
+ ) {
519
+ delete itemValue.heartbeat_interval_time;
520
+ this.ctx.sendRequest(itemValue);
521
+ this.delHeartBeatData(itemKey);
522
+ }
523
+ }
524
+ }
525
+ }
526
+
527
+ stopHeartBeatInterval() {
528
+ this.heartbeatIntervalTimer && clearInterval(this.heartbeatIntervalTimer);
529
+ this.heartbeatIntervalTimer = null;
530
+ }
531
+
532
+ saveHeartBeatData(type) {
533
+ let pageleaveProperties = this.getPageLeaveProperties();
534
+ pageleaveProperties.H_time = _.now();
535
+ if (type === "first") {
536
+ pageleaveProperties.event_duration = 3;
537
+ }
538
+
539
+ let data = addProps(
540
+ {
541
+ type: "track",
542
+ event: "H_WebPageLeave",
543
+ properties: pageleaveProperties,
544
+ },
545
+ this.ctx
546
+ );
547
+
548
+ data.heartbeat_interval_time = this.heartbeatIntervalTime;
549
+ _.saveObjectVal(this.storageName + this.pageId, data);
550
+ }
551
+
552
+ delHeartBeatData(storageKey) {
553
+ if (_.localStorage.isSupport()) {
554
+ _.localStorage.remove(storageKey || this.storageName + this.pageId);
555
+ }
556
+ }
557
+
558
+ isCollectUrl(url) {
559
+ if (_.check.isFunction(this.option.isCollectUrl)) {
560
+ if (_.check.isString(url)) {
561
+ return this.option.isCollectUrl(url);
562
+ } else {
563
+ return true;
564
+ }
565
+ }
566
+ return true;
567
+ }
568
+
569
+ getPageLeaveProperties() {
570
+ let duration = (_.now() - this.startTime) / 1000;
571
+ if (
572
+ !_.check.isNumber(duration) ||
573
+ duration < 0 ||
574
+ duration > this.maxDuration
575
+ ) {
576
+ duration = 0;
577
+ }
578
+ duration = Number(duration.toFixed(3));
579
+
580
+ let referrer = _.getReferrer(this.currentPageUrl);
581
+ let viewportPosition =
582
+ document.documentElement?.scrollTop ||
583
+ window.pageYOffset ||
584
+ document.body?.scrollTop ||
585
+ 0;
586
+ viewportPosition = Math.round(viewportPosition) || 0;
587
+
588
+ let data = {
589
+ H_title: this.title,
590
+ H_url: this.url,
591
+ H_url_path: _.URL(this.url).pathname,
592
+ H_referrer_host: referrer ? _.getHostname(referrer) : "",
593
+ H_referrer: referrer,
594
+ H_viewport_position: viewportPosition,
595
+ };
596
+ if (duration) {
597
+ data.event_duration = duration;
598
+ }
599
+ data = _.extend(data, this.option.custom_props);
600
+ return data;
601
+ }
602
+ }
603
+
604
+ let plugin = {
605
+ SiteLinker: SiteLinker,
606
+ PageLoad: PageLoad,
607
+ PageLeave: PageLeave,
608
+ };
609
+
610
+ export default plugin;