Skip to content

Commit 9850ed5

Browse files
committed
Initial commit
0 parents  commit 9850ed5

27 files changed

+5815
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/.awcache/
2+
/dist/
3+
*.crx
4+
.DS_STORE
5+
/node_modules
6+
yarn-error.log
7+
*.pem

README.md

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Appear.in meeting room helper browser extension
2+
3+
This browser extension assists in the following ways:
4+
5+
* Allows you to configure defaults when entering a room
6+
* Camera off
7+
* Muted audio
8+
* Apply those defaults to links that you open/follow automatically
9+
* Keep a list of links to your favourite meeting rooms
10+
11+
## Building the extension
12+
13+
Install all the dependencies first:
14+
15+
```bash
16+
yarn
17+
```
18+
19+
and then the extension can be built:
20+
21+
```bash
22+
yarn build
23+
```
24+
25+
This produces a compiled extension file with a name like `appear-in-meeting-room-helper-x.x.x.crx`.
26+
27+
## Installing the extension
28+
29+
Open your chrome browser and navigate to [chrome://extensions](chrome://extensions).
30+
Drag and drop the `appear-in-meeting-room-helper-x.x.x.crx` file onto the page and
31+
accept the warning.
32+
33+
## Developing the extension
34+
35+
The extension is written with TypeScript and it's built with Webpack.
36+
37+
1. Run `yarn` to install dependencies
38+
2. Run `yarn watch` to build and watch for file changes
39+
3. Go to [chrome://extensions](chrome://extensions)
40+
4. Remove the extension if already installed
41+
5. Check on developer mode
42+
6. Load unpacked extension and point to this projects `dist` dir
43+
7. Hack away
44+
8. Remember to hit reload against the extension on [chrome://extensions](chrome://extensions)
45+
46+
### Tests
47+
48+
The tests are written with Jest in TypeScript and can be run with:
49+
50+
```bash
51+
yarn test
52+
```
53+
54+
### Linting
55+
56+
There is a simple ts-lint setup configured that can be executed with:
57+
58+
```bash
59+
yarn lint
60+
```
61+
62+
## Icon credit
63+
64+
https://www.flaticon.com/free-icon/webcam-video-call_70572#term=camera&page=5&position=78

package.json

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "appear-in-meeting-room-helper-browser-extension",
3+
"repository": "[email protected]:treffynnon/appear-in-helper-browser-extension.git",
4+
"author": "Simon Holywell <[email protected]>",
5+
"version": "1.0.0",
6+
"description": "Auto enter appear.in meetings muted and cam off (configurable).",
7+
"license": "MIT",
8+
"engines": {
9+
"node": ">=8.9.1",
10+
"npm": ">=5.5.1",
11+
"yarn": ">=1.3.2"
12+
},
13+
"devDependencies": {
14+
"@types/chrome": "^0.0.54",
15+
"@types/jest": "^21.1.8",
16+
"@types/text-encoding": "^0.0.32",
17+
"awesome-typescript-loader": "^3.4.1",
18+
"chalk": "^2.3.0",
19+
"chrome-location": "^1.2.1",
20+
"copy-webpack-plugin": "^4.2.3",
21+
"css-loader": "^0.28.7",
22+
"html-loader": "^0.5.1",
23+
"html-webpack-plugin": "^2.30.1",
24+
"jest": "^21.2.1",
25+
"jest-cli": "^21.2.1",
26+
"mv": "^2.1.1",
27+
"on-build-webpack": "^0.1.0",
28+
"style-loader": "^0.19.0",
29+
"ts-jest": "^21.2.4",
30+
"tslint": "^5.8.0",
31+
"typescript": "^2.6.2",
32+
"uglifyjs-webpack-plugin": "^1.1.2",
33+
"webpack": "^3.8.1",
34+
"webpack-merge": "^4.1.1",
35+
"write-file-webpack-plugin": "^4.2.0"
36+
},
37+
"scripts": {
38+
"build": "webpack --config webpack.prod.js",
39+
"watch": "webpack -w --config webpack.dev.js",
40+
"lint": "tslint -c tslint.json 'src/**/*.{ts,tsx,js}' '*.{ts,tsx,js}'",
41+
"test": "jest"
42+
},
43+
"jest": {
44+
"moduleFileExtensions": [
45+
"ts",
46+
"tsx",
47+
"js"
48+
],
49+
"transform": {
50+
"\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
51+
},
52+
"testRegex": "/__tests__/.*\\.[tj]sx?$"
53+
}
54+
}

src/icons/19.png

1.22 KB
Loading

src/icons/96.png

6.37 KB
Loading

src/index.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { navigateToRoom } from './utils/redirect';
2+
import { getSettings } from './utils/settings';
3+
const loc = new URL(window.location.href);
4+
5+
const has = (url: URL, param: string) =>
6+
url.searchParams.has(param) && url.searchParams.get(param) === 'off';
7+
8+
getSettings()
9+
.then(settings => {
10+
if (settings.applyToAllMeetingUrls
11+
|| settings.applyToAllMeetingUrls === undefined) {
12+
const urlDefects = [
13+
settings.audio && !has(loc, 'audio'),
14+
!settings.audio && has(loc, 'audio'),
15+
settings.video && !has(loc, 'video'),
16+
!settings.video && has(loc, 'video'),
17+
];
18+
if (urlDefects.filter(x => x).length > 0) {
19+
navigateToRoom(loc);
20+
}
21+
}
22+
});

src/manifest.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"manifest_version": 2,
3+
"name": "Appear.in meeting room helper",
4+
"description": "",
5+
"version": "0.0.0",
6+
"icons": {
7+
"16": "icons/19.png",
8+
"48": "icons/96.png",
9+
"128": "icons/96.png"
10+
},
11+
"browser_action": {
12+
"default_icon": "icons/19.png",
13+
"default_popup": "popup/index.html",
14+
"default_title": "Appear.in meeting room helper"
15+
},
16+
"content_scripts": [
17+
{
18+
"matches": ["https://appear.in/*"],
19+
"js": ["js/vendor.js", "js/index.js"]
20+
}
21+
],
22+
"options_ui": {
23+
"page": "options/index.html",
24+
"chrome_style": true
25+
},
26+
"permissions": [
27+
"storage"
28+
]
29+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as options from '../roomList';
2+
3+
describe('options page tests', () => {
4+
describe('import/export functionality', () => {
5+
it('should be able to clean a supplied list of meeting rooms', () => {
6+
const defaultRoomList = `
7+
zoo
8+
https://appear.in/zoo
9+
https://appear.in/example
10+
intensity
11+
http://example.org/danger.zone
12+
`;
13+
expect(options.cleanRoomList(defaultRoomList)).toEqual([
14+
'example',
15+
'intensity',
16+
'zoo',
17+
]);
18+
});
19+
});
20+
});

src/options/applyToAllMeetingUrls.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as store from '../utils/store';
2+
3+
const getInput = (): HTMLInputElement =>
4+
document.getElementById('apply-to-followed-urls') as HTMLInputElement;
5+
6+
export const loadApplyToAllMeetingUrls = () => {
7+
return store.get(['applyToAllMeetingUrls'])
8+
.then(({ applyToAllMeetingUrls }) => {
9+
if (applyToAllMeetingUrls === undefined) {
10+
return getInput().checked = true;
11+
}
12+
getInput().checked = applyToAllMeetingUrls;
13+
});
14+
}
15+
16+
export const saveApplyToAllMeetingUrls = () => {
17+
return store.set({ applyToAllMeetingUrls: getInput().checked })
18+
.then(() => {});
19+
};

src/options/index.html

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Appear.in meeting room helper options</title>
5+
<style>
6+
textarea {
7+
width: 100%;
8+
height: 150px;
9+
}
10+
.option-group {
11+
border: 1px solid #ccc;
12+
margin-bottom: 20px;
13+
}
14+
.option-group-title {
15+
font-size: larger;
16+
}
17+
.option-hint {
18+
font-size: smaller;
19+
}
20+
</style>
21+
</head>
22+
<body>
23+
<div id="container">
24+
<fieldset class="option-group">
25+
<legend class="option-group-title">Global options</legend>
26+
<div class="option option-checkbox">
27+
<input type="checkbox" name="apply-to-followed-urls" id="apply-to-followed-urls" />
28+
<label for="apply-to-followed-urls">
29+
Apply default settings to all meeting room links I open
30+
(even those outside my favourite rooms list).
31+
</label>
32+
</div>
33+
</fieldset>
34+
35+
<fieldset class="option-group">
36+
<legend class="option-group-title">My rooms</legend>
37+
<div class="option">
38+
<label for="my-rooms">
39+
Import/export rooms
40+
</label>
41+
<div>
42+
<textarea name="my-rooms" id="my-rooms"></textarea>
43+
<div class="option-hint">List each room name or URL on a new line.</div>
44+
</div>
45+
</div>
46+
</fieldset>
47+
<button id="save-options">Save</button>
48+
</div>
49+
<script src="../js/vendor.js"></script>
50+
<script src="index.js"></script>
51+
</body>
52+
53+
</html>

src/options/index.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { loadRoomList, saveRoomList } from "./roomList";
2+
import { loadApplyToAllMeetingUrls, saveApplyToAllMeetingUrls } from "./applyToAllMeetingUrls";
3+
4+
loadRoomList();
5+
loadApplyToAllMeetingUrls();
6+
7+
document.getElementById('save-options').addEventListener('click', e => {
8+
Promise.all([
9+
saveRoomList(),
10+
saveApplyToAllMeetingUrls()
11+
])
12+
.then(() => {
13+
const n = document.createElement('strong');
14+
n.innerText = 'Saved!';
15+
(e.target as HTMLButtonElement).parentNode.appendChild(n);
16+
setTimeout(() => n.remove(), 1000);
17+
})
18+
.catch(e => {
19+
const n = document.createElement('strong');
20+
n.innerText = 'Error!';
21+
(e.target as HTMLButtonElement).parentNode.appendChild(n);
22+
setTimeout(() => n.remove(), 1000);
23+
});
24+
});

src/options/roomList.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as list from '../utils/list';
2+
import { isAppearInUri, appearInUriToRoomName } from '../utils/appear.in';
3+
import { isAbsoluteUri } from '../utils/uri';
4+
import * as store from '../utils/store';
5+
6+
const myRoomsFieldId = 'my-rooms';
7+
8+
const cleanRoom = (room: string): string => {
9+
const r = room.trim();
10+
if (isAbsoluteUri(r) && !isAppearInUri(r)) {
11+
return '';
12+
} else if (isAppearInUri(r)) {
13+
return appearInUriToRoomName(r);
14+
}
15+
return r;
16+
};
17+
18+
const roomTransducer = (acc: Array<string>, x: string): Array<string> => {
19+
const cx = cleanRoom(x);
20+
if (!cx || acc.includes(cx)) return acc;
21+
return list.push(acc, cx);
22+
};
23+
24+
export const cleanRoomList = (roomList: string): Array<string> =>
25+
roomList
26+
.trim()
27+
.split('\n')
28+
.reduce(roomTransducer, [])
29+
.sort();
30+
31+
export const getRoomList = (): Array<string> =>
32+
cleanRoomList((document.getElementById(myRoomsFieldId) as HTMLTextAreaElement).value);
33+
34+
export const setRoomList = (roomList: Array<string>): void => {
35+
(document.getElementById(myRoomsFieldId) as HTMLTextAreaElement)
36+
.value = roomList.join('\n');
37+
};
38+
39+
export const saveRoomList = () => {
40+
const roomList = getRoomList();
41+
return store.set({ roomList })
42+
.then(() => setRoomList(roomList));
43+
};
44+
45+
export const loadRoomList = () => {
46+
return store.get(['roomList'])
47+
.then(({ roomList }) => setRoomList(roomList));
48+
};

0 commit comments

Comments
 (0)