@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.
@@ -7,9 +7,9 @@ jobs:
7
7
  - uses: actions/checkout@v3
8
8
  - uses: actions/setup-node@v3
9
9
  with:
10
- node-version: 16
11
- cache: yarn
10
+ node-version: 22
11
+ cache: npm
12
12
  - name: Install dependencies
13
- run: yarn install --frozen-lockfile
13
+ run: npm ci
14
14
  - name: Lint
15
- run: yarn lint-check
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: 16
13
- registry-url: 'https://registry.npmjs.org'
14
- # Defaults to the user or organization that owns the workflow file
15
- scope: '@octocat'
16
- cache: yarn
17
- - run: yarn
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
@@ -15,7 +15,7 @@ Table of contents:
15
15
  ## Installation
16
16
 
17
17
  ```bash
18
- yarn add @dpgradio/creative
18
+ npm i @dpgradio/creative
19
19
  ```
20
20
 
21
21
  ## CSS: Fonts and Colors
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>
@@ -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": "8.1.2",
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": "yarn lint-check --fix"
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 refreshToken() {
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
  }
@@ -1,4 +1,4 @@
1
- export const setupAirbrake = async (
1
+ export const setupAirbrake = (
2
2
  AirbrakeNotifier,
3
3
  { projectId, projectKey, version, environment = 'production' },
4
4
  app = null
package/vite.config.js ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vite'
2
+
3
+ export default defineConfig({
4
+ root: './dev',
5
+ server: {
6
+ open: true,
7
+ },
8
+ resolve: {
9
+ alias: {
10
+ '@dpgradio/creative': '../src',
11
+ },
12
+ },
13
+ })