casdoor 1.4.0 → 1.5.1
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/.eslintrc.js +1 -0
- package/README.md +15 -3
- package/manifest.json +14 -15
- package/package.json +1 -1
- package/src/background.js +333 -77
- package/src/content-script.js +311 -110
- package/src/match.js +94 -0
- package/src/popup.css +133 -77
- package/src/popup.html +31 -18
- package/src/popup.js +101 -65
- package/src/config.js +0 -47
- package/src/oauth.js +0 -219
- package/src/sdk.js +0 -138
package/.eslintrc.js
CHANGED
package/README.md
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
# casdoor-chrome-extension
|
|
2
2
|
|
|
3
|
-
The Casdoor extension
|
|
4
|
-
|
|
5
|
-
This extension is very simple to use. We will show you the steps below.
|
|
3
|
+
The Casdoor browser extension signs users into the systems that have no single sign-on, by filling their sign-in forms with the accounts kept in [Casdoor](https://casdoor.ai). The code is open-sourced at: https://github.com/casdoor/casdoor-chrome-extension
|
|
6
4
|
|
|
7
5
|
## Demo video
|
|
8
6
|
|
|
9
7
|

|
|
10
8
|
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
1. The admin turns on **Form autofill** in the application of a legacy system (the "Form autofill" tab of the application's edit page) and enters its sign-in page URLs, e.g. `https://oa.example.com/login`. The host must be the same, `*.example.com` covers its subdomains, and a path without `*` matches as a prefix. The fields are found automatically, or set with CSS selectors.
|
|
12
|
+
2. Each user's account of the system is a managed account on the user's page, or saved by the user: when **Let users save their accounts** is on, the extension offers to save the account that the user signs in with by hand.
|
|
13
|
+
3. When the user opens a sign-in page, the extension asks Casdoor for the account of that page and fills it in, and clicks the sign-in button when **Submit automatically** is on. A sign-in page that comes back within 30 seconds is filled but not submitted again.
|
|
14
|
+
|
|
15
|
+
Casdoor only hands an account out for a page of the system's own sign-in URLs, records every time it does, and can keep the passwords encrypted (the organization's "Encrypted fields", with `fieldEncryptionKey` set). Form autofill needs a Casdoor Enterprise edition license. On a Casdoor without it, the extension fills the managed accounts on the pages that match the application's "Signin URL", a regular expression, as its first versions did.
|
|
16
|
+
|
|
11
17
|
## Usage
|
|
12
18
|
|
|
13
19
|
### Install the extension
|
|
14
20
|
|
|
15
21
|
1. Go to `chrome://extensions` and check the box for **Developer mode** in the top right.
|
|
16
22
|
2. Click `Load unpacked` button, then choose this project folder.
|
|
23
|
+
|
|
24
|
+
### Sign in to Casdoor
|
|
25
|
+
|
|
26
|
+
1. Open the extension, enter the Casdoor URL and the name of the application to sign in with.
|
|
27
|
+
2. Add the address shown at the bottom of the extension (`https://<extension id>.chromiumapp.org/`) to the redirect URLs of that application, and allow the `token` grant type in it.
|
|
28
|
+
3. Click **Sign in to Casdoor**. The extension then lists the systems it fills for you.
|
package/manifest.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "Casdoor
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "
|
|
2
|
+
"name": "Casdoor",
|
|
3
|
+
"description": "Signs you into the systems without single sign-on by filling their sign-in forms with your accounts kept in Casdoor.",
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"manifest_version": 3,
|
|
6
6
|
"permissions": [
|
|
7
7
|
"storage",
|
|
8
8
|
"identity"
|
|
9
9
|
],
|
|
10
|
+
"host_permissions": [
|
|
11
|
+
"https://*/*",
|
|
12
|
+
"http://*/*"
|
|
13
|
+
],
|
|
10
14
|
"action": {
|
|
11
15
|
"default_popup": "/src/popup.html",
|
|
12
16
|
"default_icon": {
|
|
@@ -22,6 +26,9 @@
|
|
|
22
26
|
"48": "/images/icon48.png",
|
|
23
27
|
"128": "/images/icon128.png"
|
|
24
28
|
},
|
|
29
|
+
"background": {
|
|
30
|
+
"service_worker": "/src/background.js"
|
|
31
|
+
},
|
|
25
32
|
"content_scripts": [
|
|
26
33
|
{
|
|
27
34
|
"matches": [
|
|
@@ -29,18 +36,10 @@
|
|
|
29
36
|
"http://*/*"
|
|
30
37
|
],
|
|
31
38
|
"js": [
|
|
32
|
-
"/src/content-script.js"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
]
|
|
39
|
+
"/src/content-script.js"
|
|
40
|
+
],
|
|
41
|
+
"all_frames": true,
|
|
42
|
+
"run_at": "document_idle"
|
|
37
43
|
}
|
|
38
|
-
],
|
|
39
|
-
"background": {
|
|
40
|
-
"service_worker": "/src/background.js"
|
|
41
|
-
},
|
|
42
|
-
"host_permissions": [
|
|
43
|
-
"https://*/*",
|
|
44
|
-
"http://*/*"
|
|
45
44
|
]
|
|
46
45
|
}
|
package/package.json
CHANGED
package/src/background.js
CHANGED
|
@@ -12,101 +12,357 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
/* global importScripts, matchFormFillSite, matchLegacySigninUrl */
|
|
16
|
+
|
|
17
|
+
importScripts("match.js");
|
|
18
|
+
|
|
19
|
+
// The service worker is the only part that talks to Casdoor and holds the access token. The content scripts ask it
|
|
20
|
+
// for the account of the page they are on, and it only answers for a frame whose own URL is a sign-in page.
|
|
21
|
+
|
|
22
|
+
const SITES_TTL = 60 * 1000;
|
|
23
|
+
const PENDING_TTL = 2 * 60 * 1000;
|
|
24
|
+
const AUTO_SUBMIT_GAP = 30 * 1000;
|
|
25
|
+
|
|
26
|
+
let sitesCache = null;
|
|
27
|
+
const lastAutoSubmits = new Map();
|
|
28
|
+
|
|
29
|
+
function getSettings() {
|
|
30
|
+
return chrome.storage.local.get(["endpoint", "applicationName", "accessToken", "userProfile", "disableAutoFill"]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// the settings of the first versions were kept in the synced storage
|
|
34
|
+
async function migrateSettings() {
|
|
35
|
+
const local = await chrome.storage.local.get(["migrated"]);
|
|
36
|
+
if (local.migrated) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const synced = await chrome.storage.sync.get(["endpoint", "applicationName", "disableAutoLogin"]);
|
|
40
|
+
const current = await chrome.storage.local.get(["endpoint", "applicationName", "disableAutoFill"]);
|
|
41
|
+
await chrome.storage.local.set({
|
|
42
|
+
endpoint: current.endpoint ?? synced.endpoint ?? "",
|
|
43
|
+
applicationName: current.applicationName ?? synced.applicationName ?? "",
|
|
44
|
+
disableAutoFill: current.disableAutoFill ?? !!synced.disableAutoLogin,
|
|
45
|
+
migrated: true,
|
|
46
|
+
});
|
|
47
|
+
await chrome.storage.sync.remove(["accessToken", "userProfile"]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
51
|
+
migrateSettings();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
chrome.storage.onChanged.addListener((changes, area) => {
|
|
55
|
+
if (area === "local" && (changes.accessToken || changes.endpoint)) {
|
|
56
|
+
sitesCache = null;
|
|
26
57
|
}
|
|
27
58
|
});
|
|
28
59
|
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
chrome.tabs.sendMessage(
|
|
32
|
-
tabId,
|
|
33
|
-
{ action: "isUserLoggedIn" },
|
|
34
|
-
function (UserLoggedIn) {
|
|
35
|
-
if (chrome.runtime.lastError) {
|
|
36
|
-
reject(chrome.runtime.lastError.message);
|
|
37
|
-
} else {
|
|
38
|
-
resolve(UserLoggedIn);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
);
|
|
42
|
-
});
|
|
60
|
+
function normalizeEndpoint(endpoint) {
|
|
61
|
+
return (endpoint || "").trim().replace(/\/+$/, "");
|
|
43
62
|
}
|
|
44
63
|
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
tabId,
|
|
49
|
-
{ action: "getManagedAccounts" },
|
|
50
|
-
function (managedAccounts) {
|
|
51
|
-
if (chrome.runtime.lastError) {
|
|
52
|
-
reject(chrome.runtime.lastError.message);
|
|
53
|
-
} else {
|
|
54
|
-
resolve(managedAccounts);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
});
|
|
64
|
+
async function signOut() {
|
|
65
|
+
sitesCache = null;
|
|
66
|
+
await chrome.storage.local.set({accessToken: "", userProfile: null});
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
function
|
|
69
|
+
async function callApi(path, options = {}) {
|
|
70
|
+
const {endpoint, accessToken} = await getSettings();
|
|
71
|
+
if (!endpoint || !accessToken) {
|
|
72
|
+
return {status: "error", msg: "", data: "Please login first"};
|
|
73
|
+
}
|
|
74
|
+
let response;
|
|
62
75
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
response = await fetch(`${endpoint}${path}`, {
|
|
77
|
+
...options,
|
|
78
|
+
headers: {...(options.headers || {}), Authorization: `Bearer ${accessToken}`},
|
|
79
|
+
});
|
|
67
80
|
} catch (error) {
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
return {status: "error", msg: error.message, unreachable: true};
|
|
82
|
+
}
|
|
83
|
+
let res;
|
|
84
|
+
try {
|
|
85
|
+
res = await response.json();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// a Casdoor without the API answers with a page
|
|
88
|
+
return {status: "error", msg: `HTTP ${response.status}`, missing: true};
|
|
89
|
+
}
|
|
90
|
+
if (res.status !== "ok" && res.data === "Please login first") {
|
|
91
|
+
await signOut();
|
|
70
92
|
}
|
|
93
|
+
return res;
|
|
71
94
|
}
|
|
72
95
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
// getSites returns the sign-in pages that the user's accounts are filled into. A Casdoor without an enterprise license
|
|
97
|
+
// or without form autofill falls back to the managed accounts with their sign-in URLs as regular expressions.
|
|
98
|
+
async function getSites() {
|
|
99
|
+
const settings = await getSettings();
|
|
100
|
+
if (!settings.endpoint || !settings.accessToken) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (sitesCache && sitesCache.endpoint === settings.endpoint && Date.now() - sitesCache.time < SITES_TTL) {
|
|
104
|
+
return sitesCache;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const res = await callApi("/api/get-form-fill-sites");
|
|
108
|
+
let cache;
|
|
109
|
+
if (res.status === "ok") {
|
|
110
|
+
cache = {mode: "sites", sites: res.data || []};
|
|
111
|
+
} else if (res.code === "edition-required" || res.missing) {
|
|
112
|
+
const accountRes = await callApi("/api/get-account?managedAccounts=1");
|
|
113
|
+
if (accountRes.status !== "ok") {
|
|
114
|
+
return null;
|
|
88
115
|
}
|
|
116
|
+
const accounts = (accountRes.data && accountRes.data.managedAccounts) || [];
|
|
117
|
+
cache = {mode: "legacy", accounts, message: res.code === "edition-required" ? res.msg : ""};
|
|
118
|
+
} else {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
sitesCache = {...cache, endpoint: settings.endpoint, time: Date.now()};
|
|
122
|
+
return sitesCache;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function findSite(cache, pageUrl) {
|
|
126
|
+
if (cache.mode === "sites") {
|
|
127
|
+
return cache.sites.find((site) => matchFormFillSite(site, pageUrl)) || null;
|
|
128
|
+
}
|
|
129
|
+
const accounts = cache.accounts.filter((account) => account.signinUrl && matchLegacySigninUrl(account.signinUrl, pageUrl));
|
|
130
|
+
if (accounts.length === 0) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
// the first versions always signed in
|
|
134
|
+
return {application: accounts[0].application, displayName: accounts[0].application, accounts: accounts.map((account) => account.username), autoSubmit: true};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function getFill(sender) {
|
|
138
|
+
const settings = await getSettings();
|
|
139
|
+
if (settings.disableAutoFill) {
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
const cache = await getSites();
|
|
143
|
+
if (!cache) {
|
|
144
|
+
return {};
|
|
89
145
|
}
|
|
146
|
+
const site = findSite(cache, sender.url);
|
|
147
|
+
if (!site) {
|
|
148
|
+
return {};
|
|
149
|
+
}
|
|
150
|
+
const key = `${sender.tab ? sender.tab.id : 0}/${site.application}`;
|
|
151
|
+
const recentlySubmitted = Date.now() - (lastAutoSubmits.get(key) || 0) < AUTO_SUBMIT_GAP;
|
|
152
|
+
return {
|
|
153
|
+
site: {
|
|
154
|
+
application: site.application,
|
|
155
|
+
displayName: site.displayName || site.application,
|
|
156
|
+
usernameSelector: site.usernameSelector || "",
|
|
157
|
+
passwordSelector: site.passwordSelector || "",
|
|
158
|
+
submitSelector: site.submitSelector || "",
|
|
159
|
+
autoSubmit: !!site.autoSubmit && !recentlySubmitted,
|
|
160
|
+
urls: site.urls || [],
|
|
161
|
+
},
|
|
162
|
+
accounts: site.accounts || [],
|
|
163
|
+
canSave: !!site.allowSave && (site.accounts || []).length === 0,
|
|
164
|
+
recentlySubmitted,
|
|
165
|
+
};
|
|
90
166
|
}
|
|
91
167
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
168
|
+
async function getAccount(message, sender) {
|
|
169
|
+
const cache = await getSites();
|
|
170
|
+
if (!cache) {
|
|
171
|
+
return {error: "not signed in"};
|
|
172
|
+
}
|
|
173
|
+
const site = findSite(cache, sender.url);
|
|
174
|
+
if (!site || site.application !== message.application) {
|
|
175
|
+
return {error: "not a sign-in page"};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (cache.mode === "legacy") {
|
|
179
|
+
const account = cache.accounts.find((item) => item.application === site.application && (!message.username || item.username === message.username));
|
|
180
|
+
return account ? {username: account.username, password: account.password} : {error: "no account"};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const query = new URLSearchParams({application: site.application, username: message.username || "", url: sender.url});
|
|
184
|
+
const res = await callApi(`/api/get-form-fill-account?${query.toString()}`);
|
|
185
|
+
if (res.status !== "ok") {
|
|
186
|
+
return {error: res.msg};
|
|
187
|
+
}
|
|
188
|
+
if (message.autoSubmit && sender.tab) {
|
|
189
|
+
lastAutoSubmits.set(`${sender.tab.id}/${site.application}`, Date.now());
|
|
190
|
+
}
|
|
191
|
+
return {username: res.data.username, password: res.data.password};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function pendingKey(tabId) {
|
|
195
|
+
return `pending-${tabId}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function capture(message, sender) {
|
|
199
|
+
if (!sender.tab || !message.username || !message.password) {
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
const cache = await getSites();
|
|
203
|
+
const site = cache && findSite(cache, sender.url);
|
|
204
|
+
if (!site || !site.allowSave || site.application !== message.application) {
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
await chrome.storage.session.set({
|
|
208
|
+
[pendingKey(sender.tab.id)]: {
|
|
209
|
+
application: site.application,
|
|
210
|
+
displayName: site.displayName || site.application,
|
|
211
|
+
username: message.username,
|
|
212
|
+
password: message.password,
|
|
213
|
+
url: sender.url,
|
|
214
|
+
time: Date.now(),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
return {};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function getPending(sender) {
|
|
221
|
+
if (!sender.tab) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const key = pendingKey(sender.tab.id);
|
|
225
|
+
const pending = (await chrome.storage.session.get([key]))[key];
|
|
226
|
+
if (!pending) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
if (Date.now() - pending.time > PENDING_TTL) {
|
|
230
|
+
await chrome.storage.session.remove([key]);
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return {application: pending.application, displayName: pending.displayName, username: pending.username};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function resolvePending(message, sender) {
|
|
237
|
+
if (!sender.tab) {
|
|
238
|
+
return {};
|
|
239
|
+
}
|
|
240
|
+
const key = pendingKey(sender.tab.id);
|
|
241
|
+
const pending = (await chrome.storage.session.get([key]))[key];
|
|
242
|
+
await chrome.storage.session.remove([key]);
|
|
243
|
+
if (!pending || !message.save) {
|
|
244
|
+
return {};
|
|
245
|
+
}
|
|
246
|
+
const res = await callApi("/api/save-form-fill-account", {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: {"Content-Type": "application/json"},
|
|
249
|
+
body: JSON.stringify({application: pending.application, username: pending.username, password: pending.password, url: pending.url}),
|
|
105
250
|
});
|
|
251
|
+
sitesCache = null;
|
|
252
|
+
return res.status === "ok" ? {saved: true} : {error: res.msg};
|
|
106
253
|
}
|
|
107
254
|
|
|
108
|
-
function
|
|
109
|
-
|
|
255
|
+
function getRedirectUri() {
|
|
256
|
+
return chrome.identity.getRedirectURL();
|
|
110
257
|
}
|
|
111
258
|
|
|
112
|
-
|
|
259
|
+
async function login(message) {
|
|
260
|
+
const endpoint = normalizeEndpoint(message.endpoint);
|
|
261
|
+
const applicationName = (message.applicationName || "").trim();
|
|
262
|
+
if (!/^https?:\/\//.test(endpoint) || !applicationName) {
|
|
263
|
+
return {error: "endpoint"};
|
|
264
|
+
}
|
|
265
|
+
await chrome.storage.local.set({endpoint, applicationName});
|
|
266
|
+
|
|
267
|
+
let application;
|
|
268
|
+
try {
|
|
269
|
+
const response = await fetch(`${endpoint}/api/get-application?id=admin/${encodeURIComponent(applicationName)}`);
|
|
270
|
+
application = (await response.json()).data;
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return {error: error.message};
|
|
273
|
+
}
|
|
274
|
+
if (!application || !application.clientId) {
|
|
275
|
+
return {error: "application"};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const state = crypto.randomUUID();
|
|
279
|
+
const authorizeUrl = `${endpoint}/login/oauth/authorize?` + new URLSearchParams({
|
|
280
|
+
client_id: application.clientId,
|
|
281
|
+
response_type: "token",
|
|
282
|
+
redirect_uri: getRedirectUri(),
|
|
283
|
+
scope: "openid profile",
|
|
284
|
+
state,
|
|
285
|
+
}).toString();
|
|
286
|
+
|
|
287
|
+
let redirectUrl;
|
|
288
|
+
try {
|
|
289
|
+
redirectUrl = await chrome.identity.launchWebAuthFlow({url: authorizeUrl, interactive: true});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return {error: error.message};
|
|
292
|
+
}
|
|
293
|
+
const params = new URLSearchParams(new URL(redirectUrl).hash.slice(1));
|
|
294
|
+
if (params.get("state") !== state || !params.get("access_token")) {
|
|
295
|
+
return {error: "token"};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
sitesCache = null;
|
|
299
|
+
await chrome.storage.local.set({accessToken: params.get("access_token")});
|
|
300
|
+
const accountRes = await callApi("/api/get-account");
|
|
301
|
+
if (accountRes.status !== "ok") {
|
|
302
|
+
await signOut();
|
|
303
|
+
return {error: accountRes.msg};
|
|
304
|
+
}
|
|
305
|
+
const user = accountRes.data;
|
|
306
|
+
await chrome.storage.local.set({userProfile: {name: user.name, displayName: user.displayName, avatar: user.avatar, owner: user.owner}});
|
|
307
|
+
return {ok: true};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function getStatus() {
|
|
311
|
+
await migrateSettings();
|
|
312
|
+
const settings = await getSettings();
|
|
313
|
+
const status = {
|
|
314
|
+
endpoint: settings.endpoint || "",
|
|
315
|
+
applicationName: settings.applicationName || "",
|
|
316
|
+
user: settings.accessToken ? settings.userProfile : null,
|
|
317
|
+
autoFill: !settings.disableAutoFill,
|
|
318
|
+
redirectUri: getRedirectUri(),
|
|
319
|
+
};
|
|
320
|
+
if (status.user) {
|
|
321
|
+
const cache = await getSites();
|
|
322
|
+
if (cache) {
|
|
323
|
+
status.mode = cache.mode;
|
|
324
|
+
status.message = cache.message || "";
|
|
325
|
+
status.sites = cache.mode === "sites"
|
|
326
|
+
? cache.sites.map((site) => ({name: site.displayName || site.application, accounts: site.accounts.length}))
|
|
327
|
+
: cache.accounts.map((account) => ({name: account.application, accounts: 1}));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return status;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const handlers = {
|
|
334
|
+
getFill: (message, sender) => getFill(sender),
|
|
335
|
+
getAccount: (message, sender) => getAccount(message, sender),
|
|
336
|
+
capture: (message, sender) => capture(message, sender),
|
|
337
|
+
getPending: (message, sender) => getPending(sender),
|
|
338
|
+
resolvePending: (message, sender) => resolvePending(message, sender),
|
|
339
|
+
login: (message) => login(message),
|
|
340
|
+
logout: () => signOut().then(() => ({ok: true})),
|
|
341
|
+
getStatus: () => getStatus(),
|
|
342
|
+
setAutoFill: (message) => chrome.storage.local.set({disableAutoFill: !message.enabled}).then(() => ({ok: true})),
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
346
|
+
const handler = handlers[message && message.type];
|
|
347
|
+
if (!handler) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
// the content scripts only reach the handlers that act on their own frame, the rest are for the popup
|
|
351
|
+
const fromExtension = (sender.url || "").startsWith(chrome.runtime.getURL(""));
|
|
352
|
+
if (!fromExtension && ["login", "logout", "getStatus", "setAutoFill"].includes(message.type)) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
handler(message, sender)
|
|
356
|
+
.then(sendResponse)
|
|
357
|
+
.catch((error) => sendResponse({error: error.message}));
|
|
358
|
+
return true;
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
362
|
+
chrome.storage.session.remove([pendingKey(tabId)]);
|
|
363
|
+
for (const key of lastAutoSubmits.keys()) {
|
|
364
|
+
if (key.startsWith(`${tabId}/`)) {
|
|
365
|
+
lastAutoSubmits.delete(key);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|