@dpgradio/creative 5.0.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/.babelrc +4 -0
- package/.eslintrc.cjs +12 -0
- package/README.md +232 -0
- package/examples/updateConfigSchema.js +27 -0
- package/examples/useApi.js +16 -0
- package/examples/useConfig.js +13 -0
- package/jest.config.cjs +195 -0
- package/package.json +31 -0
- package/src/__test__/utils/openLink.test.js +44 -0
- package/src/api/api.js +60 -0
- package/src/api/endpoints/Channels.js +7 -0
- package/src/api/endpoints/Config.js +15 -0
- package/src/api/endpoints/Endpoint.js +37 -0
- package/src/api/endpoints/Members.js +7 -0
- package/src/api/request.js +100 -0
- package/src/app/hybrid.js +107 -0
- package/src/config/config.js +60 -0
- package/src/index.js +15 -0
- package/src/privacy/dataLayer.js +73 -0
- package/src/privacy/privacy.js +100 -0
- package/src/share/ImageGeneratorProperties.js +28 -0
- package/src/share/ShareResult.js +33 -0
- package/src/share/Shareable.js +55 -0
- package/src/share.js +5 -0
- package/src/socket/SocketConnection.js +169 -0
- package/src/socket/SocketStation.js +16 -0
- package/src/socket/SocketSubscription.js +47 -0
- package/src/socket/socket.js +36 -0
- package/src/utils/cdnUrl.js +12 -0
- package/src/utils/loadScript.js +28 -0
- package/src/utils/openLink.js +14 -0
- package/src/utils/tap.js +10 -0
- package/styles/colors/joe.css +19 -0
- package/styles/colors/qmusic.css +17 -0
- package/styles/fonts/joe.css +34 -0
- package/styles/fonts/qmusic.css +48 -0
- package/styles/fonts/willy.css +2 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// eslint-disable-next-line no-unused-vars -- used in JSDoc
|
|
2
|
+
import { Api } from '../api.js'
|
|
3
|
+
// eslint-disable-next-line no-unused-vars -- used in JSDoc
|
|
4
|
+
import Request from '../request.js'
|
|
5
|
+
|
|
6
|
+
export default class Endpoint {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Api} api
|
|
9
|
+
*/
|
|
10
|
+
constructor(api) {
|
|
11
|
+
this.api = api
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Callback for requesting data.
|
|
16
|
+
*
|
|
17
|
+
* @callback requestCallback
|
|
18
|
+
* @param {Request} request
|
|
19
|
+
* @returns {object}
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {requestCallback} callback
|
|
24
|
+
* @returns {object}
|
|
25
|
+
*/
|
|
26
|
+
async requestData(callback, key = 'data') {
|
|
27
|
+
const response = await callback(this.api.request())
|
|
28
|
+
|
|
29
|
+
if (response === null) {
|
|
30
|
+
throw new Error(`Endpoint returned invalid JSON.`)
|
|
31
|
+
}
|
|
32
|
+
if (response[key] === undefined) {
|
|
33
|
+
throw new Error(`Key '${key}' not found in response: ${JSON.stringify(response)}`)
|
|
34
|
+
}
|
|
35
|
+
return response[key]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import tap from '../utils/tap.js'
|
|
2
|
+
|
|
3
|
+
export default class Request {
|
|
4
|
+
constructor(baseUrl, version) {
|
|
5
|
+
this.baseUrl = baseUrl
|
|
6
|
+
this.version = version
|
|
7
|
+
this.queryParameters = {}
|
|
8
|
+
this.data = null
|
|
9
|
+
this.headers = {}
|
|
10
|
+
this.fetchOptions = {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
withQueryParameters(queryParameters) {
|
|
14
|
+
this.queryParameters = { ...this.queryParameters, ...queryParameters }
|
|
15
|
+
|
|
16
|
+
return this
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
withData(data) {
|
|
20
|
+
this.data = { ...(this.data || {}), ...data }
|
|
21
|
+
|
|
22
|
+
return this
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
withFetchOptions(fetchOptions) {
|
|
26
|
+
this.fetchOptions = { ...this.fetchOptions, ...fetchOptions }
|
|
27
|
+
|
|
28
|
+
return this
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
withHeader(key, value) {
|
|
32
|
+
return this.withHeaders({ [key]: value })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
withHeaders(headers) {
|
|
36
|
+
this.headers = { ...this.headers, ...headers }
|
|
37
|
+
|
|
38
|
+
return this
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async get(endpoint, parameters = {}) {
|
|
42
|
+
return await this.withQueryParameters(parameters).fetchJson(endpoint)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async post(endpoint, data = {}, parameters = {}) {
|
|
46
|
+
return await this.withData(data).withQueryParameters(parameters).fetchJson(endpoint, 'POST')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async put(endpoint, data = {}, parameters = {}) {
|
|
50
|
+
return await this.withData(data).withQueryParameters(parameters).fetchJson(endpoint, 'PUT')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async patch(endpoint, data = {}, parameters = {}) {
|
|
54
|
+
return await this.withData(data).withQueryParameters(parameters).fetchJson(endpoint, 'PATCH')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async delete(endpoint, data = {}, parameters = {}) {
|
|
58
|
+
return await this.withData(data).withQueryParameters(parameters).fetchJson(endpoint, 'DELETE')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async fetchJson(endpoint, method = 'GET') {
|
|
62
|
+
const url = this.constructUrl(endpoint)
|
|
63
|
+
|
|
64
|
+
this.withHeader('Accept', 'application/json')
|
|
65
|
+
|
|
66
|
+
if (this.data !== null) {
|
|
67
|
+
this.withHeader('Content-Type', 'application/json')
|
|
68
|
+
this.withFetchOptions({ body: JSON.stringify(this.data) })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const response = await fetch(url, {
|
|
72
|
+
method,
|
|
73
|
+
headers: this.headers,
|
|
74
|
+
...this.fetchOptions,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`API request (${method} ${url}) failed: [${response.status}] ${response.statusText}\n\n` +
|
|
80
|
+
`RESPONSE\n--------\n${await response.text()}`
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
return await response.json()
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
constructUrl(endpoint) {
|
|
92
|
+
const baseUrlWithProtocol = this.baseUrl.startsWith('http') ? this.baseUrl : `https://${this.baseUrl}`
|
|
93
|
+
|
|
94
|
+
const endpointWithoutSlash = endpoint.replace(/^\/+/, '')
|
|
95
|
+
|
|
96
|
+
return tap(new URL(`${baseUrlWithProtocol}/${this.version}/${endpointWithoutSlash}`), (url) => {
|
|
97
|
+
url.search = new URLSearchParams(this.queryParameters).toString()
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import jwtDecode from 'jwt-decode'
|
|
2
|
+
|
|
3
|
+
// Modern versions of the radio apps set up specific User-Agents:
|
|
4
|
+
// Android User-Agent: Qmusic/7.6.1 (nl.qmusic.app; build:21726; Android 11; Sdk:30; Manufacturer:OnePlus; Model: IN2013) OkHttp/ 4.9.1
|
|
5
|
+
// iOS User-Agent: Joe/265 (be.vmma.joe.app; build:1; iOS 14.8.1) Alamofire/265
|
|
6
|
+
const androidRegexp =
|
|
7
|
+
/^(?<brand>.+)\/(?<storeVersion>[0-9.]+) \((?<buildName>.+); build:(?<buildVersion>\d+); (?<platform>Android) (?<osVersion>\d+); Sdk:(?<sdkVersion>\d+); Manufacturer:(?<manufacturer>.+); Model: (?<model>.+)\)/
|
|
8
|
+
const iOSRegexp =
|
|
9
|
+
/^(?<brand>.+)\/(?<buildVersion>[0-9.]+) \((?<buildName>.+); build:(?<internalBuildVersion>\d+); (?<platform>iOS) (?<osVersion>\d+\.\d+\.\d+)\)/
|
|
10
|
+
|
|
11
|
+
class Hybrid {
|
|
12
|
+
constructor() {
|
|
13
|
+
// Hook this on window so it can be required in multiple packs
|
|
14
|
+
window._hybridEventSubscriptions = window._hybridEventSubscriptions || {}
|
|
15
|
+
|
|
16
|
+
this.appInfo = this.detectApp(window.appVersion || navigator.userAgent)
|
|
17
|
+
|
|
18
|
+
this._cachedRadioTokenOnLoad = undefined
|
|
19
|
+
this.on('appLoad', (context) => {
|
|
20
|
+
this._cachedRadioTokenOnLoad = context?.radioToken ?? null
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
isNativeApp() {
|
|
25
|
+
return this.appInfo.platform !== 'browser'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isVersion({ android, ios }) {
|
|
29
|
+
const { platform, buildName, buildVersion } = this.appInfo
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
(platform === 'Android' && android[buildName] && android[buildName] <= parseInt(buildVersion, 10)) ||
|
|
33
|
+
(platform === 'iOS' && ios <= parseInt(buildVersion, 10))
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
call(method, options = {}) {
|
|
38
|
+
if (window.webkit && window.webkit.messageHandlers) {
|
|
39
|
+
const handler = window.webkit.messageHandlers[method]
|
|
40
|
+
handler && handler.postMessage(options)
|
|
41
|
+
} else if (window.Android) {
|
|
42
|
+
// Note: It's important to either directly call window.Android[method], or bind it with
|
|
43
|
+
// const ref = window.android[method].bind(Android)
|
|
44
|
+
// ref()
|
|
45
|
+
// ref: https://bugs.chromium.org/p/chromium/issues/detail?id=514628
|
|
46
|
+
window.Android[method](JSON.stringify(options))
|
|
47
|
+
} else {
|
|
48
|
+
console.error('Call on unsupported device', method, options)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
on(method, fn, once = false) {
|
|
53
|
+
if (!window._hybridEventSubscriptions[method]) {
|
|
54
|
+
window._hybridEventSubscriptions[method] = []
|
|
55
|
+
}
|
|
56
|
+
window._hybridEventSubscriptions[method].push({ fn, once })
|
|
57
|
+
this.ensureTriggerExists(method)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
one(method, fn) {
|
|
61
|
+
this.one(method, fn, true)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
appLoaded() {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
if (this._cachedRadioTokenOnLoad !== undefined) {
|
|
67
|
+
return resolve(this._cachedRadioTokenOnLoad)
|
|
68
|
+
}
|
|
69
|
+
this.on('appLoad', (context) => {
|
|
70
|
+
context?.radioToken ? resolve(context.radioToken) : reject('No radio token provided')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ensureTriggerExists(method) {
|
|
76
|
+
if (window[method]) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
window[method] = (args) => {
|
|
81
|
+
const callbacks = window._hybridEventSubscriptions[method] || []
|
|
82
|
+
for (const callback of callbacks) {
|
|
83
|
+
callback.fn(args)
|
|
84
|
+
if (callback.once) {
|
|
85
|
+
callback.delete = true // Mark for deletion
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
window._hybridEventSubscriptions[method] = callbacks.filter((callback) => !callback.delete) // Delete all marked
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
detectApp(userAgent) {
|
|
93
|
+
for (const regexp of [androidRegexp, iOSRegexp]) {
|
|
94
|
+
const match = userAgent.match(regexp)
|
|
95
|
+
if (match) {
|
|
96
|
+
return match.groups
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { platform: 'browser' }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
decodeRadioToken(token) {
|
|
103
|
+
return jwtDecode(token)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default new Hybrid()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import api from '../api/api.js'
|
|
2
|
+
|
|
3
|
+
const dotSyntaxAccess = (object, path) => path.split('.').reduce((o, p) => o[p], object)
|
|
4
|
+
|
|
5
|
+
class Configuration {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.stationId = null
|
|
8
|
+
this.appId = null
|
|
9
|
+
this.rawConfig = null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
setStation(stationId) {
|
|
13
|
+
this.stationId = stationId
|
|
14
|
+
|
|
15
|
+
return this
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async retrieveConfig(appId, ...stationIds) {
|
|
19
|
+
this.appId = appId
|
|
20
|
+
this.stationId = stationIds[0]
|
|
21
|
+
|
|
22
|
+
this.rawConfig = await api.global().config.app(appId, stationIds)
|
|
23
|
+
|
|
24
|
+
return this
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
config(property = null) {
|
|
28
|
+
if (!this.rawConfig) {
|
|
29
|
+
throw new Error('No config retrieved. First use [config.retrieveConfig] to retrieve the configuration.')
|
|
30
|
+
}
|
|
31
|
+
if (!this.stationId) {
|
|
32
|
+
throw new Error('No station set. First use [config.setStation] to set the station.')
|
|
33
|
+
}
|
|
34
|
+
if (!this.appId) {
|
|
35
|
+
throw new Error('No app identifier set. First use [config.retrieveConfig] to retrieve the configuration.')
|
|
36
|
+
}
|
|
37
|
+
if (!this.rawConfig[this.stationId]?.[this.appId]) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`No configuration found for station [${this.stationId}] and app [${this.appId}]. Make sure the app is enabled for this station.`
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const stationConfig = {
|
|
44
|
+
...this.rawConfig[this.stationId],
|
|
45
|
+
app: this.rawConfig[this.stationId][this.appId],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return property ? dotSyntaxAccess(stationConfig, property) : stationConfig
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async replaceSchema(appId, schema) {
|
|
52
|
+
await api.global().config.updateSchema(appId, schema)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const configuration = new Configuration()
|
|
57
|
+
|
|
58
|
+
export default configuration
|
|
59
|
+
|
|
60
|
+
export const config = configuration.config.bind(configuration)
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { default as hybrid } from './app/hybrid'
|
|
2
|
+
|
|
3
|
+
export { default as privacy } from './privacy/privacy'
|
|
4
|
+
export { default as dataLayer } from './privacy/dataLayer'
|
|
5
|
+
|
|
6
|
+
export { default as socket } from './socket/socket'
|
|
7
|
+
|
|
8
|
+
export { default as configuration, config } from './config/config'
|
|
9
|
+
|
|
10
|
+
export { default as api } from './api/api'
|
|
11
|
+
|
|
12
|
+
export { default as loadScript } from './utils/loadScript'
|
|
13
|
+
export { default as openLink } from './utils/openLink'
|
|
14
|
+
export { default as tap } from './utils/tap'
|
|
15
|
+
export { cdnImageUrl, cdnUrl } from './utils/cdnUrl'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import hybrid from '../app/hybrid'
|
|
2
|
+
import { config } from '../config/config'
|
|
3
|
+
import loadScript from '../utils/loadScript'
|
|
4
|
+
|
|
5
|
+
class DataLayer {
|
|
6
|
+
constructor() {
|
|
7
|
+
window.dataLayer = window.dataLayer || []
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
initialize(gtmId = 'GTM-TW99VZN') {
|
|
11
|
+
loadScript(`https://www.googletagmanager.com/gtm.js?id=${gtmId}`)
|
|
12
|
+
|
|
13
|
+
this.pushGtmStart()
|
|
14
|
+
this.pushUserWhenAuthenticated()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
push(data) {
|
|
18
|
+
window.dataLayer.push(data)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pushGtmStart() {
|
|
22
|
+
this.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pushEvent(event, data) {
|
|
26
|
+
this.push({ event, ...data })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pushUserWhenAuthenticated() {
|
|
30
|
+
hybrid.on('authenticated', ({ radioToken }) => {
|
|
31
|
+
if (radioToken) {
|
|
32
|
+
this.pushEvent('account_id', this._formatUserInformation(radioToken))
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async pushVirtualPageView(brand = config('gtm_brand')) {
|
|
38
|
+
const user = await this._getUserInformationOnLoad()
|
|
39
|
+
|
|
40
|
+
this.pushEvent('VirtualPageView', {
|
|
41
|
+
virtualPageURL: {
|
|
42
|
+
...window.location,
|
|
43
|
+
platform: 'browser',
|
|
44
|
+
brand,
|
|
45
|
+
},
|
|
46
|
+
...user,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async _getUserInformationOnLoad() {
|
|
51
|
+
if (!hybrid.isNativeApp()) {
|
|
52
|
+
return {}
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const radioToken = await hybrid.appLoaded()
|
|
56
|
+
return this._formatUserInformation(radioToken)
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('User information could not be loaded:', error)
|
|
59
|
+
return {}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_formatUserInformation(radioToken) {
|
|
64
|
+
return {
|
|
65
|
+
user: {
|
|
66
|
+
account_id: hybrid.decodeRadioToken(radioToken).uid,
|
|
67
|
+
loggedIn: true,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default new DataLayer()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { config } from '../config/config'
|
|
2
|
+
import loadScript from '../utils/loadScript'
|
|
3
|
+
|
|
4
|
+
class Privacy {
|
|
5
|
+
constructor() {
|
|
6
|
+
window._privacy = window._privacy || []
|
|
7
|
+
|
|
8
|
+
this.consent = undefined
|
|
9
|
+
this.consentSubscribers = []
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load the CMP script asynchronously.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} privacyManagerId e.g. '148844'
|
|
16
|
+
* @param {string} websiteUrl e.g. 'https://qmusic.nl'
|
|
17
|
+
* @param {string} cmpCname e.g. 'https://cmp.qmusic.nl'
|
|
18
|
+
*/
|
|
19
|
+
initialize(
|
|
20
|
+
privacyManagerId = config('privacy_manager_id'),
|
|
21
|
+
websiteUrl = config('website_url'),
|
|
22
|
+
cmpCname = config('cmp_cname')
|
|
23
|
+
) {
|
|
24
|
+
window.cmpProperties = {
|
|
25
|
+
privacyManagerId,
|
|
26
|
+
cmpCname,
|
|
27
|
+
baseUrl: websiteUrl,
|
|
28
|
+
language: 'nl',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
loadScript('https://myprivacy-static.dpgmedia.net/consent.js')
|
|
32
|
+
|
|
33
|
+
this.requestConsentInformation()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Request consent information from the CMP.
|
|
38
|
+
*
|
|
39
|
+
* This method should be called immediately after loading the page.
|
|
40
|
+
* The timeout is set to 10 seconds, after which we assume the CMP could not be loaded (potentially blocked by the browser).
|
|
41
|
+
* In that case, we set the consent to null to indicate that we don't know the consent status.
|
|
42
|
+
* When {@link waitForConsent} is called, it will immediately resolve with null instead of waiting for its own timeout.
|
|
43
|
+
*
|
|
44
|
+
* @param {number} timeoutTime
|
|
45
|
+
*/
|
|
46
|
+
requestConsentInformation(timeoutTime = 10000) {
|
|
47
|
+
const timeout = setTimeout(() => {
|
|
48
|
+
this.consent = null
|
|
49
|
+
this.consentSubscribers.forEach((subscriber) => subscriber(null))
|
|
50
|
+
}, timeoutTime)
|
|
51
|
+
|
|
52
|
+
this.pushFunctional(() => {
|
|
53
|
+
clearTimeout(timeout)
|
|
54
|
+
this.consent = new Consent(window._privacy.consentString, window._privacy.purposesProvider.purposes)
|
|
55
|
+
this.consentSubscribers.forEach((subscriber) => subscriber(this.consent))
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wait for {@param timeoutTime} milliseconds for consent information to be available.
|
|
61
|
+
*
|
|
62
|
+
* @param {number} timeoutTime
|
|
63
|
+
* @returns {Promise<Consent>}
|
|
64
|
+
*/
|
|
65
|
+
waitForConsent(timeoutTime = 1000) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
if (this.consent) {
|
|
68
|
+
return resolve(this.consent)
|
|
69
|
+
}
|
|
70
|
+
const timeout = setTimeout(() => reject(new Error('Retrieving consent information timed out.')), timeoutTime)
|
|
71
|
+
this.consentSubscribers.push((consent) => {
|
|
72
|
+
clearTimeout(timeout)
|
|
73
|
+
resolve(consent)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
push(type, callback) {
|
|
79
|
+
window._privacy.push([type, callback])
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pushFunctional(callback) {
|
|
83
|
+
this.push('functional', callback)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const targetedAdvertisingPurposes = [3, 4]
|
|
88
|
+
|
|
89
|
+
class Consent {
|
|
90
|
+
constructor(consentString, purposes) {
|
|
91
|
+
this.consentString = consentString
|
|
92
|
+
this.purposes = purposes
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
allowsTargetedAdvertising() {
|
|
96
|
+
return targetedAdvertisingPurposes.every((purpose) => this.purposes.has(purpose.toString()))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default new Privacy()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default class ImageGeneratorProperties {
|
|
2
|
+
constructor(url) {
|
|
3
|
+
this.url = url
|
|
4
|
+
this.width = 800
|
|
5
|
+
this.height = 800
|
|
6
|
+
this.payload = {}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
withDimensions(width, height) {
|
|
10
|
+
this.width = width
|
|
11
|
+
this.height = height
|
|
12
|
+
return this
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
withPayload(payload) {
|
|
16
|
+
this.payload = payload
|
|
17
|
+
return this
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
toJson() {
|
|
21
|
+
return {
|
|
22
|
+
url: this.url,
|
|
23
|
+
width: this.width,
|
|
24
|
+
height: this.height,
|
|
25
|
+
payload: this.payload,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import openLink from '../utils/openLink'
|
|
2
|
+
|
|
3
|
+
export default class ShareResult {
|
|
4
|
+
constructor(shareable, { url, image }) {
|
|
5
|
+
this.shareable = shareable
|
|
6
|
+
this.url = url
|
|
7
|
+
this.image = image
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
facebookUrl() {
|
|
11
|
+
return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(this.url)}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
whatsappUrl() {
|
|
15
|
+
return `https://api.whatsapp.com/send?text=${encodeURIComponent(this.shareable.messageText)}%20${this.url}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
instagramUrl() {
|
|
19
|
+
return this.image
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
openFacebookUrl() {
|
|
23
|
+
openLink(this.facebookUrl())
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
openWhatsappUrl() {
|
|
27
|
+
openLink(this.whatsappUrl())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
openInstagramUrl() {
|
|
31
|
+
openLink(this.instagramUrl())
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import ShareResult from './ShareResult'
|
|
3
|
+
|
|
4
|
+
export default class Shareable {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.title = ''
|
|
7
|
+
this.description = ''
|
|
8
|
+
this.messageText = ''
|
|
9
|
+
this.redirectUrl = ''
|
|
10
|
+
this.domain = ''
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
withTitle(title) {
|
|
14
|
+
this.title = title
|
|
15
|
+
return this
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
withDescription(description) {
|
|
19
|
+
this.description = description
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
withMessageText(messageText) {
|
|
24
|
+
this.messageText = messageText
|
|
25
|
+
return this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
redirectTo(redirectUrl) {
|
|
29
|
+
this.redirectUrl = redirectUrl
|
|
30
|
+
return this
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fromDomain(domain) {
|
|
34
|
+
this.domain = domain
|
|
35
|
+
return this
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
generateUsingImage(imageGeneratorProperties) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
axios({
|
|
41
|
+
url: 'https://dba1du5ckc.execute-api.eu-west-3.amazonaws.com/prod/',
|
|
42
|
+
method: 'post',
|
|
43
|
+
data: {
|
|
44
|
+
title: this.title,
|
|
45
|
+
description: this.description,
|
|
46
|
+
image_generator: imageGeneratorProperties.toJson(),
|
|
47
|
+
redirect_url: this.redirectUrl,
|
|
48
|
+
domain: this.domain,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
.then((response) => resolve(new ShareResult(this, response.data)))
|
|
52
|
+
.catch(reject)
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|