@dpgradio/creative 8.1.2 → 9.0.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/.github/workflows/lint.yml +4 -4
- package/.github/workflows/publish.yml +6 -7
- package/README.md +1 -1
- package/dev/index.html +19 -0
- package/dev/main.js +20 -0
- package/examples/gtm.html +19 -0
- package/examples/gtm.js +25 -0
- package/package.json +5 -3
- package/src/api/endpoints/Members.js +4 -0
- package/src/app/authentication.js +18 -1
- package/src/socket/SocketConnection.js +45 -0
- package/src/utils/setupAirbrake.js +1 -1
- package/vite.config.js +13 -0
- package/package-lock.json +0 -8362
|
@@ -7,9 +7,9 @@ jobs:
|
|
|
7
7
|
- uses: actions/checkout@v3
|
|
8
8
|
- uses: actions/setup-node@v3
|
|
9
9
|
with:
|
|
10
|
-
node-version:
|
|
11
|
-
cache:
|
|
10
|
+
node-version: 22
|
|
11
|
+
cache: npm
|
|
12
12
|
- name: Install dependencies
|
|
13
|
-
run:
|
|
13
|
+
run: npm ci
|
|
14
14
|
- name: Lint
|
|
15
|
-
run:
|
|
15
|
+
run: npm run lint-check
|
|
@@ -9,13 +9,12 @@ jobs:
|
|
|
9
9
|
- uses: actions/checkout@v3
|
|
10
10
|
- uses: actions/setup-node@v3
|
|
11
11
|
with:
|
|
12
|
-
node-version:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- run:
|
|
18
|
-
- run: yarn publish --new-version ${{ github.event.release.tag_name }}
|
|
12
|
+
node-version: 22
|
|
13
|
+
cache: npm
|
|
14
|
+
registry-url: "https://registry.npmjs.org" # This is needed to make npm publish work...
|
|
15
|
+
- run: npm ci
|
|
16
|
+
- run: npm version ${{ github.event.release.tag_name }} --no-git-tag-version --allow-same-version
|
|
17
|
+
- run: npm publish
|
|
19
18
|
env:
|
|
20
19
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
21
20
|
|
package/README.md
CHANGED
package/dev/index.html
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Dev playground</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div class="container">
|
|
10
|
+
<h1>Dev playground</h1>
|
|
11
|
+
|
|
12
|
+
<button onclick="testSocket()">Test Socket Connection</button>
|
|
13
|
+
|
|
14
|
+
<pre id="output"></pre>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<script type="module" src="./main.js"></script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
package/dev/main.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { socket } from '@dpgradio/creative'
|
|
2
|
+
|
|
3
|
+
const output = document.getElementById('output')
|
|
4
|
+
|
|
5
|
+
function log(...args) {
|
|
6
|
+
const timestamp = new Date().toLocaleTimeString()
|
|
7
|
+
const message = args.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg))).join(' ')
|
|
8
|
+
|
|
9
|
+
output.textContent += `[${timestamp}] ${message}\n`
|
|
10
|
+
output.scrollTop = output.scrollHeight
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
window.testSocket = function () {
|
|
14
|
+
log('Connecting to Qmusic socket...')
|
|
15
|
+
const connection = socket.connect('qmusic_be', 'https://socket.qmusic.be/api', {
|
|
16
|
+
debug: true,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
connection.subscribe('plays').on('play', log, { backlog: 1 })
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>GTM and Privacy Test with Nonce</title>
|
|
7
|
+
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="
|
|
9
|
+
default-src 'self' 'nonce-abcd1234' *.radio.dpgmedia.net;
|
|
10
|
+
script-src 'self' 'nonce-abcd1234' *.radio.dpgmedia.net;
|
|
11
|
+
style-src 'self' 'unsafe-inline';
|
|
12
|
+
">
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<h1>GTM and Privacy Test with Nonce</h1>
|
|
16
|
+
|
|
17
|
+
<script nonce="abcd1234" type="module" src="gtm.js"></script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
package/examples/gtm.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { configuration, dataLayer, privacy } from '../src/index.js'
|
|
2
|
+
|
|
3
|
+
async function testGTMAndPrivacy() {
|
|
4
|
+
const stationId = 'qmusic_be'
|
|
5
|
+
await configuration.retrieveConfigForStation(stationId)
|
|
6
|
+
console.log('Configuration retrieved:', configuration.config())
|
|
7
|
+
|
|
8
|
+
// Generate a random nonce (in a real scenario, this should be generated server-side)
|
|
9
|
+
// const nonce = Math.random().toString(36).substring(2, 15);
|
|
10
|
+
const nonce = 'abcd1234'
|
|
11
|
+
// Initialize dataLayer with the nonce
|
|
12
|
+
dataLayer.initialize({ nonce: nonce })
|
|
13
|
+
|
|
14
|
+
// Push a custom event to test
|
|
15
|
+
dataLayer.pushEvent('test_event', { message: 'Hello, GTM!' })
|
|
16
|
+
|
|
17
|
+
console.log('GTM initialized with nonce:', nonce)
|
|
18
|
+
|
|
19
|
+
// Initialize privacy settings with default values and nonce
|
|
20
|
+
privacy.initialize(undefined, undefined, undefined, { nonce: nonce })
|
|
21
|
+
|
|
22
|
+
console.log('Privacy settings initialized')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
testGTMAndPrivacy()
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dpgradio/creative",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.1",
|
|
4
4
|
"description": "Support package for standalone Javascript applications",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "jest",
|
|
8
8
|
"lint-check": "eslint --ext .js --ignore-path .gitignore src",
|
|
9
|
-
"lint": "
|
|
9
|
+
"lint": "npm run lint-check --fix",
|
|
10
|
+
"dev": "vite dev --open"
|
|
10
11
|
},
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"eslint": "^8.32.0",
|
|
34
35
|
"eslint-plugin-import": "^2.27.5",
|
|
35
36
|
"jest": "^29.4.0",
|
|
36
|
-
"jest-environment-jsdom": "^29.4.0"
|
|
37
|
+
"jest-environment-jsdom": "^29.4.0",
|
|
38
|
+
"vite": "^6.3.5"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -5,6 +5,10 @@ export default class Members extends Endpoint {
|
|
|
5
5
|
return await this.api.request().get('/members/me')
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
async basicInfo(profileId, email) {
|
|
9
|
+
return await this.api.request().get(`/members/basic-info/${profileId}`, { email: email })
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
async updateProfile(profile) {
|
|
9
13
|
return await this.api.request().put('/members/me', { profile })
|
|
10
14
|
}
|
|
@@ -19,6 +19,8 @@ class Authentication {
|
|
|
19
19
|
|
|
20
20
|
this.radioTokenListeners = []
|
|
21
21
|
this.askingForLoginListeners = []
|
|
22
|
+
|
|
23
|
+
this._refreshTokenPromise = null
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
initialize() {
|
|
@@ -114,7 +116,7 @@ class Authentication {
|
|
|
114
116
|
})
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
async
|
|
119
|
+
async _doRefreshToken() {
|
|
118
120
|
if (hybrid.isNativeApp()) {
|
|
119
121
|
const updatedToken = Promise.race([
|
|
120
122
|
new Promise((resolve) => {
|
|
@@ -136,6 +138,9 @@ class Authentication {
|
|
|
136
138
|
'Content-Type': 'application/json',
|
|
137
139
|
},
|
|
138
140
|
})
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
throw new Error(`Failed to refresh token: ${response.status} ${response.statusText}`)
|
|
143
|
+
}
|
|
139
144
|
const token = (await response.json()).radioToken
|
|
140
145
|
localStorage.setItem(RADIO_TOKEN_LOCAL_STORAGE_KEY, token)
|
|
141
146
|
return token
|
|
@@ -145,6 +150,18 @@ class Authentication {
|
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
152
|
}
|
|
153
|
+
|
|
154
|
+
async refreshToken() {
|
|
155
|
+
if (this._refreshTokenPromise) {
|
|
156
|
+
return this._refreshTokenPromise
|
|
157
|
+
}
|
|
158
|
+
this._refreshTokenPromise = this._doRefreshToken()
|
|
159
|
+
try {
|
|
160
|
+
return await this._refreshTokenPromise
|
|
161
|
+
} finally {
|
|
162
|
+
this._refreshTokenPromise = null
|
|
163
|
+
}
|
|
164
|
+
}
|
|
148
165
|
}
|
|
149
166
|
|
|
150
167
|
export default new Authentication()
|
|
@@ -11,6 +11,7 @@ export default class SocketConnection {
|
|
|
11
11
|
this.messages = []
|
|
12
12
|
this.lastAttempt = 1
|
|
13
13
|
this.reconnecting = false
|
|
14
|
+
this.heartbeatTimeout = null
|
|
14
15
|
|
|
15
16
|
this.connect()
|
|
16
17
|
}
|
|
@@ -31,11 +32,18 @@ export default class SocketConnection {
|
|
|
31
32
|
|
|
32
33
|
this.reconnecting = false
|
|
33
34
|
this.flushMessages()
|
|
35
|
+
this._startHeartbeatTimeout()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.socket.onheartbeat = () => {
|
|
39
|
+
this._resetHeartbeatTimeout()
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
this.socket.onmessage = (event) => {
|
|
37
43
|
this.log('>>>', event.data)
|
|
38
44
|
|
|
45
|
+
this._resetHeartbeatTimeout()
|
|
46
|
+
|
|
39
47
|
try {
|
|
40
48
|
const message = JSON.parse(event.data)
|
|
41
49
|
this.processMessage(message)
|
|
@@ -48,6 +56,8 @@ export default class SocketConnection {
|
|
|
48
56
|
this.socket.onclose = (event) => {
|
|
49
57
|
this.log(`Disconnected ${event.code}`, event)
|
|
50
58
|
|
|
59
|
+
this._clearHeartbeatTimeout()
|
|
60
|
+
|
|
51
61
|
// This is a technical issue, try again
|
|
52
62
|
if ([1000, 1001, 1002, 1003, 1004, 1005, 1006, 2000].includes(event.code)) {
|
|
53
63
|
this.reconnecting = true
|
|
@@ -166,4 +176,39 @@ export default class SocketConnection {
|
|
|
166
176
|
this._sendAuthentication(auth)
|
|
167
177
|
}
|
|
168
178
|
}
|
|
179
|
+
|
|
180
|
+
_isWebsocketTransport() {
|
|
181
|
+
return (
|
|
182
|
+
this.socket.protocol === 'websocket' ||
|
|
183
|
+
(this.socket._transport && this.socket._transport.transportName === 'websocket')
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_startHeartbeatTimeout() {
|
|
188
|
+
if (this.options.heartbeatTimeout === false || !this._isWebsocketTransport()) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this._clearHeartbeatTimeout()
|
|
193
|
+
|
|
194
|
+
this.heartbeatTimeout = setTimeout(() => {
|
|
195
|
+
this.log('Heartbeat timeout - closing connection')
|
|
196
|
+
this.socket._close(1006, 'Heartbeat timeout')
|
|
197
|
+
}, this.options.heartbeatTimeout || 35000)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
_resetHeartbeatTimeout() {
|
|
201
|
+
if (!this._isWebsocketTransport()) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this._startHeartbeatTimeout()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
_clearHeartbeatTimeout() {
|
|
209
|
+
if (this.heartbeatTimeout) {
|
|
210
|
+
clearTimeout(this.heartbeatTimeout)
|
|
211
|
+
this.heartbeatTimeout = null
|
|
212
|
+
}
|
|
213
|
+
}
|
|
169
214
|
}
|