68 Commits
1.0.1 ... dev

Author SHA1 Message Date
Jorrin
b5be448511 Merge pull request #31 from TheScreechingBagel/patch-1
add bmov.app
2024-04-21 10:23:15 +02:00
Screeching Bagel
c9bf3286ae add bmov.app 2024-04-20 16:55:02 -07:00
William Oldham
3f33d9bc4a Merge pull request #30 from movie-web/fix-android
Fix Android
2024-04-05 21:27:36 +01:00
William Oldham
86a659f478 Add explicit support for Firefox Android 2024-04-05 21:24:00 +01:00
William Oldham
dbdbf10c5d Close the prompt popup when clicking the button 2024-04-05 21:23:49 +01:00
William Oldham
6fca55a6c3 Bump version 2024-03-28 20:33:47 +00:00
William Oldham
50eadd3a14 Merge pull request #28 from movie-web/fix/#1047
fix ts error, fix styling on PermissionRequest
2024-03-28 20:33:24 +00:00
Jorrin
b2ba74bd97 fix ts error, fix styling on PermissionRequest 2024-03-28 20:26:09 +01:00
William Oldham
bb61fef30a Merge pull request #25 from movie-web/remove-lonelil
Remove Lonelil, fix centering and enable strict mode
2024-03-15 19:44:19 +00:00
William Oldham
8e179e4455 Remove any in error handling 2024-03-15 19:42:51 +00:00
William Oldham
327f175241 Do safe checking on exception message 2024-03-15 19:39:03 +00:00
William Oldham
c72ec41228 Bump version 2024-03-15 19:24:07 +00:00
William Oldham
0bd1a39b53 Remove react import 2024-03-15 19:24:03 +00:00
William Oldham
de0a1b3849 Fix centering of permission grant page 2024-03-15 18:53:44 +00:00
William Oldham
8ba0544468 Enable TS strict mode and fix all associated errors 2024-03-15 18:49:12 +00:00
William Oldham
a884c785f0 Remove lonelil as a redirect, Firefox didn't like 2024-03-15 18:48:46 +00:00
William Oldham
0801f64e1b Fix content type for extension deployment 2024-03-05 21:13:59 +00:00
William Oldham
8735f7b4b2 Update deploying.yml 2024-03-05 21:13:22 +00:00
William Oldham
90f941431c Bump extension to 1.1.2 2024-03-05 18:51:26 +00:00
William Oldham
df05547581 Merge pull request #23 from movie-web/feature/#867
add inactive icon if the domain is not whitelisted
2024-03-05 18:51:01 +00:00
William Oldham
a8e3de1451 Update inactive logo and bold enabled/disabled 2024-03-05 18:46:37 +00:00
Jorrin
1801ee238d Delete icon.development.png 2024-03-04 21:51:15 +01:00
William Oldham
434b2475cb Merge pull request #22 from movie-web/feature/update-default-domains
update default domain whitelist, remove movie-web references
2024-03-04 20:49:24 +00:00
William Oldham
50166457b8 Merge pull request #21 from movie-web/fix/#952
add firstPartyDomain for first-party isolation
2024-03-04 20:48:19 +00:00
Jorrin
e2ea8a3c7e remove unused useEffect 2024-03-04 21:45:37 +01:00
Jorrin
213828e818 add inactive icon if the domain is not whitelisted 2024-03-03 22:56:13 +01:00
Jorrin
755bba3e1e update default domain whitelist, remove movie-web references 2024-03-03 20:20:34 +01:00
Jorrin
e7ca90b75f add firstPartyDomain for first-party isolation 2024-03-03 20:14:56 +01:00
Jorrin
d74f0abbf6 Merge pull request #20 from movie-web/fix/improve-error-message
Improve "Domain is not whitelisted" error message
2024-03-03 13:45:24 +01:00
Jorrin
cade0b50ab Add more details to the error message 2024-03-01 21:59:38 +01:00
Jorrin
1ac8147cdd add instruction to domain whitelist error message 2024-03-01 21:57:18 +01:00
William Oldham
6d1fa16553 Bump version 2024-02-21 18:53:08 +00:00
William Oldham
a8417c75a3 Merge pull request #18 from movie-web/fix/#904
always create dynamic rule for fetch request
2024-02-21 18:50:40 +00:00
Jorrin
abf26103e1 always create dynamic rule for fetch request 2024-02-15 14:17:08 +01:00
William Oldham
03e7649bc1 Merge pull request #17 from movie-web/finalize-extension
Update texts + fix responsiveness of new pages
2024-02-10 20:04:53 +00:00
mrjvs
ec4e3b7392 Fix linting 2024-02-10 21:03:06 +01:00
mrjvs
ce31771e5f Update texts + fix responsiveness of new pages
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
2024-02-10 21:00:06 +01:00
mrjvs
a84eb7b0e5 Merge pull request #15 from movie-web/perms
Permission screens
2024-02-07 19:29:21 +01:00
mrjvs
d0ed9952bf Bump version 2024-02-07 19:11:19 +01:00
mrjvs
f9323c1fe3 Fix some styling 2024-02-07 19:07:01 +01:00
Jip Fr
f9444c87fb Merge branch 'perms' of github.com:movie-web/extension into perms 2024-02-07 18:45:37 +01:00
Jip Fr
7d19b32e71 Fix import order 2024-02-07 18:45:34 +01:00
Jip Fr
8294771ac2 Permission page
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠠⢄⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠒⠁⠐⠛⠫⡉⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠔⠊⠁⢀⣠⣶⠯⠋⠉⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠊⠁⠀⢀⣠⣶⠟⠉⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⠈⠍⢑⡨⣴⣶⣈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠉⠀⠀⠀⢀⠔⠙⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢤⣼⠃⠀⠈⠈⣰⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣴⣾⡄⠀⠀⠀⣠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣘⣿⠇⠀⠀⠰⢚⣽⣿⡿⠿⣿⡆⠀⠀⠀⠀⠀⣀⣠⣴⣶⣿⣿⣿⣿⣿⣿⣿⣀⠤⠜⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢀⢽⡏⠀⢠⣴⣶⣿⣿⠙⡇⠐⣸⣁⣠⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢀⠊⠀⢀⣿⣿⠿⠉⠉⠀⠀⠀⠈⠙⢿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠠⠂⠀⠀⡼⢿⣍⠀⠀⠀⠀⠀⣠⠂⠀⢈⢿⣿⣿⣿⢟⣵⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⡔⠁⠀⠀⢰⣿⣿⣿⣷⣄⠀⢀⣼⣁⠀⠀⣼⣈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⢀⠎⠀⠀⠀⠠⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣿⡇⣮⢿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢀⠁⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⢿⣧⢻⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠠⣀⠀⠀⡠⠔⠺⡿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠉⠁⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⢸⣣⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣯⠰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⡿⠿⠿⠿⠟⠉⠉⠀⠙⠿⠿⠋⠉⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠉⠛⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
2024-02-07 18:45:21 +01:00
mrjvs
9bdee875b8 Merge branch 'perms' of https://github.com/movie-web/extension into perms 2024-02-07 18:43:19 +01:00
mrjvs
0092b4191e SetupScreen completed 2024-02-07 18:42:57 +01:00
Jip Fr
dca80c1d04 Put real copy on permission request page 2024-02-07 18:26:56 +01:00
Jip Fr
2f75a9d2ac Permissions page 2024-02-07 18:25:37 +01:00
mrjvs
725e9641e2 Added button and beginning of setup screen 2024-02-07 18:16:42 +01:00
mrjvs
825d96a527 Add icons 2024-02-07 17:42:20 +01:00
mrjvs
b900fbdaa8 Fix label position 2024-02-07 17:27:04 +01:00
mrjvs
c0c733ecfc Fix fonts 2024-02-07 17:23:49 +01:00
mrjvs
263051a305 Less permissions + optional host permissions + no external assets 2024-02-07 16:47:52 +01:00
mrjvs
04e7a8d23c Merge pull request #14 from movie-web/fix/#876
fix headers not being applied to request
2024-02-07 16:11:58 +01:00
Jorrin
3468459693 comma seperated Set-Cookie instead 2024-02-07 16:09:14 +01:00
Jorrin
710b40aa8a add cookies 2024-02-06 20:15:45 +01:00
Jorrin
34c3709b2e fix headers not being applied to request 2024-02-05 22:04:01 +01:00
mrjvs
08f8fbbcfd bump version 2024-01-31 20:05:46 +01:00
mrjvs
bd2733a110 Merge pull request #10 from movie-web/fix-firefox-and-brave
Fix firefox and brave not working
2024-01-31 19:45:26 +01:00
mrjvs
9f34642a96 Add some logging and use full urls 2024-01-31 18:55:34 +01:00
mrjvs
e1fa034074 Add background script to reload on load. Painful fix 2024-01-31 18:55:22 +01:00
William Oldham
3b88b136e8 Merge pull request #8 from movie-web/firefox-android-styling
Make frame compatible with firefox android
2024-01-25 21:28:39 +00:00
mrjvs
67ef3a1e88 Merge pull request #7 from movie-web/feature/#820
Add FormData, URLSearchParams and User-Agent support
2024-01-25 20:37:22 +01:00
mrjvs
302b6ef71c Make frame compatible with firefox android 2024-01-25 20:25:00 +01:00
Jorrin
c740bc6685 Replace record type logic with their own bodyType 2024-01-25 19:38:06 +01:00
Jorrin
f49cd01b7a return body if body type is undefined 2024-01-25 19:27:19 +01:00
Jorrin
65ff4ab91a mr feedback 2024-01-25 19:25:44 +01:00
Jorrin
a2647a58d6 cleanup and fix targetDomain 2024-01-25 16:38:04 +01:00
Jorrin
658f59eb24 serialize body 2024-01-24 19:41:20 +01:00
41 changed files with 919 additions and 278 deletions

10
.github/SECURITY.md vendored
View File

@@ -2,13 +2,9 @@
## Supported Versions ## Supported Versions
The movie-web maintainers only support the latest version of movie-web published at https://movie-web.app. The latest version of movie-web is the only version that is supported, as it is the only version that is being actively developed.
This published version is equivalent to the master branch.
Support is not provided for any forks or mirrors of movie-web.
## Reporting a Vulnerability ## Reporting a Vulnerability
There are two ways you can contact the movie-web maintainers to report a vulnerability: You can contact the movie-web maintainers to report a vulnerability:
- Email [security@movie-web.app](mailto:security@movie-web.app) - Report the vulnerability in the [movie-web Discord server](https://movie-web.github.io/links/discord)
- Report the vulnerability in the [movie-web Discord server](https://discord.movie-web.app)

View File

@@ -94,8 +94,8 @@ jobs:
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./chrome/chrome-mv3-prod.zip asset_path: ./chrome/chrome-mv3-prod.zip
asset_name: extension-mw.chrome.crx asset_name: extension-mw.chrome.zip
asset_content_type: application/x-chrome-extension asset_content_type: application/zip
- name: Upload Firefox release - name: Upload Firefox release
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1

BIN
assets/active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
assets/inactive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

93
assets/inter/OFL.txt Normal file
View File

@@ -0,0 +1,93 @@
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

BIN
assets/inter/bold.ttf Normal file

Binary file not shown.

BIN
assets/inter/regular.ttf Normal file

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{ {
"name": "@movie-web/extension", "name": "@movie-web/extension",
"displayName": "movie-web extension", "displayName": "movie-web extension",
"version": "1.0.1", "version": "1.1.4",
"description": "Enhance your movie-web experience with just one click", "description": "Enhance your movie-web experience with just one click",
"author": "movie-web", "author": "movie-web",
"scripts": { "scripts": {
@@ -44,17 +44,30 @@
"manifest": { "manifest": {
"permissions": [ "permissions": [
"declarativeNetRequest", "declarativeNetRequest",
"tabs" "activeTab",
"cookies"
], ],
"host_permissions": [ "optional_host_permissions": [
"<all_urls>", "<all_urls>"
"https://dev.movie-web.app/*",
"https://movie-web.app/*"
], ],
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "{3fd86354-c73f-4395-9e26-2c5c984579bf}" "id": "{3fd86354-c73f-4395-9e26-2c5c984579bf}"
},
"gecko_android": {
"id": "{3fd86354-c73f-4395-9e26-2c5c984579bf}"
} }
} },
"web_accessible_resources": [
{
"resources": [
"assets/active.png",
"assets/inactive.png"
],
"matches": [
"<all_urls>"
]
}
]
} }
} }

View File

@@ -1,8 +1,17 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;800&display=swap'); @import url("./font.css");
html {
min-height: 300px;
min-width: 300px;
height: 100%;
}
body { body {
min-height: 300px;
min-width: 300px;
margin: 0; margin: 0;
font-family: 'Inter', sans-serif; height: 100%;
max-height: 100%;
} }
.popup { .popup {
@@ -12,3 +21,8 @@ body {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
#__plasmo {
height: 100%;
background-color: #0a0a10;
}

13
src/background.ts Normal file
View File

@@ -0,0 +1,13 @@
import { isChrome } from '~utils/extension';
// Both brave and firefox for some reason need this extension reload,
// If this isn't done, they will never load properly and will fail updateDynamicRules()
if (isChrome()) {
chrome.runtime.onStartup.addListener(() => {
chrome.runtime.reload();
});
} else {
browser.runtime.onStartup.addListener(() => {
browser.runtime.reload();
});
}

View File

@@ -14,6 +14,8 @@ type Response = BaseResponse<{
const handler: PlasmoMessaging.MessageHandler<BaseRequest, Response> = async (req, res) => { const handler: PlasmoMessaging.MessageHandler<BaseRequest, Response> = async (req, res) => {
try { try {
if (!req.sender?.tab?.url) throw new Error('No tab URL found in the request.');
const version = getVersion(); const version = getVersion();
res.send({ res.send({
success: true, success: true,
@@ -24,7 +26,7 @@ const handler: PlasmoMessaging.MessageHandler<BaseRequest, Response> = async (re
} catch (err) { } catch (err) {
res.send({ res.send({
success: false, success: false,
error: err.message, error: err instanceof Error ? err.message : String(err),
}); });
} }
}; };

View File

@@ -2,9 +2,13 @@ import type { PlasmoMessaging } from '@plasmohq/messaging';
import type { BaseRequest } from '~types/request'; import type { BaseRequest } from '~types/request';
import type { BaseResponse } from '~types/response'; import type { BaseResponse } from '~types/response';
import { removeDynamicRules, setDynamicRules } from '~utils/declarativeNetRequest';
import { isFirefox } from '~utils/extension';
import { makeFullUrl } from '~utils/fetcher'; import { makeFullUrl } from '~utils/fetcher';
import { assertDomainWhitelist } from '~utils/storage'; import { assertDomainWhitelist } from '~utils/storage';
const MAKE_REQUEST_DYNAMIC_RULE = 23498;
export interface Request extends BaseRequest { export interface Request extends BaseRequest {
baseUrl?: string; baseUrl?: string;
headers?: Record<string, string>; headers?: Record<string, string>;
@@ -12,7 +16,8 @@ export interface Request extends BaseRequest {
query?: Record<string, string>; query?: Record<string, string>;
readHeaders?: Record<string, string>; readHeaders?: Record<string, string>;
url: string; url: string;
body?: string | FormData | URLSearchParams; body?: any;
bodyType?: 'string' | 'FormData' | 'URLSearchParams' | 'object';
} }
type Response<T> = BaseResponse<{ type Response<T> = BaseResponse<{
@@ -24,31 +29,72 @@ type Response<T> = BaseResponse<{
}; };
}>; }>;
const mapBodyToFetchBody = (body: Request['body'], bodyType: Request['bodyType']): BodyInit => {
if (bodyType === 'FormData') {
const formData = new FormData();
body.forEach(([key, value]: [any, any]) => {
formData.append(key, value.toString());
});
}
if (bodyType === 'URLSearchParams') {
return new URLSearchParams(body);
}
if (bodyType === 'object') {
return JSON.stringify(body);
}
if (bodyType === 'string') {
return body;
}
return body;
};
const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (req, res) => { const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (req, res) => {
try { try {
if (!req.sender?.tab?.url) throw new Error('No tab URL found in the request.');
if (!req.body) throw new Error('No request body found in the request.');
const url = makeFullUrl(req.body.url, req.body);
await assertDomainWhitelist(req.sender.tab.url); await assertDomainWhitelist(req.sender.tab.url);
const response = await fetch(makeFullUrl(req.body.url, req.body), { await setDynamicRules({
ruleId: MAKE_REQUEST_DYNAMIC_RULE,
targetDomains: [new URL(url).hostname],
requestHeaders: req.body.headers,
});
const response = await fetch(url, {
method: req.body.method, method: req.body.method,
headers: req.body.headers, headers: req.body.headers,
body: req.body.body, body: mapBodyToFetchBody(req.body.body, req.body.bodyType),
}); });
await removeDynamicRules([MAKE_REQUEST_DYNAMIC_RULE]);
const contentType = response.headers.get('content-type'); const contentType = response.headers.get('content-type');
const body = contentType?.includes('application/json') ? await response.json() : await response.text(); const body = contentType?.includes('application/json') ? await response.json() : await response.text();
const cookies = await (chrome || browser).cookies.getAll({
url: response.url,
...(isFirefox() && {
firstPartyDomain: new URL(response.url).hostname,
}),
});
res.send({ res.send({
success: true, success: true,
response: { response: {
statusCode: response.status, statusCode: response.status,
headers: Object.fromEntries(response.headers.entries()), // Headers object isn't serializable headers: {
...Object.fromEntries(response.headers.entries()),
'Set-Cookie': cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join(', '),
},
body, body,
finalUrl: response.url, finalUrl: response.url,
}, },
}); });
} catch (err) { } catch (err) {
console.error('failed request', err);
res.send({ res.send({
success: false, success: false,
error: err.message, error: err instanceof Error ? err.message : String(err),
}); });
} }
}; };

View File

@@ -11,6 +11,9 @@ type Request = BaseRequest & {
const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (req, res) => { const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (req, res) => {
try { try {
if (!req.sender?.tab?.id) throw new Error('No tab ID found in the request.');
if (!req.body) throw new Error('No body found in the request.');
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
searchParams.set('redirectUrl', req.body.redirectUrl); searchParams.set('redirectUrl', req.body.redirectUrl);
const url = (chrome || browser).runtime.getURL(`/tabs/${req.body.page}.html?${searchParams.toString()}`); const url = (chrome || browser).runtime.getURL(`/tabs/${req.body.page}.html?${searchParams.toString()}`);
@@ -29,7 +32,7 @@ const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (re
} catch (err) { } catch (err) {
res.send({ res.send({
success: false, success: false,
error: err.message, error: err instanceof Error ? err.message : String(err),
}); });
} }
}; };

View File

@@ -2,7 +2,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging';
import type { BaseRequest } from '~types/request'; import type { BaseRequest } from '~types/request';
import type { BaseResponse } from '~types/response'; import type { BaseResponse } from '~types/response';
import { isChrome } from '~utils/extension'; import { setDynamicRules } from '~utils/declarativeNetRequest';
import { assertDomainWhitelist } from '~utils/storage'; import { assertDomainWhitelist } from '~utils/storage';
interface Request extends BaseRequest { interface Request extends BaseRequest {
@@ -13,115 +13,20 @@ interface Request extends BaseRequest {
responseHeaders?: Record<string, string>; responseHeaders?: Record<string, string>;
} }
const mapHeadersToDeclarativeNetRequestHeaders = (
headers: Record<string, string>,
op: string,
): { header: string; operation: any; value: string }[] => {
return Object.entries(headers).map(([name, value]) => ({
header: name,
operation: op,
value,
}));
};
const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (req, res) => { const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (req, res) => {
try { try {
await assertDomainWhitelist(req.sender.tab.url); if (!req.sender?.tab?.url) throw new Error('No tab URL found in the request.');
if (isChrome()) { if (!req.body) throw new Error('No request body found in the request.');
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [req.body.ruleId],
addRules: [
{
id: req.body.ruleId,
condition: {
...(req.body.targetDomains && { requestDomains: req.body.targetDomains }),
...(req.body.targetRegex && { regexFilter: req.body.targetRegex }),
},
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
...(req.body.requestHeaders && Object.keys(req.body.requestHeaders).length > 0
? {
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(
req.body.requestHeaders,
chrome.declarativeNetRequest.HeaderOperation.SET,
),
}
: {}),
responseHeaders: [
{
header: 'Access-Control-Allow-Origin',
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: '*',
},
{
header: 'Access-Control-Allow-Methods',
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
},
{
header: 'Access-Control-Allow-Headers',
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: '*',
},
...mapHeadersToDeclarativeNetRequestHeaders(
req.body.responseHeaders ?? {},
chrome.declarativeNetRequest.HeaderOperation.SET,
),
],
},
},
],
});
if (chrome.runtime.lastError?.message) throw new Error(chrome.runtime.lastError.message);
} else {
await browser.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [req.body.ruleId],
addRules: [
{
id: req.body.ruleId,
condition: {
...(req.body.targetDomains && { requestDomains: req.body.targetDomains }),
...(req.body.targetRegex && { regexFilter: req.body.targetRegex }),
},
action: {
type: 'modifyHeaders',
...(req.body.requestHeaders && Object.keys(req.body.requestHeaders).length > 0
? {
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(req.body.requestHeaders, 'set'),
}
: {}),
responseHeaders: [
{
header: 'Access-Control-Allow-Origin',
operation: 'set',
value: '*',
},
{
header: 'Access-Control-Allow-Methods',
operation: 'set',
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
},
{
header: 'Access-Control-Allow-Headers',
operation: 'set',
value: '*',
},
...mapHeadersToDeclarativeNetRequestHeaders(req.body.responseHeaders ?? {}, 'set'),
],
},
},
],
});
if (browser.runtime.lastError?.message) throw new Error(browser.runtime.lastError.message);
}
await assertDomainWhitelist(req.sender.tab.url);
await setDynamicRules(req.body);
res.send({ res.send({
success: true, success: true,
}); });
} catch (err) { } catch (err) {
res.send({ res.send({
success: false, success: false,
error: err.message, error: err instanceof Error ? err.message : String(err),
}); });
} }
}; };

View File

@@ -1,6 +1,6 @@
.bottom-label { .bottom-label {
position: absolute; position: absolute;
bottom: .25rem; bottom: 1rem;
display: flex; display: flex;
align-items: center; align-items: center;
gap: .5rem; gap: .5rem;
@@ -9,9 +9,18 @@
font-size: 14px; font-size: 14px;
} }
.top-right-label {
position: absolute;
right: 1rem;
top: 1rem;
font-weight: normal;
color: #4A4863;
font-size: 14px;
}
.dot { .dot {
width: 2px; width: 3px;
height: 2px; height: 3px;
background: currentColor; background: currentColor;
border-radius: 100px; border-radius: 100px;
} }

View File

@@ -12,3 +12,9 @@ export function BottomLabel() {
</h3> </h3>
); );
} }
export function TopRightLabel() {
const version = useVersion({ prefixed: true });
return <h3 className="top-right-label">{version}</h3>;
}

41
src/components/Button.css Normal file
View File

@@ -0,0 +1,41 @@
.button {
padding: 1rem;
border-radius: 12px;
border: 0;
font-size: 1rem;
text-align: center;
cursor: pointer;
transition: background-color 100ms ease-in-out, color 100ms ease-in-out, transform 100ms ease-in-out;
}
.button.full {
width: 100%;
}
.button, .button:focus, .button:active, .button:visited {
text-decoration: none;
}
.button:active {
transform: scale(1.05);
}
.button.button-secondary {
background-color: #222033;
color: white;
}
.button.button-secondary:hover {
background-color: #2c2941;
color: white;
}
.button.button-primary {
background-color: #4F328A;
color: white;
}
.button.button-primary:hover {
background-color: #5E3F9D;
color: white;
}

27
src/components/Button.tsx Normal file
View File

@@ -0,0 +1,27 @@
import type { ReactNode } from 'react';
import './Button.css';
export interface ButtonProps {
type?: 'primary' | 'secondary';
href?: string;
children?: ReactNode;
onClick?: () => void;
full?: boolean;
className?: string;
}
export function Button(props: ButtonProps) {
const classes = `button button-${props.type ?? 'primary'} ${props.className ?? ''} ${props.full ? 'full' : ''}`;
if (props.href)
return (
<a href={props.href} className={classes} target="_blank" rel="noreferrer">
{props.children}
</a>
);
return (
<button className={classes} type="button" onClick={props.onClick}>
{props.children}
</button>
);
}

View File

@@ -5,11 +5,11 @@
font-size: 16px; font-size: 16px;
} }
.disabled svg { .disabled .icon {
display: block; font-size: 1.5rem;
width: 1.5rem; margin-bottom: 1rem;
height: 1.5rem; text-align: center;
margin: 0 auto; display: inline-block;
color: #B44868; color: #B44868;
} }

View File

@@ -4,7 +4,9 @@ import { Icon } from './Icon';
export function DisabledScreen() { export function DisabledScreen() {
return ( return (
<div className="disabled"> <div className="disabled">
<Icon name="warningCircle" /> <div className="icon">
<Icon name="warningCircle" />
</div>
<p> <p>
The <strong>movie-web extension</strong> can not be used on this page The <strong>movie-web extension</strong> can not be used on this page
</p> </p>

View File

@@ -1,4 +1,6 @@
.frame { .frame {
height: 100%;
width: 100%;
background-color: #0a080e; background-color: #0a080e;
background-image: radial-gradient(271.48% 132.05% at 136.13% 65.62%, #271945b3 0%, #1c1c2c00 100%), radial-gradient(671.15% 123.02% at 76.68% -34.38%, #272753 0%, #17172000 100%); background-image: radial-gradient(271.48% 132.05% at 136.13% 65.62%, #271945b3 0%, #1c1c2c00 100%), radial-gradient(671.15% 123.02% at 76.68% -34.38%, #272753 0%, #17172000 100%);
} }

View File

@@ -7,9 +7,5 @@ export interface FrameProps {
} }
export function Frame(props: FrameProps) { export function Frame(props: FrameProps) {
return ( return <div className="frame">{props.children}</div>;
<div className="frame" style={{ width: 300, height: 300 }}>
{props.children}
</div>
);
} }

View File

@@ -1,10 +1,25 @@
const icons = { const icons = {
power: `<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg"><g filter="url(#filter0_i_1153_291)"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.4375 2.875C16.4375 2.36114 16.2334 1.86833 15.87 1.50498C15.5067 1.14163 15.0139 0.9375 14.5 0.9375C13.9861 0.9375 13.4933 1.14163 13.13 1.50498C12.7666 1.86833 12.5625 2.36114 12.5625 2.875V15.7917C12.5625 16.3055 12.7666 16.7983 13.13 17.1617C13.4933 17.525 13.9861 17.7292 14.5 17.7292C15.0139 17.7292 15.5067 17.525 15.87 17.1617C16.2334 16.7983 16.4375 16.3055 16.4375 15.7917V2.875ZM9.14475 6.42708C9.35678 6.28621 9.53899 6.10495 9.68097 5.89366C9.82295 5.68237 9.92192 5.44519 9.97224 5.19565C10.0226 4.94611 10.0232 4.6891 9.97422 4.4393C9.92521 4.1895 9.82748 3.9518 9.68661 3.73977C9.54574 3.52774 9.36448 3.34553 9.15319 3.20355C8.9419 3.06157 8.70471 2.9626 8.45517 2.91228C8.20563 2.86197 7.94863 2.86129 7.69883 2.9103C7.44903 2.95931 7.21132 3.05704 6.99929 3.19792C5.13427 4.43483 3.60458 6.11435 2.54683 8.08651C1.48907 10.0587 0.936176 12.2621 0.937502 14.5C0.937502 21.9904 7.00963 28.0625 14.5 28.0625C21.9904 28.0625 28.0625 21.9904 28.0625 14.5C28.0625 9.78025 25.651 5.62625 22.0007 3.19792C21.5725 2.91358 21.0489 2.811 20.545 2.91274C20.0412 3.01448 19.5984 3.3122 19.314 3.74042C19.0297 4.16863 18.9271 4.69226 19.0289 5.19611C19.1306 5.69995 19.4283 6.14275 19.8565 6.42708C21.5907 7.5775 22.9083 9.25578 23.6143 11.2134C24.3203 13.1711 24.3771 15.3041 23.7763 17.2965C23.1755 19.289 21.9491 21.035 20.2786 22.2761C18.6081 23.5172 16.5824 24.1873 14.5013 24.1873C12.4202 24.1873 10.3945 23.5172 8.72399 22.2761C7.05349 21.035 5.82705 19.289 5.22627 17.2965C4.62548 15.3041 4.68228 13.1711 5.38826 11.2134C6.09424 9.25578 7.41057 7.5775 9.14475 6.42708Z" fill="currentColor" /></g><defs><filter id="filter0_i_1153_291" x="0.9375" y="0.9375" width="27.125" height="27.125"filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix" /><feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" /><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /><feOffset /><feGaussianBlur stdDeviation="2" /><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" /><feBlend mode="normal" in2="shape" result="effect1_innerShadow_1153_291" /></filter></defs></svg>`, power: `<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg"><g filter="url(#filter0_i_1153_291)"><path fill-rule="evenodd" clip-rule="evenodd" d="M16.4375 2.875C16.4375 2.36114 16.2334 1.86833 15.87 1.50498C15.5067 1.14163 15.0139 0.9375 14.5 0.9375C13.9861 0.9375 13.4933 1.14163 13.13 1.50498C12.7666 1.86833 12.5625 2.36114 12.5625 2.875V15.7917C12.5625 16.3055 12.7666 16.7983 13.13 17.1617C13.4933 17.525 13.9861 17.7292 14.5 17.7292C15.0139 17.7292 15.5067 17.525 15.87 17.1617C16.2334 16.7983 16.4375 16.3055 16.4375 15.7917V2.875ZM9.14475 6.42708C9.35678 6.28621 9.53899 6.10495 9.68097 5.89366C9.82295 5.68237 9.92192 5.44519 9.97224 5.19565C10.0226 4.94611 10.0232 4.6891 9.97422 4.4393C9.92521 4.1895 9.82748 3.9518 9.68661 3.73977C9.54574 3.52774 9.36448 3.34553 9.15319 3.20355C8.9419 3.06157 8.70471 2.9626 8.45517 2.91228C8.20563 2.86197 7.94863 2.86129 7.69883 2.9103C7.44903 2.95931 7.21132 3.05704 6.99929 3.19792C5.13427 4.43483 3.60458 6.11435 2.54683 8.08651C1.48907 10.0587 0.936176 12.2621 0.937502 14.5C0.937502 21.9904 7.00963 28.0625 14.5 28.0625C21.9904 28.0625 28.0625 21.9904 28.0625 14.5C28.0625 9.78025 25.651 5.62625 22.0007 3.19792C21.5725 2.91358 21.0489 2.811 20.545 2.91274C20.0412 3.01448 19.5984 3.3122 19.314 3.74042C19.0297 4.16863 18.9271 4.69226 19.0289 5.19611C19.1306 5.69995 19.4283 6.14275 19.8565 6.42708C21.5907 7.5775 22.9083 9.25578 23.6143 11.2134C24.3203 13.1711 24.3771 15.3041 23.7763 17.2965C23.1755 19.289 21.9491 21.035 20.2786 22.2761C18.6081 23.5172 16.5824 24.1873 14.5013 24.1873C12.4202 24.1873 10.3945 23.5172 8.72399 22.2761C7.05349 21.035 5.82705 19.289 5.22627 17.2965C4.62548 15.3041 4.68228 13.1711 5.38826 11.2134C6.09424 9.25578 7.41057 7.5775 9.14475 6.42708Z" fill="currentColor" /></g><defs><filter id="filter0_i_1153_291" x="0.9375" y="0.9375" width="27.125" height="27.125"filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix" /><feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" /><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /><feOffset /><feGaussianBlur stdDeviation="2" /><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" /><feBlend mode="normal" in2="shape" result="effect1_innerShadow_1153_291" /></filter></defs></svg>`,
warningCircle: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`, warningCircle: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`,
github: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 496 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>`,
cookie: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M247.2 17c-22.1-3.1-44.6 .9-64.4 11.4l-74 39.5C89.1 78.4 73.2 94.9 63.4 115L26.7 190.6c-9.8 20.1-13 42.9-9.1 64.9l14.5 82.8c3.9 22.1 14.6 42.3 30.7 57.9l60.3 58.4c16.1 15.6 36.6 25.6 58.7 28.7l83 11.7c22.1 3.1 44.6-.9 64.4-11.4l74-39.5c19.7-10.5 35.6-27 45.4-47.2l36.7-75.5c9.8-20.1 13-42.9 9.1-64.9l-14.6-82.8c-3.9-22.1-14.6-42.3-30.7-57.9L388.9 57.5c-16.1-15.6-36.6-25.6-58.7-28.7L247.2 17zM208 144a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM144 336a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm224-64a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>`,
windows: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M432 64H208c-8.8 0-16 7.2-16 16V96H128V80c0-44.2 35.8-80 80-80H432c44.2 0 80 35.8 80 80V304c0 44.2-35.8 80-80 80H416V320h16c8.8 0 16-7.2 16-16V80c0-8.8-7.2-16-16-16zM0 192c0-35.3 28.7-64 64-64H320c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V192zm64 32c0 17.7 14.3 32 32 32H288c17.7 0 32-14.3 32-32s-14.3-32-32-32H96c-17.7 0-32 14.3-32 32z"/></svg>`,
shield: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M269.4 2.9C265.2 1 260.7 0 256 0s-9.2 1-13.4 2.9L54.3 82.8c-22 9.3-38.4 31-38.3 57.2c.5 99.2 41.3 280.7 213.6 363.2c16.7 8 36.1 8 52.8 0C454.7 420.7 495.5 239.2 496 140c.1-26.2-16.3-47.9-38.3-57.2L269.4 2.9zM160 154.4c0-5.8 4.7-10.4 10.4-10.4h.2c3.4 0 6.5 1.6 8.5 4.3l40 53.3c3 4 7.8 6.4 12.8 6.4h48c5 0 9.8-2.4 12.8-6.4l40-53.3c2-2.7 5.2-4.3 8.5-4.3h.2c5.8 0 10.4 4.7 10.4 10.4V272c0 53-43 96-96 96s-96-43-96-96V154.4zM216 288a16 16 0 1 0 0-32 16 16 0 1 0 0 32zm96-16a16 16 0 1 0 -32 0 16 16 0 1 0 32 0z"/></svg>`,
logo: `<svg width="1em" height="1em" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.2254 4.95486L10.5285 5.65174L9.83162 4.95486L10.5285 4.25799L7.7414 1.4709L7.04453 2.16777L6.34766 1.4709L7.04453 0.774022L6.34766 0.0771484L0.0761918 6.34861L0.773066 7.04549L1.46994 6.34861L2.16681 7.04549L1.46994 7.74236L4.25743 10.5299L4.95431 9.83298L5.65118 10.5299L4.95431 11.2267L5.65118 11.9236L11.9226 5.65214L11.2254 4.95486ZM2.86529 6.35021L2.16681 5.65174L2.86369 4.95487L3.56056 5.65174L2.86529 6.35021ZM4.25904 4.95647L3.56056 4.25799L4.25703 3.56152L4.95391 4.25839L4.25904 4.95647ZM5.65278 3.56272L4.95431 2.86424L5.65078 2.16777L6.34766 2.86464L5.65278 3.56272ZM6.34766 9.83258L5.65078 9.13571L6.34766 8.43883L7.04453 9.13571L6.34766 9.83258ZM7.7414 8.43883L7.04453 7.74196L7.741 7.04549L8.43788 7.74236L7.7414 8.43883ZM9.13515 7.04509L8.43828 6.34821L9.13475 5.65174L9.83162 6.34861L9.13515 7.04509Z" fill="currentColor"/></svg>`,
network: `<svg width="1em" height="1em" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M256 64H384v64H256V64zM240 0c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48h48v32H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h96v32H80c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48H240c26.5 0 48-21.5 48-48V368c0-26.5-21.5-48-48-48H192V288H448v32H400c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48H560c26.5 0 48-21.5 48-48V368c0-26.5-21.5-48-48-48H512V288h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H352V192h48c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48H240zM96 448V384H224v64H96zm320-64H544v64H416V384z"/></svg>`,
}; };
export type Icons = keyof typeof icons; export type Icons = keyof typeof icons;
export function Icon(props: { name: Icons }) { export function Icon(props: { name: Icons }) {
// eslint-disable-next-line react/no-danger return (
return <div dangerouslySetInnerHTML={{ __html: icons[props.name] }} />; <div
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: icons[props.name] }}
style={{
width: '1em',
height: '1em',
}}
/>
);
} }

View File

@@ -1,19 +0,0 @@
import { Icon } from '~components/Icon';
import '~tabs/PermissionGrant.css';
export interface PermissionMissingProps {
onGrant?: () => void;
}
export function PermissionMissingScreen(props: PermissionMissingProps) {
return (
<div className="disabled">
<Icon name="warningCircle" />
<p style={{ paddingBottom: 25, paddingTop: 10 }}>The extension is missing permissions it needs to function</p>
<button type="button" className="grant-permission-btn" onClick={props.onGrant}>
Grant Permission
</button>
</div>
);
}

View File

@@ -0,0 +1,41 @@
.setup-screen .title {
font-size: 1.5rem;
color: white;
}
.setup-screen .paragraph {
font-size: 1rem;
color: #666485;
max-width: 220px;
}
.setup-screen .top {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
.setup-screen {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
box-sizing: border-box;
padding: 2rem;
text-align: center;
}
.setup-screen .icon {
background-color: #0b0b1b77;
color: #945DCC;
height: 40px;
font-size: 20px;
width: 40px;
border-radius: 9999px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}

View File

@@ -0,0 +1,31 @@
import { useCallback } from 'react';
import { Button } from '~components/Button';
import { Icon } from '~components/Icon';
import './SetupScreen.css';
export function SetupScreen() {
const open = useCallback(() => {
const url = (chrome || browser).runtime.getURL(`/tabs/PermissionRequest.html`);
(chrome || browser).tabs.create({ url });
window.close();
}, []);
return (
<div className="setup-screen">
<div className="top">
<div className="icon">
<Icon name="logo" />
</div>
<h1 className="title">Let&apos;s get this set up!</h1>
<p className="paragraph" style={{ paddingBottom: 25, paddingTop: 10 }}>
To get started, we need to setup some things first. Click the button below to continue.
</p>
</div>
<Button onClick={open} full>
Continue
</Button>
</div>
);
}

View File

@@ -36,7 +36,7 @@ export function ToggleButton(props: ToggleButtonProps) {
</button> </button>
</div> </div>
<p> <p>
Extension {props.active ? 'enabled' : 'disabled'} <br /> on <strong>{props.domain}</strong> Extension <strong>{props.active ? 'enabled' : 'disabled'}</strong> <br /> on <strong>{props.domain}</strong>
</p> </p>
</div> </div>
); );

View File

@@ -2,8 +2,7 @@ import { relayMessage } from '@plasmohq/messaging';
import type { PlasmoCSConfig } from 'plasmo'; import type { PlasmoCSConfig } from 'plasmo';
export const config: PlasmoCSConfig = { export const config: PlasmoCSConfig = {
// <all_urls> works for chrome, but not for firefox, so we add explicit domains for firefox matches: ['<all_urls>'],
matches: ['<all_urls>', 'https://dev.movie-web.app/*', 'https://movie-web.app/*'],
}; };
relayMessage({ relayMessage({

28
src/font.css Normal file
View File

@@ -0,0 +1,28 @@
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400;
src: url(data-base64:~assets/inter/regular.ttf);
}
@font-face {
font-family: "Inter";
font-style: bold;
font-weight: 700;
src: url(data-base64:~assets/inter/bold.ttf);
}
body, html {
font-family: "Inter", Arial, Helvetica, sans-serif;
font-size: 13px;
}
* {
margin: 0;
padding: 0;
}
button {
font-family: inherit;
font-size: inherit;
}

View File

@@ -15,5 +15,5 @@ export function useDomain(): null | string {
}; };
}, []); }, []);
return makeUrlIntoDomain(domain); return domain ? makeUrlIntoDomain(domain) : null;
} }

View File

@@ -8,12 +8,12 @@ export function useDomainWhitelist() {
const removeDomain = useCallback((domain: string | null) => { const removeDomain = useCallback((domain: string | null) => {
if (!domain) return; if (!domain) return;
setDomainWhitelist((s) => [...s.filter((v) => v !== domain)]); setDomainWhitelist((s) => [...(s ?? []).filter((v) => v !== domain)]);
}, []); }, []);
const addDomain = useCallback((domain: string | null) => { const addDomain = useCallback((domain: string | null) => {
if (!domain) return; if (!domain) return;
setDomainWhitelist((s) => [...s.filter((v) => v !== domain), domain]); setDomainWhitelist((s) => [...(s ?? []).filter((v) => v !== domain), domain]);
}, []); }, []);
return { return {
@@ -23,10 +23,15 @@ export function useDomainWhitelist() {
}; };
} }
export function useToggleWhitelistDomain(domain: string) { export function useToggleWhitelistDomain(domain: string | null) {
const { domainWhitelist, addDomain, removeDomain } = useDomainWhitelist(); const { domainWhitelist, addDomain, removeDomain } = useDomainWhitelist();
const isWhitelisted = domainWhitelist.includes(domain); const isWhitelisted = domainWhitelist.includes(domain ?? '');
const { grantPermission } = usePermission(); const { grantPermission } = usePermission();
const iconPath = (chrome || browser).runtime.getURL(isWhitelisted ? 'assets/active.png' : 'assets/inactive.png');
(chrome || browser).action.setIcon({
path: iconPath,
});
const toggle = useCallback(() => { const toggle = useCallback(() => {
if (!isWhitelisted) { if (!isWhitelisted) {

View File

@@ -1,28 +1,35 @@
import { BottomLabel } from '~components/BottomLabel'; import { BottomLabel, TopRightLabel } from '~components/BottomLabel';
import { DisabledScreen } from '~components/DisabledScreen'; import { DisabledScreen } from '~components/DisabledScreen';
import { Frame } from '~components/Frame'; import { Frame } from '~components/Frame';
import { PermissionMissingScreen } from '~components/PermissionMissingScreen'; import { SetupScreen } from '~components/SetupScreen';
import { ToggleButton } from '~components/ToggleButton'; import { ToggleButton } from '~components/ToggleButton';
import { useDomain } from '~hooks/useDomain'; import { useDomain } from '~hooks/useDomain';
import { useToggleWhitelistDomain } from '~hooks/useDomainWhitelist'; import { useToggleWhitelistDomain } from '~hooks/useDomainWhitelist';
import './Popup.css';
import { usePermission } from '~hooks/usePermission'; import { usePermission } from '~hooks/usePermission';
import './Popup.css';
function IndexPopup() { function IndexPopup() {
const domain = useDomain(); const domain = useDomain();
const { isWhitelisted, toggle } = useToggleWhitelistDomain(domain); const { isWhitelisted, toggle } = useToggleWhitelistDomain(domain);
const { grantPermission, hasPermission } = usePermission(); const { hasPermission } = usePermission();
let page = 'toggle'; let page = 'toggle';
if (!hasPermission) page = 'perm'; if (!hasPermission) page = 'perm';
else if (!domain) page = 'disabled'; else if (!domain) page = 'disabled';
return ( return page === 'perm' ? (
<Frame> <Frame>
<div className="popup"> <div className="popup">
{page === 'toggle' ? <ToggleButton active={isWhitelisted} onClick={toggle} domain={domain} /> : null} <SetupScreen />
<TopRightLabel />
</div>
</Frame>
) : (
<Frame>
<div className="popup">
{page === 'toggle' && domain ? <ToggleButton active={isWhitelisted} onClick={toggle} domain={domain} /> : null}
{page === 'disabled' ? <DisabledScreen /> : null} {page === 'disabled' ? <DisabledScreen /> : null}
{page === 'perm' ? <PermissionMissingScreen onGrant={grantPermission} /> : null}
<BottomLabel /> <BottomLabel />
</div> </div>
</Frame> </Frame>

View File

@@ -1,26 +1,23 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;800&display=swap'); @import url("../font.css");
* {
margin: 0;
padding: 0;
}
html { html {
height: 100%; height: 100%;
font-size: 16px;
} }
body { body {
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
font-family: 'Inter', sans-serif;
} }
#__plasmo { #__plasmo {
display: flex;
flex-direction: column;
height: 100%; height: 100%;
background-color: #0a0a10; background-color: #0A0A10;
} }
.container { .container.permission-grant {
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
@@ -28,76 +25,49 @@ body {
justify-content: center; justify-content: center;
} }
.inner-container { .permission-grant .inner-container {
width: 400px; width: 400px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
} }
.permission-card { .permission-grant .permission-card {
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column;
justify-content: center; justify-content: center;
background-color: #0f0f1b; border-radius: 20px;
border-radius: 10px; padding: 40px;
height: 125px; padding-top: 50px;
padding-right: 40px;
padding-left: 40px;
font-size: 14px; font-size: 14px;
border: 1px solid #20202d; border: 1px solid #272A37;
} }
.footer { .permission-grant h1 {
display: flex; font-size: 1.25rem;
margin-bottom: 0.5rem;
} }
.color-white { .permission-grant .permission-card > p {
font-size: 1rem;
margin-top: .5rem;
margin-bottom: 1rem;
}
.permission-grant .color-white {
color: #ffffff; color: #ffffff;
} }
.text-color { .permission-grant .text-color {
color: #73739d; color: #73739d;
} }
.go-back-btn, .permission-grant .buttons {
.grant-permission-btn { width: 100%;
color: #ffffff; margin-top: 2rem;
padding: 10px 20px;
border-radius: 10px;
border: 1px solid #0000;
cursor: pointer;
font-size: 14px;
} }
.go-back-btn { .permission-grant .buttons>*+* {
background: margin-top: 1rem;
linear-gradient(to right, #151522, #181b2a) padding-box,
linear-gradient(50deg, #151522, #181b2a, #456b95) border-box;
}
.grant-permission-btn {
background:
linear-gradient(to right, #482179, #8a39e6) padding-box,
linear-gradient(50deg, #482179, #4f3585, #b79ae0) border-box;
}
.go-back-btn:hover {
background:
linear-gradient(to right, #2a334e, #2f3552) padding-box,
linear-gradient(50deg, #2a334e, #2f3552, #6086b7) border-box;
}
.grant-permission-btn:hover {
background:
linear-gradient(to right, #603a9a, #a25ff5) padding-box,
linear-gradient(50deg, #603a9a, #653c9f, #d9aef1) border-box;
}
.grant-permission-btn:disabled {
background:
linear-gradient(to right, #311e4b, #6b4b99) padding-box,
linear-gradient(50deg, #311e4b, #3b265b, #704fa5) border-box;
cursor: not-allowed;
opacity: 0.5;
} }

View File

@@ -1,21 +1,34 @@
import { useDomainWhitelist } from '~hooks/useDomainWhitelist'; import { Button } from '~components/Button';
import { usePermission } from '~hooks/usePermission'; import { usePermission } from '~hooks/usePermission';
import { makeUrlIntoDomain } from '~utils/domains'; import { makeUrlIntoDomain } from '~utils/domains';
import './PermissionGrant.css'; import './PermissionGrant.css';
export default function PermissionGrant() { export default function PermissionGrant() {
const { domainWhitelist } = useDomainWhitelist(); const { grantPermission } = usePermission();
const { hasPermission, grantPermission } = usePermission();
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
const redirectUrl = queryParams.get('redirectUrl') ?? 'https://movie-web.app'; const redirectUrl = queryParams.get('redirectUrl') ?? undefined;
const domain = makeUrlIntoDomain(redirectUrl); const domain = redirectUrl ? makeUrlIntoDomain(redirectUrl) : undefined;
const permissionsGranted = domainWhitelist.includes(domain) && hasPermission; if (!domain) {
return (
<div className="permission-grant container">
<div className="inner-container">
<div className="permission-card">
<h1 className="color-white">Permission</h1>
<p className="text-color" style={{ textAlign: 'center' }}>
No domain found to grant permission to.
</p>
</div>
</div>
</div>
);
}
const redirectBack = () => { const redirectBack = () => {
chrome.tabs.getCurrent((tab) => { chrome.tabs.getCurrent((tab) => {
if (!tab?.id) return;
chrome.tabs.update(tab.id, { url: redirectUrl }); chrome.tabs.update(tab.id, { url: redirectUrl });
}); });
}; };
@@ -27,30 +40,22 @@ export default function PermissionGrant() {
}; };
return ( return (
<div className="container"> <div className="permission-grant container">
<div className="inner-container"> <div className="inner-container">
<h1 className="color-white">Permission</h1>
<p className="text-color" style={{ fontSize: 13 }}>
Websites need to ask for permission <br /> before they can use this extension
</p>
<div className="permission-card"> <div className="permission-card">
<h1 className="color-white">Permission</h1>
<p className="text-color" style={{ textAlign: 'center' }}> <p className="text-color" style={{ textAlign: 'center' }}>
The website <span className="color-white">{domain}</span> wants to <br /> use the extension on their page. The website <span className="color-white">{domain}</span> wants to <br /> use the extension on their page.
Do you trust them?
</p> </p>
</div> <div className="buttons">
<div className="footer"> <Button full onClick={handleGrantPermission}>
<button type="button" className="go-back-btn" onClick={redirectBack}> Grant Permission
Go back </Button>
</button> <Button full onClick={redirectBack} type="secondary">
<div style={{ flex: 1 }} /> Decline
<button </Button>
type="button" </div>
className="grant-permission-btn"
onClick={handleGrantPermission}
disabled={permissionsGranted}
>
Grant Permission
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,141 @@
@import url("../font.css");
html {
font-size: 16px;
}
body {
background-color: #0A0A10;
color: white;
padding-bottom: 50px;
}
#__plasmo {
height: unset;
}
.permission-request.container {
width: 90%;
margin: 100px auto;
max-width: 628px;
}
.permission-request .inner-container {
display: flex;
flex-direction: column;
}
.permission-request h1 {
font-size: 2rem;
font-weight: bold;
}
.permission-request h2 {
font-size: 1.5rem;
font-weight: bold;
margin-top: 4rem;
margin-bottom: 1rem;
}
.permission-request .text-color {
color: #7C7C97;
}
.permission-request .paragraph {
font-size: 1rem;
margin-top: 20px;
max-width: 500px;
line-height: 1.3;
}
.permission-request .card-list {
margin: 1rem 0;
}
.permission-request .card-list>*+* {
margin-top: 1rem;
}
.permission-request .card {
padding: 25px 20px;
border: 1px solid #272A37;
display: grid;
grid-template-columns: auto 1fr auto;
gap: 1rem;
border-radius: 11px;
}
@media screen and (max-width: 550px) {
.permission-request .card {
grid-template-columns: auto;
grid-template-rows: auto auto auto;
}
}
.permission-request .card .icon-circle {
width: 2rem;
height: 2rem;
font-size: 1rem;
background-color: rgba(39, 42, 55, 0.35);
border: 1px solid #272A37;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.permission-request .card.purple, .permission-request .card.purple .icon-circle {
border-color: #49277C;
}
.permission-request .card.purple .icon-circle {
background-color: rgba(51, 27, 87, .4);
}
.permission-request .card .icon-circle > div {
display: block;
width: 1rem;
height: 1rem;
}
.permission-request .card h3 {
font-size: 1rem;
font-weight: bold;
color: white;
margin-top: 5px;
}
.permission-request .card .paragraph {
margin-top: 0.5rem;
}
.permission-request .card .center-y {
display: flex;
align-items: center;
}
.permission-request .card button {
padding: 1rem;
border-radius: 10px;
border: 0;
font-size: 1rem;
font-weight: bold;
background-color: #222033;
color: white;
}
.permission-request .card:not(.purple) .icon-circle {
color: #7C7C97;
}
.permission-request .footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100px;
background: linear-gradient(to top, #0A0A10 30%, transparent);
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -0,0 +1,107 @@
import { useCallback } from 'react';
import { Button } from '~components/Button';
import { Icon } from '~components/Icon';
import { usePermission } from '~hooks/usePermission';
import './PermissionRequest.css';
function Card(props: { purple?: boolean; children: React.ReactNode; icon?: React.ReactNode; right?: React.ReactNode }) {
return (
<div className={['card', props.purple ? 'purple' : ''].join(' ')}>
<div>
<div className="icon-circle">{props.icon}</div>
</div>
<div>{props.children}</div>
{props.right ? <div className="center-y">{props.right}</div> : null}
</div>
);
}
export default function PermissionRequest() {
const { grantPermission } = usePermission();
const grant = useCallback(() => {
grantPermission().then(() => window.close());
}, [grantPermission]);
return (
<div className="container permission-request">
<div className="inner-container">
<h1 className="color-white">
We need some <br /> browser permissions
</h1>
<p className="text-color paragraph">
We don&apos;t like it either, but the movie-web extension needs quite a few permissions to function. Listed
below is an explanation for all permissions we need.
</p>
<div className="card-list" style={{ marginTop: '2.5rem' }}>
<Card
purple
icon={<Icon name="github" />}
right={
<Button type="secondary" href="https://github.com/movie-web/extension">
Read source code
</Button>
}
>
<h3>Read the source code on GitHub</h3>
<p className="text-color paragraph">
Don&apos;t trust us? Read the code and decide for yourself if it&apos;s safe!
</p>
</Card>
</div>
<h2>Permission list</h2>
<div className="card-list">
<Card icon={<Icon name="windows" />}>
<h3>Read & change data from all sites</h3>
<p className="text-color paragraph">
This is so the extension can gather content from the sources. We need to be able to reach those sources.
Unfortunately that requires us to request the permissions from all sites.
</p>
</Card>
<Card icon={<Icon name="network" />}>
<h3>Network Requests</h3>
<p className="text-color paragraph">
This permission allows the extension to instruct the browser how to request data from sites. In more
technical terms, this allows movie-web to modify HTTP headers that it wouldn&apos;t normally be allowed
to.
</p>
<p className="text-color paragraph">
You won&apos;t be prompted for this permission, it&apos;s included in Read & change data from all sites.
</p>
</Card>
<Card icon={<Icon name="cookie" />}>
<h3>Read and write cookies</h3>
<p className="text-color paragraph">
Some sources use cookies for authentication. We need to be able to read and set those cookies.
</p>
<p className="text-color paragraph">
You won&apos;t be prompted for this permission, it&apos;s included in Read & change data from all sites.
</p>
</Card>
<Card icon={<Icon name="shield" />}>
<h3>Active tab</h3>
<p className="text-color paragraph">
To determine which site has access to the extension or not, we need to know what tab you&apos;re currently
using.
</p>
<p className="text-color paragraph">
This permission is given to all extensions by default, so your browser won&apos;t prompt you for it.
</p>
</Card>
</div>
<div className="footer">
<div style={{ width: '250px' }}>
<Button full onClick={grant}>
Grant Permission
</Button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,128 @@
import { isChrome } from './extension';
interface DynamicRule {
ruleId: number;
targetDomains?: [string, ...string[]];
targetRegex?: string;
requestHeaders?: Record<string, string>;
responseHeaders?: Record<string, string>;
}
const mapHeadersToDeclarativeNetRequestHeaders = (
headers: Record<string, string>,
op: string,
): { header: string; operation: any; value: string }[] => {
return Object.entries(headers).map(([name, value]) => ({
header: name,
operation: op,
value,
}));
};
export const setDynamicRules = async (body: DynamicRule) => {
if (isChrome()) {
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [body.ruleId],
addRules: [
{
id: body.ruleId,
condition: {
...(body.targetDomains && { requestDomains: body.targetDomains }),
...(body.targetRegex && { regexFilter: body.targetRegex }),
},
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
...(body.requestHeaders && Object.keys(body.requestHeaders).length > 0
? {
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(
body.requestHeaders,
chrome.declarativeNetRequest.HeaderOperation.SET,
),
}
: {}),
responseHeaders: [
{
header: 'Access-Control-Allow-Origin',
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: '*',
},
{
header: 'Access-Control-Allow-Methods',
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
},
{
header: 'Access-Control-Allow-Headers',
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: '*',
},
{
header: 'Access-Control-Allow-Credentials',
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: 'true',
},
...mapHeadersToDeclarativeNetRequestHeaders(
body.responseHeaders ?? {},
chrome.declarativeNetRequest.HeaderOperation.SET,
),
],
},
},
],
});
if (chrome.runtime.lastError?.message) throw new Error(chrome.runtime.lastError.message);
} else {
await browser.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [body.ruleId],
addRules: [
{
id: body.ruleId,
condition: {
...(body.targetDomains && { requestDomains: body.targetDomains }),
...(body.targetRegex && { regexFilter: body.targetRegex }),
},
action: {
type: 'modifyHeaders',
...(body.requestHeaders && Object.keys(body.requestHeaders).length > 0
? {
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(body.requestHeaders, 'set'),
}
: {}),
responseHeaders: [
{
header: 'Access-Control-Allow-Origin',
operation: 'set',
value: '*',
},
{
header: 'Access-Control-Allow-Methods',
operation: 'set',
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
},
{
header: 'Access-Control-Allow-Headers',
operation: 'set',
value: '*',
},
{
header: 'Access-Control-Allow-Credentials',
operation: 'set',
value: 'true',
},
...mapHeadersToDeclarativeNetRequestHeaders(body.responseHeaders ?? {}, 'set'),
],
},
},
],
});
if (browser.runtime.lastError?.message) throw new Error(browser.runtime.lastError.message);
}
};
export const removeDynamicRules = async (ruleIds: number[]) => {
await (chrome || browser).declarativeNetRequest.updateDynamicRules({
removeRuleIds: ruleIds,
});
if ((chrome || browser).runtime.lastError?.message)
throw new Error((chrome || browser).runtime.lastError?.message ?? 'Unknown error');
};

View File

@@ -1,3 +1,11 @@
export const isChrome = () => { export const isChrome = () => {
return chrome.runtime.getURL('').startsWith('chrome-extension://'); return chrome.runtime.getURL('').startsWith('chrome-extension://');
}; };
export const isFirefox = () => {
try {
return browser.runtime.getURL('').startsWith('moz-extension://');
} catch {
return false;
}
};

View File

@@ -3,7 +3,15 @@ import { useStorage } from '@plasmohq/storage/hook';
import { makeUrlIntoDomain } from '~utils/domains'; import { makeUrlIntoDomain } from '~utils/domains';
export const DEFAULT_DOMAIN_WHITELIST = ['movie-web.app', 'dev.movie-web.app']; export const DEFAULT_DOMAIN_WHITELIST = [
'mw.lonelil.ru',
'watch.qtchaos.de',
'bmov.app',
'bmov.vercel.app',
'stream.thehairy.me',
'scootydooter.vercel.app',
'movie-web-me.vercel.app',
];
export const storage = new Storage(); export const storage = new Storage();
@@ -31,5 +39,9 @@ export const isDomainWhitelisted = async (url: string | undefined) => {
export const assertDomainWhitelist = async (url: string) => { export const assertDomainWhitelist = async (url: string) => {
const isWhiteListed = await isDomainWhitelisted(url); const isWhiteListed = await isDomainWhitelisted(url);
if (!isWhiteListed) throw new Error('Domain is not whitelisted'); const currentDomain = makeUrlIntoDomain(url);
if (!isWhiteListed)
throw new Error(
`${currentDomain} is not whitelisted. Open the extension and click on the power button to whitelist the site.`,
);
}; };

View File

@@ -1,7 +1,7 @@
import { isChrome } from './extension'; import { isChrome } from './extension';
export function queryCurrentDomain(cb: (domain: string | null) => void) { export function queryCurrentDomain(cb: (domain: string | null) => void) {
const handle = (tabUrl: string | null) => { const handle = (tabUrl: string | undefined) => {
if (!tabUrl) cb(null); if (!tabUrl) cb(null);
else cb(tabUrl); else cb(tabUrl);
}; };

View File

@@ -1,18 +1,13 @@
{ {
"extends": "plasmo/templates/tsconfig.base", "extends": "plasmo/templates/tsconfig.base",
"exclude": [ "exclude": ["node_modules"],
"node_modules" "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"],
],
"include": [
".plasmo/index.d.ts",
"./**/*.ts",
"./**/*.tsx"
],
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx",
"strict": true,
"paths": { "paths": {
"~*": [ "~*": ["./src/*"]
"./src/*"
]
}, },
"baseUrl": "." "baseUrl": "."
} }