@v-tilt/browser 1.1.0 → 1.1.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.
- package/dist/array.js +1 -1
- package/dist/array.js.map +1 -1
- package/dist/array.no-external.js +1 -1
- package/dist/array.no-external.js.map +1 -1
- package/dist/constants.d.ts +172 -10
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +182 -50
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +182 -50
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/session.d.ts +2 -2
- package/dist/types.d.ts +154 -38
- package/dist/user-manager.d.ts +2 -2
- package/dist/vtilt.d.ts +19 -11
- package/lib/config.js +6 -13
- package/lib/constants.d.ts +172 -10
- package/lib/constants.js +644 -439
- package/lib/request.js +13 -13
- package/lib/session.d.ts +2 -2
- package/lib/session.js +3 -3
- package/lib/types.d.ts +154 -38
- package/lib/types.js +6 -0
- package/lib/user-manager.d.ts +2 -2
- package/lib/user-manager.js +38 -11
- package/lib/vtilt.d.ts +19 -11
- package/lib/vtilt.js +73 -42
- package/lib/web-vitals.js +1 -1
- package/package.json +13 -12
- package/LICENSE +0 -21
package/lib/vtilt.js
CHANGED
|
@@ -15,16 +15,28 @@ const rate_limiter_1 = require("./rate-limiter");
|
|
|
15
15
|
const utils_1 = require("./utils");
|
|
16
16
|
const event_utils_1 = require("./utils/event-utils");
|
|
17
17
|
const globals_1 = require("./utils/globals");
|
|
18
|
+
/*
|
|
19
|
+
STYLE GUIDE:
|
|
20
|
+
|
|
21
|
+
Naming conventions:
|
|
22
|
+
- snake_case: Config options and public API methods (e.g., capture_pageview, get_distinct_id)
|
|
23
|
+
- camelCase: Internal variables and class properties (e.g., sessionManager, requestQueue)
|
|
24
|
+
- _snake_case: Private methods that can be mangled/minified (e.g., _init, _dom_loaded)
|
|
25
|
+
- __snake_case: Internal but not minified, signals internal use (e.g., __loaded, __request_queue)
|
|
26
|
+
- UPPER_CASE: Constants and globals (e.g., PRIMARY_INSTANCE_NAME, ENQUEUE_REQUESTS)
|
|
27
|
+
|
|
28
|
+
Use TypeScript accessibility modifiers (private/protected) for non-public members.
|
|
29
|
+
*/
|
|
18
30
|
// Helper to check if value is an array
|
|
19
31
|
const isArray = Array.isArray;
|
|
20
32
|
class VTilt {
|
|
21
33
|
constructor(config = {}) {
|
|
22
34
|
this.__loaded = false; // Matches snippet's window.vt.__loaded check
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
35
|
+
this._initial_pageview_captured = false;
|
|
36
|
+
this._visibility_state_listener = null;
|
|
25
37
|
this.__request_queue = []; // Legacy queue for DOM loaded handler (pre-init)
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
38
|
+
this._has_warned_about_config = false; // Track if we've already warned about missing config
|
|
39
|
+
this._set_once_properties_sent = false; // Track if $set_once with initial props has been sent (only send once per page load)
|
|
28
40
|
this.configManager = new config_1.ConfigManager(config);
|
|
29
41
|
const fullConfig = this.configManager.getConfig();
|
|
30
42
|
// Auto-detect domain from location if not provided
|
|
@@ -42,7 +54,7 @@ class VTilt {
|
|
|
42
54
|
eventsBurstLimit: 100,
|
|
43
55
|
captureWarning: (message) => {
|
|
44
56
|
// Send rate limit warning event (bypasses rate limiting)
|
|
45
|
-
this.
|
|
57
|
+
this._capture_internal(rate_limiter_1.RATE_LIMIT_WARNING_EVENT, {
|
|
46
58
|
$$client_ingestion_warning_message: message,
|
|
47
59
|
});
|
|
48
60
|
},
|
|
@@ -50,12 +62,12 @@ class VTilt {
|
|
|
50
62
|
// Initialize retry queue for failed requests
|
|
51
63
|
// Retries with exponential backoff: 3s, 6s, 12s... up to 30 minutes
|
|
52
64
|
this.retryQueue = new retry_queue_1.RetryQueue({
|
|
53
|
-
sendRequest: (req) => this.
|
|
54
|
-
sendBeacon: (req) => this.
|
|
65
|
+
sendRequest: (req) => this._send_http_request(req),
|
|
66
|
+
sendBeacon: (req) => this._send_beacon_request(req),
|
|
55
67
|
});
|
|
56
68
|
// Initialize request queue for event batching
|
|
57
69
|
// Events are batched and sent every 3 seconds (configurable)
|
|
58
|
-
this.requestQueue = new request_queue_1.RequestQueue((req) => this.
|
|
70
|
+
this.requestQueue = new request_queue_1.RequestQueue((req) => this._send_batched_request(req), { flush_interval_ms: 3000 });
|
|
59
71
|
}
|
|
60
72
|
/**
|
|
61
73
|
* Initializes a new instance of the VTilt tracking object.
|
|
@@ -126,16 +138,32 @@ class VTilt {
|
|
|
126
138
|
this.historyAutocapture = new history_autocapture_1.HistoryAutocapture(this);
|
|
127
139
|
this.historyAutocapture.startIfEnabled();
|
|
128
140
|
// Set up page unload handler to flush queued events
|
|
129
|
-
this.
|
|
141
|
+
this._setup_unload_handler();
|
|
142
|
+
// Enable the request queue for batched sending (PostHog pattern)
|
|
143
|
+
this._start_queue_if_opted_in();
|
|
130
144
|
// Capture initial pageview (with visibility check)
|
|
131
|
-
|
|
145
|
+
// Only if capture_pageview is enabled (default: true)
|
|
146
|
+
if (fullConfig.capture_pageview !== false) {
|
|
147
|
+
this._capture_initial_pageview();
|
|
148
|
+
}
|
|
132
149
|
return this;
|
|
133
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Start the request queue if user hasn't opted out
|
|
153
|
+
* Following PostHog's pattern - called from both _init() and _dom_loaded()
|
|
154
|
+
* Safe to call multiple times as enable() is idempotent
|
|
155
|
+
*/
|
|
156
|
+
_start_queue_if_opted_in() {
|
|
157
|
+
// TODO: Add opt-out check when consent management is implemented
|
|
158
|
+
// if (!this.is_capturing()) return;
|
|
159
|
+
// Enable batched sending
|
|
160
|
+
this.requestQueue.enable();
|
|
161
|
+
}
|
|
134
162
|
/**
|
|
135
163
|
* Set up handler to flush event queue on page unload
|
|
136
164
|
* Uses both beforeunload and pagehide for maximum compatibility
|
|
137
165
|
*/
|
|
138
|
-
|
|
166
|
+
_setup_unload_handler() {
|
|
139
167
|
if (!globals_1.window) {
|
|
140
168
|
return;
|
|
141
169
|
}
|
|
@@ -183,16 +211,16 @@ class VTilt {
|
|
|
183
211
|
* Returns true if projectId and token are present, false otherwise
|
|
184
212
|
* Logs a warning only once per instance if not configured
|
|
185
213
|
*/
|
|
186
|
-
|
|
214
|
+
_is_configured() {
|
|
187
215
|
const config = this.configManager.getConfig();
|
|
188
216
|
if (config.projectId && config.token) {
|
|
189
217
|
return true;
|
|
190
218
|
}
|
|
191
219
|
// Only warn once to avoid console spam
|
|
192
|
-
if (!this.
|
|
220
|
+
if (!this._has_warned_about_config) {
|
|
193
221
|
console.warn("VTilt: projectId and token are required for tracking. " +
|
|
194
222
|
"Events will be skipped until init() or updateConfig() is called with these fields.");
|
|
195
|
-
this.
|
|
223
|
+
this._has_warned_about_config = true;
|
|
196
224
|
}
|
|
197
225
|
return false;
|
|
198
226
|
}
|
|
@@ -201,8 +229,8 @@ class VTilt {
|
|
|
201
229
|
*/
|
|
202
230
|
buildUrl() {
|
|
203
231
|
const config = this.configManager.getConfig();
|
|
204
|
-
const { proxyUrl, proxy,
|
|
205
|
-
// Use proxy endpoint to handle
|
|
232
|
+
const { proxyUrl, proxy, api_host, token } = config;
|
|
233
|
+
// Use proxy endpoint to handle backend authentication
|
|
206
234
|
if (proxyUrl) {
|
|
207
235
|
// Use the full proxy URL as provided
|
|
208
236
|
return proxyUrl;
|
|
@@ -211,8 +239,8 @@ class VTilt {
|
|
|
211
239
|
// Construct the proxy URL from the proxy domain with token
|
|
212
240
|
return `${proxy}/api/tracking?token=${token}`;
|
|
213
241
|
}
|
|
214
|
-
else if (
|
|
215
|
-
const cleanHost =
|
|
242
|
+
else if (api_host) {
|
|
243
|
+
const cleanHost = api_host.replace(/\/+$/gm, "");
|
|
216
244
|
return `${cleanHost}/api/tracking?token=${token}`;
|
|
217
245
|
}
|
|
218
246
|
else {
|
|
@@ -228,7 +256,7 @@ class VTilt {
|
|
|
228
256
|
sendRequest(url, event, shouldEnqueue) {
|
|
229
257
|
// Validate configuration (only warns once per instance)
|
|
230
258
|
// This is the single place where validation happens for all sending methods
|
|
231
|
-
if (!this.
|
|
259
|
+
if (!this._is_configured()) {
|
|
232
260
|
return;
|
|
233
261
|
}
|
|
234
262
|
// Check if we should queue requests (for older browsers before DOM is loaded)
|
|
@@ -248,11 +276,11 @@ class VTilt {
|
|
|
248
276
|
* Called by RequestQueue when flushing
|
|
249
277
|
* Uses RetryQueue for automatic retry on failure
|
|
250
278
|
*/
|
|
251
|
-
|
|
279
|
+
_send_batched_request(req) {
|
|
252
280
|
const { transport } = req;
|
|
253
281
|
// Use sendBeacon for reliable delivery on page unload
|
|
254
282
|
if (transport === "sendBeacon") {
|
|
255
|
-
this.
|
|
283
|
+
this._send_beacon_request(req);
|
|
256
284
|
return;
|
|
257
285
|
}
|
|
258
286
|
// Use retry queue for normal requests (handles failures automatically)
|
|
@@ -263,7 +291,7 @@ class VTilt {
|
|
|
263
291
|
* Uses GZip compression for payloads > 1KB
|
|
264
292
|
* Used by RetryQueue for retryable requests
|
|
265
293
|
*/
|
|
266
|
-
|
|
294
|
+
_send_http_request(req) {
|
|
267
295
|
return new Promise((resolve) => {
|
|
268
296
|
const { url, events } = req;
|
|
269
297
|
// Prepare payload (single event or batch)
|
|
@@ -288,7 +316,7 @@ class VTilt {
|
|
|
288
316
|
* Send request using sendBeacon for reliable delivery on page unload
|
|
289
317
|
* Uses GZip compression for payloads > 1KB
|
|
290
318
|
*/
|
|
291
|
-
|
|
319
|
+
_send_beacon_request(req) {
|
|
292
320
|
const { url, events } = req;
|
|
293
321
|
// Prepare payload (single event or batch)
|
|
294
322
|
const payload = events.length === 1 ? events[0] : { events };
|
|
@@ -360,7 +388,7 @@ class VTilt {
|
|
|
360
388
|
// and cause unnecessary server processing for identity creation/updates.
|
|
361
389
|
const setOnce = {};
|
|
362
390
|
// Only include initial props if we haven't sent them yet this page load
|
|
363
|
-
if (!this.
|
|
391
|
+
if (!this._set_once_properties_sent && Object.keys(initialProps).length > 0) {
|
|
364
392
|
Object.assign(setOnce, initialProps);
|
|
365
393
|
}
|
|
366
394
|
// Always merge with user-provided $set_once if present
|
|
@@ -382,8 +410,8 @@ class VTilt {
|
|
|
382
410
|
...payload, // User-provided payload (can override base and person properties)
|
|
383
411
|
};
|
|
384
412
|
// Mark that $set_once with initial props has been sent (only do this once per page load)
|
|
385
|
-
if (!this.
|
|
386
|
-
this.
|
|
413
|
+
if (!this._set_once_properties_sent && Object.keys(initialProps).length > 0) {
|
|
414
|
+
this._set_once_properties_sent = true;
|
|
387
415
|
}
|
|
388
416
|
// Add title only to $pageview events
|
|
389
417
|
if (name === "$pageview" && globals_1.document) {
|
|
@@ -436,7 +464,7 @@ class VTilt {
|
|
|
436
464
|
* Internal capture method that bypasses rate limiting
|
|
437
465
|
* Used for system events like rate limit warnings
|
|
438
466
|
*/
|
|
439
|
-
|
|
467
|
+
_capture_internal(name, payload) {
|
|
440
468
|
this.capture(name, payload, { skip_client_rate_limiting: true });
|
|
441
469
|
}
|
|
442
470
|
/**
|
|
@@ -506,7 +534,7 @@ class VTilt {
|
|
|
506
534
|
// New distinct_id and properties go in payload
|
|
507
535
|
this.capture("$identify", {
|
|
508
536
|
distinct_id: newDistinctId, // New distinct_id in payload
|
|
509
|
-
$anon_distinct_id:
|
|
537
|
+
$anon_distinct_id: anonymousId, // Previous ID in payload
|
|
510
538
|
$set: userPropertiesToSet || {},
|
|
511
539
|
$set_once: userPropertiesToSetOnce || {},
|
|
512
540
|
});
|
|
@@ -641,8 +669,9 @@ class VTilt {
|
|
|
641
669
|
}
|
|
642
670
|
/**
|
|
643
671
|
* Capture initial pageview with visibility check
|
|
672
|
+
* Note: The capture_pageview config check happens at the call site (in _init)
|
|
644
673
|
*/
|
|
645
|
-
|
|
674
|
+
_capture_initial_pageview() {
|
|
646
675
|
if (!globals_1.document) {
|
|
647
676
|
return;
|
|
648
677
|
}
|
|
@@ -651,17 +680,17 @@ class VTilt {
|
|
|
651
680
|
// This is useful to avoid `prerender` calls from Chrome/Wordpress/SPAs
|
|
652
681
|
// that are not visible to the user
|
|
653
682
|
if (globals_1.document.visibilityState !== "visible") {
|
|
654
|
-
if (!this.
|
|
655
|
-
this.
|
|
656
|
-
this.
|
|
683
|
+
if (!this._visibility_state_listener) {
|
|
684
|
+
this._visibility_state_listener = () => {
|
|
685
|
+
this._capture_initial_pageview();
|
|
657
686
|
};
|
|
658
|
-
(0, utils_1.addEventListener)(globals_1.document, "visibilitychange", this.
|
|
687
|
+
(0, utils_1.addEventListener)(globals_1.document, "visibilitychange", this._visibility_state_listener);
|
|
659
688
|
}
|
|
660
689
|
return;
|
|
661
690
|
}
|
|
662
691
|
// Extra check here to guarantee we only ever trigger a single initial pageview event
|
|
663
|
-
if (!this.
|
|
664
|
-
this.
|
|
692
|
+
if (!this._initial_pageview_captured) {
|
|
693
|
+
this._initial_pageview_captured = true;
|
|
665
694
|
// Wait a bit for SPA routers
|
|
666
695
|
setTimeout(() => {
|
|
667
696
|
// Double-check we're still in browser environment (defensive check)
|
|
@@ -676,9 +705,9 @@ class VTilt {
|
|
|
676
705
|
this.capture("$pageview", payload);
|
|
677
706
|
}, 300);
|
|
678
707
|
// After we've captured the initial pageview, we can remove the listener
|
|
679
|
-
if (this.
|
|
680
|
-
globals_1.document.removeEventListener("visibilitychange", this.
|
|
681
|
-
this.
|
|
708
|
+
if (this._visibility_state_listener) {
|
|
709
|
+
globals_1.document.removeEventListener("visibilitychange", this._visibility_state_listener);
|
|
710
|
+
this._visibility_state_listener = null;
|
|
682
711
|
}
|
|
683
712
|
}
|
|
684
713
|
}
|
|
@@ -741,16 +770,17 @@ class VTilt {
|
|
|
741
770
|
}
|
|
742
771
|
/**
|
|
743
772
|
* Called when DOM is loaded - processes queued requests and enables batching
|
|
773
|
+
* Following PostHog's pattern in _dom_loaded()
|
|
744
774
|
*/
|
|
745
775
|
_dom_loaded() {
|
|
746
|
-
// Enable the request queue for batched sending
|
|
747
|
-
this.requestQueue.enable();
|
|
748
776
|
// Process all pre-init queued requests
|
|
749
777
|
this.__request_queue.forEach((item) => {
|
|
750
778
|
this._send_retriable_request(item);
|
|
751
779
|
});
|
|
752
780
|
// Clear the legacy queue
|
|
753
781
|
this.__request_queue = [];
|
|
782
|
+
// Enable the request queue for batched sending (PostHog pattern)
|
|
783
|
+
this._start_queue_if_opted_in();
|
|
754
784
|
}
|
|
755
785
|
}
|
|
756
786
|
exports.VTilt = VTilt;
|
|
@@ -765,7 +795,8 @@ let ENQUEUE_REQUESTS = !SUPPORTS_REQUEST &&
|
|
|
765
795
|
(globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("MSIE")) === -1 &&
|
|
766
796
|
(globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("Mozilla")) === -1;
|
|
767
797
|
// Expose ENQUEUE_REQUESTS to window for request queuing
|
|
768
|
-
|
|
798
|
+
// Use typeof check to safely access global window object
|
|
799
|
+
if (typeof globals_1.window !== "undefined" && globals_1.window !== null) {
|
|
769
800
|
globals_1.window.__VTILT_ENQUEUE_REQUESTS = ENQUEUE_REQUESTS;
|
|
770
801
|
}
|
|
771
802
|
/**
|
|
@@ -781,7 +812,7 @@ const add_dom_loaded_handler = function () {
|
|
|
781
812
|
dom_loaded_handler.done = true;
|
|
782
813
|
// Disable request queuing now that DOM is loaded
|
|
783
814
|
ENQUEUE_REQUESTS = false;
|
|
784
|
-
if (typeof globals_1.window !== "undefined") {
|
|
815
|
+
if (typeof globals_1.window !== "undefined" && globals_1.window !== null) {
|
|
785
816
|
globals_1.window.__VTILT_ENQUEUE_REQUESTS = false;
|
|
786
817
|
}
|
|
787
818
|
// Process queued requests for all instances
|
package/lib/web-vitals.js
CHANGED
|
@@ -7,7 +7,7 @@ class WebVitalsManager {
|
|
|
7
7
|
constructor(config, instance) {
|
|
8
8
|
this.instance = instance;
|
|
9
9
|
// Load web-vitals if enabled and in browser environment (not SSR)
|
|
10
|
-
if (config.
|
|
10
|
+
if (config.capture_performance && globals_1.window) {
|
|
11
11
|
try {
|
|
12
12
|
// Dynamically require web-vitals, ignore type error since it's a runtime import
|
|
13
13
|
// eslint-disable-next-line
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v-tilt/browser",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "vTilt browser tracking library",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"module": "dist/module.js",
|
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -b && rollup -c",
|
|
17
|
+
"dev": "rollup -c -w",
|
|
18
|
+
"type-check": "tsc --noEmit",
|
|
19
|
+
"lint": "eslint src --ext .ts",
|
|
20
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
21
|
+
"clean": "rimraf lib dist",
|
|
22
|
+
"prepublishOnly": "pnpm run build"
|
|
23
|
+
},
|
|
15
24
|
"keywords": [
|
|
16
25
|
"analytics",
|
|
17
26
|
"tracking",
|
|
@@ -29,14 +38,14 @@
|
|
|
29
38
|
"@rollup/plugin-terser": "^0.4.4",
|
|
30
39
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
31
40
|
"@types/node": "^20.10.5",
|
|
41
|
+
"@v-tilt/eslint-config": "workspace:*",
|
|
32
42
|
"eslint": "^9.0.0",
|
|
33
43
|
"rimraf": "^5.0.5",
|
|
34
44
|
"rollup": "^4.9.1",
|
|
35
45
|
"rollup-plugin-dts": "^6.2.3",
|
|
36
46
|
"rollup-plugin-terser": "^7.0.2",
|
|
37
47
|
"rollup-plugin-visualizer": "^6.0.3",
|
|
38
|
-
"typescript": "^5.3.3"
|
|
39
|
-
"@v-tilt/eslint-config": "1.0.0"
|
|
48
|
+
"typescript": "^5.3.3"
|
|
40
49
|
},
|
|
41
50
|
"dependencies": {
|
|
42
51
|
"fflate": "^0.8.2",
|
|
@@ -49,13 +58,5 @@
|
|
|
49
58
|
"web-vitals": {
|
|
50
59
|
"optional": true
|
|
51
60
|
}
|
|
52
|
-
},
|
|
53
|
-
"scripts": {
|
|
54
|
-
"build": "tsc -b && rollup -c",
|
|
55
|
-
"dev": "rollup -c -w",
|
|
56
|
-
"type-check": "tsc --noEmit",
|
|
57
|
-
"lint": "eslint src --ext .ts",
|
|
58
|
-
"lint:fix": "eslint src --ext .ts --fix",
|
|
59
|
-
"clean": "rimraf lib dist"
|
|
60
61
|
}
|
|
61
|
-
}
|
|
62
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Tinybird.co
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|