on master help sign in
Got things working (:
commit0793483146cfeb680bd8fb325ba0746294c4cd62 authoriko <ilyakooo0@gmail.com> dateSat 23 Jul 2022, 16:42 (4 years ago) parent0e2d198
6 files changed+383−17
backend/Bridge.purs+10−9 backend/Elm.purs+2−1 backend/Main.purs+2 bw/src/BW.js+345−6 packages.dhall+23 spago.dhall+1−1
backend/Bridge.purs+10−9
@@ -1,9 +1,9 @@
11-- File auto generated by purescript-bridge! --
22module Bridge where
33
4−import Data.Argonaut.Decode.Generic (genericDecodeJsonWith)
5−import Data.Argonaut.Encode.Generic (genericEncodeJsonWith)
6−import Data.Argonaut.Types.Generic as Argonaut
4+import Data.Argonaut.Aeson.Decode.Generic (genericDecodeAeson)
5+import Data.Argonaut.Aeson.Encode.Generic (genericEncodeAeson)
6+import Data.Argonaut.Aeson.Options as Argonaut
77import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
88import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson)
99import Data.Generic.Rep (class Generic)
@@ -19,9 +19,9 @@ newtype Cmd_Login =
1919 }
2020
2121instance encodeJsonCmd_Login :: EncodeJson Cmd_Login where
22− encodeJson = genericEncodeJsonWith Argonaut.defaultEncoding
22+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
2323instance decodeJsonCmd_Login :: DecodeJson Cmd_Login where
24− decodeJson = genericDecodeJsonWith Argonaut.defaultEncoding
24+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
2525derive instance genericCmd_Login :: Generic Cmd_Login _
2626derive instance eqCmd_Login :: Eq Cmd_Login
2727derive instance ordCmd_Login :: Ord Cmd_Login
@@ -30,9 +30,9 @@ newtype Cmd =
3030 Login Cmd_Login
3131
3232instance encodeJsonCmd :: EncodeJson Cmd where
33− encodeJson = genericEncodeJsonWith Argonaut.defaultEncoding
33+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
3434instance decodeJsonCmd :: DecodeJson Cmd where
35− decodeJson = genericDecodeJsonWith Argonaut.defaultEncoding
35+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
3636derive instance genericCmd :: Generic Cmd _
3737derive instance eqCmd :: Eq Cmd
3838derive instance ordCmd :: Ord Cmd
@@ -41,9 +41,10 @@ data Sub =
4141 Hello
4242
4343instance encodeJsonSub :: EncodeJson Sub where
44− encodeJson = genericEncodeJsonWith Argonaut.defaultEncoding
44+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
4545instance decodeJsonSub :: DecodeJson Sub where
46− decodeJson = genericDecodeJsonWith Argonaut.defaultEncoding
46+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
4747derive instance genericSub :: Generic Sub _
4848derive instance eqSub :: Eq Sub
4949derive instance ordSub :: Ord Sub
50+
backend/Elm.purs+2−1
@@ -8,7 +8,7 @@ import Data.Either
88import Prelude
99
1010import Bridge as Bridge
11−import Data.Argonaut (decodeJson, encodeJson)
11+import Data.Argonaut (decodeJson, encodeJson, stringify)
1212import Effect (Effect)
1313import Effect.Class.Console (log)
1414import FFI as FFI
@@ -18,6 +18,7 @@ send elm a = FFI.sendElmEvent elm "bridgeSub" (encodeJson a)
1818
1919subscribe :: FFI.Elm → (Bridge.Cmd → Effect Unit) -> Effect Unit
2020subscribe elm f = FFI.createElmSubscription elm "bridgeCmd" $ \a → do
21+ log $ stringify a
2122 case decodeJson a of
2223 Right (cmd :: Bridge.Cmd) -> f cmd
2324 Left err -> log $ show err
backend/Main.purs+2
@@ -2,6 +2,7 @@ module Main where
22
33import Prelude
44
5+import BW as WB
56import Bridge (Sub(..))
67import Effect (Effect)
78import Effect.Console (log)
@@ -11,6 +12,7 @@ import FFI as FFI
1112
1213main :: Effect Unit
1314main = do
15+ bwAPI <- WB.getAPI
1416 app <- FFI.startElm "elm"
1517 Elm.subscribe app \c -> log $ "Got cmd"
1618 Elm.send app Hello
bw/src/BW.js+345−6
@@ -8,12 +8,9 @@ import { StateMigrationService } from "../../deps/bw/libs/shared/dist/src/servic
88import { TokenService } from "../../deps/bw/libs/shared/dist/src/services/token.service";
99import { WebCryptoFunctionService } from "../../deps/bw/libs/shared/dist/src/services/webCryptoFunction.service";
1010
11−import { BrowserCryptoService } from "../../deps/bw/libs/shared/dist/src/services/browserCrypto.service";
12−import BrowserMessagingService from "../../deps/bw/libs/shared/dist/src/services/browserMessaging.service";
13−import BrowserMessagingPrivateModeBackgroundService from "../../deps/bw/libs/shared/dist/src/services/browserMessagingPrivateModeBackground.service";
14−import BrowserPlatformUtilsService from "../../deps/bw/libs/shared/dist/src/services/browserPlatformUtils.service";
15−import BrowserStorageService from "../../deps/bw/libs/shared/dist/src/services/browserStorage.service";
16−import I18nService from "../../deps/bw/libs/shared/dist/src/services/i18n.service";
11+// import BrowserStorageService from "../../deps/bw/libs/shared/dist/src/services/browserStorage.service";
12+import { CryptoService } from "../../deps/bw/libs/shared/dist/src/abstractions/crypto.service";
13+import { I18nService } from "../../deps/bw/libs/shared/dist/src/services/i18n.service";
1714import { StateService } from "../../deps/bw/libs/shared/dist/src/services/state.service";
1815
1916export function getAPI() {
@@ -159,3 +156,345 @@ class BrowserStorageService {
159156 this.storage.removeItem(key)
160157 }
161158}
159+
160+class BrowserCryptoService extends CryptoService {
161+ async retrieveKeyFromStorage(keySuffix) {
162+ if (keySuffix === "biometric") {
163+ await this.platformUtilService.authenticateBiometric();
164+ return (await this.getKey())?.keyB64;
165+ }
166+
167+ return await super.retrieveKeyFromStorage(keySuffix);
168+ }
169+}
170+
171+class BrowserMessagingService {
172+ send(subscriber, arg = {}) {
173+ const message = Object.assign({}, { command: subscriber }, arg);
174+ chrome.runtime.sendMessage(message);
175+ }
176+}
177+
178+class BrowserMessagingPrivateModeBackgroundService {
179+ send(subscriber, arg = {}) {
180+ const message = Object.assign({}, { command: subscriber }, arg);
181+ window.bitwardenPopupMainMessageListener(message);
182+ }
183+}
184+
185+class BrowserPlatformUtilsService {
186+ constructor(messagingService, stateService, clipboardWriteCallback, biometricCallback) {
187+ this.messagingService = messagingService;
188+ this.stateService = stateService;
189+ this.clipboardWriteCallback = clipboardWriteCallback;
190+ this.biometricCallback = biometricCallback;
191+ this.showDialogResolves = new Map();
192+ this.passwordDialogResolves = new Map();
193+ this.deviceCache = null;
194+ this.prefersColorSchemeDark = window.matchMedia("(prefers-color-scheme: dark)");
195+ }
196+ getDevice() {
197+ if (this.deviceCache) {
198+ return this.deviceCache;
199+ }
200+ if (navigator.userAgent.indexOf(" Firefox/") !== -1 ||
201+ navigator.userAgent.indexOf(" Gecko/") !== -1) {
202+ this.deviceCache = DeviceType.FirefoxExtension;
203+ }
204+ else if ((!!window.opr && !!opr.addons) ||
205+ !!window.opera ||
206+ navigator.userAgent.indexOf(" OPR/") >= 0) {
207+ this.deviceCache = DeviceType.OperaExtension;
208+ }
209+ else if (navigator.userAgent.indexOf(" Edg/") !== -1) {
210+ this.deviceCache = DeviceType.EdgeExtension;
211+ }
212+ else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) {
213+ this.deviceCache = DeviceType.VivaldiExtension;
214+ }
215+ else if (window.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) {
216+ this.deviceCache = DeviceType.ChromeExtension;
217+ }
218+ else if (navigator.userAgent.indexOf(" Safari/") !== -1) {
219+ this.deviceCache = DeviceType.SafariExtension;
220+ }
221+ return this.deviceCache;
222+ }
223+ getDeviceString() {
224+ const device = DeviceType[this.getDevice()].toLowerCase();
225+ return device.replace("extension", "");
226+ }
227+ getClientType() {
228+ return ClientType.Browser;
229+ }
230+ isFirefox() {
231+ return this.getDevice() === DeviceType.FirefoxExtension;
232+ }
233+ isChrome() {
234+ return this.getDevice() === DeviceType.ChromeExtension;
235+ }
236+ isEdge() {
237+ return this.getDevice() === DeviceType.EdgeExtension;
238+ }
239+ isOpera() {
240+ return this.getDevice() === DeviceType.OperaExtension;
241+ }
242+ isVivaldi() {
243+ return this.getDevice() === DeviceType.VivaldiExtension;
244+ }
245+ isSafari() {
246+ return this.getDevice() === DeviceType.SafariExtension;
247+ }
248+ isIE() {
249+ return false;
250+ }
251+ isMacAppStore() {
252+ return false;
253+ }
254+ isViewOpen() {
255+ return __awaiter(this, void 0, void 0, function* () {
256+ if (yield BrowserApi.isPopupOpen()) {
257+ return true;
258+ }
259+ if (this.isSafari()) {
260+ return false;
261+ }
262+ const sidebarView = this.sidebarViewName();
263+ const sidebarOpen = sidebarView != null && chrome.extension.getViews({ type: sidebarView }).length > 0;
264+ if (sidebarOpen) {
265+ return true;
266+ }
267+ const tabOpen = chrome.extension.getViews({ type: "tab" }).length > 0;
268+ return tabOpen;
269+ });
270+ }
271+ lockTimeout() {
272+ return null;
273+ }
274+ launchUri(uri, options) {
275+ BrowserApi.createNewTab(uri, options && options.extensionPage === true);
276+ }
277+ saveFile(win, blobData, blobOptions, fileName) {
278+ BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
279+ }
280+ getApplicationVersion() {
281+ return Promise.resolve(BrowserApi.getApplicationVersion());
282+ }
283+ supportsWebAuthn(win) {
284+ return typeof PublicKeyCredential !== "undefined";
285+ }
286+ supportsDuo() {
287+ return true;
288+ }
289+ showToast(type, title, text, options) {
290+ this.messagingService.send("showToast", {
291+ text: text,
292+ title: title,
293+ type: type,
294+ options: options,
295+ });
296+ }
297+ showDialog(body, title, confirmText, cancelText, type, bodyIsHtml = false) {
298+ const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
299+ this.messagingService.send("showDialog", {
300+ text: bodyIsHtml ? null : body,
301+ html: bodyIsHtml ? body : null,
302+ title: title,
303+ confirmText: confirmText,
304+ cancelText: cancelText,
305+ type: type,
306+ dialogId: dialogId,
307+ });
308+ return new Promise((resolve) => {
309+ this.showDialogResolves.set(dialogId, { resolve: resolve, date: new Date() });
310+ });
311+ }
312+ isDev() {
313+ return process.env.ENV === "development";
314+ }
315+ isSelfHost() {
316+ return false;
317+ }
318+ copyToClipboard(text, options) {
319+ let win = window;
320+ let doc = window.document;
321+ if (options && (options.window || options.win)) {
322+ win = options.window || options.win;
323+ doc = win.document;
324+ }
325+ else if (options && options.doc) {
326+ doc = options.doc;
327+ }
328+ const clearing = options ? !!options.clearing : false;
329+ const clearMs = options && options.clearMs ? options.clearMs : null;
330+ if (this.isSafari()) {
331+ SafariApp.sendMessageToApp("copyToClipboard", text).then(() => {
332+ if (!clearing && this.clipboardWriteCallback != null) {
333+ this.clipboardWriteCallback(text, clearMs);
334+ }
335+ });
336+ }
337+ else if (this.isFirefox() &&
338+ win.navigator.clipboard &&
339+ win.navigator.clipboard.writeText) {
340+ win.navigator.clipboard.writeText(text).then(() => {
341+ if (!clearing && this.clipboardWriteCallback != null) {
342+ this.clipboardWriteCallback(text, clearMs);
343+ }
344+ });
345+ }
346+ else if (win.clipboardData && win.clipboardData.setData) {
347+ // IE specific code path to prevent textarea being shown while dialog is visible.
348+ win.clipboardData.setData("Text", text);
349+ if (!clearing && this.clipboardWriteCallback != null) {
350+ this.clipboardWriteCallback(text, clearMs);
351+ }
352+ }
353+ else if (doc.queryCommandSupported && doc.queryCommandSupported("copy")) {
354+ if (this.isChrome() && text === "") {
355+ text = "\u0000";
356+ }
357+ const textarea = doc.createElement("textarea");
358+ textarea.textContent = text == null || text === "" ? " " : text;
359+ // Prevent scrolling to bottom of page in MS Edge.
360+ textarea.style.position = "fixed";
361+ doc.body.appendChild(textarea);
362+ textarea.select();
363+ try {
364+ // Security exception may be thrown by some browsers.
365+ if (doc.execCommand("copy") && !clearing && this.clipboardWriteCallback != null) {
366+ this.clipboardWriteCallback(text, clearMs);
367+ }
368+ }
369+ catch (e) {
370+ // eslint-disable-next-line
371+ console.warn("Copy to clipboard failed.", e);
372+ }
373+ finally {
374+ doc.body.removeChild(textarea);
375+ }
376+ }
377+ }
378+ readFromClipboard(options) {
379+ return __awaiter(this, void 0, void 0, function* () {
380+ let win = window;
381+ let doc = window.document;
382+ if (options && (options.window || options.win)) {
383+ win = options.window || options.win;
384+ doc = win.document;
385+ }
386+ else if (options && options.doc) {
387+ doc = options.doc;
388+ }
389+ if (this.isSafari()) {
390+ return yield SafariApp.sendMessageToApp("readFromClipboard");
391+ }
392+ else if (this.isFirefox() &&
393+ win.navigator.clipboard &&
394+ win.navigator.clipboard.readText) {
395+ return yield win.navigator.clipboard.readText();
396+ }
397+ else if (doc.queryCommandSupported && doc.queryCommandSupported("paste")) {
398+ const textarea = doc.createElement("textarea");
399+ // Prevent scrolling to bottom of page in MS Edge.
400+ textarea.style.position = "fixed";
401+ doc.body.appendChild(textarea);
402+ textarea.focus();
403+ try {
404+ // Security exception may be thrown by some browsers.
405+ if (doc.execCommand("paste")) {
406+ return textarea.value;
407+ }
408+ }
409+ catch (e) {
410+ // eslint-disable-next-line
411+ console.warn("Read from clipboard failed.", e);
412+ }
413+ finally {
414+ doc.body.removeChild(textarea);
415+ }
416+ }
417+ return null;
418+ });
419+ }
420+ resolveDialogPromise(dialogId, confirmed) {
421+ if (this.showDialogResolves.has(dialogId)) {
422+ const resolveObj = this.showDialogResolves.get(dialogId);
423+ resolveObj.resolve(confirmed);
424+ this.showDialogResolves.delete(dialogId);
425+ }
426+ // Clean up old promises
427+ this.showDialogResolves.forEach((val, key) => {
428+ const age = new Date().getTime() - val.date.getTime();
429+ if (age > DialogPromiseExpiration) {
430+ this.showDialogResolves.delete(key);
431+ }
432+ });
433+ }
434+ resolvePasswordDialogPromise(dialogId, canceled, password) {
435+ return __awaiter(this, void 0, void 0, function* () {
436+ let result = false;
437+ if (this.passwordDialogResolves.has(dialogId)) {
438+ const resolveObj = this.passwordDialogResolves.get(dialogId);
439+ if (yield resolveObj.tryResolve(canceled, password)) {
440+ this.passwordDialogResolves.delete(dialogId);
441+ result = true;
442+ }
443+ }
444+ // Clean up old promises
445+ this.passwordDialogResolves.forEach((val, key) => {
446+ const age = new Date().getTime() - val.date.getTime();
447+ if (age > DialogPromiseExpiration) {
448+ this.passwordDialogResolves.delete(key);
449+ }
450+ });
451+ return result;
452+ });
453+ }
454+ supportsBiometric() {
455+ return __awaiter(this, void 0, void 0, function* () {
456+ const platformInfo = yield BrowserApi.getPlatformInfo();
457+ if (platformInfo.os === "android") {
458+ return false;
459+ }
460+ if (this.isFirefox()) {
461+ return parseInt((yield browser.runtime.getBrowserInfo()).version.split(".")[0], 10) >= 87;
462+ }
463+ return true;
464+ });
465+ }
466+ authenticateBiometric() {
467+ return this.biometricCallback();
468+ }
469+ sidebarViewName() {
470+ if (window.chrome.sidebarAction && this.isFirefox()) {
471+ return "sidebar";
472+ }
473+ else if (this.isOpera() && typeof opr !== "undefined" && opr.sidebarAction) {
474+ return "sidebar_panel";
475+ }
476+ return null;
477+ }
478+ supportsSecureStorage() {
479+ return false;
480+ }
481+ getDefaultSystemTheme() {
482+ return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light);
483+ }
484+ onDefaultSystemThemeChange(callback) {
485+ this.prefersColorSchemeDark.addEventListener("change", ({ matches }) => {
486+ callback(matches ? ThemeType.Dark : ThemeType.Light);
487+ });
488+ }
489+ getEffectiveTheme() {
490+ return __awaiter(this, void 0, void 0, function* () {
491+ const theme = (yield this.stateService.getTheme());
492+ if (theme == null || theme === ThemeType.System) {
493+ return this.getDefaultSystemTheme();
494+ }
495+ else {
496+ return theme;
497+ }
498+ });
499+ }
500+}
packages.dhall+23
@@ -104,3 +104,26 @@ let upstream =
104104
105105in upstream
106106 with bw = ./bw/spago.dhall as Location
107+ with argonaut-aeson-generic =
108+ { dependencies =
109+ [ "argonaut-codecs"
110+ , "argonaut-core"
111+ , "argonaut-generic"
112+ , "arrays"
113+ , "bifunctors"
114+ , "control"
115+ , "effect"
116+ , "either"
117+ , "foldable-traversable"
118+ , "foreign-object"
119+ , "maybe"
120+ , "partial"
121+ , "prelude"
122+ , "psci-support"
123+ , "record"
124+ , "test-unit"
125+ , "typelevel-prelude"
126+ ]
127+ , repo = "https://github.com/ilyakooo0/purescript-argonaut-aeson-generic.git"
128+ , version = "purescript-0.14"
129+ }
spago.dhall+1−1
@@ -15,8 +15,8 @@ to generate this file without the comments in this block.
1515 [ "aff"
1616 , "aff-promise"
1717 , "argonaut"
18+ , "argonaut-aeson-generic"
1819 , "argonaut-codecs"
19− , "argonaut-generic"
2020 , "bw"
2121 , "console"
2222 , "effect"