on master help sign in
Got things working (:
0793483iko
backend/Bridge.purs+10−9
@@ -1,9 +1,9 @@
1-- File auto generated by purescript-bridge! --
1-- File auto generated by purescript-bridge! --
2module Bridge where
2module Bridge where
3
3
4−import Data.Argonaut.Decode.Generic (genericDecodeJsonWith)
4+import Data.Argonaut.Aeson.Decode.Generic (genericDecodeAeson)
5−import Data.Argonaut.Encode.Generic (genericEncodeJsonWith)
5+import Data.Argonaut.Aeson.Encode.Generic (genericEncodeAeson)
6−import Data.Argonaut.Types.Generic as Argonaut
6+import Data.Argonaut.Aeson.Options as Argonaut
7import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
7import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
8import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson)
8import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson)
9import Data.Generic.Rep (class Generic)
9import Data.Generic.Rep (class Generic)
@@ -19,9 +19,9 @@ newtype Cmd_Login =
19 }
19 }
20
20
21instance encodeJsonCmd_Login :: EncodeJson Cmd_Login where
21instance encodeJsonCmd_Login :: EncodeJson Cmd_Login where
22− encodeJson = genericEncodeJsonWith Argonaut.defaultEncoding
22+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
23instance decodeJsonCmd_Login :: DecodeJson Cmd_Login where
23instance decodeJsonCmd_Login :: DecodeJson Cmd_Login where
24− decodeJson = genericDecodeJsonWith Argonaut.defaultEncoding
24+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
25derive instance genericCmd_Login :: Generic Cmd_Login _
25derive instance genericCmd_Login :: Generic Cmd_Login _
26derive instance eqCmd_Login :: Eq Cmd_Login
26derive instance eqCmd_Login :: Eq Cmd_Login
27derive instance ordCmd_Login :: Ord Cmd_Login
27derive instance ordCmd_Login :: Ord Cmd_Login
@@ -30,9 +30,9 @@ newtype Cmd =
30 Login Cmd_Login
30 Login Cmd_Login
31
31
32instance encodeJsonCmd :: EncodeJson Cmd where
32instance encodeJsonCmd :: EncodeJson Cmd where
33− encodeJson = genericEncodeJsonWith Argonaut.defaultEncoding
33+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
34instance decodeJsonCmd :: DecodeJson Cmd where
34instance decodeJsonCmd :: DecodeJson Cmd where
35− decodeJson = genericDecodeJsonWith Argonaut.defaultEncoding
35+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
36derive instance genericCmd :: Generic Cmd _
36derive instance genericCmd :: Generic Cmd _
37derive instance eqCmd :: Eq Cmd
37derive instance eqCmd :: Eq Cmd
38derive instance ordCmd :: Ord Cmd
38derive instance ordCmd :: Ord Cmd
@@ -41,9 +41,10 @@ data Sub =
41 Hello
41 Hello
42
42
43instance encodeJsonSub :: EncodeJson Sub where
43instance encodeJsonSub :: EncodeJson Sub where
44− encodeJson = genericEncodeJsonWith Argonaut.defaultEncoding
44+ encodeJson = genericEncodeAeson Argonaut.defaultOptions
45instance decodeJsonSub :: DecodeJson Sub where
45instance decodeJsonSub :: DecodeJson Sub where
46− decodeJson = genericDecodeJsonWith Argonaut.defaultEncoding
46+ decodeJson = genericDecodeAeson Argonaut.defaultOptions
47derive instance genericSub :: Generic Sub _
47derive instance genericSub :: Generic Sub _
48derive instance eqSub :: Eq Sub
48derive instance eqSub :: Eq Sub
49derive instance ordSub :: Ord Sub
49derive instance ordSub :: Ord Sub
50+
backend/Elm.purs+2−1
@@ -8,7 +8,7 @@ import Data.Either
8import Prelude
8import Prelude
9
9
10import Bridge as Bridge
10import Bridge as Bridge
11−import Data.Argonaut (decodeJson, encodeJson)
11+import Data.Argonaut (decodeJson, encodeJson, stringify)
12import Effect (Effect)
12import Effect (Effect)
13import Effect.Class.Console (log)
13import Effect.Class.Console (log)
14import FFI as FFI
14import FFI as FFI
@@ -18,6 +18,7 @@ send elm a = FFI.sendElmEvent elm "bridgeSub" (encodeJson a)
18
18
19subscribe :: FFI.Elm → (Bridge.Cmd → Effect Unit) -> Effect Unit
19subscribe :: FFI.Elm → (Bridge.Cmd → Effect Unit) -> Effect Unit
20subscribe elm f = FFI.createElmSubscription elm "bridgeCmd" $ \a → do
20subscribe elm f = FFI.createElmSubscription elm "bridgeCmd" $ \a → do
21+ log $ stringify a
21 case decodeJson a of
22 case decodeJson a of
22 Right (cmd :: Bridge.Cmd) -> f cmd
23 Right (cmd :: Bridge.Cmd) -> f cmd
23 Left err -> log $ show err
24 Left err -> log $ show err
backend/Main.purs+2
@@ -2,6 +2,7 @@ module Main where
2
2
3import Prelude
3import Prelude
4
4
5+import BW as WB
5import Bridge (Sub(..))
6import Bridge (Sub(..))
6import Effect (Effect)
7import Effect (Effect)
7import Effect.Console (log)
8import Effect.Console (log)
@@ -11,6 +12,7 @@ import FFI as FFI
11
12
12main :: Effect Unit
13main :: Effect Unit
13main = do
14main = do
15+ bwAPI <- WB.getAPI
14 app <- FFI.startElm "elm"
16 app <- FFI.startElm "elm"
15 Elm.subscribe app \c -> log $ "Got cmd"
17 Elm.subscribe app \c -> log $ "Got cmd"
16 Elm.send app Hello
18 Elm.send app Hello
bw/src/BW.js+345−6
@@ -8,12 +8,9 @@ import { StateMigrationService } from "../../deps/bw/libs/shared/dist/src/servic
8import { TokenService } from "../../deps/bw/libs/shared/dist/src/services/token.service";
8import { TokenService } from "../../deps/bw/libs/shared/dist/src/services/token.service";
9import { WebCryptoFunctionService } from "../../deps/bw/libs/shared/dist/src/services/webCryptoFunction.service";
9import { WebCryptoFunctionService } from "../../deps/bw/libs/shared/dist/src/services/webCryptoFunction.service";
10
10
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";
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";
16−import I18nService from "../../deps/bw/libs/shared/dist/src/services/i18n.service";
13+import { I18nService } from "../../deps/bw/libs/shared/dist/src/services/i18n.service";
17import { StateService } from "../../deps/bw/libs/shared/dist/src/services/state.service";
14import { StateService } from "../../deps/bw/libs/shared/dist/src/services/state.service";
18
15
19export function getAPI() {
16export function getAPI() {
@@ -159,3 +156,345 @@ class BrowserStorageService {
159 this.storage.removeItem(key)
156 this.storage.removeItem(key)
160 }
157 }
161}
158}
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 =
104
104
105in upstream
105in upstream
106 with bw = ./bw/spago.dhall as Location
106 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.
15 [ "aff"
15 [ "aff"
16 , "aff-promise"
16 , "aff-promise"
17 , "argonaut"
17 , "argonaut"
18+ , "argonaut-aeson-generic"
18 , "argonaut-codecs"
19 , "argonaut-codecs"
19− , "argonaut-generic"
20 , "bw"
20 , "bw"
21 , "console"
21 , "console"
22 , "effect"
22 , "effect"