Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare and demonstrate usage in a browser environment #15

Merged
merged 2 commits into from Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
@@ -1,5 +1,6 @@
# Custom
lib/
dist/
.npmrc
.idea

Expand Down
29 changes: 25 additions & 4 deletions README.md
@@ -1,5 +1,8 @@
# matrix-widget-api
JavaScript/TypeScript API for widgets & web clients to communicate.

![npm](https://img.shields.io/npm/v/matrix-widget-api?style=for-the-badge)

JavaScript/TypeScript SDK for widgets & clients to communicate.

For help and support, visit [#matrix-dev:matrix.org](https://matrix.to/#/#matrix-dev:matrix.org) on Matrix.

Expand All @@ -8,6 +11,20 @@ For help and support, visit [#matrix-dev:matrix.org](https://matrix.to/#/#matrix
This is currently not validated and thus should not be relied upon until this notice goes away. Installation
instructions will take this notice's place.

## Using the API without a bundler

If you're looking to drop the widget-api into a web browser without the use of a bundler, add a `script`
tag similar to the following:

```html
<script src="https://unpkg.com/matrix-widget-api@0.1.0/dist/api.min.js"></script>
```

Note that the version number may need changing to match the current release.

Once included, the widget-api will be available under `mxwidgets`. For example, `new mxwidgets.WidgetApi(...)`
to instantiate the `WidgetApi` class.

## Usage for widgets

The general usage for this would be:
Expand All @@ -26,7 +43,7 @@ api.on(`action:${WidgetApiToWidgetAction.UpdateVisibility}`, (ev: CustomEvent<IV
console.log(ev.detail); // custom handling here
api.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
});
api.on("com.example.my_action", (ev: CustomEvent<ICustomActionRequest>) => {
api.on("action:com.example.my_action", (ev: CustomEvent<ICustomActionRequest>) => {
ev.preventDefault(); // we're handling it, so stop the widget API from doing something.
console.log(ev.detail); // custom handling here
api.transport.reply(ev.detail, {custom: "reply"});
Expand All @@ -43,9 +60,13 @@ api.setAlwaysOnScreen(true);
api.transport.send("com.example.my_action", {isExample: true});
```

For a more complete example, see the `examples` directory of this repo.

## Usage for web clients

Sorry, this JS API is geared towards web-based widgets and clients 😢
This SDK is meant for use in browser-based applications. The concepts may be transferable to other platforms,
though currently this SDK is intended to only be used by browsers. In the future it may be possible for this
SDK to provide an interface for other platforms.

TODO: Improve this

Expand All @@ -61,4 +82,4 @@ api.on("ready", () => {

// Eventually, stop the API handling
api.stop();
```
```
2 changes: 2 additions & 0 deletions examples/.gitignore
@@ -0,0 +1,2 @@
api.js
api.min.js
38 changes: 38 additions & 0 deletions examples/widget/index.css
@@ -0,0 +1,38 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

html, body {
background-color: #ffffff;
color: #000000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
padding: 20px;
}

button {
border: none;
color: #ffffff;
background-color: #2a9d8f;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
}

#stickyState {
color: #3d5a80;
}
106 changes: 106 additions & 0 deletions examples/widget/index.html
@@ -0,0 +1,106 @@
<!--
Copyright 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Example Widget</title>

<!--
TESTING IN ELEMENT WEB
----------------------

To test this widget in Element Web, set up the widget library so it
can be resolved properly, then serve this directory off a web server.
An easy web server can be made with the http-server NPM package.

Once served, use the following command to add the widget to a room:
/addwidget http://localhost:8080/#/?widgetId=$matrix_widget_id&user_id=$matrix_user_id

The widget should then load and present an interface for sticking the
widget to the screen (if approved for the capability). It is recommended
to have the JS console open to watch for errors and to see how the widget
works.

Note: this uses the fragment to pass parameters to avoid leaking widget
information to the web server. It is recommended to take a similar approach
with your own widgets.
-->

<!-- CSS is just for aesthetics and not important to the example -->
<link href="index.css" rel="stylesheet" />
</head>
<body>
<!-- The widget will be loaded into this container -->
<div id="container">Loading...</div>

<!-- Include the widget library -->
<script src="api.js"></script>

<!-- Bring in some utilities that aren't critical to the example -->
<script src="utils.js"></script>

<!-- The actual widget functionality -->
<script type="text/javascript">
try {
const qs = parseFragment();
const widgetId = assertParam(qs, 'widgetId');
const userId = assertParam(qs, 'userId');
let isSticky = false;

// Set up the widget API as soon as possible to avoid problems with the client
const widgetApi = new mxwidgets.WidgetApi(widgetId);
widgetApi.requestCapability(mxwidgets.MatrixCapabilities.AlwaysOnScreen);

widgetApi.on("ready", function() {
// Fill in the basic widget details now that we're allowed to operate.
document.getElementById("container").innerHTML = "Hello <span id='userId'></span>!<br /><br />"
+ "Currently stuck on screen: <span id='stickyState'></span><br /><br />"
+ "<button onclick='toggleSticky()'>Toggle sticky state</button>";

// Fill in the user ID using innerText to avoid XSS
document.getElementById("userId").innerText = userId;

// Update the UI and ensure that we end up not sticky to start
sendStickyState();
});

// Start the widget as soon as possible too, otherwise the client might time us out.
widgetApi.start();

function toggleSticky() {
// called by the button when clicked - toggle the sticky state
isSticky = !isSticky;
sendStickyState();
}

function updateStickyState() {
document.getElementById("stickyState").innerText = isSticky.toString();
}

function sendStickyState() {
updateStickyState(); // update first to make the UI go faster than the request
widgetApi.setAlwaysOnScreen(isSticky).then(function(r) {
console.log("[Widget] Client responded with: ", r);
}).catch(function(e) {
handleError(e);
});
}
} catch (e) {
handleError(e);
}
</script>
</body>
</html>
31 changes: 31 additions & 0 deletions examples/widget/utils.js
@@ -0,0 +1,31 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

function parseFragment() {
const fragmentString = (window.location.hash || "?");
return new URLSearchParams(fragmentString.substring(Math.max(fragmentString.indexOf('?'), 0)));
}

function assertParam(fragment, name) {
const val = fragment.get(name);
if (!val) throw new Error(`${name} is not present in URL - cannot load widget`);
return val;
}

function handleError(e) {
console.error(e);
document.getElementById("container").innerText = "There was an error with the widget. See JS console for details.";
}
10 changes: 8 additions & 2 deletions package.json
Expand Up @@ -11,9 +11,12 @@
"prepublishOnly": "yarn build",
"start": "tsc -w",
"clean": "rimraf lib dist",
"build": "yarn clean && yarn build:compile && yarn build:types",
"build": "yarn clean && yarn build:compile && yarn build:types && yarn build:browser",
"build:compile": "babel -d lib --verbose --extensions \".ts\" src",
"build:types": "tsc --emitDeclarationOnly",
"build:browser": "yarn build:browser:dev && yarn build:browser:prod",
"build:browser:dev": "browserify lib/index.js --debug --s mxwidgets -o dist/api.js",
"build:browser:prod": "browserify lib/index.js --s mxwidgets -p tinyify -o dist/api.min.js",
"lint": "yarn lint:types && yarn lint:ts",
"lint:ts": "eslint src",
"lint:types": "tsc --noEmit",
Expand All @@ -22,6 +25,7 @@
"files": [
"src",
"lib",
"dist",
"package.json",
"README.md",
"LICENSE",
Expand All @@ -35,10 +39,12 @@
"@babel/preset-typescript": "^7.10.4",
"@types/events": "^3.0.0",
"babel-eslint": "^10.1.0",
"browserify": "^17.0.0",
"eslint": "^7.8.1",
"eslint-config-matrix-org": "^0.1.2",
"eslint-plugin-babel": "^5.3.1",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"tinyify": "^3.0.0"
},
"dependencies": {
"events": "^3.2.0"
Expand Down