ep_webrtc 2.5.45 → 2.5.46
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/AGENTS.md +8 -2
- package/index.js +8 -12
- package/package.json +4 -2
- package/static/css/styles.css +16 -0
- package/static/js/index.js +25 -1
- package/static/tests/frontend-new/specs/history_mode.spec.ts +79 -0
- package/static/js/adapter.js +0 -3480
package/AGENTS.md
CHANGED
|
@@ -37,12 +37,18 @@ ep_webrtc/
|
|
|
37
37
|
|
|
38
38
|
## Helpers used
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
`ep_plugin_helpers` is a dependency. Adopted in the helpers-adoption sweep:
|
|
41
|
+
|
|
42
|
+
- **`template()`** — `eejsBlock_mySettings` and `eejsBlock_styles` render their templates via the factory (plugin-qualified paths, e.g. `ep_webrtc/templates/settings.ejs`) instead of hand-rolled `eejs.require(...)`.
|
|
43
|
+
- **`logger()`** — the pre-init fallback logger is a named log4js logger (`logger('ep_webrtc')`), later replaced by the per-plugin logger core supplies in `init_ep_webrtc`.
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
## Helpers NOT used
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
The plugin-specific server logic stays hand-rolled because the generic helpers don't fit:
|
|
49
|
+
|
|
50
|
+
- **`settings()`** — `clientVars` does per-pad ICE-server sharding (HMAC) plus ephemeral TURN credential fetching (coturn / xirsys), and `loadSettings` does a custom `_.mergeWith` array-replace + `disabled` validation. The generic relay only passes settings through verbatim.
|
|
51
|
+
- **`messageRelay()`** — `handleMessage` / `handleRTCMessage` route signalling messages P2P (broadcast vs. targeted unicast to a specific author) and meter STATS errors; the generic relay can't express that.
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
## Running tests locally
|
package/index.js
CHANGED
|
@@ -17,15 +17,13 @@
|
|
|
17
17
|
const _ = require('lodash');
|
|
18
18
|
const {Buffer} = require('buffer');
|
|
19
19
|
const crypto = require('crypto');
|
|
20
|
-
const
|
|
20
|
+
const {template, logger: createLogger} = require('ep_plugin_helpers');
|
|
21
21
|
const sessioninfos = require('ep_etherpad-lite/node/handler/PadMessageHandler').sessioninfos;
|
|
22
22
|
const stats = require('ep_etherpad-lite/node/stats');
|
|
23
23
|
const util = require('util');
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
logger[level] = console[level].bind(console, 'ep_webrtc:');
|
|
28
|
-
}
|
|
25
|
+
// Overwritten with the per-plugin logger supplied by core in init_ep_webrtc.
|
|
26
|
+
let logger = createLogger('ep_webrtc');
|
|
29
27
|
|
|
30
28
|
const defaultSettings = {
|
|
31
29
|
// The defaults here are overridden by the values in the `ep_webrtc` object from `settings.json`.
|
|
@@ -258,16 +256,14 @@ exports.init_ep_webrtc = async (hookName, {logger: l}) => {
|
|
|
258
256
|
|
|
259
257
|
exports.setSocketIO = (hookName, {io}) => { socketio = io; };
|
|
260
258
|
|
|
261
|
-
exports.eejsBlock_mySettings = (
|
|
262
|
-
|
|
259
|
+
exports.eejsBlock_mySettings = template('ep_webrtc/templates/settings.ejs', {
|
|
260
|
+
vars: () => ({
|
|
263
261
|
audio_hard_disabled: settings.audio.disabled === 'hard',
|
|
264
262
|
video_hard_disabled: settings.video.disabled === 'hard',
|
|
265
|
-
},
|
|
266
|
-
};
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
267
265
|
|
|
268
|
-
exports.eejsBlock_styles = (
|
|
269
|
-
context.content += eejs.require('./templates/styles.html', {}, module);
|
|
270
|
-
};
|
|
266
|
+
exports.eejsBlock_styles = template('ep_webrtc/templates/styles.html');
|
|
271
267
|
|
|
272
268
|
exports.loadSettings = async (hookName, {settings: {ep_webrtc: s = {}}}) => {
|
|
273
269
|
settings = _.mergeWith({}, defaultSettings, s, (objV, srcV, key, obj, src) => {
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"url": "git@github.com:ether/ep_webrtc.git",
|
|
6
6
|
"type": "git"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.5.
|
|
8
|
+
"version": "2.5.46",
|
|
9
9
|
"description": "WebRTC based audio/video chat to Etherpad",
|
|
10
10
|
"author": "John McLear <john@mclear.co.uk>",
|
|
11
11
|
"contributors": [],
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"abort-controller": "^3.0.0",
|
|
17
|
+
"ep_plugin_helpers": "^0.6.7",
|
|
17
18
|
"lodash": "^4.18.1",
|
|
18
|
-
"node-fetch": "^3.3.2"
|
|
19
|
+
"node-fetch": "^3.3.2",
|
|
20
|
+
"webrtc-adapter": "^9.0.5"
|
|
19
21
|
},
|
|
20
22
|
"funding": {
|
|
21
23
|
"type": "individual",
|
package/static/css/styles.css
CHANGED
|
@@ -194,3 +194,19 @@
|
|
|
194
194
|
margin-left: 5px;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* In-pad history mode (timeslider) renders the read-only pad inside an
|
|
200
|
+
* absolutely-positioned iframe — core's #history-frame-mount, inset:0,
|
|
201
|
+
* z-index:4 — that overlays #editorcontainerbox. #rtcbox is a normal
|
|
202
|
+
* in-flow child of that same container, so without this it paints
|
|
203
|
+
* *underneath* the history iframe and the live call appears to vanish the
|
|
204
|
+
* moment you start scrubbing history. The call never actually drops (the
|
|
205
|
+
* pad page stays alive in history mode); we just lift the video column
|
|
206
|
+
* above the overlay so it stays visible and usable while a reviewer scrubs
|
|
207
|
+
* through revisions and gives play-by-play commentary on the same call.
|
|
208
|
+
*/
|
|
209
|
+
body.history-mode #rtcbox {
|
|
210
|
+
position: relative;
|
|
211
|
+
z-index: 5;
|
|
212
|
+
}
|
package/static/js/index.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
require('
|
|
18
|
+
require('webrtc-adapter');
|
|
19
19
|
const padcookie = require('ep_etherpad-lite/static/js/pad_cookie').padcookie;
|
|
20
20
|
|
|
21
21
|
let enableDebugLogging = false;
|
|
@@ -507,6 +507,17 @@ exports.rtc = new class {
|
|
|
507
507
|
});
|
|
508
508
|
$(window).on('beforeunload', () => { this.hangupAll(); });
|
|
509
509
|
$(window).on('unload', () => { this.hangupAll(); });
|
|
510
|
+
// Core fires userJoinOrUpdate only for *remote* users, so editing your own
|
|
511
|
+
// name (or colour) in the userlist never reaches the self-view tile. Watch
|
|
512
|
+
// the userlist's own name field and colour swatch and re-sync the local
|
|
513
|
+
// tile. Delegated so it survives the userlist re-rendering its controls;
|
|
514
|
+
// deferred a tick so it runs after core's notifyChangeName/renderMyUserInfo
|
|
515
|
+
// has updated pad.myUserInfo.
|
|
516
|
+
const refreshSelfView =
|
|
517
|
+
() => setTimeout(() => this.updatePeerNameAndColor(this.getUserFromId(this.getUserId())), 0);
|
|
518
|
+
$(document).on(
|
|
519
|
+
'change.ep_webrtc blur.ep_webrtc', '#myusernameedit', refreshSelfView);
|
|
520
|
+
$(document).on('click.ep_webrtc', '#mycolorpickersave', refreshSelfView);
|
|
510
521
|
// Skip auto-activation when the host has no audio/video device the
|
|
511
522
|
// browser can see. Bisecting against ether/ep_webrtc CI proved that
|
|
512
523
|
// the OUTER pad's activate() chain — specifically when getUserMedia
|
|
@@ -823,6 +834,19 @@ exports.rtc = new class {
|
|
|
823
834
|
|
|
824
835
|
getUserFromId(userId) {
|
|
825
836
|
if (!this._pad || !this._pad.collabClient) return null;
|
|
837
|
+
// The local user is not part of collabClient's connected-users set, so
|
|
838
|
+
// resolve their own name/colour from the pad's author fields. Without this
|
|
839
|
+
// the self-view shows "unnamed" even when the user has a name, and it never
|
|
840
|
+
// reflects a name change the user makes in the userlist (core does not fire
|
|
841
|
+
// the userJoinOrUpdate hook for the local user — see postAceInit).
|
|
842
|
+
if (userId === this.getUserId()) {
|
|
843
|
+
const myInfo = this._pad.myUserInfo || {};
|
|
844
|
+
return {
|
|
845
|
+
userId,
|
|
846
|
+
name: (this._pad.getUserName && this._pad.getUserName()) || myInfo.name,
|
|
847
|
+
colorId: myInfo.colorId != null ? myInfo.colorId : clientVars.userColor,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
826
850
|
const result = this._pad.collabClient
|
|
827
851
|
.getConnectedUsers()
|
|
828
852
|
.filter((user) => user.userId === userId);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {expect, test} from '@playwright/test';
|
|
2
|
+
import {goToNewPadWithParams, installFakeGetUserMedia} from '../helper/utils';
|
|
3
|
+
|
|
4
|
+
// Etherpad 3.2.x enters the timeslider "in-place" on the pad URL (pad_mode.ts):
|
|
5
|
+
// it never reloads the page or drops the pad socket, so an active ep_webrtc
|
|
6
|
+
// call keeps running. The catch is purely visual — core mounts the read-only
|
|
7
|
+
// history view in an absolutely-positioned iframe (#history-frame-mount,
|
|
8
|
+
// inset:0, z-index:4) that overlays #editorcontainerbox, where ep_webrtc's
|
|
9
|
+
// #rtcbox lives. Without our `body.history-mode #rtcbox { z-index: 5 }` rule
|
|
10
|
+
// the video column paints underneath that overlay and the call appears to
|
|
11
|
+
// vanish the moment you scrub history. These tests prove the call stays
|
|
12
|
+
// visible (and stacked above the overlay) in history mode and survives
|
|
13
|
+
// exiting back to live editing.
|
|
14
|
+
test.describe('ep_webrtc in history mode (timeslider)', () => {
|
|
15
|
+
// serial: the tests share one page and walk it through live -> history -> live.
|
|
16
|
+
test.describe.configure({mode: 'serial'});
|
|
17
|
+
|
|
18
|
+
let page: import('@playwright/test').Page;
|
|
19
|
+
|
|
20
|
+
test.beforeAll(async ({browser}) => {
|
|
21
|
+
test.setTimeout(60_000);
|
|
22
|
+
page = await browser.newPage();
|
|
23
|
+
await goToNewPadWithParams(page, {});
|
|
24
|
+
await installFakeGetUserMedia(page);
|
|
25
|
+
// Start the call explicitly (blocks until activation completes), then wait
|
|
26
|
+
// for the self-view <video> so we know the call is genuinely up.
|
|
27
|
+
await page.evaluate(() => (window as any).ep_webrtc.activate());
|
|
28
|
+
await page.waitForFunction(() => (window as any).$('#rtcbox').data('initialized'),
|
|
29
|
+
undefined, {timeout: 5000});
|
|
30
|
+
await page.waitForFunction(() => document.querySelectorAll('#rtcbox video').length >= 1,
|
|
31
|
+
undefined, {timeout: 5000});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test.afterAll(async () => {
|
|
35
|
+
await page.close();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('entering history mode keeps the call visible and above the overlay', async () => {
|
|
39
|
+
// pad_mode.ts enters history in-place when a revision hash appears
|
|
40
|
+
// (hashchange listener). Use the canonical '#rev/N' form so enterHistory
|
|
41
|
+
// doesn't pushState a second entry — keeps the exit test's button the
|
|
42
|
+
// single deterministic way back to live mode.
|
|
43
|
+
await page.evaluate(() => { window.location.hash = '#rev/0'; });
|
|
44
|
+
await page.waitForSelector('body.history-mode', {timeout: 10_000});
|
|
45
|
+
await page.waitForSelector('#history-frame-mount iframe', {timeout: 10_000});
|
|
46
|
+
|
|
47
|
+
// The page never reloaded, so the call is still up: the self-view <video>
|
|
48
|
+
// is still in the DOM.
|
|
49
|
+
expect(await page.locator('#rtcbox video').count()).toBeGreaterThanOrEqual(1);
|
|
50
|
+
|
|
51
|
+
// Our rule applied: #rtcbox is lifted above the z-index:4 history overlay.
|
|
52
|
+
expect(await page.evaluate(
|
|
53
|
+
() => getComputedStyle(document.querySelector('#rtcbox')!).zIndex)).toBe('5');
|
|
54
|
+
|
|
55
|
+
// And it actually wins the stack: hit-testing the centre of the video tile
|
|
56
|
+
// lands inside #rtcbox, not the overlay iframe (#history-frame-mount).
|
|
57
|
+
const rtcboxIsOnTop = await page.evaluate(() => {
|
|
58
|
+
const tile = document.querySelector('#rtcbox .video-container') ||
|
|
59
|
+
document.querySelector('#rtcbox')!;
|
|
60
|
+
const r = tile.getBoundingClientRect();
|
|
61
|
+
const el = document.elementFromPoint(
|
|
62
|
+
Math.round(r.left + r.width / 2), Math.round(r.top + r.height / 2));
|
|
63
|
+
for (let n: Element | null = el; n; n = n.parentElement) {
|
|
64
|
+
if (n.id === 'rtcbox') return true;
|
|
65
|
+
if (n.id === 'history-frame-mount') return false;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
});
|
|
69
|
+
expect(rtcboxIsOnTop).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('exiting history mode leaves the call running', async () => {
|
|
73
|
+
// The history banner's "return to pad" button calls pad_mode's exitHistory().
|
|
74
|
+
await page.click('#history-banner-return');
|
|
75
|
+
await page.waitForFunction(() => !document.body.classList.contains('history-mode'),
|
|
76
|
+
undefined, {timeout: 10_000});
|
|
77
|
+
expect(await page.locator('#rtcbox video').count()).toBeGreaterThanOrEqual(1);
|
|
78
|
+
});
|
|
79
|
+
});
|