@blotoutio/providers-consentiq-sdk 1.64.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.
- package/README.md +10 -0
- package/index.cjs.js +96 -0
- package/index.d.ts +1 -0
- package/index.js +99 -0
- package/index.mjs +94 -0
- package/package.json +42 -0
- package/stores/shopify/index.cjs.js +262 -0
- package/stores/shopify/index.js +265 -0
- package/stores/shopify/index.mjs +260 -0
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# providers-consentiq-sdk
|
|
2
|
+
|
|
3
|
+
Browser SDK for the ConsentIQ provider. Collects the visitor's consent decisions
|
|
4
|
+
from the host store's privacy API (and any external CMP) and forwards them to the
|
|
5
|
+
Blotout `consentIQ/save-data` endpoint.
|
|
6
|
+
|
|
7
|
+
Delivered through the EdgeTag tag's `/load` + `/init` path — the core bundle
|
|
8
|
+
registers the provider, and a per-store adapter (`stores/shopify`) is appended by
|
|
9
|
+
`/load` only for the matching platform. This replaces the standalone ConsentIQ
|
|
10
|
+
Shopify app embed.
|
package/index.cjs.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const packageName = 'consentIQ';
|
|
4
|
+
|
|
5
|
+
const canLog = () => {
|
|
6
|
+
try {
|
|
7
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const prefix = `[EdgeTag]`;
|
|
14
|
+
const logger = {
|
|
15
|
+
log: (...args) => {
|
|
16
|
+
if (canLog()) {
|
|
17
|
+
console.log(prefix, ...args);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
error: (...args) => {
|
|
21
|
+
if (canLog()) {
|
|
22
|
+
console.error(prefix, ...args);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
info: (...args) => {
|
|
26
|
+
if (canLog()) {
|
|
27
|
+
console.info(prefix, ...args);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
trace: (...args) => {
|
|
31
|
+
if (canLog()) {
|
|
32
|
+
console.trace(prefix, ...args);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Global registry key shared across the separately-loaded consentIQ bundles
|
|
39
|
+
* (core `index` and the per-store adapter). `Symbol.for` resolves to the same
|
|
40
|
+
* symbol in every bundle, so the store adapter can register its factory here
|
|
41
|
+
* and the core `init` can read it back.
|
|
42
|
+
*/
|
|
43
|
+
const registryKey = Symbol.for('consentiq');
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* consentIQ SDK entry point. Runs in the top-level browser window only.
|
|
47
|
+
*
|
|
48
|
+
* Resolves the store adapter registered by the per-store bundle (e.g.
|
|
49
|
+
* stores/shopify) and hands off to its `collectAnalytics`, which forwards the
|
|
50
|
+
* visitor's consent decisions to the Blotout `save-data` endpoint. When no
|
|
51
|
+
* adapter is present (platform without a consentIQ store bundle) this is a
|
|
52
|
+
* no-op.
|
|
53
|
+
*/
|
|
54
|
+
const init = (params) => {
|
|
55
|
+
var _a, _b, _c, _d, _e;
|
|
56
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
57
|
+
// Not a browser (e.g. server-side SDK) — nothing to collect.
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (window.top !== window) {
|
|
61
|
+
// Only collect from the top-level window, never nested iframes.
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const registry = ((_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {}));
|
|
66
|
+
const storeAPI = ((_b = registry.storeAPI) !== null && _b !== void 0 ? _b : (registry.storeAPI = (_c = registry.storeAPIFactory) === null || _c === void 0 ? void 0 : _c.call(registry)));
|
|
67
|
+
if (!storeAPI) {
|
|
68
|
+
// No store adapter loaded for this platform — nothing to collect.
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
storeAPI.collectAnalytics({
|
|
72
|
+
baseUrl: params.baseUrl,
|
|
73
|
+
isNewSession: (_e = (_d = params.session) === null || _d === void 0 ? void 0 : _d.isNewSession) !== null && _e !== void 0 ? _e : false,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
logger.error('Failed to initialize consentIQ:', error);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
82
|
+
const data = {
|
|
83
|
+
name: packageName,
|
|
84
|
+
init,
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
if (typeof window !== 'undefined') {
|
|
88
|
+
window.edgetagProviders = window.edgetagProviders || [];
|
|
89
|
+
window.edgetagProviders.push(data);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
logger.error('Failed to register consentIQ provider', error);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = data;
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '@blotoutio/providers-consentiq-sdk'
|
package/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
var ProvidersConsentiqSdk = (function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const packageName = 'consentIQ';
|
|
5
|
+
|
|
6
|
+
const canLog = () => {
|
|
7
|
+
try {
|
|
8
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const prefix = `[EdgeTag]`;
|
|
15
|
+
const logger = {
|
|
16
|
+
log: (...args) => {
|
|
17
|
+
if (canLog()) {
|
|
18
|
+
console.log(prefix, ...args);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
error: (...args) => {
|
|
22
|
+
if (canLog()) {
|
|
23
|
+
console.error(prefix, ...args);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
info: (...args) => {
|
|
27
|
+
if (canLog()) {
|
|
28
|
+
console.info(prefix, ...args);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
trace: (...args) => {
|
|
32
|
+
if (canLog()) {
|
|
33
|
+
console.trace(prefix, ...args);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Global registry key shared across the separately-loaded consentIQ bundles
|
|
40
|
+
* (core `index` and the per-store adapter). `Symbol.for` resolves to the same
|
|
41
|
+
* symbol in every bundle, so the store adapter can register its factory here
|
|
42
|
+
* and the core `init` can read it back.
|
|
43
|
+
*/
|
|
44
|
+
const registryKey = Symbol.for('consentiq');
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* consentIQ SDK entry point. Runs in the top-level browser window only.
|
|
48
|
+
*
|
|
49
|
+
* Resolves the store adapter registered by the per-store bundle (e.g.
|
|
50
|
+
* stores/shopify) and hands off to its `collectAnalytics`, which forwards the
|
|
51
|
+
* visitor's consent decisions to the Blotout `save-data` endpoint. When no
|
|
52
|
+
* adapter is present (platform without a consentIQ store bundle) this is a
|
|
53
|
+
* no-op.
|
|
54
|
+
*/
|
|
55
|
+
const init = (params) => {
|
|
56
|
+
var _a, _b, _c, _d, _e;
|
|
57
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
58
|
+
// Not a browser (e.g. server-side SDK) — nothing to collect.
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (window.top !== window) {
|
|
62
|
+
// Only collect from the top-level window, never nested iframes.
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const registry = ((_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {}));
|
|
67
|
+
const storeAPI = ((_b = registry.storeAPI) !== null && _b !== void 0 ? _b : (registry.storeAPI = (_c = registry.storeAPIFactory) === null || _c === void 0 ? void 0 : _c.call(registry)));
|
|
68
|
+
if (!storeAPI) {
|
|
69
|
+
// No store adapter loaded for this platform — nothing to collect.
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
storeAPI.collectAnalytics({
|
|
73
|
+
baseUrl: params.baseUrl,
|
|
74
|
+
isNewSession: (_e = (_d = params.session) === null || _d === void 0 ? void 0 : _d.isNewSession) !== null && _e !== void 0 ? _e : false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.error('Failed to initialize consentIQ:', error);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
83
|
+
const data = {
|
|
84
|
+
name: packageName,
|
|
85
|
+
init,
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
if (typeof window !== 'undefined') {
|
|
89
|
+
window.edgetagProviders = window.edgetagProviders || [];
|
|
90
|
+
window.edgetagProviders.push(data);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
logger.error('Failed to register consentIQ provider', error);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return data;
|
|
98
|
+
|
|
99
|
+
})();
|
package/index.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const packageName = 'consentIQ';
|
|
2
|
+
|
|
3
|
+
const canLog = () => {
|
|
4
|
+
try {
|
|
5
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const prefix = `[EdgeTag]`;
|
|
12
|
+
const logger = {
|
|
13
|
+
log: (...args) => {
|
|
14
|
+
if (canLog()) {
|
|
15
|
+
console.log(prefix, ...args);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
error: (...args) => {
|
|
19
|
+
if (canLog()) {
|
|
20
|
+
console.error(prefix, ...args);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
info: (...args) => {
|
|
24
|
+
if (canLog()) {
|
|
25
|
+
console.info(prefix, ...args);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
trace: (...args) => {
|
|
29
|
+
if (canLog()) {
|
|
30
|
+
console.trace(prefix, ...args);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Global registry key shared across the separately-loaded consentIQ bundles
|
|
37
|
+
* (core `index` and the per-store adapter). `Symbol.for` resolves to the same
|
|
38
|
+
* symbol in every bundle, so the store adapter can register its factory here
|
|
39
|
+
* and the core `init` can read it back.
|
|
40
|
+
*/
|
|
41
|
+
const registryKey = Symbol.for('consentiq');
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* consentIQ SDK entry point. Runs in the top-level browser window only.
|
|
45
|
+
*
|
|
46
|
+
* Resolves the store adapter registered by the per-store bundle (e.g.
|
|
47
|
+
* stores/shopify) and hands off to its `collectAnalytics`, which forwards the
|
|
48
|
+
* visitor's consent decisions to the Blotout `save-data` endpoint. When no
|
|
49
|
+
* adapter is present (platform without a consentIQ store bundle) this is a
|
|
50
|
+
* no-op.
|
|
51
|
+
*/
|
|
52
|
+
const init = (params) => {
|
|
53
|
+
var _a, _b, _c, _d, _e;
|
|
54
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
55
|
+
// Not a browser (e.g. server-side SDK) — nothing to collect.
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (window.top !== window) {
|
|
59
|
+
// Only collect from the top-level window, never nested iframes.
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const registry = ((_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {}));
|
|
64
|
+
const storeAPI = ((_b = registry.storeAPI) !== null && _b !== void 0 ? _b : (registry.storeAPI = (_c = registry.storeAPIFactory) === null || _c === void 0 ? void 0 : _c.call(registry)));
|
|
65
|
+
if (!storeAPI) {
|
|
66
|
+
// No store adapter loaded for this platform — nothing to collect.
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
storeAPI.collectAnalytics({
|
|
70
|
+
baseUrl: params.baseUrl,
|
|
71
|
+
isNewSession: (_e = (_d = params.session) === null || _d === void 0 ? void 0 : _d.isNewSession) !== null && _e !== void 0 ? _e : false,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger.error('Failed to initialize consentIQ:', error);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
80
|
+
const data = {
|
|
81
|
+
name: packageName,
|
|
82
|
+
init,
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
if (typeof window !== 'undefined') {
|
|
86
|
+
window.edgetagProviders = window.edgetagProviders || [];
|
|
87
|
+
window.edgetagProviders.push(data);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
logger.error('Failed to register consentIQ provider', error);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { data as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blotoutio/providers-consentiq-sdk",
|
|
3
|
+
"version": "1.64.0",
|
|
4
|
+
"description": "ConsentIQ Browser SDK for EdgeTag",
|
|
5
|
+
"author": "Blotout",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/blotoutio/edgetag-sdk",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./index.cjs.js",
|
|
13
|
+
"module": "./index.mjs",
|
|
14
|
+
"types": "./index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"require": "./index.cjs.js",
|
|
18
|
+
"import": "./index.mjs",
|
|
19
|
+
"default": "./index.js"
|
|
20
|
+
},
|
|
21
|
+
"./stores/shopify/index.js": {
|
|
22
|
+
"require": "./stores/shopify/index.cjs.js",
|
|
23
|
+
"import": "./stores/shopify/index.mjs",
|
|
24
|
+
"default": "./stores/shopify/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/blotoutio/edgetag-sdk.git"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"package.json",
|
|
33
|
+
"README.md",
|
|
34
|
+
"index.js",
|
|
35
|
+
"index.cjs.js",
|
|
36
|
+
"index.mjs",
|
|
37
|
+
"index.d.ts",
|
|
38
|
+
"stores/shopify/index.js",
|
|
39
|
+
"stores/shopify/index.cjs.js",
|
|
40
|
+
"stores/shopify/index.mjs"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global registry key shared across the separately-loaded consentIQ bundles
|
|
5
|
+
* (core `index` and the per-store adapter). `Symbol.for` resolves to the same
|
|
6
|
+
* symbol in every bundle, so the store adapter can register its factory here
|
|
7
|
+
* and the core `init` can read it back.
|
|
8
|
+
*/
|
|
9
|
+
const registryKey = Symbol.for('consentiq');
|
|
10
|
+
|
|
11
|
+
const canLog = () => {
|
|
12
|
+
try {
|
|
13
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const prefix = `[EdgeTag]`;
|
|
20
|
+
const logger = {
|
|
21
|
+
log: (...args) => {
|
|
22
|
+
if (canLog()) {
|
|
23
|
+
console.log(prefix, ...args);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
error: (...args) => {
|
|
27
|
+
if (canLog()) {
|
|
28
|
+
console.error(prefix, ...args);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
info: (...args) => {
|
|
32
|
+
if (canLog()) {
|
|
33
|
+
console.info(prefix, ...args);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
trace: (...args) => {
|
|
37
|
+
if (canLog()) {
|
|
38
|
+
console.trace(prefix, ...args);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getOnetrustConsent = () => {
|
|
44
|
+
try {
|
|
45
|
+
const name = 'OptanonConsent';
|
|
46
|
+
const cookie = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
|
47
|
+
if (cookie && cookie.length > 2) {
|
|
48
|
+
const match = decodeURIComponent(cookie[2]).match(new RegExp('&groups=([^&]+)'));
|
|
49
|
+
if (match && match.length > 1) {
|
|
50
|
+
const data = match[1].split(',');
|
|
51
|
+
if (!data) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
analyticsSetting: data.some((g) => g === 'C0002:1' || g === '2:1')
|
|
56
|
+
? 'allowed'
|
|
57
|
+
: 'disallowed',
|
|
58
|
+
marketingSetting: data.some((g) => g === 'C0004:1' || g === '4:1' || g === 'C0005:1' || g === '5:1')
|
|
59
|
+
? 'allowed'
|
|
60
|
+
: 'disallowed',
|
|
61
|
+
preferencesSetting: data.some((g) => g === 'C0003:1' || g === '3:1')
|
|
62
|
+
? 'allowed'
|
|
63
|
+
: 'disallowed',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const getOsanoConsent = () => {
|
|
74
|
+
try {
|
|
75
|
+
const osano = window.Osano;
|
|
76
|
+
if (!(osano === null || osano === void 0 ? void 0 : osano.cm)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
analyticsSetting: osano.cm.analytics ? 'allowed' : 'disallowed',
|
|
81
|
+
marketingSetting: osano.cm.marketing ? 'allowed' : 'disallowed',
|
|
82
|
+
preferencesSetting: osano.cm.personalization ? 'allowed' : 'disallowed',
|
|
83
|
+
saleOfDataSetting: osano.cm.optOut ? 'disallowed' : 'allowed',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const getTermlyConsent = () => {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
try {
|
|
93
|
+
const state = (_b = (_a = window.Termly) === null || _a === void 0 ? void 0 : _a.getConsentState) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
94
|
+
if (!state) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
analyticsSetting: state.analytics ? 'allowed' : 'disallowed',
|
|
99
|
+
marketingSetting: state.advertising || state.social_networking ? 'allowed' : 'disallowed',
|
|
100
|
+
preferencesSetting: state.performance ? 'allowed' : 'disallowed',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Reads the current decision from a supported external CMP (OneTrust, Osano, or
|
|
109
|
+
* Termly), if one is present. Applies regardless of host platform and takes
|
|
110
|
+
* precedence over the platform-derived `*Setting` fields in {@link sendConsentData}.
|
|
111
|
+
*/
|
|
112
|
+
const getConsentSettings = () => {
|
|
113
|
+
const oneTrustConsent = getOnetrustConsent();
|
|
114
|
+
if (oneTrustConsent) {
|
|
115
|
+
return oneTrustConsent;
|
|
116
|
+
}
|
|
117
|
+
const osanoConsent = getOsanoConsent();
|
|
118
|
+
if (osanoConsent) {
|
|
119
|
+
return osanoConsent;
|
|
120
|
+
}
|
|
121
|
+
const termlyConsent = getTermlyConsent();
|
|
122
|
+
if (termlyConsent) {
|
|
123
|
+
return termlyConsent;
|
|
124
|
+
}
|
|
125
|
+
return {};
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* POSTs a consent snapshot to the Blotout backend `consentIQ/save-data`
|
|
129
|
+
* endpoint. The platform collector builds `body`; an external CMP still wins via
|
|
130
|
+
* {@link getConsentSettings}. Errors are logged and swallowed so consent capture
|
|
131
|
+
* never breaks the host page.
|
|
132
|
+
*/
|
|
133
|
+
const sendConsentData = async (baseUrl, body) => {
|
|
134
|
+
const finalBody = {
|
|
135
|
+
...body,
|
|
136
|
+
analyticsSetting: 'unknown',
|
|
137
|
+
marketingSetting: 'unknown',
|
|
138
|
+
preferencesSetting: 'unknown',
|
|
139
|
+
saleOfDataSetting: 'unknown',
|
|
140
|
+
// An external CMP (OneTrust/Osano/Termly) wins over the 'unknown' defaults.
|
|
141
|
+
...getConsentSettings(),
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch(`${baseUrl}/providers/consentIQ/save-data`, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
body: JSON.stringify(finalBody),
|
|
147
|
+
headers: { 'Content-Type': 'application/json' },
|
|
148
|
+
credentials: 'include',
|
|
149
|
+
});
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
throw new Error(`save-data returned ${response.status} ${response.statusText}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
logger.error('[consentIQ] Failed to send consent data', e);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const processConsent = (consent) => {
|
|
160
|
+
if (consent == null) {
|
|
161
|
+
return 'unknown';
|
|
162
|
+
}
|
|
163
|
+
return consent ? '1' : '0';
|
|
164
|
+
};
|
|
165
|
+
// Shopify's boolean helpers (marketingAllowed(), etc.) always return a boolean
|
|
166
|
+
// and coerce an undecided visitor to a region default, so they cannot express
|
|
167
|
+
// "no decision". currentVisitorConsent() reports the truthful per-category state
|
|
168
|
+
// as 'yes' | 'no' | '' (empty = no decision); we read that and map empty to
|
|
169
|
+
// 'unknown'.
|
|
170
|
+
const hasConsentDecision = () => {
|
|
171
|
+
var _a, _b, _c;
|
|
172
|
+
const consent = (_c = (_b = (_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.customerPrivacy) === null || _b === void 0 ? void 0 : _b.currentVisitorConsent) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
173
|
+
return (!!consent && Object.values(consent).some((v) => v === 'yes' || v === 'no'));
|
|
174
|
+
};
|
|
175
|
+
const waitForCustomerPrivacy = () => {
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
if (hasConsentDecision()) {
|
|
178
|
+
resolve(true);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
let attempts = 0;
|
|
182
|
+
const interval = setInterval(() => {
|
|
183
|
+
attempts++;
|
|
184
|
+
if (hasConsentDecision()) {
|
|
185
|
+
clearInterval(interval);
|
|
186
|
+
resolve(true);
|
|
187
|
+
}
|
|
188
|
+
else if (attempts >= 20) {
|
|
189
|
+
clearInterval(interval);
|
|
190
|
+
resolve(false);
|
|
191
|
+
}
|
|
192
|
+
}, 250);
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
const mapVisitorConsent = (value) => {
|
|
196
|
+
if (value === 'yes') {
|
|
197
|
+
return '1';
|
|
198
|
+
}
|
|
199
|
+
if (value === 'no') {
|
|
200
|
+
return '0';
|
|
201
|
+
}
|
|
202
|
+
return 'unknown';
|
|
203
|
+
};
|
|
204
|
+
const getConsentData = () => {
|
|
205
|
+
var _a, _b, _c;
|
|
206
|
+
const cp = (_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.customerPrivacy;
|
|
207
|
+
const consent = (_b = cp === null || cp === void 0 ? void 0 : cp.currentVisitorConsent) === null || _b === void 0 ? void 0 : _b.call(cp);
|
|
208
|
+
return {
|
|
209
|
+
analyticsAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['analytics']),
|
|
210
|
+
marketingAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['marketing']),
|
|
211
|
+
saleOfDataAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['sale_of_data']),
|
|
212
|
+
preferencesAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['preferences']),
|
|
213
|
+
saleOfDataRegion: processConsent((_c = cp === null || cp === void 0 ? void 0 : cp.saleOfDataRegion) === null || _c === void 0 ? void 0 : _c.call(cp)),
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Collects consent from Shopify's Customer Privacy API and forwards it to the
|
|
218
|
+
* Blotout backend. On a new session it waits for the privacy API to become ready
|
|
219
|
+
* (via {@link waitForCustomerPrivacy}) and sends the current state; it then
|
|
220
|
+
* registers a `visitorConsentCollected` listener so later changes are forwarded
|
|
221
|
+
* in real time.
|
|
222
|
+
*
|
|
223
|
+
* @param ctx - {@link CollectAnalyticsContext} with the backend `baseUrl` and
|
|
224
|
+
* whether this is a new session.
|
|
225
|
+
*/
|
|
226
|
+
const collectShopifyConsentAnalytics = ({ baseUrl, isNewSession, }) => {
|
|
227
|
+
if (isNewSession) {
|
|
228
|
+
waitForCustomerPrivacy().then(() => {
|
|
229
|
+
sendConsentData(baseUrl, getConsentData());
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const onVisitorConsentCollected = (event) => {
|
|
233
|
+
var _a, _b, _c;
|
|
234
|
+
const ev = event;
|
|
235
|
+
const detail = ((_a = ev.detail) !== null && _a !== void 0 ? _a : {});
|
|
236
|
+
const cp = (_b = window.Shopify) === null || _b === void 0 ? void 0 : _b.customerPrivacy;
|
|
237
|
+
sendConsentData(baseUrl, {
|
|
238
|
+
analyticsAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.analyticsAllowed),
|
|
239
|
+
marketingAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.marketingAllowed),
|
|
240
|
+
saleOfDataAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.saleOfDataAllowed),
|
|
241
|
+
preferencesAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.preferencesAllowed),
|
|
242
|
+
saleOfDataRegion: processConsent((_c = cp === null || cp === void 0 ? void 0 : cp.saleOfDataRegion) === null || _c === void 0 ? void 0 : _c.call(cp)),
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
document.addEventListener('visitorConsentCollected', onVisitorConsentCollected);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
var _a, _b;
|
|
249
|
+
var _c;
|
|
250
|
+
/**
|
|
251
|
+
* Shopify consentIQ store adapter. Loaded as a separate bundle by the CDN
|
|
252
|
+
* `/load` handler only when the platform is Shopify, and self-registers its
|
|
253
|
+
* factory on the shared registry so the core `init` can pick it up.
|
|
254
|
+
*/
|
|
255
|
+
const createShopifyStoreAPI = () => ({
|
|
256
|
+
platform: 'shopify',
|
|
257
|
+
collectAnalytics: collectShopifyConsentAnalytics,
|
|
258
|
+
});
|
|
259
|
+
if (typeof window !== 'undefined') {
|
|
260
|
+
(_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {});
|
|
261
|
+
(_b = (_c = window[registryKey]).storeAPIFactory) !== null && _b !== void 0 ? _b : (_c.storeAPIFactory = createShopifyStoreAPI);
|
|
262
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Global registry key shared across the separately-loaded consentIQ bundles
|
|
6
|
+
* (core `index` and the per-store adapter). `Symbol.for` resolves to the same
|
|
7
|
+
* symbol in every bundle, so the store adapter can register its factory here
|
|
8
|
+
* and the core `init` can read it back.
|
|
9
|
+
*/
|
|
10
|
+
const registryKey = Symbol.for('consentiq');
|
|
11
|
+
|
|
12
|
+
const canLog = () => {
|
|
13
|
+
try {
|
|
14
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const prefix = `[EdgeTag]`;
|
|
21
|
+
const logger = {
|
|
22
|
+
log: (...args) => {
|
|
23
|
+
if (canLog()) {
|
|
24
|
+
console.log(prefix, ...args);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
error: (...args) => {
|
|
28
|
+
if (canLog()) {
|
|
29
|
+
console.error(prefix, ...args);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
info: (...args) => {
|
|
33
|
+
if (canLog()) {
|
|
34
|
+
console.info(prefix, ...args);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
trace: (...args) => {
|
|
38
|
+
if (canLog()) {
|
|
39
|
+
console.trace(prefix, ...args);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getOnetrustConsent = () => {
|
|
45
|
+
try {
|
|
46
|
+
const name = 'OptanonConsent';
|
|
47
|
+
const cookie = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
|
48
|
+
if (cookie && cookie.length > 2) {
|
|
49
|
+
const match = decodeURIComponent(cookie[2]).match(new RegExp('&groups=([^&]+)'));
|
|
50
|
+
if (match && match.length > 1) {
|
|
51
|
+
const data = match[1].split(',');
|
|
52
|
+
if (!data) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
analyticsSetting: data.some((g) => g === 'C0002:1' || g === '2:1')
|
|
57
|
+
? 'allowed'
|
|
58
|
+
: 'disallowed',
|
|
59
|
+
marketingSetting: data.some((g) => g === 'C0004:1' || g === '4:1' || g === 'C0005:1' || g === '5:1')
|
|
60
|
+
? 'allowed'
|
|
61
|
+
: 'disallowed',
|
|
62
|
+
preferencesSetting: data.some((g) => g === 'C0003:1' || g === '3:1')
|
|
63
|
+
? 'allowed'
|
|
64
|
+
: 'disallowed',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const getOsanoConsent = () => {
|
|
75
|
+
try {
|
|
76
|
+
const osano = window.Osano;
|
|
77
|
+
if (!(osano === null || osano === void 0 ? void 0 : osano.cm)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
analyticsSetting: osano.cm.analytics ? 'allowed' : 'disallowed',
|
|
82
|
+
marketingSetting: osano.cm.marketing ? 'allowed' : 'disallowed',
|
|
83
|
+
preferencesSetting: osano.cm.personalization ? 'allowed' : 'disallowed',
|
|
84
|
+
saleOfDataSetting: osano.cm.optOut ? 'disallowed' : 'allowed',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const getTermlyConsent = () => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
try {
|
|
94
|
+
const state = (_b = (_a = window.Termly) === null || _a === void 0 ? void 0 : _a.getConsentState) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
95
|
+
if (!state) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
analyticsSetting: state.analytics ? 'allowed' : 'disallowed',
|
|
100
|
+
marketingSetting: state.advertising || state.social_networking ? 'allowed' : 'disallowed',
|
|
101
|
+
preferencesSetting: state.performance ? 'allowed' : 'disallowed',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Reads the current decision from a supported external CMP (OneTrust, Osano, or
|
|
110
|
+
* Termly), if one is present. Applies regardless of host platform and takes
|
|
111
|
+
* precedence over the platform-derived `*Setting` fields in {@link sendConsentData}.
|
|
112
|
+
*/
|
|
113
|
+
const getConsentSettings = () => {
|
|
114
|
+
const oneTrustConsent = getOnetrustConsent();
|
|
115
|
+
if (oneTrustConsent) {
|
|
116
|
+
return oneTrustConsent;
|
|
117
|
+
}
|
|
118
|
+
const osanoConsent = getOsanoConsent();
|
|
119
|
+
if (osanoConsent) {
|
|
120
|
+
return osanoConsent;
|
|
121
|
+
}
|
|
122
|
+
const termlyConsent = getTermlyConsent();
|
|
123
|
+
if (termlyConsent) {
|
|
124
|
+
return termlyConsent;
|
|
125
|
+
}
|
|
126
|
+
return {};
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* POSTs a consent snapshot to the Blotout backend `consentIQ/save-data`
|
|
130
|
+
* endpoint. The platform collector builds `body`; an external CMP still wins via
|
|
131
|
+
* {@link getConsentSettings}. Errors are logged and swallowed so consent capture
|
|
132
|
+
* never breaks the host page.
|
|
133
|
+
*/
|
|
134
|
+
const sendConsentData = async (baseUrl, body) => {
|
|
135
|
+
const finalBody = {
|
|
136
|
+
...body,
|
|
137
|
+
analyticsSetting: 'unknown',
|
|
138
|
+
marketingSetting: 'unknown',
|
|
139
|
+
preferencesSetting: 'unknown',
|
|
140
|
+
saleOfDataSetting: 'unknown',
|
|
141
|
+
// An external CMP (OneTrust/Osano/Termly) wins over the 'unknown' defaults.
|
|
142
|
+
...getConsentSettings(),
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const response = await fetch(`${baseUrl}/providers/consentIQ/save-data`, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
body: JSON.stringify(finalBody),
|
|
148
|
+
headers: { 'Content-Type': 'application/json' },
|
|
149
|
+
credentials: 'include',
|
|
150
|
+
});
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new Error(`save-data returned ${response.status} ${response.statusText}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
logger.error('[consentIQ] Failed to send consent data', e);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const processConsent = (consent) => {
|
|
161
|
+
if (consent == null) {
|
|
162
|
+
return 'unknown';
|
|
163
|
+
}
|
|
164
|
+
return consent ? '1' : '0';
|
|
165
|
+
};
|
|
166
|
+
// Shopify's boolean helpers (marketingAllowed(), etc.) always return a boolean
|
|
167
|
+
// and coerce an undecided visitor to a region default, so they cannot express
|
|
168
|
+
// "no decision". currentVisitorConsent() reports the truthful per-category state
|
|
169
|
+
// as 'yes' | 'no' | '' (empty = no decision); we read that and map empty to
|
|
170
|
+
// 'unknown'.
|
|
171
|
+
const hasConsentDecision = () => {
|
|
172
|
+
var _a, _b, _c;
|
|
173
|
+
const consent = (_c = (_b = (_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.customerPrivacy) === null || _b === void 0 ? void 0 : _b.currentVisitorConsent) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
174
|
+
return (!!consent && Object.values(consent).some((v) => v === 'yes' || v === 'no'));
|
|
175
|
+
};
|
|
176
|
+
const waitForCustomerPrivacy = () => {
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
if (hasConsentDecision()) {
|
|
179
|
+
resolve(true);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let attempts = 0;
|
|
183
|
+
const interval = setInterval(() => {
|
|
184
|
+
attempts++;
|
|
185
|
+
if (hasConsentDecision()) {
|
|
186
|
+
clearInterval(interval);
|
|
187
|
+
resolve(true);
|
|
188
|
+
}
|
|
189
|
+
else if (attempts >= 20) {
|
|
190
|
+
clearInterval(interval);
|
|
191
|
+
resolve(false);
|
|
192
|
+
}
|
|
193
|
+
}, 250);
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const mapVisitorConsent = (value) => {
|
|
197
|
+
if (value === 'yes') {
|
|
198
|
+
return '1';
|
|
199
|
+
}
|
|
200
|
+
if (value === 'no') {
|
|
201
|
+
return '0';
|
|
202
|
+
}
|
|
203
|
+
return 'unknown';
|
|
204
|
+
};
|
|
205
|
+
const getConsentData = () => {
|
|
206
|
+
var _a, _b, _c;
|
|
207
|
+
const cp = (_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.customerPrivacy;
|
|
208
|
+
const consent = (_b = cp === null || cp === void 0 ? void 0 : cp.currentVisitorConsent) === null || _b === void 0 ? void 0 : _b.call(cp);
|
|
209
|
+
return {
|
|
210
|
+
analyticsAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['analytics']),
|
|
211
|
+
marketingAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['marketing']),
|
|
212
|
+
saleOfDataAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['sale_of_data']),
|
|
213
|
+
preferencesAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['preferences']),
|
|
214
|
+
saleOfDataRegion: processConsent((_c = cp === null || cp === void 0 ? void 0 : cp.saleOfDataRegion) === null || _c === void 0 ? void 0 : _c.call(cp)),
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Collects consent from Shopify's Customer Privacy API and forwards it to the
|
|
219
|
+
* Blotout backend. On a new session it waits for the privacy API to become ready
|
|
220
|
+
* (via {@link waitForCustomerPrivacy}) and sends the current state; it then
|
|
221
|
+
* registers a `visitorConsentCollected` listener so later changes are forwarded
|
|
222
|
+
* in real time.
|
|
223
|
+
*
|
|
224
|
+
* @param ctx - {@link CollectAnalyticsContext} with the backend `baseUrl` and
|
|
225
|
+
* whether this is a new session.
|
|
226
|
+
*/
|
|
227
|
+
const collectShopifyConsentAnalytics = ({ baseUrl, isNewSession, }) => {
|
|
228
|
+
if (isNewSession) {
|
|
229
|
+
waitForCustomerPrivacy().then(() => {
|
|
230
|
+
sendConsentData(baseUrl, getConsentData());
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const onVisitorConsentCollected = (event) => {
|
|
234
|
+
var _a, _b, _c;
|
|
235
|
+
const ev = event;
|
|
236
|
+
const detail = ((_a = ev.detail) !== null && _a !== void 0 ? _a : {});
|
|
237
|
+
const cp = (_b = window.Shopify) === null || _b === void 0 ? void 0 : _b.customerPrivacy;
|
|
238
|
+
sendConsentData(baseUrl, {
|
|
239
|
+
analyticsAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.analyticsAllowed),
|
|
240
|
+
marketingAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.marketingAllowed),
|
|
241
|
+
saleOfDataAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.saleOfDataAllowed),
|
|
242
|
+
preferencesAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.preferencesAllowed),
|
|
243
|
+
saleOfDataRegion: processConsent((_c = cp === null || cp === void 0 ? void 0 : cp.saleOfDataRegion) === null || _c === void 0 ? void 0 : _c.call(cp)),
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
document.addEventListener('visitorConsentCollected', onVisitorConsentCollected);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
var _a, _b;
|
|
250
|
+
var _c;
|
|
251
|
+
/**
|
|
252
|
+
* Shopify consentIQ store adapter. Loaded as a separate bundle by the CDN
|
|
253
|
+
* `/load` handler only when the platform is Shopify, and self-registers its
|
|
254
|
+
* factory on the shared registry so the core `init` can pick it up.
|
|
255
|
+
*/
|
|
256
|
+
const createShopifyStoreAPI = () => ({
|
|
257
|
+
platform: 'shopify',
|
|
258
|
+
collectAnalytics: collectShopifyConsentAnalytics,
|
|
259
|
+
});
|
|
260
|
+
if (typeof window !== 'undefined') {
|
|
261
|
+
(_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {});
|
|
262
|
+
(_b = (_c = window[registryKey]).storeAPIFactory) !== null && _b !== void 0 ? _b : (_c.storeAPIFactory = createShopifyStoreAPI);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
})();
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global registry key shared across the separately-loaded consentIQ bundles
|
|
3
|
+
* (core `index` and the per-store adapter). `Symbol.for` resolves to the same
|
|
4
|
+
* symbol in every bundle, so the store adapter can register its factory here
|
|
5
|
+
* and the core `init` can read it back.
|
|
6
|
+
*/
|
|
7
|
+
const registryKey = Symbol.for('consentiq');
|
|
8
|
+
|
|
9
|
+
const canLog = () => {
|
|
10
|
+
try {
|
|
11
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const prefix = `[EdgeTag]`;
|
|
18
|
+
const logger = {
|
|
19
|
+
log: (...args) => {
|
|
20
|
+
if (canLog()) {
|
|
21
|
+
console.log(prefix, ...args);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
error: (...args) => {
|
|
25
|
+
if (canLog()) {
|
|
26
|
+
console.error(prefix, ...args);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
info: (...args) => {
|
|
30
|
+
if (canLog()) {
|
|
31
|
+
console.info(prefix, ...args);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
trace: (...args) => {
|
|
35
|
+
if (canLog()) {
|
|
36
|
+
console.trace(prefix, ...args);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getOnetrustConsent = () => {
|
|
42
|
+
try {
|
|
43
|
+
const name = 'OptanonConsent';
|
|
44
|
+
const cookie = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
|
45
|
+
if (cookie && cookie.length > 2) {
|
|
46
|
+
const match = decodeURIComponent(cookie[2]).match(new RegExp('&groups=([^&]+)'));
|
|
47
|
+
if (match && match.length > 1) {
|
|
48
|
+
const data = match[1].split(',');
|
|
49
|
+
if (!data) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
analyticsSetting: data.some((g) => g === 'C0002:1' || g === '2:1')
|
|
54
|
+
? 'allowed'
|
|
55
|
+
: 'disallowed',
|
|
56
|
+
marketingSetting: data.some((g) => g === 'C0004:1' || g === '4:1' || g === 'C0005:1' || g === '5:1')
|
|
57
|
+
? 'allowed'
|
|
58
|
+
: 'disallowed',
|
|
59
|
+
preferencesSetting: data.some((g) => g === 'C0003:1' || g === '3:1')
|
|
60
|
+
? 'allowed'
|
|
61
|
+
: 'disallowed',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const getOsanoConsent = () => {
|
|
72
|
+
try {
|
|
73
|
+
const osano = window.Osano;
|
|
74
|
+
if (!(osano === null || osano === void 0 ? void 0 : osano.cm)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
analyticsSetting: osano.cm.analytics ? 'allowed' : 'disallowed',
|
|
79
|
+
marketingSetting: osano.cm.marketing ? 'allowed' : 'disallowed',
|
|
80
|
+
preferencesSetting: osano.cm.personalization ? 'allowed' : 'disallowed',
|
|
81
|
+
saleOfDataSetting: osano.cm.optOut ? 'disallowed' : 'allowed',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const getTermlyConsent = () => {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
try {
|
|
91
|
+
const state = (_b = (_a = window.Termly) === null || _a === void 0 ? void 0 : _a.getConsentState) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
92
|
+
if (!state) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
analyticsSetting: state.analytics ? 'allowed' : 'disallowed',
|
|
97
|
+
marketingSetting: state.advertising || state.social_networking ? 'allowed' : 'disallowed',
|
|
98
|
+
preferencesSetting: state.performance ? 'allowed' : 'disallowed',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Reads the current decision from a supported external CMP (OneTrust, Osano, or
|
|
107
|
+
* Termly), if one is present. Applies regardless of host platform and takes
|
|
108
|
+
* precedence over the platform-derived `*Setting` fields in {@link sendConsentData}.
|
|
109
|
+
*/
|
|
110
|
+
const getConsentSettings = () => {
|
|
111
|
+
const oneTrustConsent = getOnetrustConsent();
|
|
112
|
+
if (oneTrustConsent) {
|
|
113
|
+
return oneTrustConsent;
|
|
114
|
+
}
|
|
115
|
+
const osanoConsent = getOsanoConsent();
|
|
116
|
+
if (osanoConsent) {
|
|
117
|
+
return osanoConsent;
|
|
118
|
+
}
|
|
119
|
+
const termlyConsent = getTermlyConsent();
|
|
120
|
+
if (termlyConsent) {
|
|
121
|
+
return termlyConsent;
|
|
122
|
+
}
|
|
123
|
+
return {};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* POSTs a consent snapshot to the Blotout backend `consentIQ/save-data`
|
|
127
|
+
* endpoint. The platform collector builds `body`; an external CMP still wins via
|
|
128
|
+
* {@link getConsentSettings}. Errors are logged and swallowed so consent capture
|
|
129
|
+
* never breaks the host page.
|
|
130
|
+
*/
|
|
131
|
+
const sendConsentData = async (baseUrl, body) => {
|
|
132
|
+
const finalBody = {
|
|
133
|
+
...body,
|
|
134
|
+
analyticsSetting: 'unknown',
|
|
135
|
+
marketingSetting: 'unknown',
|
|
136
|
+
preferencesSetting: 'unknown',
|
|
137
|
+
saleOfDataSetting: 'unknown',
|
|
138
|
+
// An external CMP (OneTrust/Osano/Termly) wins over the 'unknown' defaults.
|
|
139
|
+
...getConsentSettings(),
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetch(`${baseUrl}/providers/consentIQ/save-data`, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
body: JSON.stringify(finalBody),
|
|
145
|
+
headers: { 'Content-Type': 'application/json' },
|
|
146
|
+
credentials: 'include',
|
|
147
|
+
});
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`save-data returned ${response.status} ${response.statusText}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
logger.error('[consentIQ] Failed to send consent data', e);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const processConsent = (consent) => {
|
|
158
|
+
if (consent == null) {
|
|
159
|
+
return 'unknown';
|
|
160
|
+
}
|
|
161
|
+
return consent ? '1' : '0';
|
|
162
|
+
};
|
|
163
|
+
// Shopify's boolean helpers (marketingAllowed(), etc.) always return a boolean
|
|
164
|
+
// and coerce an undecided visitor to a region default, so they cannot express
|
|
165
|
+
// "no decision". currentVisitorConsent() reports the truthful per-category state
|
|
166
|
+
// as 'yes' | 'no' | '' (empty = no decision); we read that and map empty to
|
|
167
|
+
// 'unknown'.
|
|
168
|
+
const hasConsentDecision = () => {
|
|
169
|
+
var _a, _b, _c;
|
|
170
|
+
const consent = (_c = (_b = (_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.customerPrivacy) === null || _b === void 0 ? void 0 : _b.currentVisitorConsent) === null || _c === void 0 ? void 0 : _c.call(_b);
|
|
171
|
+
return (!!consent && Object.values(consent).some((v) => v === 'yes' || v === 'no'));
|
|
172
|
+
};
|
|
173
|
+
const waitForCustomerPrivacy = () => {
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
if (hasConsentDecision()) {
|
|
176
|
+
resolve(true);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
let attempts = 0;
|
|
180
|
+
const interval = setInterval(() => {
|
|
181
|
+
attempts++;
|
|
182
|
+
if (hasConsentDecision()) {
|
|
183
|
+
clearInterval(interval);
|
|
184
|
+
resolve(true);
|
|
185
|
+
}
|
|
186
|
+
else if (attempts >= 20) {
|
|
187
|
+
clearInterval(interval);
|
|
188
|
+
resolve(false);
|
|
189
|
+
}
|
|
190
|
+
}, 250);
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
const mapVisitorConsent = (value) => {
|
|
194
|
+
if (value === 'yes') {
|
|
195
|
+
return '1';
|
|
196
|
+
}
|
|
197
|
+
if (value === 'no') {
|
|
198
|
+
return '0';
|
|
199
|
+
}
|
|
200
|
+
return 'unknown';
|
|
201
|
+
};
|
|
202
|
+
const getConsentData = () => {
|
|
203
|
+
var _a, _b, _c;
|
|
204
|
+
const cp = (_a = window.Shopify) === null || _a === void 0 ? void 0 : _a.customerPrivacy;
|
|
205
|
+
const consent = (_b = cp === null || cp === void 0 ? void 0 : cp.currentVisitorConsent) === null || _b === void 0 ? void 0 : _b.call(cp);
|
|
206
|
+
return {
|
|
207
|
+
analyticsAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['analytics']),
|
|
208
|
+
marketingAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['marketing']),
|
|
209
|
+
saleOfDataAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['sale_of_data']),
|
|
210
|
+
preferencesAllowed: mapVisitorConsent(consent === null || consent === void 0 ? void 0 : consent['preferences']),
|
|
211
|
+
saleOfDataRegion: processConsent((_c = cp === null || cp === void 0 ? void 0 : cp.saleOfDataRegion) === null || _c === void 0 ? void 0 : _c.call(cp)),
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Collects consent from Shopify's Customer Privacy API and forwards it to the
|
|
216
|
+
* Blotout backend. On a new session it waits for the privacy API to become ready
|
|
217
|
+
* (via {@link waitForCustomerPrivacy}) and sends the current state; it then
|
|
218
|
+
* registers a `visitorConsentCollected` listener so later changes are forwarded
|
|
219
|
+
* in real time.
|
|
220
|
+
*
|
|
221
|
+
* @param ctx - {@link CollectAnalyticsContext} with the backend `baseUrl` and
|
|
222
|
+
* whether this is a new session.
|
|
223
|
+
*/
|
|
224
|
+
const collectShopifyConsentAnalytics = ({ baseUrl, isNewSession, }) => {
|
|
225
|
+
if (isNewSession) {
|
|
226
|
+
waitForCustomerPrivacy().then(() => {
|
|
227
|
+
sendConsentData(baseUrl, getConsentData());
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const onVisitorConsentCollected = (event) => {
|
|
231
|
+
var _a, _b, _c;
|
|
232
|
+
const ev = event;
|
|
233
|
+
const detail = ((_a = ev.detail) !== null && _a !== void 0 ? _a : {});
|
|
234
|
+
const cp = (_b = window.Shopify) === null || _b === void 0 ? void 0 : _b.customerPrivacy;
|
|
235
|
+
sendConsentData(baseUrl, {
|
|
236
|
+
analyticsAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.analyticsAllowed),
|
|
237
|
+
marketingAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.marketingAllowed),
|
|
238
|
+
saleOfDataAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.saleOfDataAllowed),
|
|
239
|
+
preferencesAllowed: processConsent(detail === null || detail === void 0 ? void 0 : detail.preferencesAllowed),
|
|
240
|
+
saleOfDataRegion: processConsent((_c = cp === null || cp === void 0 ? void 0 : cp.saleOfDataRegion) === null || _c === void 0 ? void 0 : _c.call(cp)),
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
document.addEventListener('visitorConsentCollected', onVisitorConsentCollected);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
var _a, _b;
|
|
247
|
+
var _c;
|
|
248
|
+
/**
|
|
249
|
+
* Shopify consentIQ store adapter. Loaded as a separate bundle by the CDN
|
|
250
|
+
* `/load` handler only when the platform is Shopify, and self-registers its
|
|
251
|
+
* factory on the shared registry so the core `init` can pick it up.
|
|
252
|
+
*/
|
|
253
|
+
const createShopifyStoreAPI = () => ({
|
|
254
|
+
platform: 'shopify',
|
|
255
|
+
collectAnalytics: collectShopifyConsentAnalytics,
|
|
256
|
+
});
|
|
257
|
+
if (typeof window !== 'undefined') {
|
|
258
|
+
(_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {});
|
|
259
|
+
(_b = (_c = window[registryKey]).storeAPIFactory) !== null && _b !== void 0 ? _b : (_c.storeAPIFactory = createShopifyStoreAPI);
|
|
260
|
+
}
|