Completely remove EteSync dependency.

master
Tom Hacohen 4 years ago
parent 8fb50df017
commit 4047638e6f

@ -10,7 +10,6 @@
"@material-ui/pickers": "^3.2.10",
"@material-ui/styles": "^4.6.0",
"etebase": "^0.7.4",
"etesync": "^0.3.1",
"fuse.js": "^5.0.9-beta",
"ical.js": "^1.4.0",
"immutable": "^4.0.0-rc.12",
@ -50,7 +49,6 @@
"@types/jest": "^24.0.4",
"@types/memoizee": "^0.4.4",
"@types/node": "^11.9.3",
"@types/node-rsa": "^1.0.0",
"@types/react": "^16.9.0",
"@types/react-big-calendar": "^0.22.3",
"@types/react-dom": "^16.9.0",
@ -60,7 +58,6 @@
"@types/react-virtualized": "^9.21.8",
"@types/redux-actions": "^2.6.1",
"@types/redux-logger": "^3.0.8",
"@types/sjcl": "^1.0.28",
"@types/urijs": "^1.15.38",
"@types/uuid": "^3.4.3",
"typescript": "~3.9.7"

@ -6,7 +6,7 @@ import * as React from "react";
import { store, persistor } from "../store";
import { resetKey } from "../store/actions";
import { EncryptionPasswordError, IntegrityError } from "etesync";
import { EncryptionPasswordError, IntegrityError } from "etebase";
import PrettyError from "../widgets/PrettyError";
interface PropsType {

@ -1,47 +0,0 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
import { shallowEqual, useSelector } from "react-redux";
import { createSelector } from "reselect";
import * as store from "../store";
export const remoteCredentialsSelector = createSelector(
(state: store.StoreState) => state.credentials.credentials,
(state: store.StoreState) => state.credentials.serviceApiUrl,
(credentials, serviceApiUrl) => {
if (!credentials) {
return null;
}
const ret: store.CredentialsDataRemote = {
credentials,
serviceApiUrl,
};
return ret;
}
);
export function useRemoteCredentials() {
return useSelector(remoteCredentialsSelector, shallowEqual);
}
export const credentialsSelector = createSelector(
(state: store.StoreState) => remoteCredentialsSelector(state),
(state: store.StoreState) => state.encryptionKey.key,
(remoteCredentials, encryptionKey) => {
if (!remoteCredentials || !encryptionKey) {
return null;
}
const ret: store.CredentialsData = {
...remoteCredentials,
encryptionKey,
};
return ret;
}
);
export function useCredentials() {
return useSelector(credentialsSelector, shallowEqual);
}

@ -1,44 +1,11 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
import { Action, createAction, createActions } from "redux-actions";
import { createAction } from "redux-actions";
import * as Etebase from "etebase";
import * as EteSync from "etesync";
import { UserInfo } from "etesync";
import { CredentialsData, EntriesData, SettingsType } from "./";
export const { fetchCredentials } = createActions({
FETCH_CREDENTIALS: (username: string, password: string, server: string) => {
const authenticator = new EteSync.Authenticator(server);
return new Promise((resolve, reject) => {
authenticator.getAuthToken(username, password).then(
(authToken) => {
const creds = new EteSync.Credentials(username, authToken);
const context = {
serviceApiUrl: server,
credentials: creds,
};
resolve(context);
},
(error) => {
reject(error);
}
);
});
},
});
export const { deriveKey } = createActions({
DERIVE_KEY: (username: string, encryptionPassword: string) => {
return EteSync.deriveKey(username, encryptionPassword);
},
});
import { SettingsType } from "./";
export const resetKey = createAction(
"RESET_KEY",
@ -133,144 +100,16 @@ export const performSync = createAction(
}
);
export const { fetchListJournal } = createActions({
FETCH_LIST_JOURNAL: (etesync: CredentialsData) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalManager = new EteSync.JournalManager(creds, apiBase);
return journalManager.list();
},
});
export const addJournal = createAction(
"ADD_JOURNAL",
(etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalManager = new EteSync.JournalManager(creds, apiBase);
return journalManager.create(journal);
},
(_etesync: CredentialsData, journal: EteSync.Journal) => {
return { item: journal };
}
);
export const updateJournal = createAction(
"UPDATE_JOURNAL",
(etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalManager = new EteSync.JournalManager(creds, apiBase);
return journalManager.update(journal);
},
(_etesync: CredentialsData, journal: EteSync.Journal) => {
return { item: journal };
}
);
export const deleteJournal = createAction(
"DELETE_JOURNAL",
(etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const journalManager = new EteSync.JournalManager(creds, apiBase);
return journalManager.delete(journal);
},
(_etesync: CredentialsData, journal: EteSync.Journal) => {
return { item: journal };
}
);
export const { fetchEntries, addEntries } = createActions({
FETCH_ENTRIES: [
(etesync: CredentialsData, journalUid: string, prevUid: string | null) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const entryManager = new EteSync.EntryManager(creds, apiBase, journalUid);
return entryManager.list(prevUid);
},
(_etesync: CredentialsData, journalUid: string, prevUid: string | null) => {
return { journal: journalUid, prevUid };
},
],
ADD_ENTRIES: [
(etesync: CredentialsData, journalUid: string, newEntries: EteSync.Entry[], prevUid: string | null) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const entryManager = new EteSync.EntryManager(creds, apiBase, journalUid);
return entryManager.create(newEntries, prevUid).then(() => newEntries);
},
(_etesync: CredentialsData, journalUid: string, newEntries: EteSync.Entry[], prevUid: string | null) => {
return { journal: journalUid, entries: newEntries, prevUid };
},
],
});
export const { fetchUserInfo } = createActions({
FETCH_USER_INFO: (etesync: CredentialsData, owner: string) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const userInfoManager = new EteSync.UserInfoManager(creds, apiBase);
return userInfoManager.fetch(owner);
},
});
export const createUserInfo = createAction(
"CREATE_USER_INFO",
(etesync: CredentialsData, userInfo: UserInfo) => {
const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl;
const userInfoManager = new EteSync.UserInfoManager(creds, apiBase);
return userInfoManager.create(userInfo);
},
(_etesync: CredentialsData, userInfo: UserInfo) => {
return { userInfo };
}
);
export function fetchAll(etesync: CredentialsData, currentEntries: EntriesData) {
return (dispatch: any) => {
return new Promise<boolean>((resolve, reject) => {
dispatch(fetchListJournal(etesync)).then((journalsAction: Action<EteSync.Journal[]>) => {
const journals = journalsAction.payload;
if (!journals || (journals.length === 0)) {
resolve(false);
return;
}
Promise.all(journals.map((journal) => {
const prevUid = currentEntries.get(journal.uid)?.last(undefined)?.uid ?? null;
// FIXME: expose it in a non-hacky way.
if (prevUid && (prevUid === (journal as any)._json.lastUid)) {
return true;
}
return dispatch(fetchEntries(etesync, journal.uid, prevUid));
})).then(() => resolve(true)).catch(reject);
}).catch(reject);
});
};
}
export const appendError = createAction(
"APPEND_ERROR",
(_etesync: CredentialsData, error: Error | Error[]) => {
(_etesync: Etebase.Account, error: Error | Error[]) => {
return Array.isArray(error) ? error : [error];
}
);
export const clearErros = createAction(
"CLEAR_ERRORS",
(_etesync: CredentialsData) => {
(_etesync: Etebase.Account) => {
return true;
}
);

@ -7,19 +7,15 @@ import { createMigrate, persistReducer, createTransform } from "redux-persist";
import session from "redux-persist/lib/storage/session";
import { List, Map as ImmutableMap } from "immutable";
import * as EteSync from "etesync";
import {
JournalsData, EntriesData, UserInfoData,
CredentialsDataRemote, SettingsType,
fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer,
SettingsType,
fetchCount, credentials, settingsReducer, encryptionKeyReducer, errorsReducer,
syncGeneral, syncCollections, collections, items,
SyncGeneralData, SyncCollectionsData, CacheCollectionsData, CacheItemsData, CredentialsData2,
} from "./reducers";
export interface StoreState {
fetchCount: number;
credentials: CredentialsDataRemote;
credentials2: CredentialsData2;
settings: SettingsType;
encryptionKey: {key: string};
@ -31,11 +27,6 @@ export interface StoreState {
collections: CacheCollectionsData;
items: CacheItemsData;
};
cache: {
journals: JournalsData;
entries: EntriesData;
userInfo: UserInfoData;
};
errors: List<Error>;
}
@ -58,19 +49,6 @@ const settingsPersistConfig = {
migrate: createMigrate(settingsMigrations, { debug: false }),
};
const credentialsMigrations = {
0: (state: any) => {
return state.value;
},
};
const credentialsPersistConfig = {
key: "credentials",
version: 0,
storage: localforage,
migrate: createMigrate(credentialsMigrations, { debug: false }),
};
const credentials2PersistConfig = {
key: "credentials2",
version: 0,
@ -82,124 +60,6 @@ const encryptionKeyPersistConfig = {
storage: session,
};
const journalsSerialize = (state: JournalsData) => {
if (state === null) {
return null;
}
return state.map((x, _uid) => x.serialize()).toJS();
};
const journalsDeserialize = (state: []) => {
if (state === null) {
return null;
}
const newState = ImmutableMap<string, EteSync.Journal>().asMutable();
Object.keys(state).forEach((uid) => {
const x = state[uid];
const ret = new EteSync.Journal({ uid }, x.version);
ret.deserialize(x);
newState.set(uid, ret);
});
return newState.asImmutable();
};
const entriesSerialize = (state: List<EteSync.Entry>) => {
if (state === null) {
return null;
}
return state.map((x) => x.serialize()).toJS();
};
const entriesDeserialize = (state: EteSync.EntryJson[]): List<EteSync.Entry> | null => {
if (state === null) {
return null;
}
return List(state.map((x) => {
const ret = new EteSync.Entry();
ret.deserialize(x);
return ret;
}));
};
const userInfoSerialize = (state: UserInfoData) => {
if (state === null) {
return null;
}
return state.serialize();
};
const userInfoDeserialize = (state: EteSync.UserInfoJson) => {
if (state === null) {
return null;
}
const ret = new EteSync.UserInfo(state.owner!, state.version);
ret.deserialize(state);
return ret;
};
const cacheSerialize = (state: any, key: string | number) => {
if (key === "entries") {
const ret = {};
state.forEach((value: List<EteSync.Entry>, mapKey: string) => {
ret[mapKey] = entriesSerialize(value);
});
return ret;
} else if (key === "journals") {
return journalsSerialize(state);
} else if (key === "userInfo") {
return userInfoSerialize(state);
}
return state;
};
const cacheDeserialize = (state: any, key: string | number) => {
if (key === "entries") {
const ret = {};
Object.keys(state).forEach((mapKey) => {
ret[mapKey] = entriesDeserialize(state[mapKey]);
});
return ImmutableMap(ret);
} else if (key === "journals") {
return journalsDeserialize(state);
} else if (key === "userInfo") {
return userInfoDeserialize(state);
}
return state;
};
const cacheMigrations = {
0: (state: any) => {
return {
...state,
journals: undefined,
};
},
2: (state: any) => {
return {
...state,
userInfo: state.userInfo?.value,
journals: state.journals?.value,
entries: undefined, // For now we just reset the entries
};
},
};
const cachePersistConfig = {
key: "cache",
version: 2,
storage: localforage,
transforms: [createTransform(cacheSerialize, cacheDeserialize)],
migrate: createMigrate(cacheMigrations, { debug: false }),
};
const syncSerialize = (state: any, key: string | number) => {
if (key === "collections") {
return state.toJS();
@ -254,7 +114,6 @@ const cache2PersistConfig = {
const reducers = combineReducers({
fetchCount,
settings: persistReducer(settingsPersistConfig, settingsReducer),
credentials: persistReducer(credentialsPersistConfig, credentials),
credentials2: persistReducer(credentials2PersistConfig, credentials),
encryptionKey: persistReducer(encryptionKeyPersistConfig, encryptionKeyReducer),
sync: persistReducer(syncPersistConfig, combineReducers({
@ -265,11 +124,6 @@ const reducers = combineReducers({
collections,
items,
})),
cache: persistReducer(cachePersistConfig, combineReducers({
entries,
journals,
userInfo,
})),
errors: errorsReducer,
});

@ -1,53 +0,0 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
import { addEntries, fetchEntries } from "./actions";
import { entries, EntriesData } from "./reducers";
import { Map } from "immutable";
import * as EteSync from "etesync";
it("Entries reducer", () => {
const jId = "24324324324";
let state = Map({}) as EntriesData;
const entry = new EteSync.Entry();
entry.deserialize({
content: "someContent",
uid: "6355209e2a2c26a6c1e6e967c2032737d538f602cf912474da83a2902f8a0a83",
});
const action = {
type: fetchEntries.toString(),
meta: { journal: jId, prevUid: null as string | null },
payload: [entry],
};
let journal;
let entry2;
state = entries(state, action as any);
journal = state.get(jId)!;
entry2 = journal.get(0)!;
expect(entry2.serialize()).toEqual(entry.serialize());
// We replace if there's no prevUid
state = entries(state, action as any);
journal = state.get(jId)!;
entry2 = journal.get(0)!;
expect(entry2.serialize()).toEqual(entry.serialize());
expect(journal.size).toBe(1);
// We extend if prevUid is set
action.meta.prevUid = entry.uid;
state = entries(state, action as any);
journal = state.get(jId)!;
expect(journal.size).toBe(2);
// Creating entries should also work the same
action.type = addEntries.toString();
state = entries(state, action as any);
journal = state.get(jId)!;
expect(journal.size).toBe(3);
});

@ -2,30 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { Action, ActionMeta, ActionFunctionAny, combineActions, handleAction, handleActions } from "redux-actions";
import { shallowEqual } from "react-redux";
import { List, Map as ImmutableMap } from "immutable";
import * as EteSync from "etesync";
import * as actions from "./actions";
export type JournalsData = ImmutableMap<string, EteSync.Journal>;
export type EntriesListData = List<EteSync.Entry>;
export type EntriesData = ImmutableMap<string, EntriesListData>;
export type UserInfoData = EteSync.UserInfo;
export interface CredentialsDataRemote {
serviceApiUrl: string;
credentials: EteSync.Credentials;
}
export interface CredentialsData extends CredentialsDataRemote {
encryptionKey: string;
}
interface BaseModel {
uid: string;
}
@ -153,156 +134,6 @@ export const items = handleActions(
ImmutableMap({})
);
function entriesListSetExtend(
state: List<any> | undefined, action: Action<EteSync.Entry[]>, extend = false) {
state = state ?? List([]);
if (action.error) {
return state;
} else {
const payload = action.payload ?? null;
if (!payload) {
return state;
}
if (extend && (state !== null)) {
if (payload !== null) {
state = state.concat(payload);
}
} else if (payload !== null) {
state = List(payload);
}
return state;
}
}
function fetchCreateEntriesReducer(state: EntriesData, action: any) {
const prevState = state.get(action.meta.journal);
const extend = action.meta.prevUid !== null;
return state.set(action.meta.journal,
entriesListSetExtend(prevState, action, extend));
}
export const entries = handleActions(
{
[actions.fetchEntries.toString()]: fetchCreateEntriesReducer,
[actions.addEntries.toString()]: fetchCreateEntriesReducer,
[actions.addJournal.toString()]: (state: EntriesData, action: any) => {
const journal = action.meta.item.uid;
return state.set(journal, List([]));
},
[actions.logout.toString()]: (state: EntriesData, _action: any) => {
return state.clear();
},
},
ImmutableMap({})
);
const setMapModelReducer = (state: JournalsData, action: Action<EteSync.Journal[]>) => {
if (action.error || !action.payload) {
return state;
}
state = state ?? ImmutableMap<string, EteSync.Journal>().asMutable();
const old = state.asMutable();
return state.withMutations((ret) => {
const items = action.payload!;
for (const item of items) {
const current = old.get(item.uid);
if (!current || !shallowEqual(current.serialize(), item.serialize())) {
ret.set(item.uid, item);
}
if (current) {
old.delete(item.uid);
}
}
// Delete all the items that were deleted remotely (not handled above).
for (const uid of old.keys()) {
ret.delete(uid);
}
});
};
const addEditMapModelReducer = (state: JournalsData, action: ActionMeta<EteSync.Journal, { item: EteSync.Journal }>) => {
if (action.error) {
return state;
} else {
let payload = (action.payload === undefined) ? null : action.payload;
payload = (action.meta === undefined) ? payload : action.meta.item;
if (!payload) {
return state;
}
const item = payload;
return state.set(item.uid, item);
}
};
const deleteMapModelReducer = (state: JournalsData, action: ActionMeta<EteSync.Journal, { item: EteSync.Journal }>) => {
if (action.error) {
return state;
} else {
let payload = (action.payload === undefined) ? null : action.payload;
payload = (action.meta === undefined) ? payload : action.meta.item;
if (!payload) {
return state;
}
const uid = payload.uid;
return state.delete(uid);
}
};
export const journals = handleActions(
{
[actions.fetchListJournal.toString()]: setMapModelReducer as any,
[actions.addJournal.toString()]: addEditMapModelReducer,
[actions.updateJournal.toString()]: addEditMapModelReducer,
[actions.deleteJournal.toString()]: deleteMapModelReducer,
[actions.logout.toString()]: (state: JournalsData, _action: any) => {
return state.clear();
},
},
ImmutableMap({})
);
export const userInfo = handleActions(
{
[combineActions(
actions.fetchUserInfo,
actions.createUserInfo
).toString()]: (state: UserInfoData | null, action: any) => {
if (action.error) {
return state;
} else {
let payload = action.payload ?? null;
if (payload === null) {
return state;
}
payload = action.meta?.userInfo ?? payload;
if (!state || !shallowEqual(state.serialize(), payload.serialize())) {
return payload;
}
return state;
}
},
[actions.logout.toString()]: (_state: any, _action: any) => {
return null;
},
},
null
);
const fetchActions = [
] as Array<ActionFunctionAny<Action<any>>>;
@ -335,12 +166,7 @@ export const fetchCount = handleAction(
export const errorsReducer = handleActions(
{
[combineActions(
actions.fetchListJournal,
actions.addJournal,
actions.updateJournal,
actions.deleteJournal,
actions.fetchEntries,
actions.addEntries
actions.performSync
).toString()]: (state: List<Error>, action: Action<any>) => {
if (action.error) {
return state.push(action.payload);

@ -1,12 +1,7 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: © 2019 EteSync Authors
// SPDX-License-Identifier: GPL-3.0-only
import * as EteSync from "etesync";
import * as Etebase from "etebase";
export const deriveKey = EteSync.deriveKey;
export const login = Etebase.Account.login;
export const signup = Etebase.Account.signup;

@ -1781,13 +1781,6 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node-rsa@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/node-rsa/-/node-rsa-1.0.0.tgz#4432df6227c5de734f5f0fbea2420ffb51c51e44"
integrity sha512-9hTSXhGDKotpq5XCm+r7wMgt5gl6bGH6VS/sV9bsKda4JmJYyOCdTenSkTUh7zsOihm2oCm5o2ZdfpUIGYKLzQ==
dependencies:
"@types/node" "*"
"@types/node@*":
version "14.0.27"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1"
@ -1890,11 +1883,6 @@
dependencies:
redux "^4.0.0"
"@types/sjcl@^1.0.28":
version "1.0.29"
resolved "https://registry.yarnpkg.com/@types/sjcl/-/sjcl-1.0.29.tgz#bd154ee15421fe24be2db4a20322f38069c1ca71"
integrity sha512-gP9M7Oq4xAoDpuueWHeOTasccV4K6iFBEBPbR0tXejKT/e/Gkla0XcJ9REmgSCICt6y8/ubcvXFtiZ4ZLQ6BKA==
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@ -2447,7 +2435,7 @@ asn1.js@^4.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
asn1@^0.2.4, asn1@~0.2.3:
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
@ -4668,15 +4656,6 @@ etebase@^0.7.4:
optionalDependencies:
react-native-sodium "^0.3.8"
etesync@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/etesync/-/etesync-0.3.1.tgz#2af257d5617679177d93fb8f6bdaf64462aa15e0"
integrity sha512-i97WjIBElLp2IRaWZtXoOfT+N0ehiUV+qhWCcJT+9To8VUBmIwA2ZzNP1yqn6yRYBjeJvrPQrBZBHiidC9OOGQ==
dependencies:
node-rsa "^1.0.6"
sjcl "git+https://github.com/etesync/sjcl"
urijs "^1.19.1"
event-emitter@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
@ -7682,13 +7661,6 @@ node-releases@^1.1.52, node-releases@^1.1.58:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084"
integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==
node-rsa@^1.0.6:
version "1.1.1"
resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d"
integrity sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==
dependencies:
asn1 "^0.2.4"
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@ -10253,10 +10225,6 @@ sisteransi@^1.0.4:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
"sjcl@git+https://github.com/etesync/sjcl":
version "1.0.7"
resolved "git+https://github.com/etesync/sjcl#f259515e3c5cf8f437cdfa99c1cf0a8ad7321556"
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"

Loading…
Cancel
Save