Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit aeacacb

Browse files
authored
Merge pull request #27 from nordeck/nic/feat/add-lifecycle-logic
Add the widget lifecycle module
2 parents e20c88c + 52f7ab8 commit aeacacb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1573
-112
lines changed

.github/dependabot.yml

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ updates:
1414
development-dependencies:
1515
dependency-type: 'development'
1616
ignore:
17+
# The version of the module api describes the compatibility with the Element version. Every
18+
# package can use a different version and we only want to update it if we need new
19+
# features.
20+
- dependency-name: '@matrix-org/react-sdk-module-api'
1721
# For TypeScript, ignore all updates. Stick to the version of @microsoft/api-extractor.
1822
- dependency-name: 'typescript'
1923
# Stick to the react version of Element

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ jobs:
168168
run: yarn install --frozen-lockfile
169169

170170
- name: build
171-
run: yarn build
171+
run: yarn e2e:build
172172

173173
- name: Install Playwright Browsers
174174
run: npx playwright install --with-deps chromium

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ This module also requires the installation of a Synapse module:
1414
- See the [Readme of the `element-web-guest-module`](./packages/element-web-guest-module/README.md) for instructions on how to install it in Element.
1515
- See the [Readme of the `synapse-guest-module`](./packages/synapse-guest-module/README.md) for instructions on how to install it in your Synapse homeserver.
1616

17+
### Element Web Widget Lifecycle Module
18+
19+
A module to approve widget capabilities so the user is asked:
20+
21+
- See the [Readme of the `element-web-widget-lifecycle-module`](./packages/element-web-widget-lifecycle-module/README.md) for instructions on how to install it in Element.
22+
1723
## Getting Started
1824

1925
Development on the module happens at [GitHub](https://github.com/nordeck/element-web-modules).

e2e/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Running the e2e tests requires Docker to be installed.
1111
### Running Tests
1212

1313
The e2e tests are testing the guest module for Element and for Synapse.
14-
Make sure to always run `yarn build` in the root folder before initially running the tests or after changing a component.
14+
Make sure to always run `yarn e2e:build` in the root folder before initially running the tests or after changing a component.
1515

1616
1. **Synapse Module**: By default, it uses the image that was built by running `yarn docker:build` in the root folder of this repository.
1717
Building the container at least once is required to run the tests.

e2e/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@
1212
"cross-fetch": "^4.0.0",
1313
"eslint": "^8.49.0",
1414
"eslint-plugin-playwright": "^0.16.0",
15-
"shx": "^0.3.4",
1615
"testcontainers": "^10.2.1",
1716
"typescript": "~5.0.4"
1817
},
1918
"scripts": {
2019
"clean": "echo \"Nothing to clean\"",
21-
"build": "yarn workspace @nordeck/element-web-guest-module build && yarn workspace @nordeck/element-web-guest-module package && shx rm -rf src/deploy/elementWeb/*.tgz && shx cp ../packages/element-web-guest-module/*.tgz src/deploy/elementWeb/element-web-guest-module.tgz && yarn workspace @nordeck/synapse-guest-module docker:build",
20+
"build": "echo \"Nothing to build\"",
2221
"docker:build": "echo \"Nothing to build\"",
2322
"tsc": "tsc",
2423
"lint": "eslint . --max-warnings=0",
2524
"depcheck": "depcheck --ignores=typescript",
2625
"e2e": "playwright test --headed --project=chromium --workers=1 --reporter=dot",
27-
"e2e:all": "yarn playwright test --reporter=dot"
26+
"e2e:all": "yarn playwright test --reporter=dot",
27+
"e2e:build": "yarn workspaces run package && shx rm -rf src/deploy/elementWeb/*.tgz && shx cp ../packages/**/*.tgz src/deploy/elementWeb/",
28+
"package": "echo \"Nothing to clean\""
2829
}
2930
}

e2e/src/deploy/elementWeb/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ RUN git clone --depth 1 --branch $ELEMENT_VERSION https://github.com/vector-im/e
1010
RUN yarn --network-timeout=200000 install
1111

1212
# Add all configurations
13-
COPY /*.tgz /src
13+
COPY /*.tgz /src/
1414
COPY /build_config.yaml /src
1515
COPY /customisations.json /src
1616

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Register the module that was created by `yarn build`
22
modules:
3-
- 'file:element-web-guest-module.tgz'
3+
- 'file:nordeck-element-web-guest-module.tgz'
4+
- 'file:nordeck-element-web-widget-lifecycle-module.tgz'

e2e/src/deploy/elementWeb/config.json

+11
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,16 @@
4848
"guest_user_homeserver_url": "{{HOMESERVER_URL}}",
4949
"skip_single_sign_on": true
5050
}
51+
},
52+
"net.nordeck.element_web.module.widget_lifecycle": {
53+
"widget_permissions": {
54+
"{{WIDGET_SERVER_URL}}/widget.html": {
55+
"preload_approved": true,
56+
"identity_approved": true,
57+
"capabilities_approved": [
58+
"org.matrix.msc2762.receive.state_event:m.room.topic"
59+
]
60+
}
61+
}
5162
}
5263
}

e2e/src/deploy/elementWeb/index.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ let container: StartedTestContainer | undefined;
2121

2222
export async function startElementWeb({
2323
homeserverUrl,
24+
widgetServerUrl,
2425
version = 'v1.11.40',
2526
}: {
2627
homeserverUrl: string;
28+
widgetServerUrl: string;
2729
version?: string;
2830
}): Promise<{ elementWebUrl: string }> {
2931
console.log(`Starting element web… (version ${version})`);
@@ -32,10 +34,9 @@ export async function startElementWeb({
3234
require.resolve('./config.json'),
3335
'utf-8',
3436
);
35-
const elementWebConfig = elementWebConfigTemplate.replace(
36-
/{{HOMESERVER_URL}}/g,
37-
homeserverUrl,
38-
);
37+
const elementWebConfig = elementWebConfigTemplate
38+
.replace(/{{HOMESERVER_URL}}/g, homeserverUrl)
39+
.replace(/{{WIDGET_SERVER_URL}}/g, widgetServerUrl);
3940

4041
const elementContainer = await GenericContainer.fromDockerfile(__dirname)
4142
.withBuildArgs({ ELEMENT_VERSION: version })

e2e/src/deploy/setup.ts

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { FullConfig } from '@playwright/test';
1818
import { startElementWeb } from './elementWeb';
1919
import { startSynapse } from './synapse';
20+
import { startWidgetServer } from './widgets';
2021

2122
export default async function globalSetup(_config: FullConfig) {
2223
const { synapseUrl, registrationSecret } = await startSynapse({
@@ -25,8 +26,14 @@ export default async function globalSetup(_config: FullConfig) {
2526
process.env.SYNAPSE_URL = synapseUrl;
2627
process.env.SYNAPSE_REGISTRATION_SECRET = registrationSecret;
2728

29+
const { widgetServerUrl } = await startWidgetServer({
30+
homeserverUrl: synapseUrl,
31+
});
32+
process.env.WIDGET_SERVER_URL = widgetServerUrl;
33+
2834
const { elementWebUrl } = await startElementWeb({
2935
homeserverUrl: synapseUrl,
36+
widgetServerUrl,
3037
});
3138
process.env.ELEMENT_WEB_URL = elementWebUrl;
3239
}

e2e/src/deploy/teardown.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
import { FullConfig } from '@playwright/test';
1818
import { stopElementWeb } from './elementWeb';
1919
import { stopSynapse } from './synapse';
20+
import { stopWidgetServer } from './widgets';
2021

2122
export default async function globalTeardown(_config: FullConfig) {
22-
await Promise.all([stopSynapse(), stopElementWeb()]);
23+
await Promise.all([stopSynapse(), stopWidgetServer(), stopElementWeb()]);
2324
}

e2e/src/deploy/widgets/index.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2023 Nordeck IT + Consulting GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { readFile } from 'fs/promises';
18+
import * as http from 'http';
19+
import { AddressInfo } from 'net';
20+
21+
let server: http.Server;
22+
23+
export async function startWidgetServer({
24+
homeserverUrl,
25+
}: {
26+
homeserverUrl: string;
27+
}): Promise<{
28+
widgetServerUrl: string;
29+
}> {
30+
console.log(`Starting widget server…`);
31+
32+
const widgetHtmlTemplate = await readFile(
33+
require.resolve('./widget.html'),
34+
'utf-8',
35+
);
36+
37+
const widgetHtml = widgetHtmlTemplate.replace(
38+
/{{HOMESERVER_URL}}/g,
39+
homeserverUrl,
40+
);
41+
42+
server = http.createServer((_, res) => {
43+
res.writeHead(200, { 'Content-Type': 'text/html' });
44+
res.end(widgetHtml);
45+
});
46+
server.listen();
47+
48+
const widgetServerUrl = `http://localhost:${
49+
(server.address() as AddressInfo).port
50+
}`;
51+
console.log('Widget server running at', widgetServerUrl);
52+
53+
return { widgetServerUrl };
54+
}
55+
56+
export async function stopWidgetServer() {
57+
if (server) {
58+
server.close();
59+
console.log('Stopped widget server');
60+
}
61+
}

e2e/src/deploy/widgets/widget.html

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<html lang="en">
2+
<head>
3+
<title>Demo Widget</title>
4+
<script>
5+
let sendEventCount = 0;
6+
window.onmessage = async (ev) => {
7+
if (ev.data.action === 'capabilities') {
8+
// Return the capabilities for this widget
9+
window.parent.postMessage(
10+
Object.assign(
11+
{
12+
response: {
13+
capabilities: [
14+
'org.matrix.msc2762.receive.state_event:m.room.topic',
15+
],
16+
},
17+
},
18+
ev.data,
19+
),
20+
'*',
21+
);
22+
} else if (ev.data.action === 'notify_capabilities') {
23+
// Ask for an openid token
24+
window.parent.postMessage(
25+
{
26+
api: 'fromWidget',
27+
widgetId: ev.data.widgetId,
28+
requestId: 'widget-' + sendEventCount,
29+
action: 'get_openid',
30+
data: {},
31+
},
32+
'*',
33+
);
34+
} else if (
35+
ev.data.action === 'get_openid' &&
36+
ev.data.response?.state === 'allowed'
37+
) {
38+
// Add the userid to the heading
39+
const { matrix_server_name, access_token } = ev.data.response;
40+
41+
const response = await fetch(
42+
`{{HOMESERVER_URL}}/_matrix/federation/v1/openid/userinfo?access_token=${access_token}`,
43+
);
44+
const { sub } = await response.json();
45+
46+
const titleElement = document.getElementById('title');
47+
titleElement.innerText = `Hello ${sub}!`;
48+
}
49+
};
50+
</script>
51+
</head>
52+
<body>
53+
<h1 id="title">Hello unknown!</h1>
54+
</body>
55+
</html>

e2e/src/fixtures.ts

+44
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type Fixtures = {
2424
alicePage: Page;
2525
aliceElementWebPage: ElementWebPage;
2626
bob: User;
27+
bobPage: Page;
28+
bobElementWebPage: ElementWebPage;
2729
guestPage: Page;
2830
guestElementWebPage: ElementWebPage;
2931
runAxeAnalysis: (page: Page) => Promise<string>;
@@ -65,6 +67,48 @@ export const test = base.extend<Fixtures>({
6567
await use(user);
6668
},
6769

70+
bobPage: async ({ browser, contextOptions, video }, use, testInfo) => {
71+
// TODO: For some reason we are missing the video in case we are using a
72+
// second context https://github.com/microsoft/playwright/issues/9002
73+
// We configure it manually instead.
74+
const videoMode = typeof video === 'string' ? video : video.mode;
75+
const videoOptions = shouldCaptureVideo(videoMode, testInfo)
76+
? {
77+
recordVideo: {
78+
dir: testInfo.outputDir,
79+
size: typeof video !== 'string' ? video.size : undefined,
80+
},
81+
}
82+
: {};
83+
84+
const context = await browser.newContext({
85+
...contextOptions,
86+
...videoOptions,
87+
});
88+
const page = await context.newPage();
89+
90+
try {
91+
await use(page);
92+
} finally {
93+
await context.close();
94+
95+
const video = page.video();
96+
97+
if (video) {
98+
const path = testInfo.outputPath('video-bob.webm');
99+
await video.saveAs(path);
100+
testInfo.attach('video', { path, contentType: 'video/webm' });
101+
}
102+
}
103+
},
104+
105+
bobElementWebPage: async ({ bobPage, bob }, use) => {
106+
const elementWebPage = new ElementWebPage(bobPage);
107+
await elementWebPage.login(bob.username, bob.password);
108+
109+
await use(elementWebPage);
110+
},
111+
68112
guestPage: async ({ browser, contextOptions, video }, use, testInfo) => {
69113
// TODO: For some reason we are missing the video in case we are using a
70114
// second context https://github.com/microsoft/playwright/issues/9002

0 commit comments

Comments
 (0)