git-watchtower 2.3.12 → 2.3.14
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/package.json +1 -1
- package/src/casino/index.js +13 -0
- package/src/casino/sounds.js +47 -7
- package/src/server/web.js +37 -1
package/package.json
CHANGED
package/src/casino/index.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { ansi, box } = require('../ui/ansi');
|
|
10
|
+
const sounds = require('./sounds');
|
|
10
11
|
|
|
11
12
|
// ============================================================================
|
|
12
13
|
// Casino Mode State
|
|
@@ -110,6 +111,18 @@ function disable() {
|
|
|
110
111
|
// up to ~15 frames × 120 ms = 1.8 s after disable, and lossMessage stayed
|
|
111
112
|
// set so isLossAnimating() reported true into the next session.
|
|
112
113
|
resetLossState();
|
|
114
|
+
// Drop the marquee render callback so a stale closure to the previous
|
|
115
|
+
// session's render() doesn't survive across enable/disable cycles. In
|
|
116
|
+
// production this is mostly hygiene (bin/git-watchtower.js wires the
|
|
117
|
+
// callback exactly once at startup against a singleton render fn), but
|
|
118
|
+
// tests that re-use the casino module saw the previous test's callback
|
|
119
|
+
// persist into the next setRenderCallback assignment.
|
|
120
|
+
marqueeCallback = null;
|
|
121
|
+
// Cancel any pending sound timeouts (jackpot bell chains, multi-play
|
|
122
|
+
// mega-jackpot files) so audio doesn't continue after the user
|
|
123
|
+
// toggled casino mode off — and so child processes from the file-play
|
|
124
|
+
// path don't get spawned by a setTimeout that fires after shutdown.
|
|
125
|
+
sounds.cancelAll();
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
/**
|
package/src/casino/sounds.js
CHANGED
|
@@ -9,6 +9,44 @@ const { execFile } = require('child_process');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Pending setTimeout handles for the multi-bell / multi-play chains used
|
|
14
|
+
* by playJackpot and playMegaJackpot. Tracked so casino.disable() (and
|
|
15
|
+
* shutdown) can cancel any in-flight chain instead of letting up to ~600
|
|
16
|
+
* ms of post-disable audio leak through.
|
|
17
|
+
* @type {Set<NodeJS.Timeout>}
|
|
18
|
+
*/
|
|
19
|
+
const _pendingTimeouts = new Set();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Schedule a callback like setTimeout, but auto-track the handle so it
|
|
23
|
+
* can be cancelled by cancelAll() and auto-removes itself on fire.
|
|
24
|
+
* @param {Function} fn
|
|
25
|
+
* @param {number} delay
|
|
26
|
+
* @returns {NodeJS.Timeout}
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
function _scheduleTracked(fn, delay) {
|
|
30
|
+
const handle = setTimeout(() => {
|
|
31
|
+
_pendingTimeouts.delete(handle);
|
|
32
|
+
try { fn(); } catch (e) { /* sounds are optional */ }
|
|
33
|
+
}, delay);
|
|
34
|
+
_pendingTimeouts.add(handle);
|
|
35
|
+
return handle;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Cancel every pending sound timeout. Idempotent. Called from
|
|
40
|
+
* casino.disable() so a jackpot fired moments before the user toggled
|
|
41
|
+
* casino mode off doesn't keep playing afterward.
|
|
42
|
+
*/
|
|
43
|
+
function cancelAll() {
|
|
44
|
+
for (const handle of _pendingTimeouts) {
|
|
45
|
+
clearTimeout(handle);
|
|
46
|
+
}
|
|
47
|
+
_pendingTimeouts.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
12
50
|
// ============================================================================
|
|
13
51
|
// Sound Configuration
|
|
14
52
|
// ============================================================================
|
|
@@ -133,10 +171,11 @@ function playJackpot() {
|
|
|
133
171
|
if (soundPath) {
|
|
134
172
|
playFile(soundPath, VOLUME.jackpot);
|
|
135
173
|
} else {
|
|
136
|
-
// Multiple bells for jackpot!
|
|
174
|
+
// Multiple bells for jackpot! Tracked so casino.disable() can
|
|
175
|
+
// cancel mid-chain — see _scheduleTracked / cancelAll.
|
|
137
176
|
playBell();
|
|
138
|
-
|
|
139
|
-
|
|
177
|
+
_scheduleTracked(playBell, 200);
|
|
178
|
+
_scheduleTracked(playBell, 400);
|
|
140
179
|
}
|
|
141
180
|
}
|
|
142
181
|
|
|
@@ -146,14 +185,14 @@ function playJackpot() {
|
|
|
146
185
|
function playMegaJackpot() {
|
|
147
186
|
const soundPath = getSoundPath('jackpot');
|
|
148
187
|
if (soundPath) {
|
|
149
|
-
// Play jackpot sound multiple times
|
|
188
|
+
// Play jackpot sound multiple times. Tracked for cancellation.
|
|
150
189
|
playFile(soundPath, VOLUME.jackpot);
|
|
151
|
-
|
|
152
|
-
|
|
190
|
+
_scheduleTracked(() => playFile(soundPath, VOLUME.jackpot), 300);
|
|
191
|
+
_scheduleTracked(() => playFile(soundPath, VOLUME.jackpot), 600);
|
|
153
192
|
} else {
|
|
154
193
|
// Lots of bells!
|
|
155
194
|
for (let i = 0; i < 5; i++) {
|
|
156
|
-
|
|
195
|
+
_scheduleTracked(playBell, i * 150);
|
|
157
196
|
}
|
|
158
197
|
}
|
|
159
198
|
}
|
|
@@ -240,6 +279,7 @@ module.exports = {
|
|
|
240
279
|
playSpin,
|
|
241
280
|
playLoss,
|
|
242
281
|
playForWinLevel,
|
|
282
|
+
cancelAll,
|
|
243
283
|
getSoundPath,
|
|
244
284
|
SOUNDS_DIR,
|
|
245
285
|
};
|
package/src/server/web.js
CHANGED
|
@@ -46,6 +46,36 @@ const SSE_KEEPALIVE_INTERVAL = 15000;
|
|
|
46
46
|
*/
|
|
47
47
|
const MAX_STALLED_PUSHES = 60;
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Content-Security-Policy header for the dashboard HTML.
|
|
51
|
+
*
|
|
52
|
+
* The dashboard uses inline <script> and inline <style> blocks (the whole
|
|
53
|
+
* UI is bundled at build time), and makes XHR / EventSource calls to
|
|
54
|
+
* /api/... on the same origin. No external resources are loaded.
|
|
55
|
+
*
|
|
56
|
+
* `default-src 'none'` denies everything not explicitly allowed.
|
|
57
|
+
* `script-src` / `style-src` need 'unsafe-inline' for the bundled blocks.
|
|
58
|
+
* `connect-src 'self'` lets XHR + SSE hit /api/... .
|
|
59
|
+
* `base-uri 'none'` blocks <base> injection from rewriting URLs.
|
|
60
|
+
* `form-action 'none'` blocks any rogue form posts.
|
|
61
|
+
* `frame-ancestors 'none'` blocks embedding (defense vs. clickjacking).
|
|
62
|
+
*
|
|
63
|
+
* Defense-in-depth — the dashboard already escapes user-supplied content
|
|
64
|
+
* everywhere we render it, but a future regression that forgets escHtml
|
|
65
|
+
* is mitigated here.
|
|
66
|
+
*/
|
|
67
|
+
const CONTENT_SECURITY_POLICY = [
|
|
68
|
+
"default-src 'none'",
|
|
69
|
+
"script-src 'self' 'unsafe-inline'",
|
|
70
|
+
"style-src 'self' 'unsafe-inline'",
|
|
71
|
+
"connect-src 'self'",
|
|
72
|
+
"img-src 'self' data:",
|
|
73
|
+
"font-src 'self'",
|
|
74
|
+
"base-uri 'none'",
|
|
75
|
+
"form-action 'none'",
|
|
76
|
+
"frame-ancestors 'none'",
|
|
77
|
+
].join('; ');
|
|
78
|
+
|
|
49
79
|
/**
|
|
50
80
|
* Actions the web dashboard is allowed to POST to /api/action. Every entry
|
|
51
81
|
* here MUST be matched by a `case` in `handleWebAction` in bin/git-watchtower.js
|
|
@@ -474,7 +504,12 @@ class WebDashboardServer {
|
|
|
474
504
|
|
|
475
505
|
// Routes
|
|
476
506
|
if (pathname === '/' && req.method === 'GET') {
|
|
477
|
-
res.writeHead(200, {
|
|
507
|
+
res.writeHead(200, {
|
|
508
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
509
|
+
'Content-Security-Policy': CONTENT_SECURITY_POLICY,
|
|
510
|
+
'X-Content-Type-Options': 'nosniff',
|
|
511
|
+
'Referrer-Policy': 'no-referrer',
|
|
512
|
+
});
|
|
478
513
|
res.end(this._cachedHtml);
|
|
479
514
|
return;
|
|
480
515
|
}
|
|
@@ -673,4 +708,5 @@ module.exports = {
|
|
|
673
708
|
STATE_PUSH_INTERVAL,
|
|
674
709
|
MAX_STALLED_PUSHES,
|
|
675
710
|
ALLOWED_ACTIONS,
|
|
711
|
+
CONTENT_SECURITY_POLICY,
|
|
676
712
|
};
|