extension-develop 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/add-dynamic-public-path.js +3 -0
- package/dist/add-hmr-accept-code.js +3 -0
- package/dist/add-query-param-to-imported-css.js +1 -0
- package/dist/ensure-hmr-for-scripts.js +3 -0
- package/dist/extensions/manager-extension/background.js +57 -0
- package/dist/extensions/manager-extension/define-initial-tab.js +67 -0
- package/dist/extensions/manager-extension/manifest.json +17 -0
- package/dist/extensions/manager-extension/pages/sakura-dark.css +268 -0
- package/dist/extensions/manager-extension/pages/sakura.css +267 -0
- package/dist/extensions/manager-extension/pages/welcome.html +49 -0
- package/dist/extensions/manager-extension/pages/welcome.js +34 -0
- package/dist/extensions/manager-extension/public/logo.png +0 -0
- package/dist/extensions/manager-extension/reload-service.js +145 -0
- package/dist/extensions/manager-extension-firefox/background.js +68 -0
- package/dist/extensions/manager-extension-firefox/define-initial-tab.js +57 -0
- package/dist/extensions/manager-extension-firefox/manifest.json +16 -0
- package/dist/extensions/manager-extension-firefox/pages/sakura-dark.css +268 -0
- package/dist/extensions/manager-extension-firefox/pages/sakura.css +267 -0
- package/dist/extensions/manager-extension-firefox/pages/welcome.html +49 -0
- package/dist/extensions/manager-extension-firefox/pages/welcome.js +34 -0
- package/dist/extensions/manager-extension-firefox/public/logo.png +0 -0
- package/dist/extensions/manager-extension-firefox/reload-service.js +130 -0
- package/dist/inject-chromium-client-loader.js +52 -0
- package/dist/inject-content-css-during-dev.js +10 -0
- package/dist/inject-firefox-client-loader.js +48 -0
- package/dist/minimum-chromium-file.mjs +1 -0
- package/dist/minimum-content-file.mjs +1 -0
- package/dist/minimum-firefox-file.mjs +1 -0
- package/dist/minimum-script-file.mjs +0 -0
- package/dist/module.d.ts +44 -0
- package/dist/module.js +178 -0
- package/dist/resolver-loader.js +1 -0
- package/dist/resolver-module.js +1 -0
- package/dist/stylelint.config.js +7 -0
- package/dist/tailwind.config.js +25 -0
- package/dist/types/css-content.d.ts +7 -0
- package/dist/types/css-modules.d.ts +19 -0
- package/dist/types/images.d.ts +58 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.ts +7 -0
- package/dist/types/polyfill.d.ts +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/* Sakura.css v1.5.0
|
|
2
|
+
* ================
|
|
3
|
+
* Minimal css theme.
|
|
4
|
+
* Project: https://github.com/oxalorg/sakura/
|
|
5
|
+
*/
|
|
6
|
+
/* Body */
|
|
7
|
+
html {
|
|
8
|
+
font-size: 62.5%;
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
10
|
+
'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
font-size: 1.8rem;
|
|
15
|
+
line-height: 1.618;
|
|
16
|
+
max-width: 38em;
|
|
17
|
+
margin: auto;
|
|
18
|
+
color: #4a4a4a;
|
|
19
|
+
background-color: #f9f9f9;
|
|
20
|
+
padding: 13px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (max-width: 684px) {
|
|
24
|
+
body {
|
|
25
|
+
font-size: 1.53rem;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
@media (max-width: 382px) {
|
|
29
|
+
body {
|
|
30
|
+
font-size: 1.35rem;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
h1,
|
|
34
|
+
h2,
|
|
35
|
+
h3,
|
|
36
|
+
h4,
|
|
37
|
+
h5,
|
|
38
|
+
h6 {
|
|
39
|
+
line-height: 1.1;
|
|
40
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
41
|
+
'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
|
42
|
+
font-weight: 700;
|
|
43
|
+
margin-top: 3rem;
|
|
44
|
+
margin-bottom: 1.5rem;
|
|
45
|
+
overflow-wrap: break-word;
|
|
46
|
+
word-wrap: break-word;
|
|
47
|
+
-ms-word-break: break-all;
|
|
48
|
+
word-break: break-word;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
h1 {
|
|
52
|
+
font-size: 2.35em;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
h2 {
|
|
56
|
+
font-size: 2em;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
h3 {
|
|
60
|
+
font-size: 1.75em;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
h4 {
|
|
64
|
+
font-size: 1.5em;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
h5 {
|
|
68
|
+
font-size: 1.25em;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
h6 {
|
|
72
|
+
font-size: 1em;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
p {
|
|
76
|
+
margin-top: 0px;
|
|
77
|
+
margin-bottom: 2.5rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
small,
|
|
81
|
+
sub,
|
|
82
|
+
sup {
|
|
83
|
+
font-size: 75%;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
hr {
|
|
87
|
+
border-color: #1d7484;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
a {
|
|
91
|
+
text-decoration: none;
|
|
92
|
+
color: #1d7484;
|
|
93
|
+
}
|
|
94
|
+
a:visited {
|
|
95
|
+
color: #144f5a;
|
|
96
|
+
}
|
|
97
|
+
a:hover {
|
|
98
|
+
color: #982c61;
|
|
99
|
+
border-bottom: 2px solid #4a4a4a;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ul {
|
|
103
|
+
padding-left: 1.4em;
|
|
104
|
+
margin-top: 0px;
|
|
105
|
+
margin-bottom: 2.5rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
li {
|
|
109
|
+
margin-bottom: 0.4em;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
blockquote {
|
|
113
|
+
margin-left: 0px;
|
|
114
|
+
margin-right: 0px;
|
|
115
|
+
padding-left: 1em;
|
|
116
|
+
padding-top: 0.8em;
|
|
117
|
+
padding-bottom: 0.8em;
|
|
118
|
+
padding-right: 0.8em;
|
|
119
|
+
border-left: 5px solid #1d7484;
|
|
120
|
+
margin-bottom: 2.5rem;
|
|
121
|
+
background-color: #f1f1f1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
blockquote p {
|
|
125
|
+
margin-bottom: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
img,
|
|
129
|
+
video {
|
|
130
|
+
height: auto;
|
|
131
|
+
max-width: 100%;
|
|
132
|
+
margin-top: 0px;
|
|
133
|
+
margin-bottom: 2.5rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Pre and Code */
|
|
137
|
+
pre {
|
|
138
|
+
background-color: #f1f1f1;
|
|
139
|
+
display: block;
|
|
140
|
+
padding: 1em;
|
|
141
|
+
overflow-x: auto;
|
|
142
|
+
margin-top: 0px;
|
|
143
|
+
margin-bottom: 2.5rem;
|
|
144
|
+
font-size: 0.9em;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
code,
|
|
148
|
+
kbd,
|
|
149
|
+
samp {
|
|
150
|
+
font-size: 0.9em;
|
|
151
|
+
padding: 0 0.5em;
|
|
152
|
+
background-color: #f1f1f1;
|
|
153
|
+
white-space: pre-wrap;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pre > code {
|
|
157
|
+
padding: 0;
|
|
158
|
+
background-color: transparent;
|
|
159
|
+
white-space: pre;
|
|
160
|
+
font-size: 1em;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Tables */
|
|
164
|
+
table {
|
|
165
|
+
text-align: justify;
|
|
166
|
+
width: 100%;
|
|
167
|
+
border-collapse: collapse;
|
|
168
|
+
margin-bottom: 2rem;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
td,
|
|
172
|
+
th {
|
|
173
|
+
padding: 0.5em;
|
|
174
|
+
border-bottom: 1px solid #f1f1f1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Buttons, forms and input */
|
|
178
|
+
input,
|
|
179
|
+
textarea {
|
|
180
|
+
border: 1px solid #4a4a4a;
|
|
181
|
+
}
|
|
182
|
+
input:focus,
|
|
183
|
+
textarea:focus {
|
|
184
|
+
border: 1px solid #1d7484;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
textarea {
|
|
188
|
+
width: 100%;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.button,
|
|
192
|
+
button,
|
|
193
|
+
input[type='submit'],
|
|
194
|
+
input[type='reset'],
|
|
195
|
+
input[type='button'],
|
|
196
|
+
input[type='file']::file-selector-button {
|
|
197
|
+
display: inline-block;
|
|
198
|
+
padding: 5px 10px;
|
|
199
|
+
text-align: center;
|
|
200
|
+
text-decoration: none;
|
|
201
|
+
white-space: nowrap;
|
|
202
|
+
background-color: #1d7484;
|
|
203
|
+
color: #f9f9f9;
|
|
204
|
+
border-radius: 1px;
|
|
205
|
+
border: 1px solid #1d7484;
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
box-sizing: border-box;
|
|
208
|
+
}
|
|
209
|
+
.button[disabled],
|
|
210
|
+
button[disabled],
|
|
211
|
+
input[type='submit'][disabled],
|
|
212
|
+
input[type='reset'][disabled],
|
|
213
|
+
input[type='button'][disabled],
|
|
214
|
+
input[type='file']::file-selector-button[disabled] {
|
|
215
|
+
cursor: default;
|
|
216
|
+
opacity: 0.5;
|
|
217
|
+
}
|
|
218
|
+
.button:hover,
|
|
219
|
+
button:hover,
|
|
220
|
+
input[type='submit']:hover,
|
|
221
|
+
input[type='reset']:hover,
|
|
222
|
+
input[type='button']:hover,
|
|
223
|
+
input[type='file']::file-selector-button:hover {
|
|
224
|
+
background-color: #982c61;
|
|
225
|
+
color: #f9f9f9;
|
|
226
|
+
outline: 0;
|
|
227
|
+
}
|
|
228
|
+
.button:focus-visible,
|
|
229
|
+
button:focus-visible,
|
|
230
|
+
input[type='submit']:focus-visible,
|
|
231
|
+
input[type='reset']:focus-visible,
|
|
232
|
+
input[type='button']:focus-visible,
|
|
233
|
+
input[type='file']::file-selector-button:focus-visible {
|
|
234
|
+
outline-style: solid;
|
|
235
|
+
outline-width: 2px;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
textarea,
|
|
239
|
+
select,
|
|
240
|
+
input {
|
|
241
|
+
color: #4a4a4a;
|
|
242
|
+
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
|
|
243
|
+
margin-bottom: 10px;
|
|
244
|
+
background-color: #f1f1f1;
|
|
245
|
+
border: 1px solid #f1f1f1;
|
|
246
|
+
border-radius: 4px;
|
|
247
|
+
box-shadow: none;
|
|
248
|
+
box-sizing: border-box;
|
|
249
|
+
}
|
|
250
|
+
textarea:focus,
|
|
251
|
+
select:focus,
|
|
252
|
+
input:focus {
|
|
253
|
+
border: 1px solid #1d7484;
|
|
254
|
+
outline: 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
input[type='checkbox']:focus {
|
|
258
|
+
outline: 1px dotted #1d7484;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
label,
|
|
262
|
+
legend,
|
|
263
|
+
fieldset {
|
|
264
|
+
display: block;
|
|
265
|
+
margin-bottom: 0.5rem;
|
|
266
|
+
font-weight: 600;
|
|
267
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Welcome!</title>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link
|
|
7
|
+
rel="icon"
|
|
8
|
+
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧩</text></svg>"
|
|
9
|
+
/>
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
11
|
+
<link rel="stylesheet" href="./sakura.css" media="screen" />
|
|
12
|
+
<link
|
|
13
|
+
rel="stylesheet"
|
|
14
|
+
href="./sakura-dark.css"
|
|
15
|
+
media="screen and (prefers-color-scheme: dark)"
|
|
16
|
+
/>
|
|
17
|
+
<style>
|
|
18
|
+
.center-middle {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
height: 100vh;
|
|
23
|
+
padding: 0;
|
|
24
|
+
margin: 0;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<div class="center-middle">
|
|
30
|
+
<h1 style="font-size: 4.2em">
|
|
31
|
+
Chrome Extension<br />
|
|
32
|
+
<a href="#"><span id="extensionName"></span></a><br />
|
|
33
|
+
ready.
|
|
34
|
+
</h1>
|
|
35
|
+
<p id="extensionDescription"></p>
|
|
36
|
+
<p>
|
|
37
|
+
<a target="_blank" href="https://github.com/cezaraugusto/extension"
|
|
38
|
+
>🧩 Extension.js</a
|
|
39
|
+
>
|
|
40
|
+
is a development tool for browser extensions with built-in support for
|
|
41
|
+
TypeScript, WebAssembly, React, and modern JavaScript.
|
|
42
|
+
</p>
|
|
43
|
+
<button id="learnMore">
|
|
44
|
+
🧩 Learn more about developing cross-browser extensions
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
<script src="welcome.js"></script>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
async function getUserExtension() {
|
|
2
|
+
const allExtensions = await chrome.management.getAll()
|
|
3
|
+
|
|
4
|
+
return allExtensions.filter((extension) => {
|
|
5
|
+
return (
|
|
6
|
+
// Do not include itself
|
|
7
|
+
extension.id !== chrome.runtime.id &&
|
|
8
|
+
// Reload extension
|
|
9
|
+
extension.id !== 'igcijhgmihmjbbahdabahfbpffalcfnn' &&
|
|
10
|
+
// Show only unpackaged extensions
|
|
11
|
+
extension.installType === 'development'
|
|
12
|
+
)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function onStartup() {
|
|
17
|
+
const userExtension = await getUserExtension()
|
|
18
|
+
const extensionName = document.getElementById('extensionName')
|
|
19
|
+
const extensionDescription = document.getElementById('extensionDescription')
|
|
20
|
+
|
|
21
|
+
extensionName.innerText = userExtension[0].name
|
|
22
|
+
extensionName.title = `• Name: ${userExtension[0].name}
|
|
23
|
+
• ID: ${userExtension[0].id}
|
|
24
|
+
• Version: ${userExtension[0].version}`
|
|
25
|
+
|
|
26
|
+
extensionDescription.innerText = userExtension[0].description
|
|
27
|
+
|
|
28
|
+
const learnMore = document.getElementById('learnMore')
|
|
29
|
+
learnMore.addEventListener('click', () => {
|
|
30
|
+
chrome.tabs.create({url: 'https://extension.js.org/'})
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onStartup()
|
|
Binary file
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const TEN_SECONDS_MS = 10 * 1000
|
|
2
|
+
let webSocket = null
|
|
3
|
+
|
|
4
|
+
export async function connect() {
|
|
5
|
+
if (webSocket) {
|
|
6
|
+
// If already connected, do nothing
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
webSocket = new WebSocket('ws://localhost:__RELOAD_PORT__')
|
|
11
|
+
|
|
12
|
+
webSocket.onerror = (event) => {
|
|
13
|
+
console.error(`[Extension.js] Connection error: ${JSON.stringify(event)}`)
|
|
14
|
+
webSocket.close()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
webSocket.onopen = () => {
|
|
18
|
+
console.info(`[Extension.js] Connection opened.`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
webSocket.onmessage = async (event) => {
|
|
22
|
+
const message = JSON.parse(event.data)
|
|
23
|
+
|
|
24
|
+
if (message.status === 'serverReady') {
|
|
25
|
+
console.info('[Extension.js] Connection ready.')
|
|
26
|
+
await requestInitialLoadData()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (message.changedFile) {
|
|
30
|
+
console.info(
|
|
31
|
+
`[Extension.js] Changes detected on ${message.changedFile}. Reloading extension...`
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
await messageAllExtensions(message.changedFile)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
webSocket.onclose = () => {
|
|
39
|
+
console.info('[Extension.js] Connection closed.')
|
|
40
|
+
webSocket = null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function disconnect() {
|
|
45
|
+
if (webSocket) {
|
|
46
|
+
webSocket.close()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function getDevExtensions() {
|
|
51
|
+
const allExtensions = await new Promise((resolve) => {
|
|
52
|
+
chrome.management.getAll(resolve)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return allExtensions.filter((extension) => {
|
|
56
|
+
return (
|
|
57
|
+
// Do not include itself
|
|
58
|
+
extension.id !== chrome.runtime.id &&
|
|
59
|
+
// Manager extension
|
|
60
|
+
extension.id !== 'hkklidinfhnfidkjiknmmbmcloigimco' &&
|
|
61
|
+
// Show only unpackaged extensions
|
|
62
|
+
extension.installType === 'development'
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function messageAllExtensions(changedFile) {
|
|
68
|
+
// Check if the external extension is ready
|
|
69
|
+
const isExtensionReady = await checkExtensionReadiness()
|
|
70
|
+
|
|
71
|
+
if (isExtensionReady) {
|
|
72
|
+
const devExtensions = await getDevExtensions()
|
|
73
|
+
const reloadAll = devExtensions.map((extension) => {
|
|
74
|
+
chrome.runtime.sendMessage(extension.id, {changedFile}, (response) => {
|
|
75
|
+
if (response) {
|
|
76
|
+
console.info('[Extension.js] Extension reloaded and ready.')
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return true
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
await Promise.all(reloadAll)
|
|
84
|
+
} else {
|
|
85
|
+
console.info('[Extension.js] External extension is not ready.')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function requestInitialLoadData() {
|
|
90
|
+
const devExtensions = await getDevExtensions()
|
|
91
|
+
|
|
92
|
+
const messagePromises = devExtensions.map(async (extension) => {
|
|
93
|
+
return await new Promise((resolve) => {
|
|
94
|
+
chrome.runtime.sendMessage(
|
|
95
|
+
extension.id,
|
|
96
|
+
{initialLoadData: true},
|
|
97
|
+
(response) => {
|
|
98
|
+
if (chrome.runtime.lastError) {
|
|
99
|
+
console.error(
|
|
100
|
+
`Error sending message to ${extension.id}: ${chrome.runtime.lastError.message}`
|
|
101
|
+
)
|
|
102
|
+
resolve(null)
|
|
103
|
+
} else {
|
|
104
|
+
resolve(response)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const responses = await Promise.all(messagePromises)
|
|
112
|
+
|
|
113
|
+
// We received the info from the use extension.
|
|
114
|
+
// All good, client is ready. Inform the server.
|
|
115
|
+
if (webSocket && webSocket.readyState === WebSocket.OPEN) {
|
|
116
|
+
const message = JSON.stringify({
|
|
117
|
+
status: 'clientReady',
|
|
118
|
+
data: responses[0]
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
webSocket.send(message)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function checkExtensionReadiness() {
|
|
126
|
+
// Delay for 1 second
|
|
127
|
+
await delay(1000)
|
|
128
|
+
// Assume the extension is ready
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function delay(ms) {
|
|
133
|
+
return await new Promise((resolve) => setTimeout(resolve, ms))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function keepAlive() {
|
|
137
|
+
const keepAliveIntervalId = setInterval(() => {
|
|
138
|
+
if (webSocket) {
|
|
139
|
+
webSocket.send(JSON.stringify({status: 'ping'}))
|
|
140
|
+
console.info('[Extension.js] Listening for changes...')
|
|
141
|
+
} else {
|
|
142
|
+
clearInterval(keepAliveIntervalId)
|
|
143
|
+
}
|
|
144
|
+
}, TEN_SECONDS_MS)
|
|
145
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {createFirefoxAddonsTab, handleFirstRun} from './define-initial-tab.js'
|
|
2
|
+
import {connect, disconnect} from './reload-service.js'
|
|
3
|
+
|
|
4
|
+
function bgGreen(str) {
|
|
5
|
+
return `background: #0A0C10; color: #26FFB8; ${str}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function handleTabOnExtensionLoad() {
|
|
9
|
+
console.log(
|
|
10
|
+
`%c
|
|
11
|
+
██████████████████████████████████████████████████████████
|
|
12
|
+
██████████████████████████████████████████████████████████
|
|
13
|
+
████████████████████████████ ██████████████████████████
|
|
14
|
+
█████████████████████████ ██████ ███████████████
|
|
15
|
+
███████████████████████ ███ ███ ████████████
|
|
16
|
+
██████████████████████ ██████ ███ ███████████
|
|
17
|
+
███████████████████████ ██████ ██████ ███████████
|
|
18
|
+
████████████████ ██████ ██████████████ ███████████
|
|
19
|
+
█████████████ ████ ████████████ ████████████
|
|
20
|
+
███████████ ██ █████████████ ███████████████
|
|
21
|
+
██████████ ██████ █████████████████ █████████████
|
|
22
|
+
███████████ ████████████████████████████ ███████████
|
|
23
|
+
█████████████ █████████████████ ██████ ██████████
|
|
24
|
+
███████████████ ██████████████ ██ ██████████
|
|
25
|
+
████████████ ████████████ ████ █████████████
|
|
26
|
+
███████████ █████████████ ██████ ███████████████
|
|
27
|
+
███████████ ██████ ██████ ███████████████████████
|
|
28
|
+
███████████ ████ ██████ ██████████████████████
|
|
29
|
+
████████████ ██ ███ ███████████████████████
|
|
30
|
+
███████████████ ██████ █████████████████████████
|
|
31
|
+
██████████████████████████ ████████████████████████████
|
|
32
|
+
██████████████████████████████████████████████████████████
|
|
33
|
+
██████████████████████████████████████████████████████████
|
|
34
|
+
`,
|
|
35
|
+
bgGreen('')
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const [initialTab] = await browser.tabs.query({
|
|
40
|
+
active: true,
|
|
41
|
+
currentWindow: true
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
initialTab.url === 'about:blank' ||
|
|
46
|
+
initialTab.url === 'about:welcome'
|
|
47
|
+
) {
|
|
48
|
+
await handleFirstRun()
|
|
49
|
+
} else {
|
|
50
|
+
await createFirefoxAddonsTab(initialTab, 'about:blank')
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Error handling tabs on extension load:', error)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
handleTabOnExtensionLoad().catch(console.error)
|
|
58
|
+
|
|
59
|
+
browser.runtime.onInstalled.addListener(async () => {
|
|
60
|
+
let isConnected = false
|
|
61
|
+
|
|
62
|
+
if (isConnected) {
|
|
63
|
+
disconnect()
|
|
64
|
+
} else {
|
|
65
|
+
await connect()
|
|
66
|
+
isConnected = true
|
|
67
|
+
}
|
|
68
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
async function getDevExtension() {
|
|
2
|
+
const allExtensions = await browser.management.getAll()
|
|
3
|
+
|
|
4
|
+
return allExtensions.filter((extension) => {
|
|
5
|
+
return (
|
|
6
|
+
// Do not include itself
|
|
7
|
+
extension.id !== browser.runtime.id &&
|
|
8
|
+
// Reload extension
|
|
9
|
+
extension.name !== 'Reload Service' &&
|
|
10
|
+
// Show only unpackaged extensions
|
|
11
|
+
extension.installType === 'development'
|
|
12
|
+
)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Create a new tab and set it to background.
|
|
17
|
+
// We want the user-selected page to be active,
|
|
18
|
+
// not about:blank.
|
|
19
|
+
export async function createFirefoxAddonsTab(initialTab, url) {
|
|
20
|
+
try {
|
|
21
|
+
// Check if an about:blank tab is already open
|
|
22
|
+
const tabs = await browser.tabs.query({url: 'about:blank'})
|
|
23
|
+
const addonsTabExist = tabs.length > 0
|
|
24
|
+
|
|
25
|
+
// Return if url exists, meaning about:blank tab is already open
|
|
26
|
+
if (addonsTabExist) return
|
|
27
|
+
|
|
28
|
+
// Create an inactive about:blank tab
|
|
29
|
+
const addonsTab = await browser.tabs.create({url, active: false})
|
|
30
|
+
|
|
31
|
+
// Get the initial page tab and move the new addons tab to the first position.
|
|
32
|
+
// This will auto-activate the initial page tab because we set the addons tab to inactive
|
|
33
|
+
await browser.tabs.move(addonsTab.id, {index: 0})
|
|
34
|
+
|
|
35
|
+
// Reactivate the user-selected initial page tab
|
|
36
|
+
await browser.tabs.update(initialTab.id, {active: true})
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Error creating and manipulating tabs:', error)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Function to handle first run logic
|
|
43
|
+
export async function handleFirstRun() {
|
|
44
|
+
// browser.tabs.update({url: 'about://debugging/'})
|
|
45
|
+
|
|
46
|
+
const devExtension = await getDevExtension()
|
|
47
|
+
|
|
48
|
+
browser.storage.local.get(devExtension.id, (result) => {
|
|
49
|
+
if (result[devExtension.id] && result[devExtension.id].didRun) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
browser.tabs.create({url: './pages/welcome.html'})
|
|
54
|
+
// Ensure the welcome page shows only once per extension installation
|
|
55
|
+
browser.storage.local.set({[devExtension.id]: {didRun: true}})
|
|
56
|
+
})
|
|
57
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Extension.js Developer Tools (Firefox)",
|
|
3
|
+
"description": "Utility for developers to manage their extensions using Firefox.",
|
|
4
|
+
"version": "1.0",
|
|
5
|
+
"manifest_version": 2,
|
|
6
|
+
"background": {
|
|
7
|
+
"scripts": ["background.js"],
|
|
8
|
+
"type": "module"
|
|
9
|
+
},
|
|
10
|
+
"icons": {
|
|
11
|
+
"16": "./public/logo.png",
|
|
12
|
+
"48": "./public/logo.png",
|
|
13
|
+
"128": "./public/logo.png"
|
|
14
|
+
},
|
|
15
|
+
"permissions": ["management", "tabs", "storage"]
|
|
16
|
+
}
|