@technoapple/ga4 1.0.4 → 1.1.0

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.
Files changed (74) hide show
  1. package/.github/workflows/node.js.yml +31 -31
  2. package/.prettierignore +1 -1
  3. package/LICENSE +21 -21
  4. package/README.md +386 -48
  5. package/REQUIREMENTS.md +548 -0
  6. package/babel.config.js +5 -5
  7. package/build/main/ga4/ga4.d.ts +13 -0
  8. package/build/main/ga4/ga4.js +24 -1
  9. package/build/main/helpers/debounce.d.ts +5 -0
  10. package/build/main/helpers/debounce.js +23 -0
  11. package/build/main/helpers/delegate.d.ts +8 -0
  12. package/build/main/helpers/delegate.js +37 -0
  13. package/build/main/helpers/dom-ready.d.ts +1 -0
  14. package/build/main/helpers/dom-ready.js +13 -0
  15. package/build/main/helpers/parse-url.d.ts +11 -0
  16. package/build/main/helpers/parse-url.js +32 -0
  17. package/build/main/helpers/session.d.ts +4 -0
  18. package/build/main/helpers/session.js +50 -0
  19. package/build/main/index.d.ts +9 -0
  20. package/build/main/index.js +19 -2
  21. package/build/main/plugins/clean-url-tracker.d.ts +17 -0
  22. package/build/main/plugins/clean-url-tracker.js +105 -0
  23. package/build/main/plugins/event-tracker.d.ts +27 -0
  24. package/build/main/plugins/event-tracker.js +76 -0
  25. package/build/main/plugins/impression-tracker.d.ts +32 -0
  26. package/build/main/plugins/impression-tracker.js +202 -0
  27. package/build/main/plugins/index.d.ts +8 -0
  28. package/build/main/plugins/index.js +20 -0
  29. package/build/main/plugins/media-query-tracker.d.ts +20 -0
  30. package/build/main/plugins/media-query-tracker.js +96 -0
  31. package/build/main/plugins/outbound-form-tracker.d.ts +17 -0
  32. package/build/main/plugins/outbound-form-tracker.js +55 -0
  33. package/build/main/plugins/outbound-link-tracker.d.ts +19 -0
  34. package/build/main/plugins/outbound-link-tracker.js +63 -0
  35. package/build/main/plugins/page-visibility-tracker.d.ts +24 -0
  36. package/build/main/plugins/page-visibility-tracker.js +93 -0
  37. package/build/main/plugins/url-change-tracker.d.ts +20 -0
  38. package/build/main/plugins/url-change-tracker.js +76 -0
  39. package/build/main/types/plugins.d.ts +78 -0
  40. package/build/main/types/plugins.js +3 -0
  41. package/build/tsconfig.tsbuildinfo +1 -1
  42. package/docs/examples/react.md +95 -0
  43. package/docs/examples/vanilla.md +65 -0
  44. package/docs/examples/vue.md +87 -0
  45. package/jest.config.ts +195 -195
  46. package/package.json +56 -56
  47. package/src/dataLayer.ts +85 -85
  48. package/src/ga4/ga4.ts +69 -40
  49. package/src/ga4/ga4option.ts +4 -4
  50. package/src/ga4/index.ts +4 -4
  51. package/src/helpers/debounce.ts +28 -0
  52. package/src/helpers/delegate.ts +51 -0
  53. package/src/helpers/dom-ready.ts +7 -0
  54. package/src/helpers/parse-url.ts +37 -0
  55. package/src/helpers/session.ts +39 -0
  56. package/src/index.ts +34 -7
  57. package/src/plugins/clean-url-tracker.ts +112 -0
  58. package/src/plugins/event-tracker.ts +90 -0
  59. package/src/plugins/impression-tracker.ts +230 -0
  60. package/src/plugins/index.ts +8 -0
  61. package/src/plugins/media-query-tracker.ts +116 -0
  62. package/src/plugins/outbound-form-tracker.ts +65 -0
  63. package/src/plugins/outbound-link-tracker.ts +72 -0
  64. package/src/plugins/page-visibility-tracker.ts +104 -0
  65. package/src/plugins/url-change-tracker.ts +84 -0
  66. package/src/types/dataLayer.ts +9 -9
  67. package/src/types/global.ts +12 -12
  68. package/src/types/gtag.ts +259 -259
  69. package/src/types/plugins.ts +98 -0
  70. package/src/util.ts +18 -18
  71. package/test/dataLayer.spec.ts +55 -55
  72. package/test/ga4.spec.ts +36 -36
  73. package/tsconfig.json +28 -28
  74. package/tsconfig.module.json +11 -11
package/src/dataLayer.ts CHANGED
@@ -1,86 +1,86 @@
1
- import { DataLayerObject } from './types/dataLayer';
2
-
3
- function isObject(target:any): boolean {
4
- if (!target) {
5
- return false;
6
- }
7
-
8
- return Object.prototype.toString.call(target) === '[object Object]';
9
- }
10
-
11
- function isArguments(target:object): boolean {
12
- if (!target) {
13
- return false;
14
- }
15
-
16
- return Object.prototype.toString.call(target) === '[object Arguments]';
17
- }
18
-
19
- function getDataValue(key:string, currentData: DataLayerObject) {
20
- if (isObject(currentData)) {
21
- const data = currentData[key];
22
- if (data) {
23
- return data;
24
- }
25
- return null;
26
- }
27
- else if (isArguments(currentData) || Array.isArray(currentData)) {
28
- const arr = Object.values(currentData);
29
- const data = arr.find(c => c === key);
30
- if (data) {
31
- return data;
32
- }
33
-
34
- const dataObj = arr.find(c => isObject(c));
35
- if (dataObj) {
36
- const data = dataObj[key];
37
- if (data) {
38
- return data;
39
- }
40
- return null;
41
- }
42
-
43
- return null;
44
- }
45
- else {
46
- // not support.
47
- return null;
48
- }
49
- }
50
-
51
- /**
52
- * get value from dataLayer
53
- * @param key key to search from dataLayer
54
- * @param getLast boolean, false (default) find the first item, true search the last value for the same key
55
- * @returns return the value if find, otherwise return empty string;
56
- */
57
- function get(key:string, getLast?:boolean): any {
58
-
59
- if (!window.dataLayer || !Array.isArray(window.dataLayer)) {
60
- return '';
61
- }
62
-
63
- if (!getLast) {
64
-
65
- for (let index = 0; index < window.dataLayer.length; index++) {
66
- const data = getDataValue(key, window.dataLayer[index]);
67
- if (!data) {
68
- continue;
69
- }
70
-
71
- return data;
72
- }
73
- }
74
- else {
75
- for (let index = window.dataLayer.length; index > 0; index--) {
76
- const data = getDataValue(key, window.dataLayer[index]);
77
- if (!data) {
78
- continue;
79
- }
80
-
81
- return data;
82
- }
83
- }
84
- }
85
-
1
+ import { DataLayerObject } from './types/dataLayer';
2
+
3
+ function isObject(target:any): boolean {
4
+ if (!target) {
5
+ return false;
6
+ }
7
+
8
+ return Object.prototype.toString.call(target) === '[object Object]';
9
+ }
10
+
11
+ function isArguments(target:object): boolean {
12
+ if (!target) {
13
+ return false;
14
+ }
15
+
16
+ return Object.prototype.toString.call(target) === '[object Arguments]';
17
+ }
18
+
19
+ function getDataValue(key:string, currentData: DataLayerObject) {
20
+ if (isObject(currentData)) {
21
+ const data = currentData[key];
22
+ if (data) {
23
+ return data;
24
+ }
25
+ return null;
26
+ }
27
+ else if (isArguments(currentData) || Array.isArray(currentData)) {
28
+ const arr = Object.values(currentData);
29
+ const data = arr.find(c => c === key);
30
+ if (data) {
31
+ return data;
32
+ }
33
+
34
+ const dataObj = arr.find(c => isObject(c));
35
+ if (dataObj) {
36
+ const data = dataObj[key];
37
+ if (data) {
38
+ return data;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ return null;
44
+ }
45
+ else {
46
+ // not support.
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * get value from dataLayer
53
+ * @param key key to search from dataLayer
54
+ * @param getLast boolean, false (default) find the first item, true search the last value for the same key
55
+ * @returns return the value if find, otherwise return empty string;
56
+ */
57
+ function get(key:string, getLast?:boolean): any {
58
+
59
+ if (!window.dataLayer || !Array.isArray(window.dataLayer)) {
60
+ return '';
61
+ }
62
+
63
+ if (!getLast) {
64
+
65
+ for (let index = 0; index < window.dataLayer.length; index++) {
66
+ const data = getDataValue(key, window.dataLayer[index]);
67
+ if (!data) {
68
+ continue;
69
+ }
70
+
71
+ return data;
72
+ }
73
+ }
74
+ else {
75
+ for (let index = window.dataLayer.length; index > 0; index--) {
76
+ const data = getDataValue(key, window.dataLayer[index]);
77
+ if (!data) {
78
+ continue;
79
+ }
80
+
81
+ return data;
82
+ }
83
+ }
84
+ }
85
+
86
86
  export {get};
package/src/ga4/ga4.ts CHANGED
@@ -1,41 +1,70 @@
1
- import { ga4Option } from "./ga4option";
2
- import { DataLayerObject } from "../types/dataLayer";
3
- import { KeyValueParams, gtag } from "../types/gtag";
4
- import {} from '../types/global';
5
-
6
- class ga4 {
7
-
8
- private static instance: ga4;
9
-
10
- private constructor() {
11
- }
12
-
13
- public init(option:ga4Option){
14
- window.dataLayer = window.dataLayer || Array<DataLayerObject>;
15
- window.gtag = window.gtag || function() {
16
- window.dataLayer.push(arguments);
17
- }
18
- window.gtag('js', new Date());
19
- window.gtag('config', option.targetId);
20
- }
21
-
22
- public static getInstance():ga4 {
23
- if (!ga4.instance) {
24
- ga4.instance = new ga4();
25
- }
26
- return ga4.instance;
27
- }
28
-
29
- public send(eventName:string, eventParameters: KeyValueParams ): boolean {
30
-
31
- window.gtag('event', eventName, eventParameters);
32
-
33
- return true;
34
- }
35
-
36
- get gtag() : gtag {
37
- return window.gtag;
38
- }
39
- }
40
-
1
+ import { ga4Option } from "./ga4option";
2
+ import { DataLayerObject } from "../types/dataLayer";
3
+ import { KeyValueParams, gtag } from "../types/gtag";
4
+ import { GA4Plugin, SendFunction } from "../types/plugins";
5
+ import {} from '../types/global';
6
+
7
+ class ga4 {
8
+
9
+ private static instance: ga4;
10
+ private _plugins: GA4Plugin[] = [];
11
+
12
+ private constructor() {
13
+ }
14
+
15
+ public init(option:ga4Option){
16
+ window.dataLayer = window.dataLayer || Array<DataLayerObject>;
17
+ window.gtag = window.gtag || function() {
18
+ window.dataLayer.push(arguments);
19
+ }
20
+ window.gtag('js', new Date());
21
+ window.gtag('config', option.targetId);
22
+ }
23
+
24
+ public static getInstance():ga4 {
25
+ if (!ga4.instance) {
26
+ ga4.instance = new ga4();
27
+ }
28
+ return ga4.instance;
29
+ }
30
+
31
+ public send(eventName:string, eventParameters: KeyValueParams ): boolean {
32
+
33
+ window.gtag('event', eventName, eventParameters);
34
+
35
+ return true;
36
+ }
37
+
38
+ /**
39
+ * Register a plugin with the GA4 instance.
40
+ * @param PluginClass The plugin class constructor
41
+ * @param options Plugin-specific configuration options
42
+ * @returns The plugin instance (call `.remove()` to unregister)
43
+ */
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ public use<T extends GA4Plugin>(
46
+ PluginClass: new (send: SendFunction, options?: any) => T,
47
+ options?: any
48
+ ): T {
49
+ const send: SendFunction = (eventName: string, params: Record<string, unknown>) => {
50
+ window.gtag('event', eventName, params as KeyValueParams);
51
+ };
52
+ const plugin = new PluginClass(send, options);
53
+ this._plugins.push(plugin);
54
+ return plugin;
55
+ }
56
+
57
+ /**
58
+ * Remove all registered plugins and clean up their listeners.
59
+ */
60
+ public removeAll(): void {
61
+ this._plugins.forEach(p => p.remove());
62
+ this._plugins = [];
63
+ }
64
+
65
+ get gtag() : gtag {
66
+ return window.gtag;
67
+ }
68
+ }
69
+
41
70
  export {ga4};
@@ -1,5 +1,5 @@
1
- interface ga4Option {
2
- targetId:string;
3
- }
4
-
1
+ interface ga4Option {
2
+ targetId:string;
3
+ }
4
+
5
5
  export {ga4Option};
package/src/ga4/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import {ga4} from './ga4';
2
-
3
- const ga = ga4.getInstance();
4
-
1
+ import {ga4} from './ga4';
2
+
3
+ const ga = ga4.getInstance();
4
+
5
5
  export {ga};
@@ -0,0 +1,28 @@
1
+ export interface DebouncedFunction<T extends (...args: never[]) => void> {
2
+ (...args: Parameters<T>): void;
3
+ cancel(): void;
4
+ }
5
+
6
+ export function debounce<T extends (...args: never[]) => void>(
7
+ fn: T,
8
+ delay: number
9
+ ): DebouncedFunction<T> {
10
+ let timer: ReturnType<typeof setTimeout> | null = null;
11
+
12
+ const debounced = function (this: unknown, ...args: Parameters<T>) {
13
+ if (timer !== null) clearTimeout(timer);
14
+ timer = setTimeout(() => {
15
+ timer = null;
16
+ fn.apply(this, args);
17
+ }, delay);
18
+ } as DebouncedFunction<T>;
19
+
20
+ debounced.cancel = () => {
21
+ if (timer !== null) {
22
+ clearTimeout(timer);
23
+ timer = null;
24
+ }
25
+ };
26
+
27
+ return debounced;
28
+ }
@@ -0,0 +1,51 @@
1
+ export interface DelegateHandle {
2
+ destroy(): void;
3
+ }
4
+
5
+ export interface DelegateOptions {
6
+ composed?: boolean;
7
+ useCapture?: boolean;
8
+ }
9
+
10
+ export function delegate(
11
+ target: EventTarget,
12
+ eventType: string,
13
+ selector: string,
14
+ handler: (event: Event, element: Element) => void,
15
+ options?: DelegateOptions
16
+ ): DelegateHandle {
17
+ const useCapture = options?.useCapture ?? false;
18
+
19
+ const listener = (event: Event) => {
20
+ let element: Element | null = event.target as Element | null;
21
+
22
+ // Handle composed events (shadow DOM)
23
+ if (options?.composed && typeof event.composedPath === 'function') {
24
+ const path = event.composedPath();
25
+ for (const node of path) {
26
+ if (node === target) break;
27
+ if (node instanceof Element && node.matches(selector)) {
28
+ handler(event, node);
29
+ return;
30
+ }
31
+ }
32
+ return;
33
+ }
34
+
35
+ while (element && element !== target) {
36
+ if (element.matches(selector)) {
37
+ handler(event, element);
38
+ return;
39
+ }
40
+ element = element.parentElement;
41
+ }
42
+ };
43
+
44
+ target.addEventListener(eventType, listener, useCapture);
45
+
46
+ return {
47
+ destroy() {
48
+ target.removeEventListener(eventType, listener, useCapture);
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,7 @@
1
+ export function domReady(callback: () => void): void {
2
+ if (document.readyState === 'loading') {
3
+ document.addEventListener('DOMContentLoaded', callback, { once: true });
4
+ } else {
5
+ callback();
6
+ }
7
+ }
@@ -0,0 +1,37 @@
1
+ export interface ParsedUrl {
2
+ href: string;
3
+ protocol: string;
4
+ hostname: string;
5
+ port: string;
6
+ pathname: string;
7
+ search: string;
8
+ hash: string;
9
+ origin: string;
10
+ }
11
+
12
+ export function parseUrl(url: string): ParsedUrl {
13
+ try {
14
+ const parsed = new URL(url, location.href);
15
+ return {
16
+ href: parsed.href,
17
+ protocol: parsed.protocol,
18
+ hostname: parsed.hostname,
19
+ port: parsed.port,
20
+ pathname: parsed.pathname,
21
+ search: parsed.search,
22
+ hash: parsed.hash,
23
+ origin: parsed.origin,
24
+ };
25
+ } catch {
26
+ return {
27
+ href: url,
28
+ protocol: '',
29
+ hostname: '',
30
+ port: '',
31
+ pathname: url,
32
+ search: '',
33
+ hash: '',
34
+ origin: '',
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,39 @@
1
+ const SESSION_KEY_PREFIX = 'ga4_session_';
2
+
3
+ export function isSessionExpired(key: string, timeoutMinutes: number): boolean {
4
+ try {
5
+ const stored = sessionStorage.getItem(SESSION_KEY_PREFIX + key);
6
+ if (!stored) return true;
7
+ const timestamp = parseInt(stored, 10);
8
+ if (isNaN(timestamp)) return true;
9
+ return (Date.now() - timestamp) > timeoutMinutes * 60 * 1000;
10
+ } catch {
11
+ return true;
12
+ }
13
+ }
14
+
15
+ export function updateSessionTimestamp(key: string): void {
16
+ try {
17
+ sessionStorage.setItem(SESSION_KEY_PREFIX + key, String(Date.now()));
18
+ } catch {
19
+ // sessionStorage may be unavailable or full
20
+ }
21
+ }
22
+
23
+ export function getSessionValue<T>(key: string): T | null {
24
+ try {
25
+ const stored = sessionStorage.getItem(SESSION_KEY_PREFIX + key);
26
+ if (stored === null) return null;
27
+ return JSON.parse(stored) as T;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ export function setSessionValue(key: string, value: unknown): void {
34
+ try {
35
+ sessionStorage.setItem(SESSION_KEY_PREFIX + key, JSON.stringify(value));
36
+ } catch {
37
+ // sessionStorage may be unavailable or full
38
+ }
39
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,34 @@
1
- import {ga} from './ga4/index';
2
- import {get} from './dataLayer';
3
-
4
- const dataLayerHelper = { get };
5
- const ga4 = ga;
6
-
7
- export {ga4, dataLayerHelper};
1
+ import {ga} from './ga4/index';
2
+ import {get} from './dataLayer';
3
+
4
+ const dataLayerHelper = { get };
5
+ const ga4 = ga;
6
+
7
+ export {ga4, dataLayerHelper};
8
+
9
+ // Plugin exports
10
+ export { EventTracker } from './plugins/event-tracker';
11
+ export { OutboundLinkTracker } from './plugins/outbound-link-tracker';
12
+ export { OutboundFormTracker } from './plugins/outbound-form-tracker';
13
+ export { PageVisibilityTracker } from './plugins/page-visibility-tracker';
14
+ export { UrlChangeTracker } from './plugins/url-change-tracker';
15
+ export { ImpressionTracker } from './plugins/impression-tracker';
16
+ export { CleanUrlTracker } from './plugins/clean-url-tracker';
17
+ export { MediaQueryTracker } from './plugins/media-query-tracker';
18
+
19
+ // Type exports
20
+ export type {
21
+ GA4Plugin,
22
+ SendFunction,
23
+ EventTrackerOptions,
24
+ OutboundLinkTrackerOptions,
25
+ OutboundFormTrackerOptions,
26
+ PageVisibilityTrackerOptions,
27
+ UrlChangeTrackerOptions,
28
+ ImpressionTrackerOptions,
29
+ ImpressionElementConfig,
30
+ CleanUrlTrackerOptions,
31
+ MediaQueryTrackerOptions,
32
+ MediaQueryDefinition,
33
+ MediaQueryDefinitionItem,
34
+ } from './types/plugins';
@@ -0,0 +1,112 @@
1
+ import { GA4Plugin, SendFunction, CleanUrlTrackerOptions } from '../types/plugins';
2
+
3
+ /**
4
+ * Normalizes URLs before they are sent with `page_view` events.
5
+ *
6
+ * Intercepts `gtag()` calls for `config` and `page_view` events
7
+ * and cleans the `page_location` and `page_path` parameters
8
+ * (strip query params, normalize trailing slashes, apply custom filters).
9
+ */
10
+ export class CleanUrlTracker implements GA4Plugin {
11
+ private opts: CleanUrlTrackerOptions;
12
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
13
+ private originalGtag: Function | null = null;
14
+
15
+ constructor(_send: SendFunction, options?: CleanUrlTrackerOptions) {
16
+ this.opts = {
17
+ stripQuery: options?.stripQuery ?? false,
18
+ queryParamsAllowlist: options?.queryParamsAllowlist,
19
+ queryParamsDenylist: options?.queryParamsDenylist,
20
+ trailingSlash: options?.trailingSlash,
21
+ urlFilter: options?.urlFilter,
22
+ };
23
+
24
+ if (typeof window !== 'undefined' && window.gtag) {
25
+ this.originalGtag = window.gtag;
26
+ const self = this;
27
+
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ (window as any).gtag = function () {
30
+ // eslint-disable-next-line prefer-rest-params
31
+ const args = Array.prototype.slice.call(arguments);
32
+
33
+ if (args.length >= 3 && typeof args[2] === 'object' && args[2] !== null) {
34
+ const isPageView = args[0] === 'event' && args[1] === 'page_view';
35
+ const isConfig = args[0] === 'config';
36
+
37
+ if (isPageView || isConfig) {
38
+ args[2] = self.cleanParams({ ...args[2] });
39
+ }
40
+ }
41
+
42
+ return self.originalGtag!.apply(window, args);
43
+ };
44
+ }
45
+ }
46
+
47
+ private cleanParams(params: Record<string, unknown>): Record<string, unknown> {
48
+ if (typeof params.page_location === 'string') {
49
+ params.page_location = this.cleanUrl(params.page_location);
50
+ }
51
+ if (typeof params.page_path === 'string') {
52
+ params.page_path = this.cleanPath(params.page_path);
53
+ }
54
+ return params;
55
+ }
56
+
57
+ cleanUrl(url: string): string {
58
+ try {
59
+ const u = new URL(url);
60
+ u.pathname = this.cleanPath(u.pathname);
61
+
62
+ if (this.opts.stripQuery) {
63
+ if (this.opts.queryParamsAllowlist && this.opts.queryParamsAllowlist.length > 0) {
64
+ const allowed = new URLSearchParams();
65
+ this.opts.queryParamsAllowlist.forEach((param) => {
66
+ if (u.searchParams.has(param)) {
67
+ allowed.set(param, u.searchParams.get(param)!);
68
+ }
69
+ });
70
+ u.search = allowed.toString() ? '?' + allowed.toString() : '';
71
+ } else {
72
+ u.search = '';
73
+ }
74
+ } else if (this.opts.queryParamsDenylist && this.opts.queryParamsDenylist.length > 0) {
75
+ this.opts.queryParamsDenylist.forEach((param) => {
76
+ u.searchParams.delete(param);
77
+ });
78
+ u.search = u.searchParams.toString() ? '?' + u.searchParams.toString() : '';
79
+ }
80
+
81
+ let result = u.toString();
82
+ if (this.opts.urlFilter) {
83
+ result = this.opts.urlFilter(result);
84
+ }
85
+ return result;
86
+ } catch {
87
+ return url;
88
+ }
89
+ }
90
+
91
+ cleanPath(path: string): string {
92
+ let result = path;
93
+
94
+ if (this.opts.trailingSlash === 'remove') {
95
+ result = result.length > 1 ? result.replace(/\/+$/, '') : result;
96
+ } else if (this.opts.trailingSlash === 'add') {
97
+ if (!result.endsWith('/') && !result.split('/').pop()?.includes('.')) {
98
+ result += '/';
99
+ }
100
+ }
101
+
102
+ return result;
103
+ }
104
+
105
+ remove(): void {
106
+ if (this.originalGtag) {
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ (window as any).gtag = this.originalGtag;
109
+ this.originalGtag = null;
110
+ }
111
+ }
112
+ }