wip
This commit is contained in:
parent
9321a0ba37
commit
ded2992e44
23 changed files with 1278 additions and 83 deletions
|
@ -17,6 +17,7 @@ import installDevtools from './installDevtools';
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { diskSpaceLinux, diskSpaceWindows, diskSpaceMac } from '../ui/util/diskspace';
|
||||
import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync/sync.js';
|
||||
|
||||
const { download } = require('electron-dl');
|
||||
const mime = require('mime');
|
||||
|
@ -326,6 +327,42 @@ ipcMain.on('get-disk-space', async (event) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Sync cryptography
|
||||
ipcMain.on('get-salt-seed', () => {
|
||||
const saltSeed = generateSaltSeed();
|
||||
rendererWindow.webContents.send('got-salt-seed', saltSeed);
|
||||
});
|
||||
|
||||
ipcMain.on('get-secrets', (event, password, email, saltseed) => {
|
||||
console.log('password, salt', password, email, saltseed);
|
||||
const callback = (result) => {
|
||||
console.log('callback result', result);
|
||||
rendererWindow.webContents.send('got-secrets', result);
|
||||
};
|
||||
deriveSecrets(password, email, saltseed, callback);
|
||||
});
|
||||
|
||||
ipcMain.handle('invoke-get-secrets', (event, password, email, saltseed) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (err, result) => {
|
||||
console.log('callback result', result);
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(result);
|
||||
};
|
||||
console.log('password, salt', password, email, saltseed);
|
||||
deriveSecrets(password, email, saltseed, callback);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('invoke-get-salt-seed', (event) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const saltSeed = generateSaltSeed();
|
||||
return resolve(saltSeed);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on('version-info-requested', () => {
|
||||
function formatRc(ver) {
|
||||
// Adds dash if needed to make RC suffix SemVer friendly
|
||||
|
@ -568,3 +605,5 @@ ipcMain.on('upgrade', (event, installerPath) => {
|
|||
});
|
||||
app.quit();
|
||||
});
|
||||
|
||||
|
||||
|
|
12
electron/sync/package.json
Normal file
12
electron/sync/package.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "testsync.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "module"
|
||||
}
|
58
electron/sync/sync.js
Normal file
58
electron/sync/sync.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import crypto from 'crypto';
|
||||
|
||||
export function generateSalt(email, seed) {
|
||||
const hashInput = (email + ':' + seed).toString('utf8');
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(hashInput);
|
||||
const hash_output = hash.digest('hex').toString('utf8');
|
||||
return hash_output;
|
||||
}
|
||||
|
||||
export function generateSaltSeed() {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
export function createHmac(serverWalletState, hmacKey) {
|
||||
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
export function checkHmac(serverWalletState, hmacKey, hmac) {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
export function deriveSecrets(rootPassword, email, saltSeed, callback) {
|
||||
const encodedPassword = Buffer.from(rootPassword.normalize('NFKC'))
|
||||
const encodedEmail = Buffer.from(email)
|
||||
const SCRYPT_N = 1 << 20;
|
||||
const SCRYPT_R = 8;
|
||||
const SCRYPT_P = 1;
|
||||
const KEY_LENGTH = 32;
|
||||
const NUM_KEYS = 2;
|
||||
const MAXMEM_MULTIPLIER = 256;
|
||||
const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * SCRYPT_N * SCRYPT_R;
|
||||
|
||||
function getKeyParts(key) {
|
||||
const lbryIdPassword = key.slice(0, KEY_LENGTH).toString('base64');
|
||||
const hmacKey = key.slice(KEY_LENGTH).toString('base64');
|
||||
return { lbryIdPassword, hmacKey }; // Buffer aa bb cc 6c
|
||||
}
|
||||
|
||||
const salt = generateSalt(encodedEmail, saltSeed);
|
||||
|
||||
const scryptCallback = (err, key) => {
|
||||
if (err) {
|
||||
// handle error
|
||||
}
|
||||
|
||||
callback(err, getKeyParts(key));
|
||||
};
|
||||
|
||||
crypto.scrypt(encodedPassword, salt, KEY_LENGTH * NUM_KEYS, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: DEFAULT_MAXMEM }, scryptCallback);
|
||||
}
|
||||
|
||||
export function walletHmac(inputString) {
|
||||
const hmac = crypto.createHmac('sha256', inputString.toString('utf8'));
|
||||
const res = hmac.digest('hex');
|
||||
return res;
|
||||
}
|
58
electron/sync/testsync.js
Normal file
58
electron/sync/testsync.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import test from 'tape';
|
||||
// import sync from '../sync.js';
|
||||
import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync.js';
|
||||
export default function doTest () {
|
||||
|
||||
test('Generate sync seed', (assert) => {
|
||||
const seed = generateSaltSeed();
|
||||
console.log('seed', seed);
|
||||
assert.pass(seed.length === 64);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
test('Generate salt', (assert) => {
|
||||
const seed = '80b3aff252588c4097df8f79ad42c8a5cf8d7c9e5339efec8bdb8bc7c5cf25ca';
|
||||
const email = 'example@example.com';
|
||||
const salt = generateSalt(email, seed);
|
||||
console.log('salt', salt);
|
||||
const expected = 'be6074a96f3ce812ea51b95d48e4b10493e14f6a329df7c0816018c6239661fe';
|
||||
const actual = salt;
|
||||
|
||||
assert.equal(actual, expected,
|
||||
'salt is expected value.');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
test('Derive Keys', (assert) => {
|
||||
const seed = '80b3aff252588c4097df8f79ad42c8a5cf8d7c9e5339efec8bdb8bc7c5cf25ca';
|
||||
const email = 'example@example.com';
|
||||
|
||||
const expectedHmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; //base64
|
||||
const expectedLbryIdPassword = 'HKo/J+x4Hsy2NkMvj2JB9RI0yrvEiB4QSA/NHPaT/cA=';
|
||||
let result;
|
||||
|
||||
function cb(e, r) {
|
||||
console.log('result', r)
|
||||
assert.equal(r.keys.hmacKey, expectedHmacKey, 'hmac is expected value');
|
||||
assert.equal(r.keys.lbryIdPassword, expectedLbryIdPassword, 'lbryid password is expected value');
|
||||
assert.end();
|
||||
}
|
||||
|
||||
deriveSecrets('pass', email, seed, cb);
|
||||
});
|
||||
|
||||
test('CreateHmac', (assert) => {
|
||||
const hmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8=';
|
||||
const sequence = 1;
|
||||
const walletState = `zo4MTkyOjE2OjE68QlIU76+W91/v/F1tu8h+kGB0Ee`;
|
||||
const expectedHmacHex = '52edbad5b0f9d8cf6189795702790cc2cb92060be24672913ab3e4b69c03698b';
|
||||
|
||||
const input_str = `${sequence}:${walletState}`;
|
||||
const hmacHex = walletHmac(input_str);
|
||||
assert.equal(hmacHex, expectedHmacHex);
|
||||
assert.end();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
doTest()
|
|
@ -192,6 +192,7 @@
|
|||
"semver": "^5.3.0",
|
||||
"strip-markdown": "^3.0.3",
|
||||
"style-loader": "^0.23.1",
|
||||
"tape": "^5.6.0",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"three-full": "^28.0.2",
|
||||
"unist-util-visit": "^2.0.3",
|
||||
|
|
|
@ -2318,5 +2318,21 @@
|
|||
"Odysee Connect --[Section in Help Page]--": "Odysee Connect",
|
||||
"Your hub has blocked this content because it subscribes to the following blocking channel:": "Your hub has blocked this content because it subscribes to the following blocking channel:",
|
||||
"Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.": "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.",
|
||||
"Creator Comment settings": "Creator Comment settings",
|
||||
"Remote Sync Settings": "Remote Sync Settings",
|
||||
"Wallet Sync": "Wallet Sync",
|
||||
"Manage cross device sync for your wallet": "Manage cross device sync for your wallet",
|
||||
"With your email and wallet encryption password, we will register you with sync. If your wallet is not currentlyencrypted, it will be after this. Bitch.": "With your email and wallet encryption password, we will register you with sync. If your wallet is not currentlyencrypted, it will be after this. Bitch.",
|
||||
"Password Again": "Password Again",
|
||||
"Enter Email": "Enter Email",
|
||||
"Sign up": "Sign up",
|
||||
"Let's get you set up. This will require you to encrypt your wallet and remember your password.": "Let's get you set up. This will require you to encrypt your wallet and remember your password.",
|
||||
"Sign in to your sync account. Or %sign_up%.": "Sign in to your sync account. Or %sign_up%.",
|
||||
"Sign up for a sync account. Or %sign_in%.": "Sign up for a sync account. Or %sign_in%.",
|
||||
"Verifying": "Verifying",
|
||||
"We have sent you an email to verify your account.": "We have sent you an email to verify your account.",
|
||||
"Doing Math": "Doing Math",
|
||||
"Hold on, doing some math.": "Hold on, doing some math.",
|
||||
"You are signed in as %email%.": "You are signed in as %email%.",
|
||||
"--end--": "--end--"
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ import SearchPage from 'page/search';
|
|||
|
||||
import SettingsCreatorPage from 'page/settingsCreator';
|
||||
import SettingsNotificationsPage from 'page/settingsNotifications';
|
||||
import SettingsSyncPage from 'page/settingsSync';
|
||||
|
||||
import SettingsPage from 'page/settings';
|
||||
import ShowPage from 'page/show';
|
||||
|
@ -203,7 +204,7 @@ function AppRouter(props: Props) {
|
|||
|
||||
{/* Odysee signin */}
|
||||
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />
|
||||
|
||||
<Route path={`/$/${PAGES.SETTINGS_SYNC}`} exact component={SettingsSyncPage} />
|
||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
|
||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
doNotifyDecryptWallet,
|
||||
doNotifyEncryptWallet,
|
||||
doNotifyForgetPassword,
|
||||
doOpenModal,
|
||||
doToggle3PAnalytics,
|
||||
} from 'redux/actions/app';
|
||||
import { doSetDaemonSetting, doClearDaemonSetting, doFindFFmpeg } from 'redux/actions/settings';
|
||||
|
@ -32,6 +33,7 @@ const perform = (dispatch) => ({
|
|||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||
confirmForgetPassword: (modalProps) => dispatch(doNotifyForgetPassword(modalProps)),
|
||||
toggle3PAnalytics: (allow) => dispatch(doToggle3PAnalytics(allow)),
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SettingSystem);
|
||||
|
|
|
@ -18,6 +18,8 @@ import { getPasswordFromCookie } from 'util/saved-passwords';
|
|||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||
import SettingEnablePrereleases from 'component/settingEnablePrereleases';
|
||||
import SettingDisableAutoUpdates from 'component/settingDisableAutoUpdates';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
|
||||
const IS_MAC = process.platform === 'darwin';
|
||||
|
||||
|
@ -148,6 +150,14 @@ export default function SettingSystem(props: Props) {
|
|||
checked={daemonSettings.save_files}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow title={__('Remote Sync Settings')}>
|
||||
<Button
|
||||
button="inverse"
|
||||
label={__('Manage')}
|
||||
icon={ICONS.ARROW_RIGHT}
|
||||
navigate={`/$/${PAGES.SETTINGS_SYNC}`}
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title={__('Share usage and diagnostic data')}
|
||||
subtitle={
|
||||
|
@ -269,24 +279,13 @@ export default function SettingSystem(props: Props) {
|
|||
title={__('Encrypt my wallet with a custom password')}
|
||||
subtitle={
|
||||
<React.Fragment>
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
learn_more: (
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/account-sync" />
|
||||
),
|
||||
}}
|
||||
>
|
||||
Wallet encryption is currently unavailable until it's supported for synced accounts. It will be
|
||||
added back soon. %learn_more%.
|
||||
</I18nMessage>
|
||||
{/* {__('Secure your local wallet data with a custom password.')}{' '}
|
||||
<strong>{__('Lost passwords cannot be recovered.')} </strong>
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />. */}
|
||||
{__('Secure your local wallet data with a custom password.')}{' '}
|
||||
<strong>{__('Lost passwords cannot be recovered.')} </strong>
|
||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />
|
||||
</React.Fragment>
|
||||
}
|
||||
>
|
||||
<FormField
|
||||
disabled
|
||||
type="checkbox"
|
||||
name="encrypt_wallet"
|
||||
onChange={() => onChangeEncryptWallet()}
|
||||
|
|
|
@ -505,3 +505,9 @@ export const LSYNC_REGISTER_FAILED = 'LSYNC_REGISTER_FAILED';
|
|||
export const LSYNC_AUTH_STARTED = 'LSYNC_AUTH_STARTED';
|
||||
export const LSYNC_AUTH_COMPLETED = 'LSYNC_AUTH_COMPLETED'; // got token
|
||||
export const LSYNC_AUTH_FAILED = 'LSYNC_AUTH_FAILED';
|
||||
export const LSYNC_DERIVE_STARTED = 'LSYNC_DERIVE_STARTED';
|
||||
export const LSYNC_DERIVE_COMPLETED = 'LSYNC_DERIVE_COMPLETED'; // got secrets
|
||||
export const LSYNC_DERIVE_FAILED = 'LSYNC_DERIVE_FAILED';
|
||||
export const LSYNC_GET_SALT_STARTED = 'LSYNC_GET_SALT_STARTED';
|
||||
export const LSYNC_GET_SALT_COMPLETED = 'LSYNC_GET_SALT_COMPLETED'; // got salt
|
||||
export const LSYNC_GET_SALT_FAILED = 'LSYNC_GET_SALT_FAILED';
|
||||
|
|
|
@ -39,3 +39,5 @@ export const COLLECTION_ADD = 'collection_add';
|
|||
export const COLLECTION_DELETE = 'collection_delete';
|
||||
export const CONFIRM_REMOVE_CARD = 'CONFIRM_REMOVE_CARD';
|
||||
export const CONFIRM_REMOVE_COMMENT = 'CONFIRM_REMOVE_COMMENT';
|
||||
export const SYNC_SIGN_IN = 'SYNC_SIGN_IN';
|
||||
export const SYNC_SIGN_UP = 'SYNC_SIGN_UP';
|
||||
|
|
|
@ -46,6 +46,7 @@ export const PAGE_TITLE = {
|
|||
[PAGES.SETTINGS]: 'Settings',
|
||||
[PAGES.SETTINGS_BLOCKED_MUTED]: 'Block and muted channels',
|
||||
[PAGES.SETTINGS_CREATOR]: 'Creator settings',
|
||||
[PAGES.SETTINGS_SYNC]: 'Sync settings',
|
||||
[PAGES.SETTINGS_NOTIFICATIONS]: 'Manage notifications',
|
||||
[PAGES.SETTINGS_STRIPE_ACCOUNT]: 'Bank Accounts',
|
||||
[PAGES.SETTINGS_STRIPE_CARD]: 'Payment Methods',
|
||||
|
|
|
@ -46,6 +46,7 @@ exports.SETTINGS_STRIPE_ACCOUNT = 'settings/tip_account';
|
|||
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
||||
exports.SETTINGS_BLOCKED_MUTED = 'settings/block_and_mute';
|
||||
exports.SETTINGS_CREATOR = 'settings/creator';
|
||||
exports.SETTINGS_SYNC = 'settings/sync';
|
||||
exports.SETTINGS_UPDATE_PWD = 'settings/update_password';
|
||||
exports.SETTINGS_OWN_COMMENTS = 'settings/ownComments';
|
||||
exports.SHOW = 'show';
|
||||
|
|
114
ui/lbrysync.js
114
ui/lbrysync.js
|
@ -1,46 +1,77 @@
|
|||
// @flow
|
||||
/*
|
||||
DeriveSecrets
|
||||
POST /
|
||||
*/
|
||||
import { LBRYSYNC_API as BASE_URL } from 'config';
|
||||
import { ipcRenderer } from 'electron';
|
||||
const BASE_URL = process.env.LBRYSYNC_BASE_URL || 'https://dev.lbry.id';
|
||||
const SYNC_API_DOWN = 'sync_api_down';
|
||||
const DUPLICATE_EMAIL = 'duplicate_email';
|
||||
const UNKNOWN_ERROR = 'unknown_api_error';
|
||||
const API_VERSION = 2;
|
||||
const NOT_FOUND = 'not_found';
|
||||
console.log('process.env.', process.env.LBRYSYNC_BASE_URL);
|
||||
|
||||
const API_VERSION = 3;
|
||||
const POST = 'POST';
|
||||
const GET = 'GET';
|
||||
// const API_URL = `${BASE_URL}/api/${API_VERSION}`;
|
||||
const AUTH_ENDPOINT = '/auth/full';
|
||||
const REGISTER_ENDPOINT = '/signup';
|
||||
// const WALLET_ENDPOINT = '/wallet';
|
||||
const WALLET_ENDPOINT = '/wallet';
|
||||
const CLIENT_SALT_SEED = '/client-salt-seed';
|
||||
|
||||
const Lbrysync = {
|
||||
apiRequestHeaders: { 'Content-Type': 'application/json' },
|
||||
apiUrl: `${BASE_URL}/api/${API_VERSION}`,
|
||||
setApiHeader: (key: string, value: string) => {
|
||||
setApiHeader: (key, value) => {
|
||||
Lbrysync.apiRequestHeaders = Object.assign(Lbrysync.apiRequestHeaders, { [key]: value });
|
||||
},
|
||||
// store state "registered email: email"
|
||||
register: async (email: string, password: string) => {
|
||||
try {
|
||||
const result = await callWithResult(REGISTER_ENDPOINT, { email, password });
|
||||
return result;
|
||||
} catch (e) {
|
||||
return e.message;
|
||||
}
|
||||
},
|
||||
// store state "lbrysynctoken: token"
|
||||
getAuthToken: async (email: string, password: string, deviceId: string) => {
|
||||
try {
|
||||
const result = await callWithResult(AUTH_ENDPOINT, { email, password, deviceId });
|
||||
return { token: result };
|
||||
} catch (e) {
|
||||
return { error: e.message };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function callWithResult(endpoint: string, params: ?{} = {}) {
|
||||
export async function fetchSaltSeed(email) {
|
||||
const buff = Buffer.from(email.toString('utf8'));
|
||||
const emailParam = buff.toString('base64');
|
||||
const result = await callWithResult(GET, CLIENT_SALT_SEED, { email: emailParam });
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getAuthToken(email, password, deviceId) {
|
||||
try {
|
||||
const result = await callWithResult(POST, AUTH_ENDPOINT, { email, password, deviceId });
|
||||
return { token: result };
|
||||
} catch (e) {
|
||||
return { error: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function register(email, password, saltSeed) {
|
||||
try {
|
||||
await callWithResult(POST, REGISTER_ENDPOINT, { email, password, clientSaltSeed: saltSeed });
|
||||
return;
|
||||
} catch (e) {
|
||||
return { error: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function pushWallet(walletState, hmac, token) {
|
||||
// token?
|
||||
const body = {
|
||||
token: token,
|
||||
encryptedWallet: walletState.encryptedWallet,
|
||||
sequence: walletState.sequence,
|
||||
hmac: hmac,
|
||||
};
|
||||
await callWithResult(POST, WALLET_ENDPOINT, { token, hmac, sequence });
|
||||
}
|
||||
|
||||
export async function pullWallet(token) {
|
||||
try {
|
||||
await callWithResult(GET, REGISTER_ENDPOINT, { token });
|
||||
return;
|
||||
} catch (e) {
|
||||
return { error: e.message };
|
||||
}
|
||||
} // token
|
||||
|
||||
function callWithResult(method, endpoint, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiCall(
|
||||
method,
|
||||
endpoint,
|
||||
params,
|
||||
(result) => {
|
||||
|
@ -51,31 +82,40 @@ function callWithResult(endpoint: string, params: ?{} = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
function apiCall(endpoint: string, params: ?{}, resolve: Function, reject: Function) {
|
||||
function apiCall(method, endpoint, params, resolve, reject) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(params),
|
||||
method: method,
|
||||
};
|
||||
|
||||
return fetch(`${Lbrysync.apiUrl}${endpoint}`, options)
|
||||
let searchString = '';
|
||||
if (method === GET) {
|
||||
const search = new URLSearchParams(params);
|
||||
searchString = `?${search}`;
|
||||
} else if (method === POST) {
|
||||
options.body = JSON.stringify(params);
|
||||
}
|
||||
return fetch(`${Lbrysync.apiUrl}${endpoint}${searchString}`, options)
|
||||
.then(handleResponse)
|
||||
.then((response) => {
|
||||
return resolve(response.result);
|
||||
return response;
|
||||
})
|
||||
.catch(reject);
|
||||
}
|
||||
|
||||
function handleResponse(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return Promise.resolve(response.json());
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 500) {
|
||||
return Promise.reject(SYNC_API_DOWN);
|
||||
return Promise.reject(500);
|
||||
}
|
||||
|
||||
if (response.status === 409) {
|
||||
return Promise.reject(DUPLICATE_EMAIL);
|
||||
return Promise.reject(409);
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
return Promise.reject(404);
|
||||
}
|
||||
return Promise.reject(UNKNOWN_ERROR);
|
||||
}
|
||||
|
|
61
ui/page/settingsSync/index.js
Normal file
61
ui/page/settingsSync/index.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { connect } from 'react-redux';
|
||||
import SettingsSync from './view';
|
||||
import { selectWalletIsEncrypted } from 'redux/selectors/wallet';
|
||||
import { doNotifyEncryptWallet, doNotifyDecryptWallet, doNotifyForgetPassword } from 'redux/actions/app';
|
||||
|
||||
import {
|
||||
selectLbrySyncRegistering,
|
||||
selectLbrySyncEmail,
|
||||
selectLbrySyncRegisterError,
|
||||
selectLbrySyncGettingSalt,
|
||||
selectLbrySyncSaltError,
|
||||
selectLbrySyncSaltSeed,
|
||||
selectLbrySyncToken,
|
||||
selectLbrySyncIsAuthenticating,
|
||||
selectLbrySyncAuthError,
|
||||
selectLbrySyncDerivingKeys,
|
||||
selectLbrySyncEncryptedHmacKey,
|
||||
selectLbrySyncEncryptedRoot,
|
||||
selectLbrySyncEncryptedProviderPass,
|
||||
} from 'redux/selectors/lbrysync';
|
||||
|
||||
import {
|
||||
doLbrysyncGetSalt,
|
||||
doLbrysyncRegister,
|
||||
doGenerateSaltSeed,
|
||||
doDeriveSecrets,
|
||||
doLbrysyncAuthenticate,
|
||||
} from 'redux/actions/lbrysync';
|
||||
|
||||
const select = (state) => ({
|
||||
walletEncrypted: selectWalletIsEncrypted(state),
|
||||
registering: selectLbrySyncRegistering(state),
|
||||
registeredEmail: selectLbrySyncEmail(state),
|
||||
registerError: selectLbrySyncRegisterError(state),
|
||||
|
||||
gettingSalt: selectLbrySyncGettingSalt(state),
|
||||
saltError: selectLbrySyncSaltError(state),
|
||||
saltSeed: selectLbrySyncSaltSeed(state),
|
||||
token: selectLbrySyncToken(state),
|
||||
|
||||
authenticating: selectLbrySyncIsAuthenticating(state),
|
||||
|
||||
authError: selectLbrySyncAuthError(state),
|
||||
|
||||
derivingKeys: selectLbrySyncDerivingKeys(state),
|
||||
encHmacKey: selectLbrySyncEncryptedHmacKey(state), // ?
|
||||
encRootPass: selectLbrySyncEncryptedRoot(state),
|
||||
encProviderPass: selectLbrySyncEncryptedProviderPass(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
|
||||
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
|
||||
getSalt: (email) => dispatch(doLbrysyncGetSalt(email)),
|
||||
generateSaltSeed: () => dispatch(doGenerateSaltSeed()),
|
||||
authenticate: () => dispatch(doLbrysyncAuthenticate()),
|
||||
deriveSecrets: (p, e, s) => dispatch(doDeriveSecrets(p, e, s)),
|
||||
register: (email, secrets, saltseed) => dispatch(doLbrysyncRegister(email, secrets, saltseed)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SettingsSync);
|
296
ui/page/settingsSync/view.jsx
Normal file
296
ui/page/settingsSync/view.jsx
Normal file
|
@ -0,0 +1,296 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import Page from 'component/page';
|
||||
import Card from 'component/common/card';
|
||||
import Button from 'component/button';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import Spinner from 'component/spinner';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
||||
type Props = {
|
||||
walletEncrypted: boolean,
|
||||
encryptWallet: (string) => void,
|
||||
decryptWallet: (string) => void,
|
||||
registering: boolean,
|
||||
email: string,
|
||||
registerError: string,
|
||||
token: string,
|
||||
authenticating: boolean,
|
||||
authError: string,
|
||||
derivingKeys: boolean,
|
||||
encHmacKey: string, // ?
|
||||
encRootPass: string,
|
||||
encProviderPass: string,
|
||||
getSalt: (string) => void,
|
||||
gettingSalt: boolean,
|
||||
saltError: string,
|
||||
saltSeed: string,
|
||||
deriveSecrets: (string, string, string) => void, // something
|
||||
};
|
||||
|
||||
export default function NotificationSettingsPage(props: Props) {
|
||||
// const { } = props;
|
||||
const {
|
||||
walletEncrypted,
|
||||
encryptWallet,
|
||||
decryptWallet,
|
||||
registering,
|
||||
registeredEmail,
|
||||
registerError,
|
||||
token,
|
||||
authenticating,
|
||||
authError,
|
||||
authenticate,
|
||||
derivingKeys,
|
||||
encHmacKey, // ?
|
||||
encRootPass,
|
||||
encProviderPass,
|
||||
getSalt,
|
||||
generateSaltSeed,
|
||||
deriveSecrets,
|
||||
gettingSalt,
|
||||
saltError,
|
||||
saltSeed,
|
||||
register,
|
||||
} = props;
|
||||
|
||||
const SIGN_IN_MODE = 'sign_in';
|
||||
const SIGN_UP_MODE = 'sign_up';
|
||||
const VERIFY_MODE = 'verify';
|
||||
const MATH_MODE = 'math';
|
||||
const DONE_MODE = 'done';
|
||||
|
||||
const [mode, setMode] = React.useState(registeredEmail ? VERIFY_MODE : SIGN_IN_MODE);
|
||||
|
||||
const [email, setEmail] = React.useState();
|
||||
const [pass, setPass] = React.useState();
|
||||
const [showPass, setShowPass] = React.useState(false);
|
||||
const [error, setError] = React.useState('');
|
||||
|
||||
React.useEffect(() => {
|
||||
let interval;
|
||||
if (!token && registeredEmail) {
|
||||
interval = setInterval(() => {
|
||||
console.log('doauthint');
|
||||
authenticate();
|
||||
}, 5000);
|
||||
}
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [registeredEmail, token, authenticate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (token && registeredEmail) {
|
||||
setMode(DONE_MODE);
|
||||
}
|
||||
}, [registeredEmail, token, setMode]);
|
||||
|
||||
const handleSignUp = async () => {
|
||||
// get salt for email to make sure
|
||||
const saltSeedOrError = await getSalt(email);
|
||||
if (saltSeedOrError.seed) {
|
||||
setError('Email already registered');
|
||||
return;
|
||||
}
|
||||
// -- if found, report email already exists - sign in?
|
||||
const saltSeed = await generateSaltSeed();
|
||||
// saltSeed = generateSaltSeed()
|
||||
setMode(MATH_MODE);
|
||||
const secrets = await deriveSecrets(pass, email, saltSeed);
|
||||
setMode(VERIFY_MODE);
|
||||
// passwords = driveKeys(root, email, saltSeed);
|
||||
try {
|
||||
const registerSuccess = await register(email, secrets, saltSeed);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
// registerSuccess = register(email, servicePassword, saltSeed)
|
||||
|
||||
// poll auth until success
|
||||
// store [token, rootPassword, providerPass, HmacKey, saltSeed, salt, registeredEmail]
|
||||
};
|
||||
|
||||
const handleSignIn = async () => {
|
||||
// get saltseed for email
|
||||
// saltSeed = getSaltSeed()
|
||||
// -- if error, report email not registered - sign up?
|
||||
// salt = generateSalt(seed)
|
||||
// passwords = deriveKeys(root, email, saltSeed);
|
||||
// token = authenticate(email, servicePassword, deviceId)
|
||||
// store [token, rootPassword, servicePassword, HmacKey, saltSeed, salt, registeredEmail]
|
||||
// kick off sync pull
|
||||
// -- possibly merge conflicts
|
||||
};
|
||||
|
||||
const doneCard = (
|
||||
<Card
|
||||
title={__('Done!')}
|
||||
subtitle={
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
email: email,
|
||||
}}
|
||||
>
|
||||
You are signed in as %email%.
|
||||
</I18nMessage>
|
||||
}
|
||||
actions={<div>Like, done and stuff...</div>}
|
||||
/>
|
||||
);
|
||||
|
||||
const verifyCard = (
|
||||
<Card
|
||||
title={__('Verifying')}
|
||||
subtitle={__('We have sent you an email to verify your account.')}
|
||||
actions={<div>Waiting for verification...</div>}
|
||||
/>
|
||||
);
|
||||
|
||||
const deriveCard = (
|
||||
<Card title={__('Doing Math')} subtitle={__('Hold on, doing some math.')} actions={<div>Math...</div>} />
|
||||
);
|
||||
|
||||
const signInCard = (
|
||||
<Card
|
||||
title={__('Sign In')}
|
||||
subtitle={
|
||||
<>
|
||||
<p>
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
sign_up: <Button button="link" onClick={() => setMode(SIGN_UP_MODE)} label={__('Sign up')} />,
|
||||
}}
|
||||
>
|
||||
Sign in to your sync account. Or %sign_up%.
|
||||
</I18nMessage>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<div>
|
||||
<Form onSubmit={handleSignIn} className="section">
|
||||
<FormField
|
||||
autoFocus
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
type="email"
|
||||
name="sign_in_email"
|
||||
label={__('Email')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<FormField
|
||||
type={showPass ? 'text' : 'password'}
|
||||
name="root_password"
|
||||
inputButton={
|
||||
<>
|
||||
<Button
|
||||
icon={showPass ? ICONS.EYE : ICONS.EYE_OFF}
|
||||
onClick={() => setShowPass(!showPass)}
|
||||
className={'editable-text__input-button'}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
label={__('Password Again')}
|
||||
value={pass}
|
||||
onChange={(e) => setPass(e.target.value)}
|
||||
/>
|
||||
<div className="section__actions">
|
||||
<Button button="primary" type="submit" label={__('Submit')} disabled={!email} />
|
||||
</div>
|
||||
<p className="help--card-actions">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
terms: <Button button="link" href="https://www.lbry.com/termsofservice" label={__('terms')} />,
|
||||
}}
|
||||
>
|
||||
By creating an account, you agree to our %terms% and confirm you're over the age of 13.
|
||||
</I18nMessage>
|
||||
</p>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const signUpCard = (
|
||||
<Card
|
||||
title={__('Sign Up')}
|
||||
subtitle={
|
||||
<>
|
||||
<p>
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
sign_in: <Button button="link" onClick={() => setMode(SIGN_UP_MODE)} label={__('Sign in')} />,
|
||||
}}
|
||||
>
|
||||
Sign up for a sync account. Or %sign_in%.
|
||||
</I18nMessage>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<div>
|
||||
<Form onSubmit={handleSignUp} className="section">
|
||||
<FormField
|
||||
autoFocus
|
||||
placeholder={__('yourstruly@example.com')}
|
||||
type="email"
|
||||
name="sign_up_email"
|
||||
label={__('Email')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<FormField
|
||||
type="password"
|
||||
name="root_password"
|
||||
inputButton={
|
||||
<>
|
||||
<Button
|
||||
icon={showPass ? ICONS.EYE : ICONS.EYE_OFF}
|
||||
onClick={() => setShowPass(!showPass)}
|
||||
className={'editable-text__input-button'}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
label={__('Password Again')}
|
||||
value={pass}
|
||||
onChange={(e) => setPass(e.target.value)}
|
||||
/>
|
||||
<div className="section__actions">
|
||||
<Button button="primary" type="submit" label={__('Submit')} disabled={!email} />
|
||||
</div>
|
||||
<p className="help--card-actions">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
terms: <Button button="link" href="https://www.lbry.com/termsofservice" label={__('terms')} />,
|
||||
}}
|
||||
>
|
||||
By creating an account, you agree to our %terms% and confirm you're over the age of 13.
|
||||
</I18nMessage>
|
||||
</p>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Page
|
||||
noFooter
|
||||
noSideNavigation
|
||||
// settingsPage
|
||||
className="card-stack"
|
||||
backout={{ title: __('Wallet Sync'), backLabel: __('Back') }}
|
||||
>
|
||||
<div className="card-stack">
|
||||
{mode === DONE_MODE && <>{doneCard}</>}
|
||||
{mode === SIGN_IN_MODE && <>{signInCard}</>}
|
||||
{mode === SIGN_UP_MODE && <>{signUpCard}</>}
|
||||
{mode === MATH_MODE && <>{deriveCard}</>}
|
||||
{mode === VERIFY_MODE && <>{verifyCard}</>}
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
|
@ -1,20 +1,56 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import Lbrysync from 'lbrysync';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { safeStoreEncrypt, safeStoreDecrypt } from 'util/saved-passwords';
|
||||
|
||||
import * as Lbrysync from 'lbrysync';
|
||||
import Lbry from 'lbry';
|
||||
import { Lbryio } from "lbryinc";
|
||||
import { selectSyncHash } from '../selectors/sync';
|
||||
export const doLbrysyncGetSalt = (email: string) => async (dispatch: Dispatch) => {
|
||||
const { fetchSaltSeed } = Lbrysync;
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_GET_SALT_STARTED,
|
||||
});
|
||||
try {
|
||||
const saltOrError = await fetchSaltSeed(email);
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_GET_SALT_COMPLETED,
|
||||
data: { email: email, saltSeed: saltOrError},
|
||||
});
|
||||
return saltOrError;
|
||||
} catch (e) {
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_GET_SALT_FAILED,
|
||||
data: { email: email, saltError: 'Not Found'},
|
||||
});
|
||||
return 'not found';
|
||||
}
|
||||
};
|
||||
|
||||
// register an email (eventually username)
|
||||
export const doLbrysyncRegister = (email: string, password: string) => async (dispatch: Dispatch) => {
|
||||
export const doLbrysyncRegister = (email: string, secrets: any, saltSeed: string) => async (dispatch: Dispatch) => {
|
||||
const { register } = Lbrysync;
|
||||
// started
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_REGISTER_STARTED,
|
||||
});
|
||||
const resultIfError = await register(email, password);
|
||||
const resultIfError = await register(email, secrets.providerPass, saltSeed);
|
||||
const encProviderPass = safeStoreEncrypt(secrets.providerPass);
|
||||
const encHmacKey = safeStoreEncrypt(secrets.hmacKey);
|
||||
const enctyptedRoot = safeStoreEncrypt(secrets.rootPassword);
|
||||
const registerData = {
|
||||
email,
|
||||
saltSeed,
|
||||
providerPass: encProviderPass,
|
||||
hmacKey: encHmacKey,
|
||||
rootPass: enctyptedRoot,
|
||||
};
|
||||
|
||||
if (!resultIfError) {
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_REGISTER_COMPLETED,
|
||||
data: email,
|
||||
data: registerData,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
|
@ -26,13 +62,19 @@ export const doLbrysyncRegister = (email: string, password: string) => async (di
|
|||
|
||||
// get token given username/password
|
||||
export const doLbrysyncAuthenticate =
|
||||
(email: string, password: string, deviceId: string) => async (dispatch: Dispatch) => {
|
||||
const { getAuthToken } = Lbrysync;
|
||||
|
||||
// started
|
||||
() => async (dispatch: Dispatch, getState: GetState) => {
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_AUTH_STARTED,
|
||||
});
|
||||
const state = getState();
|
||||
const { lbrysync } = state;
|
||||
const { registeredEmail: email, encryptedProviderPass } = lbrysync;
|
||||
const status = await Lbry.status();
|
||||
const { installation_id: deviceId } = status;
|
||||
const password = safeStoreDecrypt(encryptedProviderPass);
|
||||
|
||||
const { getAuthToken } = Lbrysync;
|
||||
|
||||
const result: { token?: string, error?: string } = await getAuthToken(email, password, deviceId);
|
||||
|
||||
if (result.token) {
|
||||
|
@ -40,10 +82,221 @@ export const doLbrysyncAuthenticate =
|
|||
type: ACTIONS.LSYNC_AUTH_COMPLETED,
|
||||
data: result.token,
|
||||
});
|
||||
} else if (result.error) {
|
||||
} else {
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_AUTH_FAILED,
|
||||
data: result.error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const doGenerateSaltSeed = () => async (dispatch: Dispatch) => {
|
||||
const result = await ipcRenderer.invoke('invoke-get-salt-seed');
|
||||
return result;
|
||||
};
|
||||
|
||||
export const doDeriveSecrets = (rootPassword: string, email: string, saltSeed: string) => async (dispatch: Dispatch) =>
|
||||
{
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_DERIVE_STARTED,
|
||||
});
|
||||
try {
|
||||
const result = await ipcRenderer.invoke('invoke-get-secrets', rootPassword, email, saltSeed);
|
||||
|
||||
const data = {
|
||||
hmacKey: result.hmacKey,
|
||||
rootPassword,
|
||||
providerPass: result.lbryIdPassword,
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_DERIVE_COMPLETED,
|
||||
data,
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
dispatch({
|
||||
type: ACTIONS.LSYNC_DERIVE_FAILED,
|
||||
data: {
|
||||
error: e,
|
||||
},
|
||||
});
|
||||
return { error: e.message };
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export async function doSetSync() {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const localHash = selectSyncHash(state);
|
||||
const { lbrysync } = state;
|
||||
const { authToken, encryptedRoot } = lbrysync;
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SYNC_STARTED,
|
||||
});
|
||||
let error;
|
||||
|
||||
try {
|
||||
const status = Lbry.wallet_status();
|
||||
if (status.is_locked) {
|
||||
throw new Error('Error parsing i18n messages file: ' + messagesFilePath + ' err: ' + err);
|
||||
}
|
||||
} catch(e) {
|
||||
error = e.message;
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
const syncData = await Lbry.sync_apply({ password: , data: response.data, blocking: true })
|
||||
}
|
||||
// return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post')
|
||||
return pushWallet(authToken)
|
||||
.then((response) => {
|
||||
if (!response.hash) {
|
||||
throw Error('No hash returned for sync/set.');
|
||||
}
|
||||
|
||||
return dispatch({
|
||||
type: ACTIONS.SET_SYNC_COMPLETED,
|
||||
data: { syncHash: response.hash },
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SYNC_FAILED,
|
||||
data: { error },
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doGetSync(passedPassword?: string, callback?: (any, ?boolean) => void) {
|
||||
|
||||
const password = passedPassword === null || passedPassword === undefined ? '' : passedPassword;
|
||||
|
||||
function handleCallback(error, hasNewData) {
|
||||
if (callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('Second argument passed to "doGetSync" must be a function');
|
||||
}
|
||||
|
||||
callback(error, hasNewData);
|
||||
}
|
||||
}
|
||||
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const localHash = selectSyncHash(state);
|
||||
const { lbrysync } = state;
|
||||
const { authToken, encryptedRoot } = lbrysync;
|
||||
const { pullWallet } = Lbrysync;
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_STARTED,
|
||||
});
|
||||
|
||||
const data = {};
|
||||
|
||||
Lbry.wallet_status()
|
||||
.then((status) => {
|
||||
if (status.is_locked) {
|
||||
return Lbry.wallet_unlock({ password });
|
||||
}
|
||||
|
||||
// Wallet is already unlocked
|
||||
return true;
|
||||
})
|
||||
.then((isUnlocked) => {
|
||||
if (isUnlocked) {
|
||||
return Lbry.sync_hash(); //unnec
|
||||
}
|
||||
data.unlockFailed = true;
|
||||
throw new Error();
|
||||
})
|
||||
// .then((hash?: string) => Lbryio.call('sync', 'get', { hash }, 'post'))
|
||||
.then((hash?: string) => pullWallet(authToken))
|
||||
.then((response: any) => {
|
||||
// get data, put it in sync apply.
|
||||
const syncHash = response.hash;
|
||||
data.syncHash = syncHash;
|
||||
data.syncData = response.data;
|
||||
data.changed = response.changed || syncHash !== localHash;
|
||||
data.hasSyncedWallet = true;
|
||||
|
||||
if (response.changed) {
|
||||
return Lbry.sync_apply({ password, data: response.data, blocking: true });
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response) {
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
handleCallback(null, data.changed);
|
||||
return;
|
||||
}
|
||||
|
||||
const { hash: walletHash, data: walletData } = response;
|
||||
|
||||
if (walletHash !== data.syncHash) {
|
||||
// different local hash, need to synchronise
|
||||
dispatch(doSetSync(data.syncHash, walletHash, walletData));
|
||||
}
|
||||
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
handleCallback(null, data.changed);
|
||||
})
|
||||
.catch((syncAttemptError) => {
|
||||
const badPasswordError =
|
||||
syncAttemptError && syncAttemptError.data && syncAttemptError.data.name === BAD_PASSWORD_ERROR_NAME;
|
||||
|
||||
if (data.unlockFailed) {
|
||||
dispatch({ type: ACTIONS.GET_SYNC_FAILED, data: { error: syncAttemptError } });
|
||||
|
||||
if (badPasswordError) {
|
||||
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
|
||||
}
|
||||
|
||||
handleCallback(syncAttemptError);
|
||||
} else if (data.hasSyncedWallet) {
|
||||
const error = (syncAttemptError && syncAttemptError.message) || 'Error getting synced wallet';
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_FAILED,
|
||||
data: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
if (badPasswordError) {
|
||||
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
|
||||
}
|
||||
|
||||
handleCallback(error);
|
||||
} else {
|
||||
const noWalletError = syncAttemptError && syncAttemptError.message === NO_WALLET_ERROR;
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_COMPLETED,
|
||||
data: {
|
||||
hasSyncedWallet: false,
|
||||
syncHash: null,
|
||||
// If there was some unknown error, bail
|
||||
fatalError: !noWalletError,
|
||||
},
|
||||
});
|
||||
|
||||
// user doesn't have a synced wallet
|
||||
// call sync_apply to get data to sync
|
||||
// first time sync. use any string for old hash
|
||||
if (noWalletError) {
|
||||
Lbry.sync_apply({ password })
|
||||
.then(({ hash: walletHash, data: syncApplyData }) => {
|
||||
dispatch(doSetSync('', walletHash, syncApplyData));
|
||||
handleCallback();
|
||||
})
|
||||
.catch((syncApplyError) => {
|
||||
handleCallback(syncApplyError);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ export const doGetSyncDesktop =
|
|||
const getSyncPending = selectGetSyncIsPending(state);
|
||||
const setSyncPending = selectSetSyncIsPending(state);
|
||||
const syncLocked = selectSyncIsLocked(state);
|
||||
|
||||
// here we instead do the new getsync with the derived password
|
||||
return getSavedPassword().then((savedPassword) => {
|
||||
const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword;
|
||||
|
||||
|
|
|
@ -2,13 +2,24 @@ import * as ACTIONS from 'constants/action_types';
|
|||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
const defaultState = {
|
||||
syncProvider: null,
|
||||
// reg
|
||||
registering: false,
|
||||
registeredEmail: null,
|
||||
registerError: null,
|
||||
syncProvider: null,
|
||||
// authtoken
|
||||
isAuthenticating: false,
|
||||
authError: null,
|
||||
authToken: null, // store this elsewhere?
|
||||
authToken: null, // store this elsewhere
|
||||
// keys
|
||||
derivingKeys: false,
|
||||
encryptedHmacKey: null,
|
||||
encryptedRoot: null,
|
||||
encryptedProviderPass: null,
|
||||
// salt
|
||||
gettingSalt: false,
|
||||
saltSeed: null,
|
||||
saltError: null,
|
||||
};
|
||||
|
||||
export const lbrysyncReducer = handleActions(
|
||||
|
@ -17,20 +28,27 @@ export const lbrysyncReducer = handleActions(
|
|||
[ACTIONS.LSYNC_REGISTER_STARTED]: (state) => ({
|
||||
...state,
|
||||
registering: true,
|
||||
registerError: null,
|
||||
}),
|
||||
[ACTIONS.LSYNC_REGISTER_COMPLETED]: (state, action) => ({
|
||||
...state,
|
||||
registeredEmail: action.data,
|
||||
registeredEmail: action.data.email,
|
||||
encryptedHmacKey: action.data.hmacKey,
|
||||
encryptedProviderPass: action.data.providerPass,
|
||||
encryptedRoot: action.data.rootPass,
|
||||
saltSeed: action.data.saltSeed,
|
||||
}),
|
||||
[ACTIONS.LSYNC_REGISTER_FAILED]: (state) => ({
|
||||
[ACTIONS.LSYNC_REGISTER_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
registeredEmail: null,
|
||||
registering: false,
|
||||
registerError: action.data.error,
|
||||
}),
|
||||
// Auth
|
||||
[ACTIONS.LSYNC_AUTH_STARTED]: (state) => ({
|
||||
...state,
|
||||
isAuthenticating: true,
|
||||
authError: null,
|
||||
}),
|
||||
[ACTIONS.LSYNC_AUTH_COMPLETED]: (state, action) => ({
|
||||
...state,
|
||||
|
@ -41,7 +59,36 @@ export const lbrysyncReducer = handleActions(
|
|||
authError: action.data,
|
||||
isAuthenticating: false,
|
||||
}),
|
||||
// ...
|
||||
// derive
|
||||
[ACTIONS.LSYNC_DERIVE_STARTED]: (state) => ({
|
||||
...state,
|
||||
derivingKeys: true,
|
||||
deriveError: null,
|
||||
}),
|
||||
[ACTIONS.LSYNC_DERIVE_COMPLETED]: (state, action) => ({
|
||||
...state,
|
||||
derivingKeys: false,
|
||||
}),
|
||||
[ACTIONS.LSYNC_DERIVE_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
deriveError: action.data.error,
|
||||
derivingKeys: false,
|
||||
}),
|
||||
// salt
|
||||
[ACTIONS.LSYNC_GET_SALT_STARTED]: (state) => ({
|
||||
...state,
|
||||
gettingSalt: true,
|
||||
saltError: null,
|
||||
}),
|
||||
[ACTIONS.LSYNC_GET_SALT_COMPLETED]: (state, action) => ({
|
||||
...state,
|
||||
gettingSalt: false,
|
||||
}),
|
||||
[ACTIONS.LSYNC_GET_SALT_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
saltError: action.data.error,
|
||||
gettingSalt: false,
|
||||
}),
|
||||
},
|
||||
defaultState
|
||||
);
|
||||
|
|
|
@ -3,14 +3,19 @@ import { createSelector } from 'reselect';
|
|||
const selectState = (state) => state.lbrysync || {};
|
||||
|
||||
export const selectLbrySyncRegistering = createSelector(selectState, (state) => state.registering);
|
||||
|
||||
export const selectLbrySyncEmail = createSelector(selectState, (state) => state.registeredEmail);
|
||||
|
||||
export const selectLbrySyncRegisterError = createSelector(selectState, (state) => state.registerError);
|
||||
|
||||
// probably shouldn't store this here.
|
||||
export const selectLbrySyncToken = createSelector(selectState, (state) => state.registering);
|
||||
export const selectLbrySyncGettingSalt = createSelector(selectState, (state) => state.gettingSalt);
|
||||
export const selectLbrySyncSaltError = createSelector(selectState, (state) => state.saltError);
|
||||
export const selectLbrySyncSaltSeed = createSelector(selectState, (state) => state.saltSeed);
|
||||
|
||||
export const selectLbrySyncIsAuthenticating = createSelector(selectState, (state) => state.isAuthenticating);
|
||||
|
||||
export const selectLbrySyncAuthError = createSelector(selectState, (state) => state.authError);
|
||||
export const selectLbrySyncToken = createSelector(selectState, (state) => state.authToken);
|
||||
|
||||
export const selectLbrySyncDerivingKeys = createSelector(selectState, (state) => state.derivingKeys);
|
||||
export const selectLbrySyncEncryptedHmacKey = createSelector(selectState, (state) => state.encryptedHmacKey);
|
||||
export const selectLbrySyncEncryptedRoot = createSelector(selectState, (state) => state.encryptedRoot);
|
||||
export const selectLbrySyncEncryptedProviderPass = createSelector(selectState, (state) => state.encryptedProviderPass);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const { safeStorage } = require('@electron/remote');
|
||||
|
||||
const { DOMAIN } = require('../../config.js');
|
||||
const AUTH_TOKEN = 'auth_token';
|
||||
const SAVED_PASSWORD = 'saved_password';
|
||||
|
@ -127,6 +129,17 @@ function doAuthTokenRefresh() {
|
|||
}
|
||||
}
|
||||
|
||||
function safeStoreEncrypt(ssVal) {
|
||||
const buffer = safeStorage.encryptString(ssVal);
|
||||
console.log('buffer', buffer.toString('base64'));
|
||||
return buffer.toString('base64');
|
||||
}
|
||||
|
||||
function safeStoreDecrypt(ssVal) {
|
||||
const buffer = safeStorage.decryptString(Buffer.from(ssVal, 'base64'));
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setSavedPassword,
|
||||
getSavedPassword,
|
||||
|
@ -137,4 +150,6 @@ module.exports = {
|
|||
deleteAuthToken,
|
||||
doSignOutCleanup,
|
||||
doAuthTokenRefresh,
|
||||
safeStoreEncrypt,
|
||||
safeStoreDecrypt,
|
||||
};
|
||||
|
|
32
ui/util/wallet-preferences.js
Normal file
32
ui/util/wallet-preferences.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
export const makeMergedPrefs = (alt, base) => {
|
||||
let finalPrefs = base;
|
||||
let baseData = base.value;
|
||||
let altData = alt.value;
|
||||
if (!altData) {
|
||||
return base;
|
||||
}
|
||||
|
||||
let mergedBlockListSet = new Set(baseData.blocked || []);
|
||||
let mergedSubscriptionsSet = new Set(baseData.subscriptions || []);
|
||||
let mergedTagsSet = new Set(baseData.tags || []);
|
||||
|
||||
const altBlocklist = altData.blocked || [];
|
||||
const altSubscriptions = altData.subscriptions || [];
|
||||
const altTags = altData.tags || [];
|
||||
|
||||
if (altBlocklist.length) {
|
||||
altBlocklist.forEach((el) => mergedBlockListSet.add(el));
|
||||
}
|
||||
if (altSubscriptions.length) {
|
||||
altSubscriptions.forEach((el) => mergedSubscriptionsSet.add(el));
|
||||
}
|
||||
if (altTags.length) {
|
||||
altTags.forEach((el) => mergedTagsSet.add(el));
|
||||
}
|
||||
|
||||
baseData.blocked = Array.from(mergedBlockListSet);
|
||||
baseData.subscriptions = Array.from(mergedSubscriptionsSet);
|
||||
baseData.tags = Array.from(mergedTagsSet);
|
||||
finalPrefs.value = baseData;
|
||||
return finalPrefs;
|
||||
};
|
274
yarn.lock
274
yarn.lock
|
@ -3801,6 +3801,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array.prototype.every@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "array.prototype.every@npm:1.1.3"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
define-properties: ^1.1.3
|
||||
es-abstract: ^1.19.0
|
||||
is-string: ^1.0.7
|
||||
checksum: bbcc864ac1271307043a16262455a6f917d183060a7e5b99c7c710ee611d40c1065f4ec674323b50cf8b987f2d0c9ca9e9ff9cbf4bcc7740f82e731ec2a58d6f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array.prototype.flat@npm:^1.2.5":
|
||||
version: 1.3.0
|
||||
resolution: "array.prototype.flat@npm:1.3.0"
|
||||
|
@ -3995,6 +4007,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"available-typed-arrays@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "available-typed-arrays@npm:1.0.5"
|
||||
checksum: 20eb47b3cefd7db027b9bbb993c658abd36d4edd3fe1060e83699a03ee275b0c9b216cc076ff3f2db29073225fb70e7613987af14269ac1fe2a19803ccc97f1a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aws-sign2@npm:~0.7.0":
|
||||
version: 0.7.0
|
||||
resolution: "aws-sign2@npm:0.7.0"
|
||||
|
@ -6471,6 +6490,29 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-equal@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "deep-equal@npm:2.0.5"
|
||||
dependencies:
|
||||
call-bind: ^1.0.0
|
||||
es-get-iterator: ^1.1.1
|
||||
get-intrinsic: ^1.0.1
|
||||
is-arguments: ^1.0.4
|
||||
is-date-object: ^1.0.2
|
||||
is-regex: ^1.1.1
|
||||
isarray: ^2.0.5
|
||||
object-is: ^1.1.4
|
||||
object-keys: ^1.1.1
|
||||
object.assign: ^4.1.2
|
||||
regexp.prototype.flags: ^1.3.0
|
||||
side-channel: ^1.0.3
|
||||
which-boxed-primitive: ^1.0.1
|
||||
which-collection: ^1.0.1
|
||||
which-typed-array: ^1.1.2
|
||||
checksum: 2bb7332badf589b540184d25098acac750e30fe11c8dce4523d03fc5db15f46881a0105e6bf0b64bb0c57213a95ed964029ff0259026ad6f7f9e0019f8200de5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-extend@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "deep-extend@npm:0.6.0"
|
||||
|
@ -6547,6 +6589,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"defined@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "defined@npm:1.0.0"
|
||||
checksum: 77672997c5001773371c4dbcce98da0b3dc43089d6da2ad87c4b800adb727633cea8723ea3889fe0c2112a2404e2fd07e3bfd0e55f7426aa6441d8992045dbd5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"del@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "del@npm:3.0.0"
|
||||
|
@ -6973,6 +7022,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotignore@npm:^0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "dotignore@npm:0.1.2"
|
||||
dependencies:
|
||||
minimatch: ^3.0.4
|
||||
bin:
|
||||
ignored: bin/ignored
|
||||
checksum: 06bab15e2a2400c6f823a0edbcd73661180f6245a4041a3fe3b9fde4b22ae74b896604df4520a877093f05c656bd080087376c9f605bccdea847664c59910f37
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"duck@npm:^0.1.12":
|
||||
version: 0.1.12
|
||||
resolution: "duck@npm:0.1.12"
|
||||
|
@ -7475,7 +7535,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-abstract@npm:^1.17.2, es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1, es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5, es-abstract@npm:^1.20.1":
|
||||
"es-abstract@npm:^1.17.2, es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1, es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5, es-abstract@npm:^1.20.0, es-abstract@npm:^1.20.1":
|
||||
version: 1.20.1
|
||||
resolution: "es-abstract@npm:1.20.1"
|
||||
dependencies:
|
||||
|
@ -7513,6 +7573,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-get-iterator@npm:^1.1.1":
|
||||
version: 1.1.2
|
||||
resolution: "es-get-iterator@npm:1.1.2"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
get-intrinsic: ^1.1.0
|
||||
has-symbols: ^1.0.1
|
||||
is-arguments: ^1.1.0
|
||||
is-map: ^2.0.2
|
||||
is-set: ^2.0.2
|
||||
is-string: ^1.0.5
|
||||
isarray: ^2.0.5
|
||||
checksum: f75e66acb6a45686fa08b3ade9c9421a70d36a0c43ed4363e67f4d7aab2226cb73dd977cb48abbaf75721b946d3cd810682fcf310c7ad0867802fbf929b17dcf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-shim-unscopables@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "es-shim-unscopables@npm:1.0.0"
|
||||
|
@ -8607,6 +8683,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"for-each@npm:^0.3.3":
|
||||
version: 0.3.3
|
||||
resolution: "for-each@npm:0.3.3"
|
||||
dependencies:
|
||||
is-callable: ^1.1.3
|
||||
checksum: 6c48ff2bc63362319c65e2edca4a8e1e3483a2fabc72fbe7feaf8c73db94fc7861bd53bc02c8a66a0c1dd709da6b04eec42e0abdd6b40ce47305ae92a25e5d28
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"for-in@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "for-in@npm:1.0.2"
|
||||
|
@ -8884,7 +8969,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
|
||||
"get-intrinsic@npm:^1.0.1, get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
|
||||
version: 1.1.2
|
||||
resolution: "get-intrinsic@npm:1.1.2"
|
||||
dependencies:
|
||||
|
@ -8902,6 +8987,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-package-type@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "get-package-type@npm:0.1.0"
|
||||
checksum: bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stdin@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "get-stdin@npm:5.0.1"
|
||||
|
@ -9303,6 +9395,23 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-dynamic-import@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "has-dynamic-import@npm:2.0.1"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
get-intrinsic: ^1.1.1
|
||||
checksum: 1cb60255cdd354a5f53997dd4c8ae0f821706ced3d1047bb810cb74400f28988b08d4d986318cb6610b79e6b9993a6592e678b6cef3ef0b71ab553eaa99b9c4d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-flag@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "has-flag@npm:2.0.0"
|
||||
checksum: 7d060d142ef6740c79991cb99afe5962b267e6e95538bf8b607026b9b1e7451288927bc8e7b4a9484a8b99935c0af023070f91ee49faef791ecd401dc58b2e8d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-flag@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "has-flag@npm:3.0.0"
|
||||
|
@ -10246,7 +10355,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-arguments@npm:^1.0.4":
|
||||
"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.0":
|
||||
version: 1.1.1
|
||||
resolution: "is-arguments@npm:1.1.1"
|
||||
dependencies:
|
||||
|
@ -10314,7 +10423,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-callable@npm:^1.1.4, is-callable@npm:^1.2.4":
|
||||
"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.4":
|
||||
version: 1.2.4
|
||||
resolution: "is-callable@npm:1.2.4"
|
||||
checksum: 1a28d57dc435797dae04b173b65d6d1e77d4f16276e9eff973f994eadcfdc30a017e6a597f092752a083c1103cceb56c91e3dadc6692fedb9898dfaba701575f
|
||||
|
@ -10395,7 +10504,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-date-object@npm:^1.0.1":
|
||||
"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.2":
|
||||
version: 1.0.5
|
||||
resolution: "is-date-object@npm:1.0.5"
|
||||
dependencies:
|
||||
|
@ -10579,6 +10688,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-map@npm:^2.0.1, is-map@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "is-map@npm:2.0.2"
|
||||
checksum: ace3d0ecd667bbdefdb1852de601268f67f2db725624b1958f279316e13fecb8fa7df91fd60f690d7417b4ec180712f5a7ee967008e27c65cfd475cc84337728
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-natural-number@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "is-natural-number@npm:4.0.1"
|
||||
|
@ -10749,7 +10865,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-regex@npm:^1.0.4, is-regex@npm:^1.1.4":
|
||||
"is-regex@npm:^1.0.4, is-regex@npm:^1.1.1, is-regex@npm:^1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "is-regex@npm:1.1.4"
|
||||
dependencies:
|
||||
|
@ -10780,6 +10896,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-set@npm:^2.0.1, is-set@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "is-set@npm:2.0.2"
|
||||
checksum: b64343faf45e9387b97a6fd32be632ee7b269bd8183701f3b3f5b71a7cf00d04450ed8669d0bd08753e08b968beda96fca73a10fd0ff56a32603f64deba55a57
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-shared-array-buffer@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "is-shared-array-buffer@npm:1.0.2"
|
||||
|
@ -10821,6 +10944,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-typed-array@npm:^1.1.9":
|
||||
version: 1.1.9
|
||||
resolution: "is-typed-array@npm:1.1.9"
|
||||
dependencies:
|
||||
available-typed-arrays: ^1.0.5
|
||||
call-bind: ^1.0.2
|
||||
es-abstract: ^1.20.0
|
||||
for-each: ^0.3.3
|
||||
has-tostringtag: ^1.0.0
|
||||
checksum: 11910f1e58755fef43bf0074e52fa5b932bf101ec65d613e0a83d40e8e4c6e3f2ee142d624ebc7624c091d3bbe921131f8db7d36ecbbb71909f2fe310c1faa65
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "is-typedarray@npm:1.0.0"
|
||||
|
@ -10837,6 +10973,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-weakmap@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "is-weakmap@npm:2.0.1"
|
||||
checksum: 1222bb7e90c32bdb949226e66d26cb7bce12e1e28e3e1b40bfa6b390ba3e08192a8664a703dff2a00a84825f4e022f9cd58c4599ff9981ab72b1d69479f4f7f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-weakref@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "is-weakref@npm:1.0.2"
|
||||
|
@ -10846,6 +10989,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-weakset@npm:^2.0.1":
|
||||
version: 2.0.2
|
||||
resolution: "is-weakset@npm:2.0.2"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
get-intrinsic: ^1.1.1
|
||||
checksum: 5d8698d1fa599a0635d7ca85be9c26d547b317ed8fd83fc75f03efbe75d50001b5eececb1e9971de85fcde84f69ae6f8346bc92d20d55d46201d328e4c74a367
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-whitespace-character@npm:^1.0.0":
|
||||
version: 1.0.4
|
||||
resolution: "is-whitespace-character@npm:1.0.4"
|
||||
|
@ -10904,6 +11057,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isarray@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "isarray@npm:2.0.5"
|
||||
checksum: bd5bbe4104438c4196ba58a54650116007fa0262eccef13a4c55b2e09a5b36b59f1e75b9fcc49883dd9d4953892e6fc007eef9e9155648ceea036e184b0f930a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isbinaryfile@npm:^3.0.2":
|
||||
version: 3.0.3
|
||||
resolution: "isbinaryfile@npm:3.0.3"
|
||||
|
@ -11500,6 +11660,7 @@ __metadata:
|
|||
strip-markdown: ^3.0.3
|
||||
style-loader: ^0.23.1
|
||||
sudo-prompt: ^9.2.1
|
||||
tape: ^5.6.0
|
||||
tempy: ^0.6.0
|
||||
terser-webpack-plugin: ^4.2.3
|
||||
three-full: ^28.0.2
|
||||
|
@ -13163,14 +13324,14 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-inspect@npm:^1.12.0, object-inspect@npm:^1.9.0":
|
||||
"object-inspect@npm:^1.12.0, object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0":
|
||||
version: 1.12.2
|
||||
resolution: "object-inspect@npm:1.12.2"
|
||||
checksum: a534fc1b8534284ed71f25ce3a496013b7ea030f3d1b77118f6b7b1713829262be9e6243acbcb3ef8c626e2b64186112cb7f6db74e37b2789b9c789ca23048b2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-is@npm:^1.0.1":
|
||||
"object-is@npm:^1.0.1, object-is@npm:^1.1.4, object-is@npm:^1.1.5":
|
||||
version: 1.1.5
|
||||
resolution: "object-is@npm:1.1.5"
|
||||
dependencies:
|
||||
|
@ -13208,6 +13369,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object.assign@npm:^4.1.3":
|
||||
version: 4.1.4
|
||||
resolution: "object.assign@npm:4.1.4"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
define-properties: ^1.1.4
|
||||
has-symbols: ^1.0.3
|
||||
object-keys: ^1.1.1
|
||||
checksum: 76cab513a5999acbfe0ff355f15a6a125e71805fcf53de4e9d4e082e1989bdb81d1e329291e1e4e0ae7719f0e4ef80e88fb2d367ae60500d79d25a6224ac8864
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object.entries@npm:^1.1.5":
|
||||
version: 1.1.5
|
||||
resolution: "object.entries@npm:1.1.5"
|
||||
|
@ -15513,7 +15686,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"regexp.prototype.flags@npm:^1.2.0, regexp.prototype.flags@npm:^1.4.1, regexp.prototype.flags@npm:^1.4.3":
|
||||
"regexp.prototype.flags@npm:^1.2.0, regexp.prototype.flags@npm:^1.3.0, regexp.prototype.flags@npm:^1.4.1, regexp.prototype.flags@npm:^1.4.3":
|
||||
version: 1.4.3
|
||||
resolution: "regexp.prototype.flags@npm:1.4.3"
|
||||
dependencies:
|
||||
|
@ -15959,6 +16132,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resumer@npm:^0.0.0":
|
||||
version: 0.0.0
|
||||
resolution: "resumer@npm:0.0.0"
|
||||
dependencies:
|
||||
through: ~2.3.4
|
||||
checksum: 21b1c257aac24840643fae9bc99ca6447a71a0039e7c6dcf64d0ead447ce511eff158d529f1b6258ad12668e66ee3e49ff14932d2b88a3bd578f483e79708104
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ret@npm:~0.1.10":
|
||||
version: 0.1.15
|
||||
resolution: "ret@npm:0.1.15"
|
||||
|
@ -16546,7 +16728,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"side-channel@npm:^1.0.4":
|
||||
"side-channel@npm:^1.0.3, side-channel@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "side-channel@npm:1.0.4"
|
||||
dependencies:
|
||||
|
@ -17139,6 +17321,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string.prototype.trim@npm:^1.2.6":
|
||||
version: 1.2.6
|
||||
resolution: "string.prototype.trim@npm:1.2.6"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
define-properties: ^1.1.4
|
||||
es-abstract: ^1.19.5
|
||||
checksum: c5968e023afa9dec6a669c1f427f59aeb74f6f7ee5b0f4b9f0ffcef1d3846aa78b02227448cc874bbfa25dd1f8fd2324041c6cade38d4a986e4ade121ce1ea79
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string.prototype.trimend@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "string.prototype.trimend@npm:1.0.5"
|
||||
|
@ -17464,6 +17657,37 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tape@npm:^5.6.0":
|
||||
version: 5.6.0
|
||||
resolution: "tape@npm:5.6.0"
|
||||
dependencies:
|
||||
array.prototype.every: ^1.1.3
|
||||
call-bind: ^1.0.2
|
||||
deep-equal: ^2.0.5
|
||||
defined: ^1.0.0
|
||||
dotignore: ^0.1.2
|
||||
for-each: ^0.3.3
|
||||
get-package-type: ^0.1.0
|
||||
glob: ^7.2.3
|
||||
has: ^1.0.3
|
||||
has-dynamic-import: ^2.0.1
|
||||
inherits: ^2.0.4
|
||||
is-regex: ^1.1.4
|
||||
minimist: ^1.2.6
|
||||
object-inspect: ^1.12.2
|
||||
object-is: ^1.1.5
|
||||
object-keys: ^1.1.1
|
||||
object.assign: ^4.1.3
|
||||
resolve: ^2.0.0-next.3
|
||||
resumer: ^0.0.0
|
||||
string.prototype.trim: ^1.2.6
|
||||
through: ^2.3.8
|
||||
bin:
|
||||
tape: bin/tape
|
||||
checksum: 867b85b6124598c69063548ffb2c4566a63f040d35aee242fd9cc7a0aedc34626feae2ef412d03e2a4817ca9389a4d94006d915b0c163b98e83af52f9258167f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-stream@npm:^1.5.2":
|
||||
version: 1.6.2
|
||||
resolution: "tar-stream@npm:1.6.2"
|
||||
|
@ -17656,7 +17880,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"through@npm:^2.3.6, through@npm:^2.3.8":
|
||||
"through@npm:2, through@npm:^2.3.6, through@npm:^2.3.8, through@npm:~2.3, through@npm:~2.3.1, through@npm:~2.3.4":
|
||||
version: 2.3.8
|
||||
resolution: "through@npm:2.3.8"
|
||||
checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd
|
||||
|
@ -19106,7 +19330,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-boxed-primitive@npm:^1.0.2":
|
||||
"which-boxed-primitive@npm:^1.0.1, which-boxed-primitive@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "which-boxed-primitive@npm:1.0.2"
|
||||
dependencies:
|
||||
|
@ -19119,6 +19343,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-collection@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "which-collection@npm:1.0.1"
|
||||
dependencies:
|
||||
is-map: ^2.0.1
|
||||
is-set: ^2.0.1
|
||||
is-weakmap: ^2.0.1
|
||||
is-weakset: ^2.0.1
|
||||
checksum: c815bbd163107ef9cb84f135e6f34453eaf4cca994e7ba85ddb0d27cea724c623fae2a473ceccfd5549c53cc65a5d82692de418166df3f858e1e5dc60818581c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-module@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "which-module@npm:2.0.0"
|
||||
|
@ -19126,6 +19362,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-typed-array@npm:^1.1.2":
|
||||
version: 1.1.8
|
||||
resolution: "which-typed-array@npm:1.1.8"
|
||||
dependencies:
|
||||
available-typed-arrays: ^1.0.5
|
||||
call-bind: ^1.0.2
|
||||
es-abstract: ^1.20.0
|
||||
for-each: ^0.3.3
|
||||
has-tostringtag: ^1.0.0
|
||||
is-typed-array: ^1.1.9
|
||||
checksum: bedf4d30a738e848404fe67fe0ace33433a7298cf3f5a4d4b2c624ba99c4d25f06a7fd6f3566c3d16af5f8a54f0c6293cbfded5b1208ce11812753990223b45a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which@npm:^1.2.10, which@npm:^1.2.14, which@npm:^1.2.9, which@npm:^1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "which@npm:1.3.1"
|
||||
|
|
Loading…
Reference in a new issue