1
0
Fork 0
mirror of https://github.com/anyproto/anytype-ts.git synced 2025-06-07 21:47:02 +09:00

JS-5614: Webclipper auth rework

This commit is contained in:
Andrew Simachev 2025-01-01 14:16:37 +01:00
parent 21eab5296a
commit 198988f19e
No known key found for this signature in database
GPG key ID: 1DFE44B21443F0EF
14 changed files with 229 additions and 27 deletions

10
dist/extension/auth/index.html vendored Normal file
View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Anytype Web Clipper</title>
</head>
<body>
<script type="text/javascript" src="../js/main.js"></script>
</body>
</html>

View file

@ -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);

82
extension/auth.tsx Normal file
View file

@ -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<RouteComponentProps> {
render () {
const { match } = this.props;
const params = match.params as any;
const page = params.page || 'index';
const Component = Components[page];
return Component ? <Component /> : null;
};
};
class Auth extends React.Component {
render () {
return (
<Router history={history}>
<Provider {...S}>
<div>
<Switch>
{Routes.map((item: any, i: number) => (
<Route path={item.path} exact={true} key={i} component={RoutePage} />
))}
</Switch>
</div>
</Provider>
</Router>
);
};
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;

View file

@ -8,7 +8,7 @@ interface State {
error: string;
};
const Challenge = observer(class Challenge extends React.Component<I.PageComponent, State> {
const Index = observer(class Index extends React.Component<I.PageComponent, State> {
ref: any = null;
state = {
@ -19,21 +19,20 @@ const Challenge = observer(class Challenge extends React.Component<I.PageCompone
super(props);
this.onSuccess = this.onSuccess.bind(this);
this.onError = this.onError.bind(this);
};
render () {
const { error } = this.state;
return (
<div className="page pageChallenge">
<div className="page pageIndex">
<Title text="Please enter the numbers from the app" />
<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;
export default Index;

View file

@ -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;

View file

@ -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) {

View file

@ -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);
};

View file

@ -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,
};

View file

@ -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))}` });
});
};
};

31
extension/scss/auth.scss Normal file
View file

@ -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; }
}
}

View file

@ -1,5 +1,6 @@
html.anytypeWebclipper-iframe,
html.anytypeWebclipper-popup {
html.anytypeWebclipper-popup,
html.anytypeWebclipper-auth {
@import "~scss/font";
@import "~scss/_mixins";

View file

@ -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 {

View file

@ -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();
});

View file

@ -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]);
};
};