diff --git a/dist/extension/auth/index.html b/dist/extension/auth/index.html new file mode 100644 index 0000000000..152adfb788 --- /dev/null +++ b/dist/extension/auth/index.html @@ -0,0 +1,10 @@ + + + + + Anytype Web Clipper + + + + + \ No newline at end of file diff --git a/electron/js/window.js b/electron/js/window.js index 3176249431..f315384b07 100644 --- a/electron/js/window.js +++ b/electron/js/window.js @@ -41,6 +41,8 @@ class WindowManager { sandbox: false, }, param.webPreferences); + console.log(param); + let win = new BrowserWindow(param); remote.enable(win.webContents); @@ -142,10 +144,16 @@ class WindowManager { }; createChallenge (options) { + const { screen } = require('electron'); + const primaryDisplay = screen.getPrimaryDisplay(); + const { width } = primaryDisplay.workAreaSize; + const win = this.create({}, { backgroundColor: '', width: 424, height: 232, + x: Math.floor(width / 2 - 212), + y: 100, titleBarStyle: 'hidden', }); @@ -153,6 +161,7 @@ class WindowManager { win.setMenu(null); is.windows || is.linux ? win.showInactive() : win.show(); + win.focus(); win.webContents.once('did-finish-load', () => { win.webContents.postMessage('challenge', options); diff --git a/extension/auth.tsx b/extension/auth.tsx new file mode 100644 index 0000000000..7da76dc4ef --- /dev/null +++ b/extension/auth.tsx @@ -0,0 +1,82 @@ +import * as React from 'react'; +import * as hs from 'history'; +import { Router, Route, Switch } from 'react-router-dom'; +import { RouteComponentProps } from 'react-router'; +import { Provider } from 'mobx-react'; +import { configure } from 'mobx'; +import { S, U } from 'Lib'; + +import Index from './auth/index'; +import Success from './auth/success'; +import Util from './lib/util'; + +import './scss/auth.scss'; + +configure({ enforceActions: 'never' }); + +const Routes = [ + { path: '/' }, + { path: '/:page' }, +]; + +const Components = { + index: Index, + success: Success, +}; + +const memoryHistory = hs.createMemoryHistory; +const history = memoryHistory(); + +class RoutePage extends React.Component { + + render () { + const { match } = this.props; + const params = match.params as any; + const page = params.page || 'index'; + const Component = Components[page]; + + return Component ? : null; + }; + +}; + +class Auth extends React.Component { + + render () { + return ( + + +
+ + {Routes.map((item: any, i: number) => ( + + ))} + +
+
+
+ ); + }; + + componentDidMount () { + U.Router.init(history); + + /* @ts-ignore */ + chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + switch (msg.type) { + case 'initAuth': + const { appKey, gatewayPort, serverPort } = msg; + + Util.init(serverPort, gatewayPort); + Util.authorize(appKey); + + sendResponse({}); + break; + }; + return true; + }); + }; + +}; + +export default Auth; \ No newline at end of file diff --git a/extension/popup/challenge.tsx b/extension/auth/index.tsx similarity index 60% rename from extension/popup/challenge.tsx rename to extension/auth/index.tsx index 96c5d2e235..8c6e98f87b 100644 --- a/extension/popup/challenge.tsx +++ b/extension/auth/index.tsx @@ -8,7 +8,7 @@ interface State { error: string; }; -const Challenge = observer(class Challenge extends React.Component { +const Index = observer(class Index extends React.Component { ref: any = null; state = { @@ -19,21 +19,20 @@ const Challenge = observer(class Challenge extends React.Component +
<Pin ref={ref => this.ref = ref} pinLength={4} onSuccess={this.onSuccess} - onError={this.onError} + onError={() => {}} /> <Error text={error} /> @@ -42,29 +41,35 @@ const Challenge = observer(class Challenge extends React.Component<I.PageCompone }; onSuccess () { - C.AccountLocalLinkSolveChallenge(S.Extension.challengeId, this.ref?.getValue().trim(), (message: any) => { + const urlParams = new URLSearchParams(window.location.search); + const data = JSON.parse(atob(urlParams.get('data') as string)); + + if (!data) { + this.setState({ error: 'Invalid data' }); + return; + }; + + Util.init(data.serverPort, data.gatewayPort); + + C.AccountLocalLinkSolveChallenge(data.challengeId, this.ref?.getValue().trim(), (message: any) => { if (message.error.code) { this.setState({ error: message.error.description }); return; }; const { appKey } = message; - const { serverPort, gatewayPort } = S.Extension; Storage.set('appKey', appKey); Util.authorize(appKey, () => { - Util.sendMessage({ type: 'initIframe', appKey, serverPort, gatewayPort }, () => {}); + Util.sendMessage({ type: 'initIframe', appKey, ...data }, () => {}); Util.sendMessage({ type: 'initMenu' }, () => {}); - U.Router.go('/create', {}); + U.Router.go('/success', {}); }); }); }; - onError () { - }; - }); -export default Challenge; \ No newline at end of file +export default Index; \ No newline at end of file diff --git a/extension/auth/success.tsx b/extension/auth/success.tsx new file mode 100644 index 0000000000..ab290a1776 --- /dev/null +++ b/extension/auth/success.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { I } from 'Lib'; + +interface State { + error: string; +}; + +const Success = observer(class Success extends React.Component<I.PageComponent, State> { + + render () { + return ( + <div className="page pageSuccess"> + <div className="title">Successfully authorized!</div> + </div> + ); + }; + +}); + +export default Success; \ No newline at end of file diff --git a/extension/entry.tsx b/extension/entry.tsx index 9a3c0854b5..924810b3b2 100644 --- a/extension/entry.tsx +++ b/extension/entry.tsx @@ -4,6 +4,7 @@ import $ from 'jquery'; import { C, U, J, S } from 'Lib'; import Popup from './popup'; import Iframe from './iframe'; +import Auth from './auth'; import Util from './lib/util'; import './scss/common.scss'; @@ -38,8 +39,8 @@ window.Anytype = { window.AnytypeGlobalConfig = { emojiUrl: J.Extension.clipper.emojiUrl, menuBorderTop: 16, - menuBorderBottom: 16, - flagsMw: { request: false }, + menuBorderBottom: 16, + flagsMw: { request: true }, }; let rootId = ''; @@ -52,6 +53,10 @@ if (Util.isPopup()) { if (Util.isIframe()) { rootId = `${J.Extension.clipper.prefix}-iframe`; component = <Iframe />; +} else +if (Util.isAuth()) { + rootId = `${J.Extension.clipper.prefix}-auth`; + component = <Auth />; }; if (!rootId) { diff --git a/extension/lib/util.ts b/extension/lib/util.ts index 56e7d7bfc0..d2055c46c1 100644 --- a/extension/lib/util.ts +++ b/extension/lib/util.ts @@ -1,7 +1,8 @@ import { S, U, J, C, dispatcher } from 'Lib'; const INDEX_POPUP = '/popup/index.html'; -const INDEX_IFRAME = '/iframe/index.html' +const INDEX_IFRAME = '/iframe/index.html'; +const INDEX_AUTH = '/auth/index.html'; class Util { @@ -20,6 +21,10 @@ class Util { return this.isExtension() && (location.pathname == INDEX_IFRAME); }; + isAuth () { + return this.isExtension() && (location.pathname == INDEX_AUTH); + }; + fromPopup (url: string) { return url.match(INDEX_POPUP); }; diff --git a/extension/popup.tsx b/extension/popup.tsx index 8e78a0767a..c39dd72df0 100644 --- a/extension/popup.tsx +++ b/extension/popup.tsx @@ -8,7 +8,6 @@ import { ListMenu } from 'Component'; import { S, U, C, J } from 'Lib'; import Index from './popup/index'; -import Challenge from './popup/challenge'; import Create from './popup/create'; import Success from './popup/success'; @@ -23,7 +22,6 @@ const Routes = [ const Components = { index: Index, - challenge: Challenge, create: Create, success: Success, }; diff --git a/extension/popup/index.tsx b/extension/popup/index.tsx index 56b260fa30..2b59ca5bd4 100644 --- a/extension/popup/index.tsx +++ b/extension/popup/index.tsx @@ -81,6 +81,7 @@ const Index = observer(class Index extends React.Component<I.PageComponent, Stat } else { /* @ts-ignore */ const manifest = chrome.runtime.getManifest(); + const { serverPort, gatewayPort } = S.Extension; C.AccountLocalLinkNewChallenge(manifest.name, (message: any) => { if (message.error.code) { @@ -89,7 +90,15 @@ const Index = observer(class Index extends React.Component<I.PageComponent, Stat }; S.Extension.challengeId = message.challengeId; - U.Router.go('/challenge', {}); + + const data = { + serverPort, + gatewayPort, + challengeId: message.challengeId, + }; + + /* @ts-ignore */ + chrome.tabs.create({ url: chrome.runtime.getURL('auth/index.html') + `?data=${btoa(JSON.stringify(data))}` }); }); }; }; diff --git a/extension/scss/auth.scss b/extension/scss/auth.scss new file mode 100644 index 0000000000..f90fcf859f --- /dev/null +++ b/extension/scss/auth.scss @@ -0,0 +1,31 @@ +html.anytypeWebclipper-auth { width: 640px; position: absolute; left: 50%; top: 50%; transform: translate3d(-50%, -50%, 0px); } + +#anytypeWebclipper-auth { + @import "~scss/_mixins"; + + .input, .select, .textarea { @include text-small; border: 1px solid var(--color-shape-secondary); width: 100%; border-radius: 1px; } + + .pin { + .input { width: 32px; height: 40px; @include text-paragraph; border-radius: 6px; } + } + + .input, .select { height: 32px; padding: 0px 10px; } + .select { display: flex; align-items: center; padding-right: 20px; } + .textarea { padding: 10px; resize: none; height: 68px; display: block; } + .buttonsWrapper { display: flex; flex-direction: column; justify-content: center; gap: 8px 0px; margin: 16px 0px 0px 0px; } + .loaderWrapper { position: fixed; left: 0px; top: 0px; width: 100%; height: 100%; background: var(--color-bg-loader); z-index: 10; } + .error { @include text-small; margin: 1em 0px 0px 0px; } + + .isFocused { border-color: var(--color-ice) !important; box-shadow: 0px 0px 0px 1px var(--color-ice); } + + .page.pageIndex, + .page.pageSuccess { display: flex; flex-direction: column; align-items: center; } + + .page.pageIndex { + .title { @include text-header3; margin: 0px 0px 24px 0px; } + } + + .page.pageSuccess { + .title { @include text-header3; } + } +} \ No newline at end of file diff --git a/extension/scss/common.scss b/extension/scss/common.scss index 6dcb09585e..568f918620 100644 --- a/extension/scss/common.scss +++ b/extension/scss/common.scss @@ -1,5 +1,6 @@ html.anytypeWebclipper-iframe, -html.anytypeWebclipper-popup { +html.anytypeWebclipper-popup, +html.anytypeWebclipper-auth { @import "~scss/font"; @import "~scss/_mixins"; diff --git a/extension/scss/popup.scss b/extension/scss/popup.scss index 3b9b557129..1ae1f10b92 100644 --- a/extension/scss/popup.scss +++ b/extension/scss/popup.scss @@ -33,11 +33,7 @@ html.anytypeWebclipper-popup { width: 268px; } .isFocused { border-color: var(--color-ice) !important; box-shadow: 0px 0px 0px 1px var(--color-ice); } - .page.pageIndex, .page.pageChallenge { padding: 50px 16px; text-align: center; } - - .page.pageChallenge { - .title { @include text-common; font-weight: 600; margin: 0px 0px 24px 0px; } - } + .page.pageIndex { padding: 50px 16px; text-align: center; } .page.pageCreate { padding: 16px; } .page.pageCreate { diff --git a/src/ts/component/form/pin.tsx b/src/ts/component/form/pin.tsx index fe3eaef464..2b997c7ca5 100644 --- a/src/ts/component/form/pin.tsx +++ b/src/ts/component/form/pin.tsx @@ -93,11 +93,12 @@ const Pin = forwardRef<PinRefProps, Props>(({ }; const onInputKeyDown = (e, index: number) => { + const current = inputRefs.current[index]; const prev = inputRefs.current[index - 1]; if (prev) { keyboard.shortcut('backspace', e, () => { - prev.setValue(''); + current.setValue(''); prev.setType('text'); prev.focus(); }); diff --git a/src/ts/lib/storage.ts b/src/ts/lib/storage.ts index 59913380c7..8016168ced 100644 --- a/src/ts/lib/storage.ts +++ b/src/ts/lib/storage.ts @@ -1,5 +1,7 @@ import { I, S, U, J } from 'Lib'; +const electron = U.Common.getElectron(); + const ACCOUNT_KEYS = [ 'spaceId', 'spaceOrder', @@ -18,6 +20,32 @@ const SPACE_KEYS = [ 'redirectInvite', ]; +const Api = { + get: (key: string) => { + if (electron.storeGet) { + return electron.storeGet(key); + } else { + localStorage.getItem(key); + }; + }, + + set: (key: string, obj: any) => { + if (electron.storeSet) { + electron.storeSet(key, obj); + } else { + localStorage.setItem(key, JSON.stringify(obj)); + }; + }, + + delete: (key: string) => { + if (electron.storeDelete) { + electron.storeDelete(key); + } else { + localStorage.removeItem(key); + }; + }, +}; + class Storage { storage: any = null; @@ -28,7 +56,7 @@ class Storage { }; get (key: string): any { - let o = U.Common.getElectron().storeGet(key); + let o = Api.get(key); if (!o) { o = this.parse(String(this.storage[key] || '')); }; @@ -80,7 +108,7 @@ class Storage { if (this.isAccountKey(key)) { this.setAccountKey(key, o); } else { - U.Common.getElectron().storeSet(key, o); + Api.set(key, o); //delete(this.storage[key]); }; }; @@ -93,6 +121,7 @@ class Storage { this.deleteAccountKey(key); } else { U.Common.getElectron().storeDelete(key); + Api.delete(key); delete(this.storage[key]); }; };