From b0f5d4894b09eb78d1b66bba7816bbc710255698 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Mon, 13 Dec 2021 18:32:03 +0800 Subject: [PATCH 01/85] feat: wip --- .../background/database/persona/helper.ts | 68 +++++++++++++++++++ .../mask/background/services/crypto/index.ts | 68 ++++++++++++++++++- .../CryptoServices/decryptFrom.ts | 13 +++- 3 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 packages/mask/background/database/persona/helper.ts diff --git a/packages/mask/background/database/persona/helper.ts b/packages/mask/background/database/persona/helper.ts new file mode 100644 index 000000000000..878cacab6bd1 --- /dev/null +++ b/packages/mask/background/database/persona/helper.ts @@ -0,0 +1,68 @@ +import type { AESJsonWebKey, ProfileIdentifier } from '@masknet/shared-base' +import type { IDBPSafeTransaction } from '../utils/openDB' +import { createReadonlyPersonaTransaction, PersonaDB } from './db' + +/** + * If has local key of a profile in the database. + * @param id Profile Identifier + */ +export async function hasLocalKeyOf( + id: ProfileIdentifier, + tx?: IDBPSafeTransaction, +) { + const result = await getLocalKeyOf(id, tx) + return !!result +} + +/** + * Try to decrypt data using local key. + * + * @param authorHint Author of the local key + * @param data Data to be decrypted + * @param iv IV + */ +export async function decryptByLocalKey( + authorHint: ProfileIdentifier | null, + data: Uint8Array, + iv: Uint8Array, +): Promise { + const candidateKeys: AESJsonWebKey[] = [] + search: { + if (!authorHint) break search + const tx = (await createReadonlyPersonaTransaction())('personas', 'profiles') + const key = await getLocalKeyOf(authorHint, tx) + key && candidateKeys.push(key) + // TODO: We may push every local key we owned to the candidate list so we can also decrypt when authorHint is null, but that might be a performance pitfall when localKey field is not indexed. + } + + let check = () => {} + return Promise.any( + candidateKeys.map(async (key): Promise => { + const k = await crypto.subtle.importKey('jwk', key, { name: 'AES-GCM', length: 256 }, false, ['decrypt']) + check() + + const result: Uint8Array = await crypto.subtle.decrypt({ iv, name: 'AES-GCM' }, k, data) + check = abort + return result + }), + ) +} + +async function getLocalKeyOf( + id: ProfileIdentifier, + tx?: IDBPSafeTransaction, +) { + tx ||= (await createReadonlyPersonaTransaction())('personas', 'profiles') + + const profile = await tx.objectStore('profiles').get(id.toText()) + if (!profile) return + if (profile.localKey) return profile.localKey + if (!profile.linkedPersona) return + + const persona = await tx.objectStore('personas').get(profile.linkedPersona.toText()) + return persona?.localKey +} + +function abort() { + throw new Error('Cancelled') +} diff --git a/packages/mask/background/services/crypto/index.ts b/packages/mask/background/services/crypto/index.ts index 1ef6b67e0fd2..af3ddff09ca7 100644 --- a/packages/mask/background/services/crypto/index.ts +++ b/packages/mask/background/services/crypto/index.ts @@ -1,3 +1,65 @@ -// import {} from '@masknet/encryption' -// await Promise.resolve() -export {} +import { encodeArrayBuffer } from '@dimensiondev/kit' +import { AESCryptoKey, IdentifierMap, PostIVIdentifier, ProfileIdentifier } from '@masknet/shared-base' +import { decryptByLocalKey, hasLocalKeyOf } from '../../database/persona/helper' +import { queryPostDB } from '../../database/post' +import { savePostKeyToDB } from '../../database/post/helper' + +export async function* decryption(payload: string | Uint8Array, network: string) { + // This module required to be loaded async because it contains WebAssembly import. + // If it join the sync module graph of the whole application, it will fail due to unknown reason. + const { decrypt, parsePayload } = await import('@masknet/encryption') + + const parse = await parsePayload(payload) + if (parse.err) return null + + const iv = parse.val.encryption.unwrapOr(null)?.iv.unwrapOr(null) + if (!iv) return null + const id = new PostIVIdentifier(network, encodeArrayBuffer(iv.buffer)) + + yield* decrypt( + { message: parse.val }, + { + getPostKeyCache: getPostKeyCache.bind(null, id), + setPostKeyCache: (key) => { + return savePostKeyToDB(id, key, { + // public post will not call this function. + // and recipients only will be set when posting/appending recipients. + recipients: new IdentifierMap(new Map()), + // TODO: fill this + postBy: ProfileIdentifier.unknown, + // TODO: fill this + // url, + }) + }, + hasLocalKeyOf: hasLocalKeyOf, + decryptByLocalKey: decryptByLocalKey, + async deriveAESKey() { + throw 'TODO' + }, + async queryAuthorPublicKey() { + throw 'TODO' + }, + async *queryPostKey_version37() { + throw 'TODO' + }, + async *queryPostKey_version38() { + throw 'TODO' + }, + async *queryPostKey_version39() { + throw 'TODO' + }, + async queryPostKey_version40() { + throw 'TODO' + }, + }, + ) + return +} +async function getPostKeyCache(id: PostIVIdentifier) { + const post = await queryPostDB(id) + if (!post?.postCryptoKey) return null + const k = await crypto.subtle.importKey('jwk', post.postCryptoKey, { name: 'AES-GCM', length: 256 }, false, [ + 'decrypt', + ]) + return k as AESCryptoKey +} diff --git a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts index 2209e58010c1..04b0ecd6a29d 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts @@ -2,7 +2,7 @@ import * as Alpha40 from '../../../crypto/crypto-alpha-40' import * as Alpha39 from '../../../crypto/crypto-alpha-39' import { GunAPI as Gun2, GunAPISubscribe as Gun2Subscribe, GunWorker } from '../../../network/gun/' import { decodeText } from '@dimensiondev/kit' -import { deconstructPayload } from '../../../utils/type-transform/Payload' +import { constructAlpha38, deconstructPayload } from '../../../utils/type-transform/Payload' import { i18n } from '../../../../shared-ui/locales_legacy' import { queryPersonaRecord, queryLocalKey } from '../../../database' import { ProfileIdentifier, PostIVIdentifier, delay } from '@masknet/shared-base' @@ -22,6 +22,7 @@ import { MaskMessages } from '../../../utils/messages' import { GunAPI } from '../../../network/gun' import { Err, Ok, Result } from 'ts-results' import { decodeTextPayloadWorker } from '../../../social-network/utils/text-payload-worker' +import { decryption } from '../../../../background/services/crypto' type Progress = ( | { progress: 'finding_person_public_key' | 'finding_post_key' | 'init' | 'decode_post' } @@ -128,6 +129,16 @@ async function* decryptFromPayloadWithProgress_raw( whoAmI: ProfileIdentifier, discoverURL: string | undefined, ): ReturnOfDecryptPostContentWithProgress { + latest: if (process.env.NODE_ENV === 'development') { + if (post.version !== -38) break latest + // Let's try the new infra! + const { DecryptProgressKind } = await import('@masknet/encryption') + for await (const progress of decryption(constructAlpha38(post), authorNetworkHint)) { + if (progress.type === DecryptProgressKind.Success) { + console.log('Decrypted by the new infra!', progress.content) + } + } + } const cacheKey = stringify(post) if (successDecryptionCache.has(cacheKey)) return successDecryptionCache.get(cacheKey)! yield makeProgress('init') From 057ca3293871e5b7de4fa21fece4493042f2958b Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 14 Dec 2021 15:29:35 +0800 Subject: [PATCH 02/85] feat: impl ecdh and query author public key --- .../background/database/persona/helper.ts | 56 ++++++++++++++++++- .../mask/background/services/crypto/index.ts | 29 ++++++++-- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/packages/mask/background/database/persona/helper.ts b/packages/mask/background/database/persona/helper.ts index 878cacab6bd1..66a6da08d009 100644 --- a/packages/mask/background/database/persona/helper.ts +++ b/packages/mask/background/database/persona/helper.ts @@ -1,7 +1,16 @@ -import type { AESJsonWebKey, ProfileIdentifier } from '@masknet/shared-base' +import { + AESCryptoKey, + AESJsonWebKey, + ECKeyIdentifier, + EC_Private_JsonWebKey, + EC_Public_CryptoKey, + IdentifierMap, + ProfileIdentifier, +} from '@masknet/shared-base' import type { IDBPSafeTransaction } from '../utils/openDB' import { createReadonlyPersonaTransaction, PersonaDB } from './db' +//#region Local key helpers /** * If has local key of a profile in the database. * @param id Profile Identifier @@ -62,6 +71,51 @@ async function getLocalKeyOf( const persona = await tx.objectStore('personas').get(profile.linkedPersona.toText()) return persona?.localKey } +//#endregion + +//#region ECDH +export async function deriveAESByECDH(pub: EC_Public_CryptoKey) { + const curve = (pub.algorithm as EcKeyAlgorithm).namedCurve || '' + const sameCurvePrivateKeys = new Map() + + db: { + const t = (await createReadonlyPersonaTransaction())('personas') + const hasPriv = await t.objectStore('personas').index('hasPrivateKey').openCursor(IDBKeyRange.only('yes')) + if (hasPriv === null) break db + for await (const { value } of hasPriv) { + if (!value.privateKey) continue + if (value.privateKey.crv !== curve) continue + sameCurvePrivateKeys.set(value.identifier, value.privateKey) + } + } + + const deriveResult = new Map() + const result = await Promise.allSettled( + [...sameCurvePrivateKeys].map(async ([id, key]) => { + const k = await crypto.subtle.importKey( + 'jwk', + key, + { name: 'ECDH', namedCurve: key.crv! } as EcKeyAlgorithm, + false, + ['deriveKey'], + ) + const result = await crypto.subtle.deriveKey( + { name: 'ECDH', public: pub }, + k, + { name: 'AES-GCM', length: 256 }, + false, + ['encrypt', 'decrypt'], + ) + deriveResult.set(id, result as AESCryptoKey) + }), + ) + const failed = result.filter((x): x is PromiseRejectedResult => x.status === 'rejected') + if (failed.length) { + console.warn('Failed to ECDH', ...failed.map((x) => x.reason)) + } + return new IdentifierMap(deriveResult, ECKeyIdentifier) +} +//#endregion function abort() { throw new Error('Cancelled') diff --git a/packages/mask/background/services/crypto/index.ts b/packages/mask/background/services/crypto/index.ts index af3ddff09ca7..619f57252003 100644 --- a/packages/mask/background/services/crypto/index.ts +++ b/packages/mask/background/services/crypto/index.ts @@ -1,6 +1,13 @@ import { encodeArrayBuffer } from '@dimensiondev/kit' -import { AESCryptoKey, IdentifierMap, PostIVIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { decryptByLocalKey, hasLocalKeyOf } from '../../database/persona/helper' +import { + AESCryptoKey, + EC_Public_CryptoKey, + IdentifierMap, + PostIVIdentifier, + ProfileIdentifier, +} from '@masknet/shared-base' +import { queryPersonaByProfileDB } from '../../database/persona/db' +import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../database/persona/helper' import { queryPostDB } from '../../database/post' import { savePostKeyToDB } from '../../database/post/helper' @@ -33,11 +40,21 @@ export async function* decryption(payload: string | Uint8Array, network: string) }, hasLocalKeyOf: hasLocalKeyOf, decryptByLocalKey: decryptByLocalKey, - async deriveAESKey() { - throw 'TODO' + async deriveAESKey(pub) { + return Array.from((await deriveAESByECDH(pub)).values()) }, - async queryAuthorPublicKey() { - throw 'TODO' + async queryAuthorPublicKey(author, signal) { + // TODO: should use decrypt hint as the value + if (!author) return null + const persona = await queryPersonaByProfileDB(author) + if (!persona) return null + return (await crypto.subtle.importKey( + 'jwk', + persona.publicKey, + { name: 'ECDH', namedCurve: persona.publicKey.crv! } as EcKeyAlgorithm, + false, + ['deriveKey'], + )) as EC_Public_CryptoKey }, async *queryPostKey_version37() { throw 'TODO' From 54f392d5ea0b7f054bc9c8e0cf00691f3554efdd Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 14 Dec 2021 16:15:02 +0800 Subject: [PATCH 03/85] feat: add more logic to decryption process --- .../mask/background/services/crypto/index.ts | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/mask/background/services/crypto/index.ts b/packages/mask/background/services/crypto/index.ts index 619f57252003..9b9cda3cd0d7 100644 --- a/packages/mask/background/services/crypto/index.ts +++ b/packages/mask/background/services/crypto/index.ts @@ -1,4 +1,5 @@ import { encodeArrayBuffer } from '@dimensiondev/kit' +import type { DecryptProgress, DecryptProgressKind } from '@masknet/encryption' import { AESCryptoKey, EC_Public_CryptoKey, @@ -11,19 +12,43 @@ import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../databas import { queryPostDB } from '../../database/post' import { savePostKeyToDB } from '../../database/post/helper' +export type DecryptionInfo = { + type: DecryptProgressKind.Info + iv?: Uint8Array +} +export type DecryptionProgress = DecryptProgress | DecryptionInfo export async function* decryption(payload: string | Uint8Array, network: string) { // This module required to be loaded async because it contains WebAssembly import. // If it join the sync module graph of the whole application, it will fail due to unknown reason. - const { decrypt, parsePayload } = await import('@masknet/encryption') + const { decrypt, parsePayload, DecryptProgressKind, PublicKeyAlgorithmEnum } = await import('@masknet/encryption') + + // TODO: read in-memory cache to avoid db lookup const parse = await parsePayload(payload) if (parse.err) return null + //#region post IV const iv = parse.val.encryption.unwrapOr(null)?.iv.unwrapOr(null) - if (!iv) return null + { + if (!iv) return null + // iv is required to identify the post and it also used in comment encryption. + const info: DecryptionInfo = { type: DecryptProgressKind.Info, iv } + yield info + } const id = new PostIVIdentifier(network, encodeArrayBuffer(iv.buffer)) + //#endregion + + //#region Store author public key + try { + const author = parse.unwrap().author.unwrap().unwrap() + const authorPub = parse.unwrap().authorPublicKey.unwrap().unwrap() + // TODO: should only store public key when author equals to the decryption hint for security reason. - yield* decrypt( + if (authorPub.algr !== PublicKeyAlgorithmEnum.secp256k1) throw new Error('TODO: support other curves') + } catch {} + //#endregion + + const progress = decrypt( { message: parse.val }, { getPostKeyCache: getPostKeyCache.bind(null, id), @@ -70,7 +95,9 @@ export async function* decryption(payload: string | Uint8Array, network: string) }, }, ) - return + + yield* progress + return null } async function getPostKeyCache(id: PostIVIdentifier) { const post = await queryPostDB(id) From 0efd5bb46937b576be86079710eb438cd98189aa Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 16 Dec 2021 14:38:00 +0800 Subject: [PATCH 04/85] chore: use sync import for encryption package --- packages/mask/background/services/crypto/index.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/mask/background/services/crypto/index.ts b/packages/mask/background/services/crypto/index.ts index 9b9cda3cd0d7..9acd1eff5286 100644 --- a/packages/mask/background/services/crypto/index.ts +++ b/packages/mask/background/services/crypto/index.ts @@ -1,5 +1,11 @@ import { encodeArrayBuffer } from '@dimensiondev/kit' -import type { DecryptProgress, DecryptProgressKind } from '@masknet/encryption' +import { + decrypt, + parsePayload, + DecryptProgressKind, + PublicKeyAlgorithmEnum, + DecryptProgress, +} from '@masknet/encryption' import { AESCryptoKey, EC_Public_CryptoKey, @@ -18,10 +24,6 @@ export type DecryptionInfo = { } export type DecryptionProgress = DecryptProgress | DecryptionInfo export async function* decryption(payload: string | Uint8Array, network: string) { - // This module required to be loaded async because it contains WebAssembly import. - // If it join the sync module graph of the whole application, it will fail due to unknown reason. - const { decrypt, parsePayload, DecryptProgressKind, PublicKeyAlgorithmEnum } = await import('@masknet/encryption') - // TODO: read in-memory cache to avoid db lookup const parse = await parsePayload(payload) From 94e89fbdba238e6cc324a21e0d5494168791358b Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 16 Dec 2021 17:21:26 +0800 Subject: [PATCH 05/85] feat: add gun related code --- .../network/gun/encryption/queryPostKey.ts | 30 +++++ .../network/gun/index.ts} | 0 packages/mask/background/network/gun/utils.ts | 37 ++++++ .../background/services/crypto/decryption.ts | 116 ++++++++++++++++++ .../mask/background/services/crypto/index.ts | 112 +---------------- .../CryptoServices/decryptFrom.ts | 2 +- .../mask/src/network/gun/version.2/gun2.ts | 2 +- 7 files changed, 186 insertions(+), 113 deletions(-) create mode 100644 packages/mask/background/network/gun/encryption/queryPostKey.ts rename packages/mask/{src/network/gun-servers.ts => background/network/gun/index.ts} (100%) create mode 100644 packages/mask/background/network/gun/utils.ts create mode 100644 packages/mask/background/services/crypto/decryption.ts diff --git a/packages/mask/background/network/gun/encryption/queryPostKey.ts b/packages/mask/background/network/gun/encryption/queryPostKey.ts new file mode 100644 index 000000000000..9aeb9cc892c3 --- /dev/null +++ b/packages/mask/background/network/gun/encryption/queryPostKey.ts @@ -0,0 +1,30 @@ +import { decodeArrayBuffer, encodeArrayBuffer } from '@dimensiondev/kit' +import type { DecryptStaticECDH_PostKey } from '@masknet/encryption' +import type { ProfileIdentifier } from '@masknet/shared-base' +import type { default as gun } from 'gun' +import { getGunData } from '../utils' +type Gun = ReturnType + +// !!! Change how this function access Gun will break the compatibility of v40 payload decryption. +export async function GUN_queryPostKey_version40( + gun: Gun, + iv: Uint8Array, + whoAmI: ProfileIdentifier, +): Promise { + // PATH ON GUN: maskbook > posts > iv > userID + const result = await getGunData(gun, 'maskbook', 'posts', encodeArrayBuffer(iv), whoAmI.userId) + if (!isValidData(result)) return null + return { + encryptedPostKey: new Uint8Array(decodeArrayBuffer(result.encryptedKey)), + postKeyIV: new Uint8Array(decodeArrayBuffer(result.salt)), + } + + type DataOnGun = { encryptedKey: string; salt: string } + function isValidData(x: typeof result): x is DataOnGun { + if (!x) return false + + const { encryptedKey, salt: encryptedKeyIV } = x + if (typeof encryptedKey !== 'string' || typeof encryptedKeyIV !== 'string') return false + return true + } +} diff --git a/packages/mask/src/network/gun-servers.ts b/packages/mask/background/network/gun/index.ts similarity index 100% rename from packages/mask/src/network/gun-servers.ts rename to packages/mask/background/network/gun/index.ts diff --git a/packages/mask/background/network/gun/utils.ts b/packages/mask/background/network/gun/utils.ts new file mode 100644 index 000000000000..979694150b9d --- /dev/null +++ b/packages/mask/background/network/gun/utils.ts @@ -0,0 +1,37 @@ +import type { default as gun } from 'gun' +import { EventIterator } from 'event-iterator' +export type GunRoot = ReturnType + +/** + * A sugar for gun.get(path[0]).get(path[1])....get(path[n]).once() + */ +export function getGunData(gun: GunRoot, ...path: string[]) { + return new Promise<{ [x: string]: unknown } | undefined>((resolve) => { + const resultNode = path.reduce((gun, path) => gun.get(path as never), gun) + resultNode.once(resolve) + }) +} + +export async function* subscribeGunData( + gun: GunRoot, + path: string[], + isT: (x: unknown) => x is T, + abortSignal: AbortSignal, +) { + yield* new EventIterator((queue) => { + // gun.off() will remove ALL listener on it + let listenerClosed = false + + abortSignal.addEventListener('abort', () => { + queue.stop() + listenerClosed = true + }) + + const resultNode = path.reduce((gun, path) => gun.get(path as never), gun) + + resultNode.on((data) => { + if (listenerClosed) return + if (isT(data)) queue.push(data) + }) + }) +} diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts new file mode 100644 index 000000000000..a4373c18502a --- /dev/null +++ b/packages/mask/background/services/crypto/decryption.ts @@ -0,0 +1,116 @@ +import { encodeArrayBuffer } from '@dimensiondev/kit' +import { + decrypt, + parsePayload, + DecryptProgressKind, + PublicKeyAlgorithmEnum, + DecryptProgress, +} from '@masknet/encryption' +import { + AESCryptoKey, + EC_Public_CryptoKey, + IdentifierMap, + PostIVIdentifier, + ProfileIdentifier, +} from '@masknet/shared-base' +import { queryPersonaByProfileDB } from '../../database/persona/db' +import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../database/persona/helper' +import { queryPostDB } from '../../database/post' +import { savePostKeyToDB } from '../../database/post/helper' +import { GUN_queryPostKey_version40 } from '../../network/gun/encryption/queryPostKey' +import type { GunRoot } from '../../network/gun/utils' + +export type DecryptionInfo = { + type: DecryptProgressKind.Info + iv?: Uint8Array +} +export type DecryptionProgress = DecryptProgress | DecryptionInfo +export async function* decryption(payload: string | Uint8Array, network: string, whoAmIHint: ProfileIdentifier | null) { + if (whoAmIHint?.isUnknown) whoAmIHint = null + // TODO: read in-memory cache to avoid db lookup + + const parse = await parsePayload(payload) + if (parse.err) return null + + //#region post IV + const iv = parse.val.encryption.unwrapOr(null)?.iv.unwrapOr(null) + { + if (!iv) return null + // iv is required to identify the post and it also used in comment encryption. + const info: DecryptionInfo = { type: DecryptProgressKind.Info, iv } + yield info + } + const id = new PostIVIdentifier(network, encodeArrayBuffer(iv.buffer)) + //#endregion + + //#region Store author public key + try { + const author = parse.unwrap().author.unwrap().unwrap() + const authorPub = parse.unwrap().authorPublicKey.unwrap().unwrap() + // TODO: should only store public key when author equals to the decryption hint for security reason. + + if (authorPub.algr !== PublicKeyAlgorithmEnum.secp256k1) throw new Error('TODO: support other curves') + } catch {} + //#endregion + + const progress = decrypt( + { message: parse.val }, + { + getPostKeyCache: getPostKeyCache.bind(null, id), + setPostKeyCache: (key) => { + return savePostKeyToDB(id, key, { + // public post will not call this function. + // and recipients only will be set when posting/appending recipients. + recipients: new IdentifierMap(new Map()), + // TODO: fill this + postBy: ProfileIdentifier.unknown, + // TODO: fill this + // url, + }) + }, + hasLocalKeyOf: hasLocalKeyOf, + decryptByLocalKey: decryptByLocalKey, + async deriveAESKey(pub) { + return Array.from((await deriveAESByECDH(pub)).values()) + }, + async queryAuthorPublicKey(author, signal) { + // TODO: should use decrypt hint as the value + if (!author) return null + const persona = await queryPersonaByProfileDB(author) + if (!persona) return null + return (await crypto.subtle.importKey( + 'jwk', + persona.publicKey, + { name: 'ECDH', namedCurve: persona.publicKey.crv! } as EcKeyAlgorithm, + false, + ['deriveKey'], + )) as EC_Public_CryptoKey + }, + // TODO: get a gun instance + async *queryPostKey_version37() { + throw 'TODO' + }, + async *queryPostKey_version38() { + throw 'TODO' + }, + async *queryPostKey_version39() { + throw 'TODO' + }, + async queryPostKey_version40(iv) { + if (!whoAmIHint) return null + return GUN_queryPostKey_version40({} as GunRoot, iv, whoAmIHint) + }, + }, + ) + + yield* progress + return null +} +async function getPostKeyCache(id: PostIVIdentifier) { + const post = await queryPostDB(id) + if (!post?.postCryptoKey) return null + const k = await crypto.subtle.importKey('jwk', post.postCryptoKey, { name: 'AES-GCM', length: 256 }, false, [ + 'decrypt', + ]) + return k as AESCryptoKey +} diff --git a/packages/mask/background/services/crypto/index.ts b/packages/mask/background/services/crypto/index.ts index 9acd1eff5286..b5dc25c3a910 100644 --- a/packages/mask/background/services/crypto/index.ts +++ b/packages/mask/background/services/crypto/index.ts @@ -1,111 +1 @@ -import { encodeArrayBuffer } from '@dimensiondev/kit' -import { - decrypt, - parsePayload, - DecryptProgressKind, - PublicKeyAlgorithmEnum, - DecryptProgress, -} from '@masknet/encryption' -import { - AESCryptoKey, - EC_Public_CryptoKey, - IdentifierMap, - PostIVIdentifier, - ProfileIdentifier, -} from '@masknet/shared-base' -import { queryPersonaByProfileDB } from '../../database/persona/db' -import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../database/persona/helper' -import { queryPostDB } from '../../database/post' -import { savePostKeyToDB } from '../../database/post/helper' - -export type DecryptionInfo = { - type: DecryptProgressKind.Info - iv?: Uint8Array -} -export type DecryptionProgress = DecryptProgress | DecryptionInfo -export async function* decryption(payload: string | Uint8Array, network: string) { - // TODO: read in-memory cache to avoid db lookup - - const parse = await parsePayload(payload) - if (parse.err) return null - - //#region post IV - const iv = parse.val.encryption.unwrapOr(null)?.iv.unwrapOr(null) - { - if (!iv) return null - // iv is required to identify the post and it also used in comment encryption. - const info: DecryptionInfo = { type: DecryptProgressKind.Info, iv } - yield info - } - const id = new PostIVIdentifier(network, encodeArrayBuffer(iv.buffer)) - //#endregion - - //#region Store author public key - try { - const author = parse.unwrap().author.unwrap().unwrap() - const authorPub = parse.unwrap().authorPublicKey.unwrap().unwrap() - // TODO: should only store public key when author equals to the decryption hint for security reason. - - if (authorPub.algr !== PublicKeyAlgorithmEnum.secp256k1) throw new Error('TODO: support other curves') - } catch {} - //#endregion - - const progress = decrypt( - { message: parse.val }, - { - getPostKeyCache: getPostKeyCache.bind(null, id), - setPostKeyCache: (key) => { - return savePostKeyToDB(id, key, { - // public post will not call this function. - // and recipients only will be set when posting/appending recipients. - recipients: new IdentifierMap(new Map()), - // TODO: fill this - postBy: ProfileIdentifier.unknown, - // TODO: fill this - // url, - }) - }, - hasLocalKeyOf: hasLocalKeyOf, - decryptByLocalKey: decryptByLocalKey, - async deriveAESKey(pub) { - return Array.from((await deriveAESByECDH(pub)).values()) - }, - async queryAuthorPublicKey(author, signal) { - // TODO: should use decrypt hint as the value - if (!author) return null - const persona = await queryPersonaByProfileDB(author) - if (!persona) return null - return (await crypto.subtle.importKey( - 'jwk', - persona.publicKey, - { name: 'ECDH', namedCurve: persona.publicKey.crv! } as EcKeyAlgorithm, - false, - ['deriveKey'], - )) as EC_Public_CryptoKey - }, - async *queryPostKey_version37() { - throw 'TODO' - }, - async *queryPostKey_version38() { - throw 'TODO' - }, - async *queryPostKey_version39() { - throw 'TODO' - }, - async queryPostKey_version40() { - throw 'TODO' - }, - }, - ) - - yield* progress - return null -} -async function getPostKeyCache(id: PostIVIdentifier) { - const post = await queryPostDB(id) - if (!post?.postCryptoKey) return null - const k = await crypto.subtle.importKey('jwk', post.postCryptoKey, { name: 'AES-GCM', length: 256 }, false, [ - 'decrypt', - ]) - return k as AESCryptoKey -} +export { type DecryptionInfo, type DecryptionProgress, decryption } from './decryption' diff --git a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts index 04b0ecd6a29d..b3489a7670b5 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts @@ -133,7 +133,7 @@ async function* decryptFromPayloadWithProgress_raw( if (post.version !== -38) break latest // Let's try the new infra! const { DecryptProgressKind } = await import('@masknet/encryption') - for await (const progress of decryption(constructAlpha38(post), authorNetworkHint)) { + for await (const progress of decryption(constructAlpha38(post), authorNetworkHint, whoAmI)) { if (progress.type === DecryptProgressKind.Success) { console.log('Decrypted by the new infra!', progress.content) } diff --git a/packages/mask/src/network/gun/version.2/gun2.ts b/packages/mask/src/network/gun/version.2/gun2.ts index 532ed47c9c4d..8307ccb168dd 100644 --- a/packages/mask/src/network/gun/version.2/gun2.ts +++ b/packages/mask/src/network/gun/version.2/gun2.ts @@ -1,5 +1,5 @@ import Gun from 'gun/gun' -import { gunServers } from '../../gun-servers' +import { gunServers } from '../../../../background/network/gun' export type PersonOnGun2 = { /** @deprecated if you want to use it, cast it to string. */ From 532e3a7d2d1fd0197078215e8487d135564bfb45 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Fri, 17 Dec 2021 16:55:57 +0800 Subject: [PATCH 06/85] chore: implement more gun related data --- .../network/gun/encryption/queryPostKey.ts | 136 +++++++++++++++++- packages/mask/background/network/gun/utils.ts | 4 +- .../background/services/crypto/decryption.ts | 24 +++- 3 files changed, 154 insertions(+), 10 deletions(-) diff --git a/packages/mask/background/network/gun/encryption/queryPostKey.ts b/packages/mask/background/network/gun/encryption/queryPostKey.ts index 9aeb9cc892c3..198258249777 100644 --- a/packages/mask/background/network/gun/encryption/queryPostKey.ts +++ b/packages/mask/background/network/gun/encryption/queryPostKey.ts @@ -1,11 +1,14 @@ import { decodeArrayBuffer, encodeArrayBuffer } from '@dimensiondev/kit' import type { DecryptStaticECDH_PostKey } from '@masknet/encryption' -import type { ProfileIdentifier } from '@masknet/shared-base' +import type { EC_Public_CryptoKey, EC_Public_JsonWebKey, ProfileIdentifier } from '@masknet/shared-base' import type { default as gun } from 'gun' -import { getGunData } from '../utils' +import { CryptoKeyToJsonWebKey } from '../../../../utils-pure' +import { getGunData, subscribeGunMapData } from '../utils' +import { EventIterator } from 'event-iterator' +import { noop } from 'lodash-unified' type Gun = ReturnType -// !!! Change how this function access Gun will break the compatibility of v40 payload decryption. +// !!! Change how this file access Gun will break the compatibility of v40 payload decryption. export async function GUN_queryPostKey_version40( gun: Gun, iv: Uint8Array, @@ -28,3 +31,130 @@ export async function GUN_queryPostKey_version40( return true } } + +namespace Version38Or39 { + export async function* GUN_queryPostKey_version39Or38( + gun: Gun, + version: -38 | -39, + iv: Uint8Array, + minePublicKey: EC_Public_CryptoKey, + networkHint: string, + abortSignal: AbortSignal, + ): AsyncGenerator { + const minePublicKeyJWK = await CryptoKeyToJsonWebKey(minePublicKey) + const { keyHash, postHash } = await calculatePostKeyPartition(version, networkHint, iv, minePublicKeyJWK) + + // ? In this step we get something like ["jzarhbyjtexiE7aB1DvQ", "jzarhuse6xlTAtblKRx9"] + const internalNodeNames = Object.keys((await getGunData(gun, postHash, keyHash)) || {}).filter((x) => x !== '_') + // ? In this step we get all keys in this category (gun2[postHash][keyHash]) + const resultPromise = internalNodeNames.map((key) => getGunData(gun, key)) + + const iter = new EventIterator((queue) => { + for (const result of resultPromise) result.then(emit, noop) + + async function main() { + const iter = subscribeGunMapData(gun, [postHash], isValidData, abortSignal) + for await (const data of iter) emit(data) + } + + main() + + function emit(result: any) { + if (abortSignal.aborted) return + if (!isValidData(result)) return + queue.push({ + encryptedPostKey: new Uint8Array(decodeArrayBuffer(result.encryptedKey)), + postKeyIV: new Uint8Array(decodeArrayBuffer(result.salt)), + }) + } + }) + yield* iter + } + + type DataOnGun = { + encryptedKey: string + salt: string + } + + function isValidData(data: unknown): data is DataOnGun { + if (!data) return false + if (typeof data !== 'object') return false + const { encryptedKey, salt } = data as DataOnGun + if (typeof encryptedKey !== 'string') return false + if (typeof salt !== 'string') return false + return true + } + + async function calculatePostKeyPartition( + version: -38 | -39, + networkHint: string, + iv: Uint8Array, + key: EC_Public_JsonWebKey, + ) { + const postHash = await hashIV(networkHint, iv) + // In version > -39, we will use stable hash to prevent unstable result for key hashing + + const keyHash = version === -39 ? await hashKey39(key) : await hashKey38(key) + return { postHash, keyHash } + } + + async function hashIV(networkHint: string, iv: Uint8Array) { + const hashPair = `9283464d-ee4e-4e8d-a7f3-cf392a88133f` + const N = 2 + + const hash = await work(encodeArrayBuffer(iv), hashPair) + return networkHint + hash.substring(0, N) + } + + // The difference between V38 and V39 is: V39 is not stable (JSON.stringify) + // it's an implementaton bug but for backward compatibility, it cannot be changed. + // Therefore we upgraded the version and use a stable hash. + async function hashKey39(key: EC_Public_JsonWebKey) { + const hashPair = `10198a2f-205f-45a6-9987-3488c80113d0` + const N = 2 + + const jwk = JSON.stringify(key) + const hash = await work(jwk, hashPair) + return hash.substring(0, N) + } + + async function hashKey38(jwk: EC_Public_JsonWebKey) { + const hashPair = `10198a2f-205f-45a6-9987-3488c80113d0` + const N = 2 + + const hash = await work(jwk.x! + jwk.y!, hashPair) + return hash.substring(0, N) + } + + // This is a self contained Gun.SEA.work implementaton that only contains code path we used. + async function work(data: string, salt: string) { + const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(data), { name: 'PBKDF2' }, false, [ + 'deriveBits', + ]) + const work = await crypto.subtle.deriveBits( + { + name: 'PBKDF2', + iterations: 100000, + salt: new TextEncoder().encode(salt), + hash: { name: 'SHA-256' }, + }, + key, + 512, + ) + const result = arrayBufferToBase64(work) + return result + + function arrayBufferToBase64(buffer: ArrayBuffer) { + let binary = '' + const bytes = new Uint8Array(buffer) + const len = bytes.byteLength + // eslint-disable-next-line no-plusplus + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]) + } + return window.btoa(binary) + } + } +} + +export const { GUN_queryPostKey_version39Or38 } = Version38Or39 diff --git a/packages/mask/background/network/gun/utils.ts b/packages/mask/background/network/gun/utils.ts index 979694150b9d..bd8bcc3815a0 100644 --- a/packages/mask/background/network/gun/utils.ts +++ b/packages/mask/background/network/gun/utils.ts @@ -12,7 +12,7 @@ export function getGunData(gun: GunRoot, ...path: string[]) { }) } -export async function* subscribeGunData( +export async function* subscribeGunMapData( gun: GunRoot, path: string[], isT: (x: unknown) => x is T, @@ -29,7 +29,7 @@ export async function* subscribeGunData( const resultNode = path.reduce((gun, path) => gun.get(path as never), gun) - resultNode.on((data) => { + resultNode.map().on((data) => { if (listenerClosed) return if (isT(data)) queue.push(data) }) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index a4373c18502a..bd99c1d0ece1 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -17,7 +17,7 @@ import { queryPersonaByProfileDB } from '../../database/persona/db' import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../database/persona/helper' import { queryPostDB } from '../../database/post' import { savePostKeyToDB } from '../../database/post/helper' -import { GUN_queryPostKey_version40 } from '../../network/gun/encryption/queryPostKey' +import { GUN_queryPostKey_version39Or38, GUN_queryPostKey_version40 } from '../../network/gun/encryption/queryPostKey' import type { GunRoot } from '../../network/gun/utils' export type DecryptionInfo = { @@ -90,11 +90,25 @@ export async function* decryption(payload: string | Uint8Array, network: string, async *queryPostKey_version37() { throw 'TODO' }, - async *queryPostKey_version38() { - throw 'TODO' + async *queryPostKey_version38(iv, signal) { + yield* GUN_queryPostKey_version39Or38( + {} as GunRoot, + -38, + iv, + {} as any, + '', + signal || new AbortController().signal, + ) }, - async *queryPostKey_version39() { - throw 'TODO' + async *queryPostKey_version39(iv, signal) { + yield* GUN_queryPostKey_version39Or38( + {} as GunRoot, + -39, + iv, + {} as any, + '', + signal || new AbortController().signal, + ) }, async queryPostKey_version40(iv) { if (!whoAmIHint) return null From fc07bd4fb2e7bbf514c69892997ba3ab99eb1701 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Mon, 20 Dec 2021 10:24:05 +0800 Subject: [PATCH 07/85] fix: typo --- cspell.json | 3 ++- .../mask/background/network/gun/encryption/queryPostKey.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cspell.json b/cspell.json index 3b35105fa48d..88e6c4bcf215 100644 --- a/cspell.json +++ b/cspell.json @@ -243,7 +243,8 @@ "solana", "Gรถrli", "resizer", - "Pageable" + "Pageable", + "plusplus" ], "ignoreWords": [ "cryptopunks", diff --git a/packages/mask/background/network/gun/encryption/queryPostKey.ts b/packages/mask/background/network/gun/encryption/queryPostKey.ts index 198258249777..c9702378ccc3 100644 --- a/packages/mask/background/network/gun/encryption/queryPostKey.ts +++ b/packages/mask/background/network/gun/encryption/queryPostKey.ts @@ -44,6 +44,7 @@ namespace Version38Or39 { const minePublicKeyJWK = await CryptoKeyToJsonWebKey(minePublicKey) const { keyHash, postHash } = await calculatePostKeyPartition(version, networkHint, iv, minePublicKeyJWK) + /* cspell:disable-next-line */ // ? In this step we get something like ["jzarhbyjtexiE7aB1DvQ", "jzarhuse6xlTAtblKRx9"] const internalNodeNames = Object.keys((await getGunData(gun, postHash, keyHash)) || {}).filter((x) => x !== '_') // ? In this step we get all keys in this category (gun2[postHash][keyHash]) @@ -107,7 +108,7 @@ namespace Version38Or39 { } // The difference between V38 and V39 is: V39 is not stable (JSON.stringify) - // it's an implementaton bug but for backward compatibility, it cannot be changed. + // it's an implementation bug but for backward compatibility, it cannot be changed. // Therefore we upgraded the version and use a stable hash. async function hashKey39(key: EC_Public_JsonWebKey) { const hashPair = `10198a2f-205f-45a6-9987-3488c80113d0` @@ -126,7 +127,7 @@ namespace Version38Or39 { return hash.substring(0, N) } - // This is a self contained Gun.SEA.work implementaton that only contains code path we used. + // This is a self contained Gun.SEA.work implementation that only contains code path we used. async function work(data: string, salt: string) { const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(data), { name: 'PBKDF2' }, false, [ 'deriveBits', From 8cb5e09d2f8a84ffb292b20c3de2469f7fa089a9 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Mon, 20 Dec 2021 11:04:55 +0800 Subject: [PATCH 08/85] chore: add more logic --- packages/encryption/src/index.ts | 1 + packages/encryption/src/payload/types.ts | 9 +++ .../background/database/persona/helper.ts | 5 +- .../background/services/crypto/decryption.ts | 56 ++++++++++++++----- .../CryptoServices/decryptFrom.ts | 13 +---- 5 files changed, 54 insertions(+), 30 deletions(-) diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index d424133cb760..e2759d0885c7 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -4,6 +4,7 @@ export { AESAlgorithmEnum, PublicKeyAlgorithmEnum, SocialNetworkEnum, + SocialNetworkEnumToProfileDomain, type AESKey, type AsymmetryCryptoKey, type Signature, diff --git a/packages/encryption/src/payload/types.ts b/packages/encryption/src/payload/types.ts index 70995ee92bf7..dc09dcbc78bb 100644 --- a/packages/encryption/src/payload/types.ts +++ b/packages/encryption/src/payload/types.ts @@ -121,3 +121,12 @@ export enum SocialNetworkEnum { Instagram = 2, Minds = 3, } +const SocialNetworkEnumToDomain: Record = { + [SocialNetworkEnum.Facebook]: 'facebook.com', + [SocialNetworkEnum.Minds]: 'minds.com', + [SocialNetworkEnum.Twitter]: 'twitter.com', + [SocialNetworkEnum.Instagram]: 'instagram.com', +} +export function SocialNetworkEnumToProfileDomain(x: SocialNetworkEnum) { + return SocialNetworkEnumToDomain[x] +} diff --git a/packages/mask/background/database/persona/helper.ts b/packages/mask/background/database/persona/helper.ts index 66a6da08d009..33bf1b8b6b58 100644 --- a/packages/mask/background/database/persona/helper.ts +++ b/packages/mask/background/database/persona/helper.ts @@ -15,12 +15,11 @@ import { createReadonlyPersonaTransaction, PersonaDB } from './db' * If has local key of a profile in the database. * @param id Profile Identifier */ -export async function hasLocalKeyOf( +export function hasLocalKeyOf( id: ProfileIdentifier, tx?: IDBPSafeTransaction, ) { - const result = await getLocalKeyOf(id, tx) - return !!result + return getLocalKeyOf(id, tx).then(Boolean) } /** diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index bd99c1d0ece1..82afc430b881 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -5,6 +5,8 @@ import { DecryptProgressKind, PublicKeyAlgorithmEnum, DecryptProgress, + SocialNetworkEnum, + SocialNetworkEnumToProfileDomain, } from '@masknet/encryption' import { AESCryptoKey, @@ -25,14 +27,29 @@ export type DecryptionInfo = { iv?: Uint8Array } export type DecryptionProgress = DecryptProgress | DecryptionInfo -export async function* decryption(payload: string | Uint8Array, network: string, whoAmIHint: ProfileIdentifier | null) { - if (whoAmIHint?.isUnknown) whoAmIHint = null - // TODO: read in-memory cache to avoid db lookup + +export interface DecryptionContext { + currentSocialNetwork: SocialNetworkEnum + currentProfile: ProfileIdentifier | null + authorHint: ProfileIdentifier | null + postURL: string | undefined +} +export type SocialNetworkEncodedPayload = + | { type: 'text'; text: string } + | { type: 'image'; text: string } + | { type: 'text'; text: string } + +export async function* decryptionWithSocialNetworkDecoding(context: DecryptionContext) {} +export async function* decryption(payload: string | Uint8Array, context: DecryptionContext) { + const { currentSocialNetwork, postURL } = context + let { currentProfile, authorHint } = context + if (currentProfile?.isUnknown) currentProfile = null + if (authorHint?.isUnknown) authorHint = null const parse = await parsePayload(payload) if (parse.err) return null - //#region post IV + //#region Identify the PostIdentifier const iv = parse.val.encryption.unwrapOr(null)?.iv.unwrapOr(null) { if (!iv) return null @@ -40,16 +57,24 @@ export async function* decryption(payload: string | Uint8Array, network: string, const info: DecryptionInfo = { type: DecryptProgressKind.Info, iv } yield info } - const id = new PostIVIdentifier(network, encodeArrayBuffer(iv.buffer)) + const id = new PostIVIdentifier( + SocialNetworkEnumToProfileDomain(currentSocialNetwork), + encodeArrayBuffer(iv.buffer), + ) //#endregion + // TODO: read in-memory cache to avoid db lookup + //#region Store author public key try { const author = parse.unwrap().author.unwrap().unwrap() const authorPub = parse.unwrap().authorPublicKey.unwrap().unwrap() - // TODO: should only store public key when author equals to the decryption hint for security reason. - - if (authorPub.algr !== PublicKeyAlgorithmEnum.secp256k1) throw new Error('TODO: support other curves') + if (author.equals(authorHint)) { + // TODO: store the public key + if (authorPub.algr !== PublicKeyAlgorithmEnum.secp256k1) throw new Error('TODO: support other curves') + } else { + // ! Author detected is not equal to AuthorHint. Skip store the public key because it might be a security problem. + } } catch {} //#endregion @@ -62,10 +87,11 @@ export async function* decryption(payload: string | Uint8Array, network: string, // public post will not call this function. // and recipients only will be set when posting/appending recipients. recipients: new IdentifierMap(new Map()), - // TODO: fill this - postBy: ProfileIdentifier.unknown, - // TODO: fill this - // url, + postBy: + authorHint || + parse.safeUnwrap().author.unwrapOr(null)?.unwrapOr(null) || + ProfileIdentifier.unknown, + url: postURL, }) }, hasLocalKeyOf: hasLocalKeyOf, @@ -74,7 +100,7 @@ export async function* decryption(payload: string | Uint8Array, network: string, return Array.from((await deriveAESByECDH(pub)).values()) }, async queryAuthorPublicKey(author, signal) { - // TODO: should use decrypt hint as the value + author ||= authorHint if (!author) return null const persona = await queryPersonaByProfileDB(author) if (!persona) return null @@ -111,8 +137,8 @@ export async function* decryption(payload: string | Uint8Array, network: string, ) }, async queryPostKey_version40(iv) { - if (!whoAmIHint) return null - return GUN_queryPostKey_version40({} as GunRoot, iv, whoAmIHint) + if (!currentProfile) return null + return GUN_queryPostKey_version40({} as GunRoot, iv, currentProfile) }, }, ) diff --git a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts index b3489a7670b5..2209e58010c1 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts @@ -2,7 +2,7 @@ import * as Alpha40 from '../../../crypto/crypto-alpha-40' import * as Alpha39 from '../../../crypto/crypto-alpha-39' import { GunAPI as Gun2, GunAPISubscribe as Gun2Subscribe, GunWorker } from '../../../network/gun/' import { decodeText } from '@dimensiondev/kit' -import { constructAlpha38, deconstructPayload } from '../../../utils/type-transform/Payload' +import { deconstructPayload } from '../../../utils/type-transform/Payload' import { i18n } from '../../../../shared-ui/locales_legacy' import { queryPersonaRecord, queryLocalKey } from '../../../database' import { ProfileIdentifier, PostIVIdentifier, delay } from '@masknet/shared-base' @@ -22,7 +22,6 @@ import { MaskMessages } from '../../../utils/messages' import { GunAPI } from '../../../network/gun' import { Err, Ok, Result } from 'ts-results' import { decodeTextPayloadWorker } from '../../../social-network/utils/text-payload-worker' -import { decryption } from '../../../../background/services/crypto' type Progress = ( | { progress: 'finding_person_public_key' | 'finding_post_key' | 'init' | 'decode_post' } @@ -129,16 +128,6 @@ async function* decryptFromPayloadWithProgress_raw( whoAmI: ProfileIdentifier, discoverURL: string | undefined, ): ReturnOfDecryptPostContentWithProgress { - latest: if (process.env.NODE_ENV === 'development') { - if (post.version !== -38) break latest - // Let's try the new infra! - const { DecryptProgressKind } = await import('@masknet/encryption') - for await (const progress of decryption(constructAlpha38(post), authorNetworkHint, whoAmI)) { - if (progress.type === DecryptProgressKind.Success) { - console.log('Decrypted by the new infra!', progress.content) - } - } - } const cacheKey = stringify(post) if (successDecryptionCache.has(cacheKey)) return successDecryptionCache.get(cacheKey)! yield makeProgress('init') From dd5345853dd6dcb70ddd26dc66e3be6d2e13f4db Mon Sep 17 00:00:00 2001 From: Jack Works Date: Mon, 20 Dec 2021 11:44:01 +0800 Subject: [PATCH 09/85] refactor: move twitter encoding into encryption package --- packages/encryption/README.md | 14 ++-- packages/encryption/src/index.ts | 2 + .../src/social-network-encode-decode/index.ts | 1 + .../social-network-encode-decode/twitter.ts | 52 ++++++++++++++ .../src/social-network-encode-decode/types.ts | 1 + .../twitter-encode-decode.ts.snap | 3 + .../encryption/tests/twitter-encode-decode.ts | 8 +++ .../background/services/crypto/decryption.ts | 12 +++- .../twitter.com/encoding.ts | 68 ++++++++----------- packages/mask/src/utils/utils.ts | 50 -------------- packages/shared-base/src/utils/parseURL.ts | 4 +- 11 files changed, 116 insertions(+), 99 deletions(-) create mode 100644 packages/encryption/src/social-network-encode-decode/index.ts create mode 100644 packages/encryption/src/social-network-encode-decode/twitter.ts create mode 100644 packages/encryption/src/social-network-encode-decode/types.ts create mode 100644 packages/encryption/tests/__snapshots__/twitter-encode-decode.ts.snap create mode 100644 packages/encryption/tests/twitter-encode-decode.ts diff --git a/packages/encryption/README.md b/packages/encryption/README.md index 65806ce5afb9..ec4864a1bca0 100644 --- a/packages/encryption/README.md +++ b/packages/encryption/README.md @@ -4,13 +4,17 @@ This package provides the ability to encrypt/decrypt Mask Network payload. This package is designed with OCap and prepared to run under SES. -## OCap requirements +## Decryption process -- Access to WebCrypto API +``` +raw message (text or image) +|> Social Network decoding (socialNetworkID) +|> Standard Decryption +``` -## Todo +## OCap requirements -- [ ] Add test cases +- Access to WebCrypto API ### Payload parser @@ -27,7 +31,7 @@ This package is designed with OCap and prepared to run under SES. ### Encryption & Decryption - [ ] Encryption -- [ ] Decryption +- [x] Decryption ### Other diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index e2759d0885c7..08b8bd7fa1e4 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -23,3 +23,5 @@ export { type DecryptProgress, type DecryptSuccess, } from './encryption' + +export { TwitterDecoder, __TwitterEncoder } from './social-network-encode-decode' diff --git a/packages/encryption/src/social-network-encode-decode/index.ts b/packages/encryption/src/social-network-encode-decode/index.ts new file mode 100644 index 000000000000..5bb68b37465c --- /dev/null +++ b/packages/encryption/src/social-network-encode-decode/index.ts @@ -0,0 +1 @@ +export * from './twitter' diff --git a/packages/encryption/src/social-network-encode-decode/twitter.ts b/packages/encryption/src/social-network-encode-decode/twitter.ts new file mode 100644 index 000000000000..7d02af6e3a2e --- /dev/null +++ b/packages/encryption/src/social-network-encode-decode/twitter.ts @@ -0,0 +1,52 @@ +import { parseURL } from '@masknet/shared-base' +import { Some, None, Option } from 'ts-results' + +/** + * @link https://github.com/DimensionDev/Maskbook/issues/198 + */ +export function __TwitterEncoder(text: string) { + return `https://mask.io/?PostData_v1=${batchReplace(text, [ + ['๐ŸŽผ', '%20'], + [':||', '%40'], + ['+', '-'], + ['=', '_'], + [/\|/g, '.'], + ])}` +} +export function TwitterDecoder(raw: string): Option { + if (!raw) return None + if (!raw.includes('%20') || !raw.includes('%40')) return None + const payloadLink = parseURL(raw) + .map((x) => x.replace(/โ€ฆ$/, '')) + .filter((x) => x.endsWith('%40'))[0] + try { + const { search, pathname } = new URL(payloadLink) + const payload = search ? search.slice(1) : pathname.slice(1) + if (!payload) return None + return Some( + `๐ŸŽผ${batchReplace( + payload + // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1476 + // eslint-disable-next-line unicorn/better-regex + .replace(/^PostData_v\d=/i, '') + .replace(/^%20/, '') + .replace(/%40$/, ''), + [ + ['-', '+'], + ['_', '='], + [/\./g, '|'], + ], + )}:||`, + ) + } catch { + return None + } +} + +function batchReplace(source: string, group: Array<[string | RegExp, string]>) { + let storage = source + for (const v of group) { + storage = storage.replace(v[0], v[1]) + } + return storage +} diff --git a/packages/encryption/src/social-network-encode-decode/types.ts b/packages/encryption/src/social-network-encode-decode/types.ts new file mode 100644 index 000000000000..80ada4a1c043 --- /dev/null +++ b/packages/encryption/src/social-network-encode-decode/types.ts @@ -0,0 +1 @@ +export interface SocialNetworkEncoder {} diff --git a/packages/encryption/tests/__snapshots__/twitter-encode-decode.ts.snap b/packages/encryption/tests/__snapshots__/twitter-encode-decode.ts.snap new file mode 100644 index 000000000000..11c6f2fdefce --- /dev/null +++ b/packages/encryption/tests/__snapshots__/twitter-encode-decode.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Can decode Twitter text payload: Twitter example 1`] = `Some("๐ŸŽผ4/4|mFXgbxPDrAD6PYRHWJW5IVlHObuFYs/LUyd08kkUkKimTx4opjHXjrgorrsyTPidL0lkeqdXnW3EzEacdlPJChko+Q5Wy113UMHYy1aV1Wheqwy/vM3PLMVaoKW/HyIL/SjJtim75bWMSHmj+vzMYwV59jLJqlTjJqhz5ObdZhAsWwRJGYIzEkg+|SKlWOvMpyF9T1jL2pr95kA==|ehROC4d3QudYHEABcBTpSuxLvxM=|_|Aq/bVWAKvodJuURGk3enjE1gUiu2SELM8IIKIlNGqOWM|1|dHdpdHRlci5jb20vd2lsbHlvbnRvdXI=:||")`; diff --git a/packages/encryption/tests/twitter-encode-decode.ts b/packages/encryption/tests/twitter-encode-decode.ts new file mode 100644 index 000000000000..a9cd882ad2e8 --- /dev/null +++ b/packages/encryption/tests/twitter-encode-decode.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@jest/globals' +import { TwitterDecoder } from '../src/social-network-encode-decode' +test('Can decode Twitter text payload', () => { + const payload = `This tweet is encrypted with #mask_io (@realMaskNetwork). ๐Ÿ“ช๐Ÿ”‘ + + Install https://mask.io/?PostData_v1=%204/4.mFXgbxPDrAD6PYRHWJW5IVlHObuFYs/LUyd08kkUkKimTx4opjHXjrgorrsyTPidL0lkeqdXnW3EzEacdlPJChko-Q5Wy113UMHYy1aV1Wheqwy/vM3PLMVaoKW/HyIL/SjJtim75bWMSHmj+vzMYwV59jLJqlTjJqhz5ObdZhAsWwRJGYIzEkg+.SKlWOvMpyF9T1jL2pr95kA_=.ehROC4d3QudYHEABcBTpSuxLvxM=._.Aq/bVWAKvodJuURGk3enjE1gUiu2SELM8IIKIlNGqOWM.1.dHdpdHRlci5jb20vd2lsbHlvbnRvdXI=%40 to decrypt it.` + expect(TwitterDecoder(payload)).toMatchSnapshot('Twitter example') +}) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 82afc430b881..fa166da10afd 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -36,10 +36,16 @@ export interface DecryptionContext { } export type SocialNetworkEncodedPayload = | { type: 'text'; text: string } - | { type: 'image'; text: string } - | { type: 'text'; text: string } + | { type: 'image'; image: Uint8Array } + | { type: 'image-url'; url: string } -export async function* decryptionWithSocialNetworkDecoding(context: DecryptionContext) {} +export async function* decryptionWithSocialNetworkDecoding( + encoded: SocialNetworkEncodedPayload, + context: DecryptionContext, +) { + let decoded!: string | Uint8Array + return yield* decryption(decoded, context) +} export async function* decryption(payload: string | Uint8Array, context: DecryptionContext) { const { currentSocialNetwork, postURL } = context let { currentProfile, authorHint } = context diff --git a/packages/mask/src/social-network-adaptor/twitter.com/encoding.ts b/packages/mask/src/social-network-adaptor/twitter.com/encoding.ts index c91dce2efe79..672d4cc2879e 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/encoding.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/encoding.ts @@ -1,5 +1,4 @@ -import { batchReplace, regexMatchAll } from '../../utils/utils' -import { parseURL } from '@masknet/shared-base' +import { TwitterDecoder, __TwitterEncoder } from '@masknet/encryption' import { isNull } from 'lodash-unified' const ICAO9303Checksum = { encode: (input: string) => { @@ -45,44 +44,33 @@ export const twitterEncoding = { } return [] }, - /** - * @link https://github.com/DimensionDev/Maskbook/issues/198 - */ - payloadEncoder: (text: string) => - `https://mask.io/?PostData_v1=${batchReplace(text, [ - ['๐ŸŽผ', '%20'], - [':||', '%40'], - ['+', '-'], - ['=', '_'], - [/\|/g, '.'], - ])}`, + payloadEncoder: __TwitterEncoder, payloadDecoder: (text: string): string[] => { - if (!text) return [] - if (!text.includes('%20') || !text.includes('%40')) return [] - const payloadLink = parseURL(text) - .map((x) => x.replace(/โ€ฆ$/, '')) - .filter((x) => x.endsWith('%40'))[0] - try { - const { search, pathname } = new URL(payloadLink) - const payload = search ? search.slice(1) : pathname.slice(1) - if (!payload) return [] - return [ - `๐ŸŽผ${batchReplace( - payload - // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1476 - // eslint-disable-next-line unicorn/better-regex - .replace(/^PostData_v\d=/i, '') - .replace(/^%20/, '') - .replace(/%40$/, ''), - [ - ['-', '+'], - ['_', '='], - [/\./g, '|'], - ], - )}:||`, - ] - } catch { - return [] - } + return TwitterDecoder(text) + .map((x) => [x]) + .unwrapOr([]) }, } + +function regexMatchAll(str: string, regexp: RegExp, index: number = 1) { + const gPos = regexp.flags.indexOf('g') + const withoutG = gPos >= 0 ? `${regexp.flags.slice(0, gPos)}${regexp.flags.slice(gPos + 1)}` : regexp.flags + const o = new RegExp(regexp.source, withoutG) + const g = new RegExp(regexp.source, `${withoutG}g`) + const r = str.match(g) + if (isNull(r)) { + return null + } + const sto = [] + for (const v of r) { + const retV = v.match(o) + if (isNull(retV)) { + continue + } + sto.push(retV[index]) + } + if (sto.length === 0) { + return null + } + return sto +} diff --git a/packages/mask/src/utils/utils.ts b/packages/mask/src/utils/utils.ts index 630207b260c0..1a348748199d 100644 --- a/packages/mask/src/utils/utils.ts +++ b/packages/mask/src/utils/utils.ts @@ -54,53 +54,3 @@ export function regexMatch(str: string, regexp: RegExp, index: number | null = 1 } return r[index] as string as any } - -/** - * enables you to use match group with flag g - * - * @return - * if no matches, return null; - * return target match group in each matches; - * - * @example - * regexMatchAll(">targettarget2<", />(.+)>> ["target", "target2"] - */ -export function regexMatchAll(str: string, regexp: RegExp, index: number = 1) { - const gPos = regexp.flags.indexOf('g') - const withoutG = gPos >= 0 ? `${regexp.flags.slice(0, gPos)}${regexp.flags.slice(gPos + 1)}` : regexp.flags - const o = new RegExp(regexp.source, withoutG) - const g = new RegExp(regexp.source, `${withoutG}g`) - const r = str.match(g) - if (isNull(r)) { - return null - } - const sto = [] - for (const v of r) { - const retV = v.match(o) - if (isNull(retV)) { - continue - } - sto.push(retV[index]) - } - if (sto.length === 0) { - return null - } - return sto -} - -/** - * batch run string.replace - * @param source the source string to replace - * @param group Array of find-replace pair, - * each pair same as the param of - * string.replace - * @return result string - */ -export function batchReplace(source: string, group: Array<[string | RegExp, string]>) { - let storage = source - for (const v of group) { - storage = storage.replace(v[0], v[1]) - } - return storage -} diff --git a/packages/shared-base/src/utils/parseURL.ts b/packages/shared-base/src/utils/parseURL.ts index 8d8bae97e548..25f9438b984d 100644 --- a/packages/shared-base/src/utils/parseURL.ts +++ b/packages/shared-base/src/utils/parseURL.ts @@ -1,6 +1,8 @@ import parser from 'anchorme' export function parseURL(text: string) { - return parser + const lib = (parser as any as { default: typeof parser }).default || parser + // CJS-ESM compatibility + return lib .list(text) .map((x) => x.string) .filter((y) => { From d962d79330f9e673b21c91e5c2f344d1ac94284c Mon Sep 17 00:00:00 2001 From: Jack Works Date: Mon, 20 Dec 2021 18:10:33 +0800 Subject: [PATCH 10/85] refactor: move stego to encryption package --- packages/encryption/package.json | 1 + .../src/image-steganography/index.ts} | 67 +++++++++--------- .../masks}/mask-transparent.png | Bin .../image-steganography/masks}/mask-v1.png | Bin .../image-steganography/masks}/mask-v2.png | Bin .../image-steganography/masks}/mask-v4.png | Bin .../src/image-steganography/utils.ts} | 3 +- packages/encryption/src/index.ts | 9 +++ packages/mask/package.json | 1 - .../components/CompositionDialog/useSubmit.ts | 2 +- .../SteganographyTextPayload.ts | 4 +- .../background-script/CryptoService.ts | 7 +- .../CryptoServices/decryptFrom.ts | 4 +- .../background-script/CryptoServices/utils.ts | 7 ++ .../mask/src/resources/image-payload/index.ts | 3 +- .../facebook.com/ui-provider.ts | 2 +- packages/mask/src/social-network/types.ts | 2 +- packages/mask/src/utils/index.ts | 1 - .../scripts/src/codegen/resource-files.ts | 5 +- pnpm-lock.yaml | 26 ++++++- 20 files changed, 96 insertions(+), 48 deletions(-) rename packages/{mask/src/extension/background-script/CryptoServices/Steganography.ts => encryption/src/image-steganography/index.ts} (53%) rename packages/{mask/src/extension/background-script/CryptoServices/SteganographyResources => encryption/src/image-steganography/masks}/mask-transparent.png (100%) rename packages/{mask/src/extension/background-script/CryptoServices/SteganographyResources => encryption/src/image-steganography/masks}/mask-v1.png (100%) rename packages/{mask/src/extension/background-script/CryptoServices/SteganographyResources => encryption/src/image-steganography/masks}/mask-v2.png (100%) rename packages/{mask/src/extension/background-script/CryptoServices/SteganographyResources => encryption/src/image-steganography/masks}/mask-v4.png (100%) rename packages/{mask/src/utils/image.ts => encryption/src/image-steganography/utils.ts} (96%) create mode 100644 packages/mask/src/extension/background-script/CryptoServices/utils.ts diff --git a/packages/encryption/package.json b/packages/encryption/package.json index 4d35d498f2c3..ab0835efd498 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -5,6 +5,7 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "dependencies": { + "@dimensiondev/stego-js": "0.11.1-20201027083223-8ab41be", "@masknet/shared-base": "workspace:*", "@msgpack/msgpack": "^2.7.0" } diff --git a/packages/mask/src/extension/background-script/CryptoServices/Steganography.ts b/packages/encryption/src/image-steganography/index.ts similarity index 53% rename from packages/mask/src/extension/background-script/CryptoServices/Steganography.ts rename to packages/encryption/src/image-steganography/index.ts index 792c19166c44..24d796f19516 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/Steganography.ts +++ b/packages/encryption/src/image-steganography/index.ts @@ -1,58 +1,56 @@ +import { GrayscaleAlgorithm } from '@dimensiondev/stego-js/cjs/grayscale.js' +import { TransformAlgorithm } from '@dimensiondev/stego-js/cjs/transform.js' +import { encode, decode } from '@dimensiondev/stego-js/cjs/dom.js' +import type { EncodeOptions } from '@dimensiondev/stego-js/cjs/stego.js' import { omit } from 'lodash-unified' -import { blobToArrayBuffer } from '@dimensiondev/kit' -import { encode, decode } from '@dimensiondev/stego-js/cjs/dom' -import { GrayscaleAlgorithm } from '@dimensiondev/stego-js/cjs/grayscale' -import { TransformAlgorithm } from '@dimensiondev/stego-js/cjs/transform' -import type { EncodeOptions, DecodeOptions } from '@dimensiondev/stego-js/cjs/stego' -import { downloadUrl } from '../../../utils/utils' -import { memoizePromise } from '../../../../utils-pure' -import { getDimension } from '../../../utils/image' -import type { ImageTemplateTypes } from '../../../resources/image-payload' +import { getDimension } from './utils' -type Dimension = { +export { GrayscaleAlgorithm } from '@dimensiondev/stego-js/cjs/grayscale.js' +export type ImageTemplateTypes = 'v2' | 'eth' + +interface Dimension { width: number height: number } - -const dimensionPreset: (Dimension & { +interface Preset extends Dimension { mask: string + deprecated?: string template?: ImageTemplateTypes options?: Partial -})[] = [ - // @deprecated legacy post +} +const dimensionPreset: Preset[] = [ { + deprecated: 'legacy post', width: 1024, height: 1240, - mask: new URL('./SteganographyResources/mask-v1.png', import.meta.url).toString(), + mask: new URL('./masks/mask-v1.png', import.meta.url).toString(), }, - // text { width: 1200, height: 681, template: 'v2', - mask: new URL('./SteganographyResources/mask-v2.png', import.meta.url).toString(), + mask: new URL('./masks/mask-v2.png', import.meta.url).toString(), }, - // lucky drop { width: 1200, height: 680, template: 'eth', - mask: new URL('./SteganographyResources/mask-transparent.png', import.meta.url).toString(), + mask: new URL('./masks/mask-transparent.png', import.meta.url).toString(), options: { cropEdgePixels: true, }, }, - // @deprecated election 2020 { + deprecated: 'event election 2020', width: 1000, height: 558, - mask: new URL('./SteganographyResources/mask-transparent.png', import.meta.url).toString(), + mask: new URL('./masks/mask-transparent.png', import.meta.url).toString(), }, - // @deprecated NFT { + deprecated: 'old NFT', width: 1000, height: 560, - mask: new URL('./SteganographyResources/mask-v4.png', import.meta.url).toString(), + mask: new URL('./masks/mask-v4.png', import.meta.url).toString(), }, ] @@ -71,18 +69,21 @@ const defaultOptions = { const isSameDimension = (dimension: Dimension, otherDimension: Dimension) => dimension.width === otherDimension.width && dimension.height === otherDimension.height -const getMaskBuf = memoizePromise(async (url: string) => blobToArrayBuffer(await downloadUrl(url)), void 0) - -export type EncodeImageOptions = { - template?: ImageTemplateTypes -} & PartialRequired, 'text' | 'pass'> +export interface SteganographyIO { + downloadImage: (url: string) => Promise +} +export type EncodeImageOptions = SteganographyIO & + Partial & + Pick & { + template?: ImageTemplateTypes + } export async function steganographyEncodeImage(buf: ArrayBuffer, options: EncodeImageOptions) { - const { template } = options + const { template, downloadImage } = options const preset = dimensionPreset.find((d) => d.template && d.template === template) if (!preset) throw new Error('Failed to find preset.') return new Uint8Array( - await encode(buf, await getMaskBuf(preset.mask), { + await encode(buf, await downloadImage(preset.mask), { ...defaultOptions, ...preset?.options, ...omit(options, 'template'), @@ -90,13 +91,13 @@ export async function steganographyEncodeImage(buf: ArrayBuffer, options: Encode ) } -export type DecodeImageOptions = PartialRequired, 'pass'> +export type DecodeImageOptions = SteganographyIO & Partial & Pick async function steganographyDecodeImage(buf: ArrayBuffer, options: DecodeImageOptions) { const dimension = getDimension(buf) const preset = dimensionPreset.find((d) => isSameDimension(d, dimension)) if (!preset) return '' - return decode(buf, await getMaskBuf(preset.mask), { + return decode(buf, await options.downloadImage(preset.mask), { ...defaultOptions, transformAlgorithm: TransformAlgorithm.FFT1D, ...options, @@ -104,5 +105,5 @@ async function steganographyDecodeImage(buf: ArrayBuffer, options: DecodeImageOp } export async function steganographyDecodeImageUrl(url: string, options: DecodeImageOptions) { - return steganographyDecodeImage(await blobToArrayBuffer(await downloadUrl(url)), options) + return steganographyDecodeImage(await options.downloadImage(url), options) } diff --git a/packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-transparent.png b/packages/encryption/src/image-steganography/masks/mask-transparent.png similarity index 100% rename from packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-transparent.png rename to packages/encryption/src/image-steganography/masks/mask-transparent.png diff --git a/packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-v1.png b/packages/encryption/src/image-steganography/masks/mask-v1.png similarity index 100% rename from packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-v1.png rename to packages/encryption/src/image-steganography/masks/mask-v1.png diff --git a/packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-v2.png b/packages/encryption/src/image-steganography/masks/mask-v2.png similarity index 100% rename from packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-v2.png rename to packages/encryption/src/image-steganography/masks/mask-v2.png diff --git a/packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-v4.png b/packages/encryption/src/image-steganography/masks/mask-v4.png similarity index 100% rename from packages/mask/src/extension/background-script/CryptoServices/SteganographyResources/mask-v4.png rename to packages/encryption/src/image-steganography/masks/mask-v4.png diff --git a/packages/mask/src/utils/image.ts b/packages/encryption/src/image-steganography/utils.ts similarity index 96% rename from packages/mask/src/utils/image.ts rename to packages/encryption/src/image-steganography/utils.ts index 6a3a3099acc4..e056d0589795 100644 --- a/packages/mask/src/utils/image.ts +++ b/packages/encryption/src/image-steganography/utils.ts @@ -1,6 +1,7 @@ /* eslint-disable no-bitwise */ -import { imgType } from '@dimensiondev/stego-js/cjs/helper' +import { imgType } from '@dimensiondev/stego-js/cjs/helper.js' +/** @internal */ export function getDimension(buf: ArrayBuffer) { const fallback = { width: 0, diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index 08b8bd7fa1e4..9dc04c614735 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -25,3 +25,12 @@ export { } from './encryption' export { TwitterDecoder, __TwitterEncoder } from './social-network-encode-decode' +export { + type DecodeImageOptions, + type EncodeImageOptions, + type ImageTemplateTypes, + type SteganographyIO, + steganographyDecodeImageUrl, + steganographyEncodeImage, + GrayscaleAlgorithm, +} from './image-steganography' diff --git a/packages/mask/package.json b/packages/mask/package.json index dff57ae3c59b..d1e604652f83 100644 --- a/packages/mask/package.json +++ b/packages/mask/package.json @@ -10,7 +10,6 @@ "@dimensiondev/mask-wallet-core": "0.1.0-20211013082857-eb62e5f", "@dimensiondev/metamask-extension-provider": "3.0.6-20210519045409-1835d4d", "@dimensiondev/snapshot.js": "0.2.0", - "@dimensiondev/stego-js": "=0.11.1-20201027083223-8ab41be", "@ethersproject/abi": "^5.4.0", "@ethersproject/address": "^5.0.4", "@ethersproject/providers": "^5.0.9", diff --git a/packages/mask/src/components/CompositionDialog/useSubmit.ts b/packages/mask/src/components/CompositionDialog/useSubmit.ts index b6fb958abd91..6f6c7ee6b9ad 100644 --- a/packages/mask/src/components/CompositionDialog/useSubmit.ts +++ b/packages/mask/src/components/CompositionDialog/useSubmit.ts @@ -2,7 +2,7 @@ import { ProfileIdentifier } from '@masknet/shared-base' import { useCallback } from 'react' import Services from '../../extension/service' import { RedPacketMetadataReader } from '../../plugins/RedPacket/SNSAdaptor/helpers' -import type { ImageTemplateTypes } from '../../resources/image-payload' +import type { ImageTemplateTypes } from '@masknet/encryption' import { activatedSocialNetworkUI, globalUIState } from '../../social-network' import { isTwitter } from '../../social-network-adaptor/twitter.com/base' import { useI18N } from '../../utils' diff --git a/packages/mask/src/components/InjectedComponents/SteganographyTextPayload.ts b/packages/mask/src/components/InjectedComponents/SteganographyTextPayload.ts index 0f8885a7343a..b509473db0c8 100644 --- a/packages/mask/src/components/InjectedComponents/SteganographyTextPayload.ts +++ b/packages/mask/src/components/InjectedComponents/SteganographyTextPayload.ts @@ -1,7 +1,7 @@ import { blobToArrayBuffer } from '@dimensiondev/kit' -import { GrayscaleAlgorithm } from '@dimensiondev/stego-js/esm/grayscale' +import { GrayscaleAlgorithm, type ImageTemplateTypes } from '@masknet/encryption' import Services from '../../extension/service' -import { ImageTemplateTypes, ImagePayloadURLs } from '../../resources/image-payload' +import { ImagePayloadURLs } from '../../resources/image-payload' import { activatedSocialNetworkUI } from '../../social-network' import { downloadUrl } from '../../utils/utils' diff --git a/packages/mask/src/extension/background-script/CryptoService.ts b/packages/mask/src/extension/background-script/CryptoService.ts index a8f57605a767..5dbe116ec82d 100644 --- a/packages/mask/src/extension/background-script/CryptoService.ts +++ b/packages/mask/src/extension/background-script/CryptoService.ts @@ -1,4 +1,6 @@ import { assertEnvironment, Environment } from '@dimensiondev/holoflows-kit' +import { steganographyEncodeImage as __steganographyEncodeImage, EncodeImageOptions } from '@masknet/encryption' +import { steganographyDownloadImage } from './CryptoServices/utils' assertEnvironment(Environment.ManifestBackground) export { encryptComment, decryptComment } from '../../crypto/crypto-alpha-40' @@ -6,7 +8,10 @@ export { encryptTo, publishPostAESKey } from './CryptoServices/encryptTo' export { appendShareTarget } from './CryptoServices/appendShareTarget' export { getPartialSharedListOfPost } from './CryptoServices/getPartialSharedListOfPost' export { verifyOthersProve } from './CryptoServices/verifyOthersProve' -export { steganographyEncodeImage } from './CryptoServices/Steganography' + +export function steganographyEncodeImage(buf: ArrayBuffer, options: Omit) { + return __steganographyEncodeImage(buf, { ...options, downloadImage: steganographyDownloadImage }) +} import type { debugShowAllPossibleHashForPost as orig } from './CryptoServices/debugShowAllPossibleHashForPost' // This module requires lazy loading otherwise it will load gun diff --git a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts index 2209e58010c1..a8c2e97d5e4e 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts @@ -14,7 +14,7 @@ import { verifyOthersProve } from './verifyOthersProve' import { publicSharedAESKey } from '../../../crypto/crypto-alpha-38' import { DecryptFailedReason } from '../../../utils/constants' import { asyncIteratorWithResult, memorizeAsyncGenerator } from '../../../utils/type-transform/asyncIteratorHelpers' -import { steganographyDecodeImageUrl } from './Steganography' +import { steganographyDecodeImageUrl } from '@masknet/encryption' import type { TypedMessage, AESJsonWebKey, Payload } from '@masknet/shared-base' import stringify from 'json-stable-stringify' import type { SharedAESKeyGun2 } from '../../../network/gun/version.2' @@ -22,6 +22,7 @@ import { MaskMessages } from '../../../utils/messages' import { GunAPI } from '../../../network/gun' import { Err, Ok, Result } from 'ts-results' import { decodeTextPayloadWorker } from '../../../social-network/utils/text-payload-worker' +import { steganographyDownloadImage } from './utils' type Progress = ( | { progress: 'finding_person_public_key' | 'finding_post_key' | 'init' | 'decode_post' } @@ -316,6 +317,7 @@ async function* decryptFromImageUrlWithProgress_raw( yield makeProgress('decode_post', true) const post = await steganographyDecodeImageUrl(url, { pass: author.toText(), + downloadImage: steganographyDownloadImage, }) if (!post.startsWith('๐ŸŽผ') && !/https:\/\/.+\..+\/(\?PostData_v\d=)?%20(.+)%40/.test(post)) return makeError(i18n.t('service_decode_image_payload_failed'), true) diff --git a/packages/mask/src/extension/background-script/CryptoServices/utils.ts b/packages/mask/src/extension/background-script/CryptoServices/utils.ts new file mode 100644 index 000000000000..6d690e5f4b88 --- /dev/null +++ b/packages/mask/src/extension/background-script/CryptoServices/utils.ts @@ -0,0 +1,7 @@ +import { blobToArrayBuffer, memoizePromise } from '@dimensiondev/kit' +import { downloadUrl } from '../../../utils/utils' + +export const steganographyDownloadImage = memoizePromise( + async (url: string) => blobToArrayBuffer(await downloadUrl(url)), + void 0, +) diff --git a/packages/mask/src/resources/image-payload/index.ts b/packages/mask/src/resources/image-payload/index.ts index e9387b091c57..5657f8a46107 100644 --- a/packages/mask/src/resources/image-payload/index.ts +++ b/packages/mask/src/resources/image-payload/index.ts @@ -1,4 +1,5 @@ -export type ImageTemplateTypes = 'v2' | 'eth' +import type { ImageTemplateTypes } from '@masknet/encryption' + export const ImagePayloadURLs: Readonly> = { v2: new URL('./normal/payload-v2.png', import.meta.url).toString(), eth: new URL('./wallet/payload-eth.png', import.meta.url).toString(), diff --git a/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts index fa7904a01bb8..b821b387c29e 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts @@ -18,7 +18,7 @@ import { PostProviderFacebook } from './collecting/posts' import { pasteImageToCompositionDefault } from '../../social-network/defaults/automation/AttachImageToComposition' import { injectPageInspectorDefault } from '../../social-network/defaults/inject/PageInspector' import { createTaskStartSetupGuideDefault } from '../../social-network/defaults/inject/StartSetupGuide' -import { GrayscaleAlgorithm } from '@dimensiondev/stego-js/esm/grayscale' +import { GrayscaleAlgorithm } from '@masknet/encryption' import { PaletteModeProviderFacebook, useThemeFacebookVariant } from './customization/custom' import { unreachable } from '@dimensiondev/kit' import { makeStyles } from '@masknet/theme' diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index 61fcb0456389..72e46a667ce5 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -12,7 +12,7 @@ import type { PaletteMode, Theme } from '@mui/material' import type { InjectedDialogClassKey, InjectedDialogProps } from '../components/shared/InjectedDialog' import type { Profile } from '../database' import type { PostInfo } from './PostInfo' -import type { GrayscaleAlgorithm } from '@dimensiondev/stego-js/umd/grayscale' +import type { GrayscaleAlgorithm } from '@masknet/encryption' import type { createSNSAdaptorSpecializedPostContext } from './utils/create-post-context' import type { Subscription } from 'use-subscription' diff --git a/packages/mask/src/utils/index.ts b/packages/mask/src/utils/index.ts index 780d335d5686..c7f0facb60e6 100644 --- a/packages/mask/src/utils/index.ts +++ b/packages/mask/src/utils/index.ts @@ -10,7 +10,6 @@ export * from './constants' export * from './dom' export * from './getTextUILength' export * from './i18n-next-ui' -export * from './image' export * from './messages' export * from './permissions' export * from './createNormalReactRoot' diff --git a/packages/scripts/src/codegen/resource-files.ts b/packages/scripts/src/codegen/resource-files.ts index 5e1076e73801..2e0fd82307f9 100644 --- a/packages/scripts/src/codegen/resource-files.ts +++ b/packages/scripts/src/codegen/resource-files.ts @@ -4,13 +4,16 @@ import { src, dest, watch, series, parallel, TaskFunction } from 'gulp' const Shared = join(__dirname, '../../../shared/') const Flow = join(__dirname, '../../../plugins/Flow') +const Encryption = join(__dirname, '../../../encryption') const CopyShared = () => src('./src/**/*.png', { cwd: Shared }).pipe(dest('./dist', { cwd: Shared })) const CopyFlow = () => src('./src/**/*.png', { cwd: Flow }).pipe(dest('./dist', { cwd: Flow })) +const CoypEncryption = () => src('./src/**/*.png', { cwd: Encryption }).pipe(dest('./dist', { cwd: Encryption })) -export const resourceCopy: TaskFunction = parallel(CopyShared, CopyFlow) +export const resourceCopy: TaskFunction = parallel(CopyShared, CopyFlow, CoypEncryption) export function resourceCopyWatch() { watch('./src/**/*.png', { cwd: Flow }, CopyFlow) + watch('./src/**/*.png', { cwd: Encryption }, CoypEncryption) return watch('./src/**/*.png', { cwd: Shared }, CopyShared) } watchTask(resourceCopy, resourceCopyWatch, 'resource-copy', 'Copy resources') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93fdb91640ef..85d8226a5f2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -239,9 +239,11 @@ importers: packages/encryption: specifiers: + '@dimensiondev/stego-js': 0.11.1-20201027083223-8ab41be '@masknet/shared-base': workspace:* '@msgpack/msgpack': ^2.7.0 dependencies: + '@dimensiondev/stego-js': 0.11.1-20201027083223-8ab41be '@masknet/shared-base': link:../shared-base '@msgpack/msgpack': 2.7.1 @@ -280,7 +282,6 @@ importers: '@dimensiondev/mask-wallet-core': 0.1.0-20211013082857-eb62e5f '@dimensiondev/metamask-extension-provider': 3.0.6-20210519045409-1835d4d '@dimensiondev/snapshot.js': 0.2.0 - '@dimensiondev/stego-js': '=0.11.1-20201027083223-8ab41be' '@dimensiondev/webextension-shim': 0.0.3-20210823035705-4257e12 '@ethersproject/abi': ^5.4.0 '@ethersproject/address': ^5.0.4 @@ -415,7 +416,6 @@ importers: '@dimensiondev/mask-wallet-core': 0.1.0-20211013082857-eb62e5f_protobufjs@6.11.2 '@dimensiondev/metamask-extension-provider': 3.0.6-20210519045409-1835d4d '@dimensiondev/snapshot.js': 0.2.0 - '@dimensiondev/stego-js': 0.11.1-20201027083223-8ab41be '@ethersproject/abi': 5.4.0 '@ethersproject/address': 5.4.0 '@ethersproject/providers': 5.4.2 @@ -24764,6 +24764,26 @@ packages: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} dev: true + github.com/ProjectOpenSea/wyvern-js/1634ad0e2f9961accca62edbf4ac624effa18552: + resolution: {tarball: https://codeload.github.com/ProjectOpenSea/wyvern-js/tar.gz/1634ad0e2f9961accca62edbf4ac624effa18552} + name: wyvern-js + version: 3.2.0 + dependencies: + '@0xproject/abi-gen': 0.1.7 + '@0xproject/assert': 0.0.11 + '@0xproject/json-schemas': 0.7.24 + '@0xproject/utils': 0.1.3 + '@0xproject/web3-wrapper': 0.1.14 + 0x.js: 0.29.2 + bn.js: 4.12.0 + ethereumjs-abi: github.com/ProjectWyvern/ethereumjs-abi/3d2d89641a6ad5984929b6ca4b646452ec74f73d + ethereumjs-util: 5.2.1 + json-loader: 0.5.7 + jsonschema: 1.4.0 + lodash: 4.17.21 + web3: 0.20.7 + dev: false + github.com/ProjectOpenSea/wyvern-js/fabb7660f23f2252c141077e32193d281036299e: resolution: {tarball: https://codeload.github.com/ProjectOpenSea/wyvern-js/tar.gz/fabb7660f23f2252c141077e32193d281036299e} name: wyvern-js @@ -24806,7 +24826,7 @@ packages: bignumber.js: 6.0.0 typed-promisify: 0.4.0 web3-provider-engine: 13.8.0 - wyvern-js: github.com/ProjectOpenSea/wyvern-js/fabb7660f23f2252c141077e32193d281036299e + wyvern-js: github.com/ProjectOpenSea/wyvern-js/1634ad0e2f9961accca62edbf4ac624effa18552 transitivePeerDependencies: - debug dev: false From 29b7f364f070a6ea9b5cfb8cba0cf93b7835bfa5 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 29 Dec 2021 16:59:32 +0800 Subject: [PATCH 11/85] chore: move gun --- .../src/social-network-encode-decode/types.ts | 1 - .../network/gun/encryption/queryPostKey.ts | 38 +++++++- .../mask/background/network/gun/instance.ts | 52 ++++++++++ packages/mask/background/network/gun/utils.ts | 23 ++++- .../background/services/crypto/decryption.ts | 38 +++++--- .../src/components/DebugModeUI/DebugList.tsx | 27 ------ .../components/DebugModeUI/PostHashDialog.tsx | 94 ------------------ .../DecryptedPost/DecryptedPost.tsx | 12 +-- .../DecryptedPost/DecryptedPostDebug.tsx | 59 ----------- .../InjectedComponents/PostInspector.tsx | 23 +---- .../background-script/CryptoService.ts | 6 -- .../CryptoServices/appendShareTarget.ts | 13 ++- .../debugShowAllPossibleHashForPost.ts | 25 ----- .../CryptoServices/decryptFrom.ts | 78 --------------- .../CryptoServices/encryptTo.ts | 7 +- .../getPartialSharedListOfPost.ts | 12 --- .../mask/src/network/gun/gun-worker.patch.ts | 5 - packages/mask/src/network/gun/index.ts | 18 ---- .../mask/src/network/gun/version.1/index.ts | 59 ----------- .../src/network/gun/version.2/gun.patch.ts | 11 --- .../mask/src/network/gun/version.2/gun2.ts | 28 ------ .../mask/src/network/gun/version.2/hash.ts | 62 ------------ .../mask/src/network/gun/version.2/index.ts | 9 -- .../mask/src/network/gun/version.2/post.ts | 97 ------------------- .../gun/worker-implementation-subscribe.ts | 1 - .../src/network/gun/worker-implementation.ts | 2 - packages/mask/src/network/gun/worker.ts | 13 --- .../mask/src/plugins/Avatar/Services/gun.ts | 35 ++----- packages/mask/src/plugins/Avatar/constants.ts | 2 +- packages/mask/src/plugins/Polls/Services.ts | 96 ++---------------- packages/mask/src/plugins/Polls/constants.ts | 2 +- 31 files changed, 163 insertions(+), 785 deletions(-) delete mode 100644 packages/encryption/src/social-network-encode-decode/types.ts create mode 100644 packages/mask/background/network/gun/instance.ts delete mode 100644 packages/mask/src/components/DebugModeUI/DebugList.tsx delete mode 100644 packages/mask/src/components/DebugModeUI/PostHashDialog.tsx delete mode 100644 packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostDebug.tsx delete mode 100644 packages/mask/src/extension/background-script/CryptoServices/debugShowAllPossibleHashForPost.ts delete mode 100644 packages/mask/src/network/gun/gun-worker.patch.ts delete mode 100644 packages/mask/src/network/gun/index.ts delete mode 100644 packages/mask/src/network/gun/version.1/index.ts delete mode 100644 packages/mask/src/network/gun/version.2/gun.patch.ts delete mode 100644 packages/mask/src/network/gun/version.2/gun2.ts delete mode 100644 packages/mask/src/network/gun/version.2/hash.ts delete mode 100644 packages/mask/src/network/gun/version.2/index.ts delete mode 100644 packages/mask/src/network/gun/version.2/post.ts delete mode 100644 packages/mask/src/network/gun/worker-implementation-subscribe.ts delete mode 100644 packages/mask/src/network/gun/worker-implementation.ts delete mode 100644 packages/mask/src/network/gun/worker.ts diff --git a/packages/encryption/src/social-network-encode-decode/types.ts b/packages/encryption/src/social-network-encode-decode/types.ts deleted file mode 100644 index 80ada4a1c043..000000000000 --- a/packages/encryption/src/social-network-encode-decode/types.ts +++ /dev/null @@ -1 +0,0 @@ -export interface SocialNetworkEncoder {} diff --git a/packages/mask/background/network/gun/encryption/queryPostKey.ts b/packages/mask/background/network/gun/encryption/queryPostKey.ts index c9702378ccc3..16a4d2d949e2 100644 --- a/packages/mask/background/network/gun/encryption/queryPostKey.ts +++ b/packages/mask/background/network/gun/encryption/queryPostKey.ts @@ -1,9 +1,9 @@ import { decodeArrayBuffer, encodeArrayBuffer } from '@dimensiondev/kit' import type { DecryptStaticECDH_PostKey } from '@masknet/encryption' -import type { EC_Public_CryptoKey, EC_Public_JsonWebKey, ProfileIdentifier } from '@masknet/shared-base' +import type { EC_Public_CryptoKey, EC_Public_JsonWebKey } from '@masknet/shared-base' import type { default as gun } from 'gun' import { CryptoKeyToJsonWebKey } from '../../../../utils-pure' -import { getGunData, subscribeGunMapData } from '../utils' +import { getGunData, pushToGunDataArray, subscribeGunMapData } from '../utils' import { EventIterator } from 'event-iterator' import { noop } from 'lodash-unified' type Gun = ReturnType @@ -12,10 +12,10 @@ type Gun = ReturnType export async function GUN_queryPostKey_version40( gun: Gun, iv: Uint8Array, - whoAmI: ProfileIdentifier, + whoAmI: string, ): Promise { // PATH ON GUN: maskbook > posts > iv > userID - const result = await getGunData(gun, 'maskbook', 'posts', encodeArrayBuffer(iv), whoAmI.userId) + const result = await getGunData(gun, 'maskbook', 'posts', encodeArrayBuffer(iv), whoAmI) if (!isValidData(result)) return null return { encryptedPostKey: new Uint8Array(decodeArrayBuffer(result.encryptedKey)), @@ -72,6 +72,33 @@ namespace Version38Or39 { yield* iter } + export type PublishedAESKeyRecord = { + aesKey: { encryptedKey: string; salt: string } + receiverKey: EC_Public_JsonWebKey + } + + /** + * Publish post keys on the gun + * @param version current payload + * @param postIV Post iv + * @param receiversKeys Keys needs to publish + */ + export async function publishPostAESKey_version39Or38( + gun: Gun, + version: -39 | -38, + postIV: Uint8Array, + networkHint: string, + receiversKeys: PublishedAESKeyRecord[], + ) { + const postHash = await hashIV(networkHint, postIV) + // Store AES key to gun + receiversKeys.forEach(async ({ aesKey, receiverKey }) => { + const keyHash = await (version === -38 ? hashKey38 : hashKey39)(receiverKey) + console.log(`gun[${postHash}][${keyHash}].push(`, aesKey, `)`) + pushToGunDataArray(gun, [postHash, keyHash], aesKey) + }) + } + type DataOnGun = { encryptedKey: string salt: string @@ -158,4 +185,5 @@ namespace Version38Or39 { } } -export const { GUN_queryPostKey_version39Or38 } = Version38Or39 +export const { GUN_queryPostKey_version39Or38, publishPostAESKey_version39Or38 } = Version38Or39 +export type PublishedAESKeyRecord_version39Or38 = Version38Or39.PublishedAESKeyRecord diff --git a/packages/mask/background/network/gun/instance.ts b/packages/mask/background/network/gun/instance.ts new file mode 100644 index 000000000000..a341ccd114b2 --- /dev/null +++ b/packages/mask/background/network/gun/instance.ts @@ -0,0 +1,52 @@ +import Gun from 'gun' +import 'gun/sea' +import 'gun/lib/radix' +import 'gun/lib/radisk' +import 'gun/lib/store' +import 'gun/lib/rindexed' +import { gunServers } from '.' +import { startEffects } from '../../../utils-pure' + +const { signal } = startEffects(import.meta.webpackHot) + +export type GunRoot = ReturnType +let gun: GunRoot | undefined +export function getGunInstance(): GunRoot { + if (gun) return gun + return (gun = createGun()) +} + +function createGun() { + class WebSocket extends globalThis.WebSocket { + constructor(url: string | URL) { + super(url) + const abort = (this.abort = () => { + gun = undefined + this.close() + console.log('[Network/gun] WebSocket of the Gun instance is killed due to inactive.') + }) + const tick = (this.tick = () => { + if (this.timer) clearTimeout(this.timer) + this.timer = setTimeout(abort, 15 * 60 * 1000) + }) + signal.addEventListener('abort', abort) + this.addEventListener('message', tick, { signal }) + } + private declare abort: () => void + private declare tick: () => void + declare timer: NodeJS.Timeout | undefined + override send(data: any) { + this.tick() + super.send(data) + } + } + + const _ = new Gun({ + peers: [...gunServers], + localStorage: false, + radisk: true, + WebSocket, + }) + _.opt({ retry: Number.POSITIVE_INFINITY }) + return _ +} diff --git a/packages/mask/background/network/gun/utils.ts b/packages/mask/background/network/gun/utils.ts index bd8bcc3815a0..053b0735ed02 100644 --- a/packages/mask/background/network/gun/utils.ts +++ b/packages/mask/background/network/gun/utils.ts @@ -1,17 +1,30 @@ -import type { default as gun } from 'gun' import { EventIterator } from 'event-iterator' -export type GunRoot = ReturnType +import type { GunRoot } from './instance' +function getGunNodeFromPath(gun: GunRoot, path: string[]) { + const resultNode = path.reduce((gun, path) => gun.get(path as never), gun) + return resultNode +} /** * A sugar for gun.get(path[0]).get(path[1])....get(path[n]).once() */ export function getGunData(gun: GunRoot, ...path: string[]) { return new Promise<{ [x: string]: unknown } | undefined>((resolve) => { - const resultNode = path.reduce((gun, path) => gun.get(path as never), gun) - resultNode.once(resolve) + getGunNodeFromPath(gun, path).once(resolve) }) } +export function setGunData(gun: GunRoot, path: string[], data: any) { + getGunNodeFromPath(gun, path).put(data) +} +export function deleteGunData(gun: GunRoot, path: string[]) { + getGunNodeFromPath(gun, path).put(null!) +} + +export function pushToGunDataArray(gun: GunRoot, path: string[], value: object) { + getGunNodeFromPath(gun, path).set(value) +} + export async function* subscribeGunMapData( gun: GunRoot, path: string[], @@ -27,7 +40,7 @@ export async function* subscribeGunMapData( listenerClosed = true }) - const resultNode = path.reduce((gun, path) => gun.get(path as never), gun) + const resultNode = getGunNodeFromPath(gun, path) resultNode.map().on((data) => { if (listenerClosed) return diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index fa166da10afd..679349515657 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -7,6 +7,7 @@ import { DecryptProgress, SocialNetworkEnum, SocialNetworkEnumToProfileDomain, + AsymmetryCryptoKey, } from '@masknet/encryption' import { AESCryptoKey, @@ -20,7 +21,7 @@ import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../databas import { queryPostDB } from '../../database/post' import { savePostKeyToDB } from '../../database/post/helper' import { GUN_queryPostKey_version39Or38, GUN_queryPostKey_version40 } from '../../network/gun/encryption/queryPostKey' -import type { GunRoot } from '../../network/gun/utils' +import { getGunInstance } from '../../network/gun/instance' export type DecryptionInfo = { type: DecryptProgressKind.Info @@ -71,18 +72,13 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt // TODO: read in-memory cache to avoid db lookup - //#region Store author public key try { - const author = parse.unwrap().author.unwrap().unwrap() - const authorPub = parse.unwrap().authorPublicKey.unwrap().unwrap() - if (author.equals(authorHint)) { - // TODO: store the public key - if (authorPub.algr !== PublicKeyAlgorithmEnum.secp256k1) throw new Error('TODO: support other curves') - } else { - // ! Author detected is not equal to AuthorHint. Skip store the public key because it might be a security problem. - } + storeAuthorPublicKey( + parse.unwrap().author.unwrap().unwrap(), + authorHint, + parse.unwrap().authorPublicKey.unwrap().unwrap(), + ) } catch {} - //#endregion const progress = decrypt( { message: parse.val }, @@ -124,7 +120,7 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt }, async *queryPostKey_version38(iv, signal) { yield* GUN_queryPostKey_version39Or38( - {} as GunRoot, + getGunInstance(), -38, iv, {} as any, @@ -134,7 +130,7 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt }, async *queryPostKey_version39(iv, signal) { yield* GUN_queryPostKey_version39Or38( - {} as GunRoot, + getGunInstance(), -39, iv, {} as any, @@ -144,7 +140,7 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt }, async queryPostKey_version40(iv) { if (!currentProfile) return null - return GUN_queryPostKey_version40({} as GunRoot, iv, currentProfile) + return GUN_queryPostKey_version40(getGunInstance(), iv, currentProfile.userId) }, }, ) @@ -160,3 +156,17 @@ async function getPostKeyCache(id: PostIVIdentifier) { ]) return k as AESCryptoKey } + +async function storeAuthorPublicKey( + payloadAuthor: ProfileIdentifier, + postAuthor: ProfileIdentifier | null, + pub: AsymmetryCryptoKey, +) { + if (payloadAuthor.equals(postAuthor)) { + if (pub.algr !== PublicKeyAlgorithmEnum.ed25519) { + throw new Error('TODO: support other curves') + } + // TODO: store the key + } + // ! Author detected is not equal to AuthorHint. Skip store the public key because it might be a security problem. +} diff --git a/packages/mask/src/components/DebugModeUI/DebugList.tsx b/packages/mask/src/components/DebugModeUI/DebugList.tsx deleted file mode 100644 index 27310ba8b50c..000000000000 --- a/packages/mask/src/components/DebugModeUI/DebugList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Fragment } from 'react' - -type AcceptableDebuggerType = string | number | undefined | boolean -function F({ content, hint }: { hint: string; content: AcceptableDebuggerType }) { - return String(content).length > 50 ? ( -
- {hint}: - {String(content)} -
- ) : ( -
  • - {hint}: - {String(content)} -
  • - ) -} -export function DebugList(props: { items: readonly (readonly [string, AcceptableDebuggerType] | JSX.Element)[] }) { - return ( -
    - {props.items.map((x) => ( - - {Array.isArray(x) ? : x} - - ))} -
    - ) -} diff --git a/packages/mask/src/components/DebugModeUI/PostHashDialog.tsx b/packages/mask/src/components/DebugModeUI/PostHashDialog.tsx deleted file mode 100644 index 259b873a3074..000000000000 --- a/packages/mask/src/components/DebugModeUI/PostHashDialog.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useState } from 'react' -import { useAsync } from 'react-use' -import Button from '@mui/material/Button' -import List from '@mui/material/List' -import ListItem from '@mui/material/ListItem' -import ListItemAvatar from '@mui/material/ListItemAvatar' -import ListItemText from '@mui/material/ListItemText' -import { useFriendsList } from '../DataSource/useActivatedUI' -import { Avatar } from '../../utils/components/Avatar' -import type { Profile } from '../../database' -import Services from '../../extension/service' -import { PostIVIdentifier } from '@masknet/shared-base' -import { deconstructPayload } from '../../utils/type-transform/Payload' -import { DialogContent, DialogContentText } from '@mui/material' -import { InjectedDialog } from '../shared/InjectedDialog' - -export interface SimpleDialogProps { - open: boolean - onClose: () => void - friends: Profile[] - hashMap: [string, string, string][] -} - -function PostHashDialog(props: SimpleDialogProps) { - const { open } = props - - const map = new Map() - for (const [id, magicCode, fingerprint] of props.hashMap) { - map.set(id, [magicCode, fingerprint]) - } - - return ( - - - - Appear in this list is not related to if you have shared this post to someone or not. - - - The "Magic Code" and the Fingerprint should be the same on your friend's Mask and your Mask. - - - If Fingerprint is not the same, it means at least one of Mask doesn't get the correct key of the - receiver. - - - If MagicCode is not the same, it means at least one of Mask calculate the post hash wrong. - - - {props.friends.map((one) => { - const [magicCode, fingerprint] = map.get(one.identifier.toText()) || ['Unknown', 'Unknown'] - return ( - - - - - - Magic code: {magicCode || 'Unknown'} -
    - Their fingerprint: {fingerprint} - - } - /> -
    - ) - })} -
    -
    -
    - ) -} - -export function DebugModeUI_PostHashDialog(props: { post: string; network: string }) { - const [open, setOpen] = useState(false) - const payload = deconstructPayload(props.post, null) - const friends = useFriendsList() - const { value: hashMap = [] } = useAsync(async () => { - if (!payload.ok) return [] - const ivID = new PostIVIdentifier(props.network, payload.val.iv) - return Services.Crypto.debugShowAllPossibleHashForPost(ivID, payload.val.version) - }, [props.post]) - const shouldHide = payload.map((x) => x.version === -38 && !!x.sharedPublic).unwrapOr(true) - if (shouldHide) return null - return ( - <> - - setOpen(false)} /> - - ) -} diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index fb28953aec07..c425ff8f0179 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -19,7 +19,6 @@ import type { import { DecryptPostSuccess, DecryptPostSuccessProps } from './DecryptedPostSuccess' import { DecryptPostAwaiting, DecryptPostAwaitingProps } from './DecryptPostAwaiting' import { DecryptPostFailed, DecryptPostFailedProps } from './DecryptPostFailed' -import { DecryptedPostDebug } from './DecryptedPostDebug' import { usePostClaimedAuthor, usePostInfoDetails, usePostInfoSharedPublic } from '../../DataSource/usePostInfo' import { asyncIteratorWithResult } from '../../../utils/type-transform/asyncIteratorHelpers' import { usePostInfo } from '../../../components/DataSource/usePostInfo' @@ -234,15 +233,6 @@ export function DecryptPost(props: DecryptPostProps) { } const rendered = render() if (!rendered) return null - return ( - <> - {rendered} - - - ) + return rendered } } diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostDebug.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostDebug.tsx deleted file mode 100644 index cb99361ac42f..000000000000 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostDebug.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Environment, isEnvironment } from '@dimensiondev/holoflows-kit' -import { useValueRef } from '@masknet/shared' -import type { ProfileIdentifier, PayloadAlpha38 } from '@masknet/shared-base' -import { useMemo } from 'react' -import type { - FailureDecryption, - SuccessDecryption, -} from '../../../extension/background-script/CryptoServices/decryptFrom' -import { debugModeSetting } from '../../../settings/settings' -import { deconstructPayload } from '../../../utils/type-transform/Payload' -import { usePostInfoDetails } from '../../DataSource/usePostInfo' -import { DebugList } from '../../DebugModeUI/DebugList' -import { DebugModeUI_PostHashDialog } from '../../DebugModeUI/PostHashDialog' - -interface DebugDisplayProps { - whoAmI: ProfileIdentifier - debugHash: string - decryptedResult: SuccessDecryption | FailureDecryption | null -} -export function DecryptedPostDebug(props: Partial) { - const postBy = usePostInfoDetails.postBy() - const postContent = usePostInfoDetails.postContent() - const payloadResult = useMemo(() => deconstructPayload(postContent), [postContent]) - const setting = useValueRef(debugModeSetting) - const isDebugging = isEnvironment(Environment.ManifestOptions) ? true : setting - - const { debugHash, decryptedResult, whoAmI } = props - if (!isDebugging) return null - const postByMyself = - if (payloadResult.err) - return ( - - ) - const payload = payloadResult.val - const ownersAESKeyEncrypted = payload.version === -38 ? payload.AESKeyEncrypted : payload.ownersAESKeyEncrypted - return ( - - ) -} diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index 7a4f7aae2896..45099a314d08 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -11,9 +11,6 @@ import { } from '@masknet/shared-base' import type { Profile } from '../../database' import { useCurrentIdentity, useFriendsList } from '../DataSource/useActivatedUI' -import { useValueRef } from '@masknet/shared' -import { debugModeSetting } from '../../settings/settings' -import { DebugList } from '../DebugModeUI/DebugList' import { usePostInfoDetails } from '../DataSource/usePostInfo' import { decodePublicKeyUI } from '../../social-network/utils/text-payload-ui' import { createInjectHooksRenderer, useActivatedPluginsSNSAdaptor } from '@masknet/plugin-infra' @@ -37,7 +34,6 @@ export function PostInspector(props: PostInspectorProps) { const postId = usePostInfoDetails.postIdentifier() const decryptedPayloadForImage = usePostInfoDetails.decryptedPayloadForImage() const postImages = usePostInfoDetails.postMetadataImages() - const isDebugging = useValueRef(debugModeSetting) const whoAmI = useCurrentIdentity() const friends = useFriendsList() const [alreadySelectedPreviously, setAlreadySelectedPreviously] = useState([]) @@ -50,24 +46,8 @@ export function PostInspector(props: PostInspectorProps) { }, [postBy, whoAmI, encryptedPost]) useEffect(() => setAlreadySelectedPreviously(sharedListOfPost ?? []), [sharedListOfPost]) - const debugInfo = isDebugging ? ( - - ) : null - if (encryptedPost.ok || postImages.length) { - if (!isDebugging) props.needZip() + props.needZip() const DecryptPostX = props.DecryptPostComponent || DecryptPost return withAdditionalContent( - {debugInfo} {props.slotPosition !== 'before' && slot} ) diff --git a/packages/mask/src/extension/background-script/CryptoService.ts b/packages/mask/src/extension/background-script/CryptoService.ts index 5dbe116ec82d..272f5d35970b 100644 --- a/packages/mask/src/extension/background-script/CryptoService.ts +++ b/packages/mask/src/extension/background-script/CryptoService.ts @@ -12,9 +12,3 @@ export { verifyOthersProve } from './CryptoServices/verifyOthersProve' export function steganographyEncodeImage(buf: ArrayBuffer, options: Omit) { return __steganographyEncodeImage(buf, { ...options, downloadImage: steganographyDownloadImage }) } - -import type { debugShowAllPossibleHashForPost as orig } from './CryptoServices/debugShowAllPossibleHashForPost' -// This module requires lazy loading otherwise it will load gun -export async function debugShowAllPossibleHashForPost(...args: Parameters) { - return (await import('./CryptoServices/debugShowAllPossibleHashForPost')).debugShowAllPossibleHashForPost(...args) -} diff --git a/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts b/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts index c0cfddbef48a..5934093db48a 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts @@ -1,5 +1,4 @@ import * as Alpha39 from '../../../crypto/crypto-alpha-39' -import { GunAPI as Gun2 } from '../../../network/gun/' import { ProfileIdentifier, PostIVIdentifier, @@ -12,6 +11,9 @@ import { cryptoProviderTable } from './cryptoProviderTable' import { updatePostDB, RecipientDetail, RecipientReason } from '../../../../background/database/post' import { getNetworkWorkerUninitialized } from '../../../social-network/worker' import { queryPrivateKey, queryLocalKey } from '../../../database' +import { publishPostAESKey_version39Or38 } from '../../../../background/network/gun/encryption/queryPostKey' +import { getGunInstance } from '../../../../background/network/gun/instance' +import { decodeArrayBuffer } from '@dimensiondev/kit' export async function appendShareTarget( version: -40 | -39 | -38, postAESKey: string | AESJsonWebKey, @@ -40,7 +42,14 @@ export async function appendShareTarget( Array.from(toKey.values()), ) const gunHint = getNetworkWorkerUninitialized(whoAmI)?.gunNetworkHint - gunHint && Gun2.publishPostAESKeyOnGun2(version, iv, gunHint, othersAESKeyEncrypted) + gunHint && + publishPostAESKey_version39Or38( + getGunInstance(), + version, + new Uint8Array(decodeArrayBuffer(iv)), + gunHint, + othersAESKeyEncrypted, + ) updatePostDB( { identifier: new PostIVIdentifier(whoAmI.network, iv), diff --git a/packages/mask/src/extension/background-script/CryptoServices/debugShowAllPossibleHashForPost.ts b/packages/mask/src/extension/background-script/CryptoServices/debugShowAllPossibleHashForPost.ts deleted file mode 100644 index 1684afe3a79c..000000000000 --- a/packages/mask/src/extension/background-script/CryptoServices/debugShowAllPossibleHashForPost.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { PostIVIdentifier } from '@masknet/shared-base' -import { hashPostSalt, hashCryptoKey, hashCryptoKeyUnstable } from '../../../network/gun/version.2/hash' -import { queryProfilesWithQuery, queryPublicKey } from '../../../database' -import { getNetworkWorkerUninitialized } from '../../../social-network/worker' - -export async function debugShowAllPossibleHashForPost(post: PostIVIdentifier, payloadVersion: -38 | -39 | -40) { - const friends = await queryProfilesWithQuery((x) => x.identifier.network === post.network) - const gunHint = getNetworkWorkerUninitialized(post.network)?.gunNetworkHint - return Promise.all( - friends - .filter((x) => x.linkedPersona) - .map( - async (x) => - [ - x.identifier.toText(), - (await hashPostSalt(post.postIV, gunHint ?? '')) + - '-' + - (await (payloadVersion <= -39 ? hashCryptoKeyUnstable : hashCryptoKey)( - (await queryPublicKey(x.identifier))!, - )), - x.linkedPersona?.fingerprint!, - ] as [identifier: string, postHash: string, fingerPrint: string], - ), - ) -} diff --git a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts index a8c2e97d5e4e..400622c82d8b 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts @@ -1,6 +1,5 @@ import * as Alpha40 from '../../../crypto/crypto-alpha-40' import * as Alpha39 from '../../../crypto/crypto-alpha-39' -import { GunAPI as Gun2, GunAPISubscribe as Gun2Subscribe, GunWorker } from '../../../network/gun/' import { decodeText } from '@dimensiondev/kit' import { deconstructPayload } from '../../../utils/type-transform/Payload' import { i18n } from '../../../../shared-ui/locales_legacy' @@ -17,9 +16,7 @@ import { asyncIteratorWithResult, memorizeAsyncGenerator } from '../../../utils/ import { steganographyDecodeImageUrl } from '@masknet/encryption' import type { TypedMessage, AESJsonWebKey, Payload } from '@masknet/shared-base' import stringify from 'json-stable-stringify' -import type { SharedAESKeyGun2 } from '../../../network/gun/version.2' import { MaskMessages } from '../../../utils/messages' -import { GunAPI } from '../../../network/gun' import { Err, Ok, Result } from 'ts-results' import { decodeTextPayloadWorker } from '../../../social-network/utils/text-payload-worker' import { steganographyDownloadImage } from './utils' @@ -182,15 +179,6 @@ async function* decryptFromPayloadWithProgress_raw( if (!mine?.privateKey) return makeError(DecryptFailedReason.MyCryptoKeyNotFound) const { publicKey: minePublic, privateKey: minePrivate } = mine - const networkWorker = getNetworkWorkerUninitialized(whoAmI) - try { - if (version === -40) throw '' - const gunNetworkHint = networkWorker!.gunNetworkHint - const { keyHash, postHash } = await ( - await import('../../../network/gun/version.2/hash') - ).calculatePostKeyPartition(version, iv, minePublic, gunNetworkHint) - yield { type: 'debug', debug: 'debug_finding_hash', hash: [postHash, keyHash] } - } catch {} if (cachedPostResult) return makeSuccessResult(cachedPostResult, ['post_key_cached']) let lastError: unknown @@ -219,74 +207,8 @@ async function* decryptFromPayloadWithProgress_raw( } yield makeProgress('finding_post_key') - const aesKeyEncrypted: Array = [] - if (version === -40) { - // Deprecated payload - // eslint-disable-next-line import/no-deprecated - const result = await GunAPI.queryVersion1PostAESKey(iv, whoAmI.userId) - if (result === undefined) return makeError(i18n.t('service_not_share_target')) - aesKeyEncrypted.push(result) - } else if (version === -39 || version === -38) { - const keys = await Gun2.queryPostKeysOnGun2(version, iv, minePublic, authorNetworkWorker.val.gunNetworkHint) - aesKeyEncrypted.push(...keys) - } - // If we can decrypt with current info, just do it. - try { - // ! Do not remove the await here. - return await decryptWith(aesKeyEncrypted) - } catch (error) { - if (error instanceof Error && error.message === i18n.t('service_not_share_target')) { - console.debug(error) - // TODO: Replace this error with: - // You do not have the necessary private key to decrypt this message. - // What to do next: You can ask your friend to visit your profile page, so that their Mask extension will detect and add you to recipients. - // ? after the auto-share with friends is done. - yield makeError(error) - } else { - return handleDOMException(error) - } - } - - // Failed, we have to wait for the future info from gun. - if (version === -40) return makeError(i18n.t('service_not_share_target')) - const subscription = Gun2Subscribe.subscribePostKeysOnGun2( - version, - iv, - minePublic, - authorNetworkWorker.val.gunNetworkHint, - ) - GunWorker?.onTerminated(() => subscription.return?.()) - for await (const aes of subscription) { - console.log('New key received, trying', aes) - try { - return await decryptWith(aes) - } catch (error) { - console.debug(error) - } - } return makeError(i18n.t('service_not_share_target')) - async function decryptWith( - key: - | Alpha39.PublishedAESKey - | Alpha40.PublishedAESKey - | Array, - ): Promise { - const [contentArrayBuffer, postAESKey] = await cryptoProvider.decryptMessage1ToNByOther({ - version, - AESKeyEncrypted: key, - authorsPublicKeyECDH: authorPersona.publicKey, - encryptedContent: encryptedText, - privateKeyECDH: minePrivate!, - iv, - }) - - // Store the key to speed up next time decrypt - setPostCache(postAESKey) - const content = decodeText(contentArrayBuffer) - return makeSuccessResult(content, ['normal_decrypted']) - } - async function decryptAsAuthor(authorIdentifier: ProfileIdentifier) { const localKey = sharePublic ? publicSharedAESKey : await queryLocalKey(authorIdentifier) if (!localKey) throw new Error(`Local key for identity ${authorIdentifier.toText()} not found`) diff --git a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts index 66a24f822a11..d10e57084c13 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts @@ -1,6 +1,5 @@ import * as Alpha38 from '../../../crypto/crypto-alpha-38' -import { GunAPI as Gun2 } from '../../../network/gun' -import { encodeArrayBuffer } from '@dimensiondev/kit' +import { decodeArrayBuffer, encodeArrayBuffer } from '@dimensiondev/kit' import { constructAlpha38 } from '../../../utils/type-transform/Payload' import { queryPrivateKey, queryLocalKey, queryProfile } from '../../../database' import { ProfileIdentifier, PostIVIdentifier } from '@masknet/shared-base' @@ -17,6 +16,8 @@ import { compressSecp256k1Key, } from '@masknet/shared-base' import { encodeTextPayloadWorker } from '../../../social-network/utils/text-payload-worker' +import { publishPostAESKey_version39Or38 } from '../../../../background/network/gun/encryption/queryPostKey' +import { getGunInstance } from '../../../../background/network/gun/instance' type EncryptedText = string type OthersAESKeyEncryptedToken = string @@ -114,7 +115,7 @@ export async function publishPostAESKey(iv: string) { if (!info) throw new Error(i18n.t('service_publish_post_aes_key_failed')) if (!info[1].length) return // Use the latest payload version here since we do not accept new post for older version. - return Gun2.publishPostAESKeyOnGun2(-38, iv, ...info) + return publishPostAESKey_version39Or38(getGunInstance(), -38, new Uint8Array(decodeArrayBuffer(iv)), ...info) } const SUMMARY_MAX_LENGTH = 40 diff --git a/packages/mask/src/extension/background-script/CryptoServices/getPartialSharedListOfPost.ts b/packages/mask/src/extension/background-script/CryptoServices/getPartialSharedListOfPost.ts index 52f2ac9d3136..5dfd305d161b 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/getPartialSharedListOfPost.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/getPartialSharedListOfPost.ts @@ -1,9 +1,6 @@ import { Profile, queryProfile } from '../../../database' import { ProfileIdentifier, Identifier, PostIVIdentifier } from '@masknet/shared-base' import { queryPostDB } from '../../../../background/database/post' -import { GunAPI } from '../../../network/gun' -//#endregion -//#region Append Recipients in future /** * Get already shared target of the post * @param postSalt @@ -18,15 +15,6 @@ export async function getPartialSharedListOfPost( if (nameInDB === 'everyone') return [] if (!nameInDB) return [] nameInDB.forEach((_, x) => ids.add(x.toText())) - if (version === -40) { - // eslint-disable-next-line import/no-deprecated - const post = await GunAPI.getVersion1PostByHash(postSalt) - if (!post) return [] - delete post._ - const nameInGun = Object.keys(post) - // ? version 40 is for old facebook only - nameInGun.forEach((x) => ids.add(new ProfileIdentifier('facebook.com', x).toText())) - } return Promise.all( Array.from(ids) .map((x) => Identifier.fromString(x, ProfileIdentifier)) diff --git a/packages/mask/src/network/gun/gun-worker.patch.ts b/packages/mask/src/network/gun/gun-worker.patch.ts deleted file mode 100644 index 008ec6c4cf08..000000000000 --- a/packages/mask/src/network/gun/gun-worker.patch.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Gun need a "window" available -import '../../setup.worker' -// @ts-ignore -globalThis.window = globalThis -export {} diff --git a/packages/mask/src/network/gun/index.ts b/packages/mask/src/network/gun/index.ts deleted file mode 100644 index aa28aa7a4aea..000000000000 --- a/packages/mask/src/network/gun/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AsyncCall, AsyncCallOptions, AsyncGeneratorCall } from 'async-call-rpc/full' -import { WorkerChannel } from 'async-call-rpc/utils/web/worker' -import { serializer } from '@masknet/shared-base' -import { OnDemandWorker } from '../../../utils-pure' -import type { _AsyncVersionOf, _AsyncGeneratorVersionOf } from 'async-call-rpc/full' - -export let GunWorker: OnDemandWorker | undefined -if (process.env.architecture) { - GunWorker = new OnDemandWorker(new URL('./worker.ts', import.meta.url), { name: 'Gun' }) - // we're in webpack bundle -} -const options: AsyncCallOptions = { - channel: new WorkerChannel(GunWorker), - serializer, -} -export const GunAPI: _AsyncVersionOf = AsyncCall({}, options) -export const GunAPISubscribe: _AsyncGeneratorVersionOf = - AsyncGeneratorCall({}, options) diff --git a/packages/mask/src/network/gun/version.1/index.ts b/packages/mask/src/network/gun/version.1/index.ts deleted file mode 100644 index b2c679f57923..000000000000 --- a/packages/mask/src/network/gun/version.1/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint import/no-deprecated: 0 */ -import Gun from 'gun' -import 'gun/lib/then' -import type { PublishedAESKey } from '../../../crypto/crypto-alpha-40' -import { gun2 } from '../version.2' - -/** - * @deprecated // ! This version will leak post targets ! // - * - * This version should be readonly now. - * - * Use exchange v2 instead! - */ -interface ApplicationStateInGunVersion1 { - maskbook: { - // This section is no longer used but we leave the type here to avoid future collisions - // users: { - // [userID: string]: { - // /** @deprecated if you want to read it, cast it to string */ - // provePostId: never - // } - // } - posts: { - // Post salt - [salt: string]: { - // ! This will leak post targets. (But keys are safe) - [receiversUsername: string]: PublishedAESKey - } - } - } -} - -function typeHelper() { - return new Gun() -} -/** @deprecated */ -export const gun1 = (gun2 as any as ReturnType).get('maskbook') -/** - * @param salt The salt of this post - * @param myUsername My username of this post - * @deprecated Do not use in new code - */ -export async function queryVersion1PostAESKey(salt: string, myUsername: string) { - const result = await gun1 - .get('posts') - .get(salt) - // @ts-expect-error - .get(myUsername).then!() - if (result?.encryptedKey && result.salt) return result - return undefined -} -export async function getVersion1PostByHash(postSalt: string) { - return ( - gun1 - .get('posts') - // @ts-expect-error - .get(postSalt).then!() - ) -} diff --git a/packages/mask/src/network/gun/version.2/gun.patch.ts b/packages/mask/src/network/gun/version.2/gun.patch.ts deleted file mode 100644 index ed339e0711ac..000000000000 --- a/packages/mask/src/network/gun/version.2/gun.patch.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Gun from 'gun' -Object.assign(globalThis, { Gun }) - -// @ts-ignore -if (typeof importScripts !== 'function') { - console.warn('Loading gun in main frame is deprecated. Please load it in a OnDemandWorker') -} - -if (typeof location === 'object' && !location.protocol.includes('extension')) { - console.warn('Loading gun in SNS is deprecated. Please use RPC and run gun in the background.') -} diff --git a/packages/mask/src/network/gun/version.2/gun2.ts b/packages/mask/src/network/gun/version.2/gun2.ts deleted file mode 100644 index 8307ccb168dd..000000000000 --- a/packages/mask/src/network/gun/version.2/gun2.ts +++ /dev/null @@ -1,28 +0,0 @@ -import Gun from 'gun/gun' -import { gunServers } from '../../../../background/network/gun' - -export type PersonOnGun2 = { - /** @deprecated if you want to use it, cast it to string. */ - // provePostId?: unknown - __notInGunButABrand__DO_NOT_USE_THIS__?: never -} - -export type SharedAESKeyGun2 = { - encryptedKey: string - salt: string -} - -export interface PostOnGun2 { - [cutNBitsFromFront_hash_ReceiversPublicKey: string]: SharedAESKeyGun2[] -} - -export interface ApplicationStateInGunVersion2 { - [sha512_base64_ProfileIdentifier_OR_sha512_base64_PostSalt: string]: PersonOnGun2 | PostOnGun2 -} - -export const gun2 = new Gun({ - peers: gunServers as any, - localStorage: false, - radisk: true, -}) -gun2.opt({ retry: Number.POSITIVE_INFINITY }) diff --git a/packages/mask/src/network/gun/version.2/hash.ts b/packages/mask/src/network/gun/version.2/hash.ts deleted file mode 100644 index df377de4ce83..000000000000 --- a/packages/mask/src/network/gun/version.2/hash.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** This file should not contain active gun instances. Only static methods on gun is allowed. */ -/** - * @see https://github.com/DimensionDev/Maskbook/wiki/Data-structure-on-Gun-version-2 - */ -import type { EC_Public_JsonWebKey } from '@masknet/shared-base' -import Gun from 'gun' -import 'gun/sea' -import { memoizePromise } from '../../../../utils-pure' - -/** - * @param version current payload version - * @param postIV Post iv - * @param partitionByCryptoKey Public key of the current user (receiver) - * @param networkHint The network specific string - */ -export async function calculatePostKeyPartition( - version: -39 | -38, - postIV: string, - partitionByCryptoKey: EC_Public_JsonWebKey, - networkHint: string, -) { - const postHash = await hashPostSalt(postIV, networkHint) - // In version > -39, we will use stable hash to prevent unstable result for key hashing - const keyHash = await (version <= -39 ? hashCryptoKeyUnstable : hashCryptoKey)(partitionByCryptoKey) - return { postHash, keyHash } -} - -export const hashPostSalt = memoizePromise( - async function (postSalt: string, networkHint: string) { - const hashPair = `9283464d-ee4e-4e8d-a7f3-cf392a88133f` - const N = 2 - - const hash = (await Gun.SEA.work(postSalt, hashPair))! - return networkHint + hash.substring(0, N) - }, - (x, y) => x + y, -) - -/** - * @param key - The key need to be hashed - */ -export const hashCryptoKeyUnstable = memoizePromise(async function (key: EC_Public_JsonWebKey) { - const hashPair = `10198a2f-205f-45a6-9987-3488c80113d0` - const N = 2 - - const jwk = JSON.stringify(key) - const hash = (await Gun.SEA.work(jwk, hashPair))! - return hash.substring(0, N) -}, undefined) - -/** - * @param key - The key need to be hashed - */ -export const hashCryptoKey = memoizePromise(async function (key: EC_Public_JsonWebKey) { - const hashPair = `10198a2f-205f-45a6-9987-3488c80113d0` - const N = 2 - - const jwk = key - if (!jwk.x || !jwk.y) throw new Error('Invalid key') - const hash = (await Gun.SEA.work(jwk.x! + jwk.y!, hashPair))! - return hash.substring(0, N) -}, undefined) diff --git a/packages/mask/src/network/gun/version.2/index.ts b/packages/mask/src/network/gun/version.2/index.ts deleted file mode 100644 index 1d0f584f809b..000000000000 --- a/packages/mask/src/network/gun/version.2/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import './gun.patch' -import 'gun/sea' -import 'gun/lib/radix' -import 'gun/lib/radisk' -import 'gun/lib/store' -import 'gun/lib/rindexed' - -export * from './post' -export * from './gun2' diff --git a/packages/mask/src/network/gun/version.2/post.ts b/packages/mask/src/network/gun/version.2/post.ts deleted file mode 100644 index bbaeac95a09e..000000000000 --- a/packages/mask/src/network/gun/version.2/post.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { gun2, SharedAESKeyGun2 } from './gun2' -import { hashPostSalt, hashCryptoKey, hashCryptoKeyUnstable, calculatePostKeyPartition } from './hash' -import type { PublishedAESKeyRecordV39OrV38 } from '../../../crypto/crypto-alpha-38' -import type { EC_Public_JsonWebKey } from '@masknet/shared-base' -import { EventIterator } from 'event-iterator' - -/** - * Query all possible keys stored on the gun - * @param version current payload version - * @param postIV Post iv - * @param partitionByCryptoKey Public key of the current user (receiver) - */ -export async function queryPostKeysOnGun2( - version: -39 | -38, - postIV: string, - partitionByCryptoKey: EC_Public_JsonWebKey, - networkHint: string, -): Promise { - const { postHash, keyHash } = await calculatePostKeyPartition(version, postIV, partitionByCryptoKey, networkHint) - - // ? here we get the internal node names of gun2[postHash][keyHash] - // ? where gun2[postHash][keyHash] is a list - const internalNodes: Record = - (await gun2 - .get(postHash) - // @ts-ignore - .get(keyHash).then!()) || {} - /* cspell:disable-next-line */ - // ? In this step we get something like ["jzarhbyjtexiE7aB1DvQ", "jzarhuse6xlTAtblKRx9"] - const internalKeys = Object.keys(internalNodes).filter((x) => x !== '_') - // ? In this step we get all keys in this category (gun2[postHash][keyHash]) - const resultPromise = internalKeys.map((key) => - gun2 - // @ts-expect-error - .get(key).then!(), - ) - const result = (await Promise.all(resultPromise)) as SharedAESKeyGun2[] - console.info(`await gun2[${postHash}][${keyHash}]\n`, result) - return result -} - -/** - * Listen on the changes of all possible keys on the gun - * @param version current payload version - * @param postSalt Post iv - * @param partitionByCryptoKey Public key of the current user (receiver) - */ -export async function* subscribePostKeysOnGun2( - version: -39 | -38, - postSalt: string, - partitionByCryptoKey: EC_Public_JsonWebKey, - networkHint: string, -) { - const iter = new EventIterator(({ push: callback }) => { - hashPostSalt(postSalt, networkHint).then((postHash) => { - // In version > -39, we will use stable hash to prevent unstable result for key hashing - ;(version <= -39 ? hashCryptoKeyUnstable : hashCryptoKey)(partitionByCryptoKey).then((keyHash) => { - gun2.get(postHash) - // @ts-ignore - .get(keyHash) - .map() - // @ts-ignore - .on((data: SharedAESKeyGun2) => { - // @ts-ignore - const { _, ...data2 } = Object.assign({}, data) - callback(data2) - }) - }) - }) - }) - yield* iter -} - -/** - * Publish post keys on the gun - * @param version current payload - * @param postSalt Post iv - * @param receiversKeys Keys needs to publish - */ -export async function publishPostAESKeyOnGun2( - version: -39 | -38, - postSalt: string, - networkHint: string, - receiversKeys: PublishedAESKeyRecordV39OrV38[], -) { - const postHash = await hashPostSalt(postSalt, networkHint) - // Store AES key to gun - receiversKeys.forEach(async ({ aesKey, receiverKey }) => { - const keyHash = await (version <= -39 ? hashCryptoKeyUnstable : hashCryptoKey)(receiverKey) - console.log(`gun[${postHash}][${keyHash}].push(`, aesKey, `)`) - gun2.get(postHash) - // @ts-ignore - .get(keyHash) - // @ts-ignore - .set(aesKey) - }) -} diff --git a/packages/mask/src/network/gun/worker-implementation-subscribe.ts b/packages/mask/src/network/gun/worker-implementation-subscribe.ts deleted file mode 100644 index e9d7cf1b21e3..000000000000 --- a/packages/mask/src/network/gun/worker-implementation-subscribe.ts +++ /dev/null @@ -1 +0,0 @@ -export { subscribePostKeysOnGun2 } from './version.2/post' diff --git a/packages/mask/src/network/gun/worker-implementation.ts b/packages/mask/src/network/gun/worker-implementation.ts deleted file mode 100644 index 3f8ff74076c4..000000000000 --- a/packages/mask/src/network/gun/worker-implementation.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { queryVersion1PostAESKey, getVersion1PostByHash } from './version.1' -export { publishPostAESKeyOnGun2, queryPostKeysOnGun2 } from './version.2' diff --git a/packages/mask/src/network/gun/worker.ts b/packages/mask/src/network/gun/worker.ts deleted file mode 100644 index a59bff8ee9fc..000000000000 --- a/packages/mask/src/network/gun/worker.ts +++ /dev/null @@ -1,13 +0,0 @@ -import './gun-worker.patch' -import { AsyncGeneratorCall, AsyncCall, AsyncCallOptions } from 'async-call-rpc/full' -import { WorkerChannel } from 'async-call-rpc/utils/web/worker' -import { serializer } from '@masknet/shared-base' -import * as Impl from './worker-implementation' -import * as Impl2 from './worker-implementation-subscribe' -const options: AsyncCallOptions = { - channel: new WorkerChannel(), - strict: { methodNotFound: false }, - serializer, -} -AsyncCall(Impl, options) -AsyncGeneratorCall(Impl2, options) diff --git a/packages/mask/src/plugins/Avatar/Services/gun.ts b/packages/mask/src/plugins/Avatar/Services/gun.ts index 8397eeba4150..763cd4edeaa7 100644 --- a/packages/mask/src/plugins/Avatar/Services/gun.ts +++ b/packages/mask/src/plugins/Avatar/Services/gun.ts @@ -1,45 +1,30 @@ import { delay } from '@masknet/shared-base' -import { gun2 } from '../../../network/gun/version.2' -import { NFT_AVATAR_GUN_SERVER } from '../constants' - -const NFTAvatarGUN = gun2.get(NFT_AVATAR_GUN_SERVER) +import { getGunInstance } from '../../../../background/network/gun/instance' +import { getGunData, setGunData } from '../../../../background/network/gun/utils' +import { NFT_AVATAR_GUN_ROOT_NODE } from '../constants' // After reinstalling the system, it cannot be retrieved for the first time, so it needs to be taken twice export async function getUserAddress(userId: string) { - let result = await NFTAvatarGUN - //@ts-expect-error - .get(userId).then!() + let result = await getGunData(getGunInstance(), NFT_AVATAR_GUN_ROOT_NODE, userId) if (!result) { await delay(500) - result = await NFTAvatarGUN - //@ts-expect-error - .get(userId).then!() + result = await getGunData(getGunInstance(), NFT_AVATAR_GUN_ROOT_NODE, userId) } - return result + return result as any as string } export async function setUserAddress(userId: string, address: string) { - // delete userId - await NFTAvatarGUN - //@ts-expect-error - .get(userId) - //@ts-expect-error - .put(null).then!() - // save userId - await NFTAvatarGUN - // @ts-expect-error - .get(userId) - // @ts-expect-error - .put(address).then!() + setGunData(getGunInstance(), [NFT_AVATAR_GUN_ROOT_NODE, userId], address) } export async function getUserAddresses() { - const NFTAvatarKeys = Object.keys(await NFTAvatarGUN).filter((x) => x !== '_') + // TODO: + const NFTAvatarKeys = Object.keys([]).filter((x) => x !== '_') const resultPromise = NFTAvatarKeys.map((key) => getUserAddress(key)) const result = (await Promise.all(resultPromise)).filter((x) => x) - return result + return result as any as string[] } diff --git a/packages/mask/src/plugins/Avatar/constants.ts b/packages/mask/src/plugins/Avatar/constants.ts index 323951389f18..7ba9c23aeca4 100644 --- a/packages/mask/src/plugins/Avatar/constants.ts +++ b/packages/mask/src/plugins/Avatar/constants.ts @@ -1,6 +1,6 @@ export const RSS3_APP = 'https://hub.pass3.me' export const NFT_AVATAR_JSON_SERVER = 'https://configuration.r2d2.to/com.maskbook.avatar.json' -export const NFT_AVATAR_GUN_SERVER = 'com.maskbook.user' +export const NFT_AVATAR_GUN_ROOT_NODE = 'com.maskbook.user' export const NFT_CONTRACT_JSON_VERIFIED_SERVER = 'https://configuration.r2d2.to/com.maskbook.verified_nft.json' export const PLUGIN_ID = 'com.maskbook.avatar' diff --git a/packages/mask/src/plugins/Polls/Services.ts b/packages/mask/src/plugins/Polls/Services.ts index f1b746476e6b..dea2f2347d31 100644 --- a/packages/mask/src/plugins/Polls/Services.ts +++ b/packages/mask/src/plugins/Polls/Services.ts @@ -1,17 +1,6 @@ import { first } from 'lodash-unified' import '../../network/gun/gun-worker.patch' -import { gun2 } from '../../network/gun/version.2' import type { PollMetaData } from './types' -import { PollGunServer } from './constants' -import { WorkerChannel } from 'async-call-rpc/utils/web/worker' -import { AsyncCall } from 'async-call-rpc' - -import * as self from './Services' -setTimeout(() => { - AsyncCall(self, { channel: new WorkerChannel() }) -}, 0) - -const PollGun = gun2.get(PollGunServer) const defaultPoll: PollGunDB = { key: '', @@ -39,22 +28,7 @@ export async function createNewPoll(poll: NewPollProps) { const resultsObj = { ...results } const optionsObj = { ...options } - const poll_item = { - ...poll, - results: resultsObj, - options: optionsObj, - start_time: start_time.getTime(), - end_time: end_time.getTime(), - } - - // @ts-ignore - const key = `${id}_${Gun.time.is()}_${Gun.text.random(4)}` - - await PollGun - // @ts-ignore - .get(key) - // @ts-ignore - .put(poll_item).then!() + // TODO: return poll } @@ -68,31 +42,15 @@ interface voteProps { export async function vote(props: voteProps) { const { poll, index } = props - let results = [0, 0] - PollGun - // @ts-ignore - .get(poll.key) - // @ts-ignore - .get('results') - .on((item) => { - // @ts-ignore - delete item._ - results = Object.values(item) - }) + const results = [0, 0] + + // TODO: const count = results[index] + 1 const newResults = { ...results, [index]: count, } - PollGun - // @ts-ignore - .get(poll.key) - // @ts-ignore - .get('results') - // @ts-ignore - .put(newResults) - return { ...poll, results: Object.values(newResults), @@ -101,59 +59,19 @@ export async function vote(props: voteProps) { export async function getPollByKey(props: { key: string }) { const keys = props.key.split('_') - let poll: PollGunDB = { + const poll: PollGunDB = { ...defaultPoll, key: props.key, id: first(keys), } - PollGun - // @ts-ignore - .get(props.key) - // @ts-ignore - .on((data: PollGunDB) => { - poll = { - ...poll, - sender: data.sender, - question: data.question, - start_time: data.start_time, - end_time: data.end_time, - } - if (data.options) { - PollGun - // @ts-ignore - .get(props.key) - // @ts-ignore - .get('options') - .on((options) => { - // @ts-ignore - delete options._ - poll.options = Object.values(options) - }) - } - if (data.results) { - PollGun - // @ts-ignore - .get(props.key) - // @ts-ignore - .get('results') - .on((results) => { - // @ts-ignore - delete results._ - poll.results = Object.values(results) - }) - } - }) + // TODO: return poll } export async function getAllExistingPolls() { const polls: PollGunDB[] = [] - PollGun.map().on(async (data, key) => { - const poll = await getPollByKey({ key }) - polls.push(poll) - }) - + // TODO: return polls } diff --git a/packages/mask/src/plugins/Polls/constants.ts b/packages/mask/src/plugins/Polls/constants.ts index aa4fe5b5b417..c43112d55d7d 100644 --- a/packages/mask/src/plugins/Polls/constants.ts +++ b/packages/mask/src/plugins/Polls/constants.ts @@ -1,7 +1,7 @@ export const pluginName = 'Poll' export const identifier = 'com.maskbook.poll' export const POLL_META_KEY_1 = 'com.maskbook.poll:1' -export const PollGunServer = 'com.maskbook.plugin.polls' +export const PollGunRootNode = 'com.maskbook.plugin.polls' export const PLUGIN_META_KEY = 'com.maskbook.poll:1' export const PLUGIN_ID = 'com.maskbook.poll' From ab5afe843ff48048d1653bb0f7b0b60e782e326d Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 29 Dec 2021 17:49:28 +0800 Subject: [PATCH 12/85] feat: impl image decode --- .../src/encryption/DecryptionTypes.ts | 4 +++- packages/encryption/src/index.ts | 1 + .../background/services/crypto/decryption.ts | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/encryption/src/encryption/DecryptionTypes.ts b/packages/encryption/src/encryption/DecryptionTypes.ts index 763a06227f8d..6fd3336dba68 100644 --- a/packages/encryption/src/encryption/DecryptionTypes.ts +++ b/packages/encryption/src/encryption/DecryptionTypes.ts @@ -99,7 +99,7 @@ export interface DecryptSuccess { type: DecryptProgressKind.Success content: TypedMessage } -enum ErrorReasons { +export enum ErrorReasons { PayloadBroken = '[@masknet/encryption] Payload is broken.', PayloadDecryptedButTypedMessageBroken = "[@masknet/encryption] Payload decrypted, but it's inner TypedMessage is broken.", CannotDecryptAsAuthor = '[@masknet/encryption] Failed decrypt as the author of this payload.', @@ -107,6 +107,8 @@ enum ErrorReasons { AuthorPublicKeyNotFound = "[@masknet/encryption] Cannot found author's public key", PrivateKeyNotFound = '[@masknet/encryption] Cannot continue to decrypt because there is no private key found.', NotShareTarget = '[@masknet/encryption] No valid key is found. Likely this post is not shared with you', + // Not used in this library. + UnrecognizedAuthor = '[@masknet/encryption] No author is recognized which is required for the image steganography decoding.', } export class DecryptError extends Error { static Reasons = ErrorReasons diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index 4845b7ae46c5..739dfec3c952 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -15,6 +15,7 @@ export { decrypt, DecryptError, DecryptProgressKind, + ErrorReasons, type DecryptOptions, type DecryptIO, type DecryptEphemeralECDH_PostKey, diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 679349515657..755cf7705a22 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -8,6 +8,10 @@ import { SocialNetworkEnum, SocialNetworkEnumToProfileDomain, AsymmetryCryptoKey, + socialNetworkDecoder, + steganographyDecodeImageUrl, + DecryptError, + ErrorReasons, } from '@masknet/encryption' import { AESCryptoKey, @@ -37,7 +41,7 @@ export interface DecryptionContext { } export type SocialNetworkEncodedPayload = | { type: 'text'; text: string } - | { type: 'image'; image: Uint8Array } + // | { type: 'image'; image: Uint8Array } | { type: 'image-url'; url: string } export async function* decryptionWithSocialNetworkDecoding( @@ -45,6 +49,18 @@ export async function* decryptionWithSocialNetworkDecoding( context: DecryptionContext, ) { let decoded!: string | Uint8Array + if (encoded.type === 'text') { + decoded = socialNetworkDecoder(context.currentSocialNetwork, encoded.text).unwrapOr(encoded.text) + } else if (encoded.type === 'image-url') { + if (!context.authorHint || context.authorHint.isUnknown) { + yield new DecryptError(ErrorReasons.UnrecognizedAuthor, undefined) + return + } + decoded = await steganographyDecodeImageUrl(encoded.url, { + pass: context.authorHint.toText(), + downloadImage: (url) => fetch(url).then((x) => x.arrayBuffer()), + }) + } return yield* decryption(decoded, context) } export async function* decryption(payload: string | Uint8Array, context: DecryptionContext) { From cf843c022b077a4ddf4501ed46f43f546d3e2ad6 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Fri, 31 Dec 2021 13:45:30 +0800 Subject: [PATCH 13/85] chore: wip --- .../DecryptedPost/DecryptedPost.tsx | 187 ++---------------- .../mask/src/extension/service-generator.ts | 1 + 2 files changed, 12 insertions(+), 176 deletions(-) diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index c425ff8f0179..da4abedf27ec 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -1,55 +1,17 @@ -import { Fragment, useEffect, useMemo, useReducer, useState } from 'react' -import { - delay, - makeTypedMessageTuple, - TypedMessageTuple, - type ProfileIdentifier, - type Payload, -} from '@masknet/shared-base' +import { useMemo } from 'react' +import { delay, TypedMessageTuple, type ProfileIdentifier } from '@masknet/shared-base' import { or } from '@masknet/theme' -import { unreachable } from '@dimensiondev/kit' -import { ServicesWithProgress } from '../../../extension/service' import type { Profile } from '../../../database' import type { DecryptionProgress, FailureDecryption, SuccessDecryption, } from '../../../extension/background-script/CryptoServices/decryptFrom' -import { DecryptPostSuccess, DecryptPostSuccessProps } from './DecryptedPostSuccess' -import { DecryptPostAwaiting, DecryptPostAwaitingProps } from './DecryptPostAwaiting' -import { DecryptPostFailed, DecryptPostFailedProps } from './DecryptPostFailed' -import { usePostClaimedAuthor, usePostInfoDetails, usePostInfoSharedPublic } from '../../DataSource/usePostInfo' -import { asyncIteratorWithResult } from '../../../utils/type-transform/asyncIteratorHelpers' -import { usePostInfo } from '../../../components/DataSource/usePostInfo' - -function progressReducer( - state: { key: string; progress: SuccessDecryption | FailureDecryption | DecryptionProgress }[], - payload: { - type: 'refresh' - key: string - progress: SuccessDecryption | FailureDecryption | DecryptionProgress - }, -) { - const { key, progress } = payload - const currentProgressIndex = state.findIndex((x) => x.key === key) - if (currentProgressIndex === -1) { - return [ - ...state, - { - key, - progress, - }, - ] - } - const currentProgress = state[currentProgressIndex].progress - if (currentProgress && currentProgress.type !== 'progress' && progress.type === 'progress') return state - state[currentProgressIndex] = { - key, - progress, - } - return [...state] -} +import { DecryptPostSuccess } from './DecryptedPostSuccess' +import { DecryptPostAwaiting } from './DecryptPostAwaiting' +import { DecryptPostFailed } from './DecryptPostFailed' +import { usePostClaimedAuthor, usePostInfoDetails } from '../../DataSource/usePostInfo' export interface DecryptPostProps { onDecrypted: (post: TypedMessageTuple) => void @@ -57,25 +19,12 @@ export interface DecryptPostProps { profiles: Profile[] alreadySelectedPreviously: Profile[] requestAppendRecipients?(to: Profile[]): Promise - successComponent?: React.ComponentType - successComponentProps?: Partial - waitingComponent?: React.ComponentType - waitingComponentProps?: Partial - failedComponent?: React.ComponentType - failedComponentProps?: Partial } export function DecryptPost(props: DecryptPostProps) { const { whoAmI, profiles, alreadySelectedPreviously, onDecrypted } = props - const deconstructedPayload = usePostInfoDetails.postPayload() const authorInPayload = usePostClaimedAuthor() - const current = usePostInfo()! const currentPostBy = usePostInfoDetails.postBy() - const decryptedPayloadForImage = usePostInfoDetails.decryptedPayloadForImage() const postBy = or(authorInPayload, currentPostBy) - const postMetadataImages = usePostInfoDetails.postMetadataImages() - const Success = props.successComponent || DecryptPostSuccess - const Awaiting = props.waitingComponent || DecryptPostAwaiting - const Failed = props.failedComponent || DecryptPostFailed const requestAppendRecipientsWrapped = useMemo(() => { if (!postBy.equals(whoAmI)) return undefined @@ -86,147 +35,33 @@ export function DecryptPost(props: DecryptPostProps) { } }, [props.requestAppendRecipients, postBy, whoAmI]) - //#region Debug info - const [debugHash, setDebugHash] = useState('Unknown') - //#endregion - - //#region Progress - const [progress, dispatch] = useReducer(progressReducer, []) - //#endregion - - //#region decrypt - - // pass 1: - // decrypt post content and image attachments - const decryptedPayloadForImageAlpha38 = decryptedPayloadForImage?.version === -38 ? decryptedPayloadForImage : null - const sharedPublic = usePostInfoSharedPublic() || decryptedPayloadForImageAlpha38?.sharedPublic || false - - useEffect(() => { - const signal = new AbortController() - async function makeProgress( - key: string, - decryptionProcess: ReturnType, - ) { - const refreshProgress = (progress: SuccessDecryption | FailureDecryption | DecryptionProgress) => - dispatch({ - type: 'refresh', - key, - progress, - }) - for await (const process of asyncIteratorWithResult(decryptionProcess)) { - if (signal.signal.aborted) - return decryptionProcess.return?.({ type: 'error', internal: true, error: 'aborted' }) - if (process.done) { - if (process.value.type === 'success') { - current.iv.value = process.value.iv - current.decryptedPayloadForImage.value = process.value.decryptedPayloadForImage - } - return refreshProgress(process.value) - } - const status = process.value - if (status.type === 'debug') { - switch (status.debug) { - case 'debug_finding_hash': - setDebugHash(status.hash.join('-')) - break - default: - unreachable(status.debug) - } - } else refreshProgress(status) - if (status.type === 'progress') { - if (status.progress === 'intermediate_success') refreshProgress(status.data) - else if (status.progress === 'iv_decrypted') current.iv.value = status.iv - else if (status.progress === 'payload_decrypted') - current.decryptedPayloadForImage.value = status.decryptedPayloadForImage - } - } - } - - const postURL = current.url.getCurrentValue()?.toString() - if (deconstructedPayload.ok) - makeProgress( - 'post text', - ServicesWithProgress.decryptFromText(deconstructedPayload.val, postBy, whoAmI.network, whoAmI, postURL), - ) - postMetadataImages.forEach((url) => { - if (signal.signal.aborted) return - makeProgress(url, ServicesWithProgress.decryptFromImageUrl(url, postBy, whoAmI.network, whoAmI, postURL)) - }) - return () => signal.abort() - }, [ - current.iv, - deconstructedPayload.ok, - (deconstructedPayload.val as Payload)?.encryptedText, - postBy.toText(), - postMetadataImages.join(), - whoAmI.toText(), - ]) - - // pass 2: - // decrypt rest attachments which depend on post content - // const decryptedPostContent = progress.find((p) => p.key === postContent) - // useEffect(() => { - // if (decryptedPostContent?.progress.type !== 'success') return - // // TODO: - // // decrypt shuffled image here - // }, [decryptedPostContent]) - - // pass 3: - // invoke callback - const firstSucceedDecrypted = progress.find((p) => p.progress.type === 'success') - useEffect(() => { - if (firstSucceedDecrypted?.progress.type !== 'success') return - onDecrypted(makeTypedMessageTuple([firstSucceedDecrypted.progress.content])) - }, [firstSucceedDecrypted, onDecrypted]) - //#endregion - - // it's not a secret post - if (!deconstructedPayload.ok && progress.every((x) => x.progress.internal)) return null - return ( - <> - {progress - // the internal progress should not display to the end-user - .filter(({ progress }) => !progress.internal) - .map(({ progress }, index) => ( - {renderProgress(progress)} - ))} - - ) + return <>In progress... function renderProgress(progress: SuccessDecryption | FailureDecryption | DecryptionProgress) { const render = () => { switch (progress.type) { case 'success': return ( - ) case 'error': return ( - ) case 'progress': - return ( - - ) + return default: return null } diff --git a/packages/mask/src/extension/service-generator.ts b/packages/mask/src/extension/service-generator.ts index d49953d0cdd9..4b378c7a13c8 100644 --- a/packages/mask/src/extension/service-generator.ts +++ b/packages/mask/src/extension/service-generator.ts @@ -1 +1,2 @@ export { decryptFromText, decryptFromImageUrl } from './background-script/CryptoServices/decryptFrom' +export { decryptionWithSocialNetworkDecoding } from '../../background/services/crypto/decryption' From fdc390b290d10d73e37d70d73babb831bfe2e88c Mon Sep 17 00:00:00 2001 From: Jack Works Date: Fri, 31 Dec 2021 15:04:16 +0800 Subject: [PATCH 14/85] feat: make it work --- .../background/services/crypto/decryption.ts | 13 +-- .../DecryptedPost/DecryptedPost.tsx | 89 +++++++------------ .../InjectedComponents/PostInspector.tsx | 19 +--- packages/mask/src/plugins/Polls/Services.ts | 1 - 4 files changed, 44 insertions(+), 78 deletions(-) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 755cf7705a22..0e72731ed520 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -56,10 +56,13 @@ export async function* decryptionWithSocialNetworkDecoding( yield new DecryptError(ErrorReasons.UnrecognizedAuthor, undefined) return } - decoded = await steganographyDecodeImageUrl(encoded.url, { - pass: context.authorHint.toText(), - downloadImage: (url) => fetch(url).then((x) => x.arrayBuffer()), - }) + decoded = socialNetworkDecoder( + context.currentSocialNetwork, + await steganographyDecodeImageUrl(encoded.url, { + pass: context.authorHint.toText(), + downloadImage: (url) => fetch(url).then((x) => x.arrayBuffer()), + }), + ).unwrapOr('') } return yield* decryption(decoded, context) } @@ -179,7 +182,7 @@ async function storeAuthorPublicKey( pub: AsymmetryCryptoKey, ) { if (payloadAuthor.equals(postAuthor)) { - if (pub.algr !== PublicKeyAlgorithmEnum.ed25519) { + if (pub.algr !== PublicKeyAlgorithmEnum.secp256k1) { throw new Error('TODO: support other curves') } // TODO: store the key diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index da4abedf27ec..c47424452089 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -1,17 +1,11 @@ -import { useMemo } from 'react' -import { delay, TypedMessageTuple, type ProfileIdentifier } from '@masknet/shared-base' -import { or } from '@masknet/theme' +import { useEffect } from 'react' +import { TypedMessageTuple, ProfileIdentifier, extractTextFromTypedMessage } from '@masknet/shared-base' import type { Profile } from '../../../database' -import type { - DecryptionProgress, - FailureDecryption, - SuccessDecryption, -} from '../../../extension/background-script/CryptoServices/decryptFrom' -import { DecryptPostSuccess } from './DecryptedPostSuccess' -import { DecryptPostAwaiting } from './DecryptPostAwaiting' -import { DecryptPostFailed } from './DecryptPostFailed' -import { usePostClaimedAuthor, usePostInfoDetails } from '../../DataSource/usePostInfo' +import { usePostInfoDetails } from '@masknet/plugin-infra' +import { ServicesWithProgress } from '../../../extension/service' +import { SocialNetworkEnum } from '@masknet/encryption' +import type { SocialNetworkEncodedPayload } from '../../../../background/services/crypto/decryption' export interface DecryptPostProps { onDecrypted: (post: TypedMessageTuple) => void @@ -21,53 +15,34 @@ export interface DecryptPostProps { requestAppendRecipients?(to: Profile[]): Promise } export function DecryptPost(props: DecryptPostProps) { - const { whoAmI, profiles, alreadySelectedPreviously, onDecrypted } = props - const authorInPayload = usePostClaimedAuthor() - const currentPostBy = usePostInfoDetails.postBy() - const postBy = or(authorInPayload, currentPostBy) + const { whoAmI } = props + const raw = extractTextFromTypedMessage(usePostInfoDetails.postMessage()) + const image = usePostInfoDetails.postMetadataImages() + const author = usePostInfoDetails.postBy() + const url = usePostInfoDetails.url() + console.log(raw.unwrapOr(''), image) - const requestAppendRecipientsWrapped = useMemo(() => { - if (!postBy.equals(whoAmI)) return undefined - if (!props.requestAppendRecipients) return undefined - return async (people: Profile[]) => { - await props.requestAppendRecipients!(people) - await delay(1500) + useEffect(() => { + const abort = new AbortController() + async function main() { + const x: SocialNetworkEncodedPayload = image.length + ? { type: 'image-url', url: image.at(0)! } + : { type: 'text', text: raw.unwrapOr('') } + const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(x, { + authorHint: author, + currentProfile: whoAmI, + currentSocialNetwork: SocialNetworkEnum.Twitter, + postURL: url?.toString(), + }) + for await (const x of process) { + if (abort.signal.aborted) return + console.log(x, 1) + } } - }, [props.requestAppendRecipients, postBy, whoAmI]) + main() - return <>In progress... + return () => abort.abort() + }) - function renderProgress(progress: SuccessDecryption | FailureDecryption | DecryptionProgress) { - const render = () => { - switch (progress.type) { - case 'success': - return ( - - ) - case 'error': - return ( - - ) - case 'progress': - return - default: - return null - } - } - const rendered = render() - if (!rendered) return null - return rendered - } + return <>In progress... } diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index 45099a314d08..d0e023836f66 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useMemo } from 'react' import { useAsync } from 'react-use' -import { DecryptPost, DecryptPostProps } from './DecryptedPost/DecryptedPost' -import { AddToKeyStore, AddToKeyStoreProps } from './AddToKeyStore' +import { DecryptPost } from './DecryptedPost/DecryptedPost' +import { AddToKeyStore } from './AddToKeyStore' import Services from '../../extension/service' import { ProfileIdentifier, @@ -20,10 +20,6 @@ const PluginHooksRenderer = createInjectHooksRenderer(useActivatedPluginsSNSAdap export interface PostInspectorProps { onDecrypted(post: TypedMessageTuple): void needZip(): void - DecryptPostProps?: Partial - DecryptPostComponent?: React.ComponentType - AddToKeyStoreProps?: Partial - AddToKeyStoreComponent?: React.ComponentType /** @default 'before' */ slotPosition?: 'before' | 'after' } @@ -31,7 +27,6 @@ export function PostInspector(props: PostInspectorProps) { const postBy = usePostInfoDetails.postBy() const postContent = usePostInfoDetails.postContent() const encryptedPost = usePostInfoDetails.postPayload() - const postId = usePostInfoDetails.postIdentifier() const decryptedPayloadForImage = usePostInfoDetails.decryptedPayloadForImage() const postImages = usePostInfoDetails.postMetadataImages() const whoAmI = useCurrentIdentity() @@ -48,9 +43,8 @@ export function PostInspector(props: PostInspectorProps) { if (encryptedPost.ok || postImages.length) { props.needZip() - const DecryptPostX = props.DecryptPostComponent || DecryptPost return withAdditionalContent( - , ) } else if (provePost.length) { - const AddToKeyStoreX = props.AddToKeyStoreComponent || AddToKeyStore - if (!AddToKeyStoreX) return null - return withAdditionalContent( - , - ) + return withAdditionalContent() } return withAdditionalContent(null) function withAdditionalContent(x: JSX.Element | null) { diff --git a/packages/mask/src/plugins/Polls/Services.ts b/packages/mask/src/plugins/Polls/Services.ts index dea2f2347d31..2ceb2df3bb34 100644 --- a/packages/mask/src/plugins/Polls/Services.ts +++ b/packages/mask/src/plugins/Polls/Services.ts @@ -1,5 +1,4 @@ import { first } from 'lodash-unified' -import '../../network/gun/gun-worker.patch' import type { PollMetaData } from './types' const defaultPoll: PollGunDB = { From fba66ac88d461d12219fe8d651f28b799e4977bb Mon Sep 17 00:00:00 2001 From: Jack Works Date: Fri, 31 Dec 2021 15:45:41 +0800 Subject: [PATCH 15/85] feat: cleanup old things --- .../src/components/DataSource/usePostInfo.ts | 2 +- .../DecryptedPost/DecryptedPost.tsx | 6 +- .../InjectedComponents/PostComments.tsx | 3 +- .../InjectedComponents/PostInspector.tsx | 68 ++++++------------- .../defaults/inject/CommentBox.tsx | 2 +- .../defaults/inject/PostInspector.tsx | 4 +- .../utils/create-post-context.ts | 20 ------ packages/plugin-infra/src/PostContext.ts | 23 +------ 8 files changed, 27 insertions(+), 101 deletions(-) diff --git a/packages/mask/src/components/DataSource/usePostInfo.ts b/packages/mask/src/components/DataSource/usePostInfo.ts index 51e502f65e66..43fdc666e360 100644 --- a/packages/mask/src/components/DataSource/usePostInfo.ts +++ b/packages/mask/src/components/DataSource/usePostInfo.ts @@ -2,7 +2,7 @@ import { activatedSocialNetworkUI } from '../../social-network' import { usePostInfoDetails } from '@masknet/plugin-infra' import type { ProfileIdentifier } from '@masknet/shared-base' -export { usePostInfo, PostInfoProvider, usePostInfoDetails, usePostInfoSharedPublic } from '@masknet/plugin-infra' +export { usePostInfo, PostInfoProvider, usePostInfoDetails } from '@masknet/plugin-infra' export function usePostLink() { const id = usePostInfoDetails.snsID() const identifier = usePostInfoDetails.identifier() diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index c99a6eec443c..0ee975ce8013 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -17,16 +17,14 @@ export interface DecryptPostProps { export function DecryptPost(props: DecryptPostProps) { const { whoAmI } = props const raw = extractTextFromTypedMessage(usePostInfoDetails.rawMessage()) - const image = usePostInfoDetails.postMetadataImages() const author = usePostInfoDetails.author() const url = usePostInfoDetails.url() - console.log(raw.unwrapOr(''), image) useEffect(() => { const abort = new AbortController() async function main() { - const x: SocialNetworkEncodedPayload = image.length - ? { type: 'image-url', url: image.at(0)! } + const x: SocialNetworkEncodedPayload = [].length + ? { type: 'image-url', url: [].at(0)! } : { type: 'text', text: raw.unwrapOr('') } const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(x, { authorHint: author, diff --git a/packages/mask/src/components/InjectedComponents/PostComments.tsx b/packages/mask/src/components/InjectedComponents/PostComments.tsx index b2fef7e58902..a2444c758e23 100644 --- a/packages/mask/src/components/InjectedComponents/PostComments.tsx +++ b/packages/mask/src/components/InjectedComponents/PostComments.tsx @@ -52,7 +52,8 @@ export function PostComment(props: PostCommentProps) { const comment = useValueRef(props.comment) const postContent = usePostInfoDetails.rawMessagePiped() const containingPayload = usePostInfoDetails.containingMaskPayload() - const iv = usePostInfoDetails.iv() + // TODO + const iv = null as string | null const postIV = containingPayload.map((x) => x.iv).unwrapOr(iv) const dec = useAsync(async () => { diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index cff6615b034e..249ca5d07f9b 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -1,18 +1,11 @@ -import { useState, useEffect, useMemo } from 'react' +import { useState, useEffect } from 'react' import { useAsync } from 'react-use' import { DecryptPost } from './DecryptedPost/DecryptedPost' -import { AddToKeyStore } from './AddToKeyStore' import Services from '../../extension/service' -import { - ProfileIdentifier, - type TypedMessageTuple, - type PayloadAlpha40_Or_Alpha39, - type PayloadAlpha38, -} from '@masknet/shared-base' +import { ProfileIdentifier, type TypedMessageTuple } from '@masknet/shared-base' import type { Profile } from '../../database' import { useCurrentIdentity, useFriendsList } from '../DataSource/useActivatedUI' import { usePostInfoDetails } from '../DataSource/usePostInfo' -import { decodePublicKeyUI } from '../../social-network/utils/text-payload-ui' import { createInjectHooksRenderer, useActivatedPluginsSNSAdaptor } from '@masknet/plugin-infra' const PluginHooksRenderer = createInjectHooksRenderer(useActivatedPluginsSNSAdaptor, (plugin) => plugin.PostInspector) @@ -25,14 +18,10 @@ export interface PostInspectorProps { } export function PostInspector(props: PostInspectorProps) { const postBy = usePostInfoDetails.author() - const postContent = usePostInfoDetails.postContent() const encryptedPost = usePostInfoDetails.containingMaskPayload() - const decryptedPayloadForImage = usePostInfoDetails.decryptedPayloadForImage() - const postImages = usePostInfoDetails.postMetadataImages() const whoAmI = useCurrentIdentity() const friends = useFriendsList() const [alreadySelectedPreviously, setAlreadySelectedPreviously] = useState([]) - const provePost = useMemo(() => decodePublicKeyUI(postContent), [postContent]) const { value: sharedListOfPost } = useAsync(async () => { if (!whoAmI || !whoAmI.identifier.equals(postBy) || !encryptedPost.ok) return [] @@ -41,44 +30,25 @@ export function PostInspector(props: PostInspectorProps) { }, [postBy, whoAmI, encryptedPost]) useEffect(() => setAlreadySelectedPreviously(sharedListOfPost ?? []), [sharedListOfPost]) - if (encryptedPost.ok || postImages.length) { + if (encryptedPost.ok) { props.needZip() - return withAdditionalContent( - { - const val = (postImages ? decryptedPayloadForImage : encryptedPost.val) as - | PayloadAlpha40_Or_Alpha39 - | PayloadAlpha38 - - const { iv, version } = val - const ownersAESKeyEncrypted = - val.version === -38 ? val.AESKeyEncrypted : val.ownersAESKeyEncrypted - - setAlreadySelectedPreviously(alreadySelectedPreviously.concat(profile)) - return Services.Crypto.appendShareTarget( - version, - ownersAESKeyEncrypted, - iv, - profile.map((x) => x.identifier), - whoAmI!.identifier, - { type: 'direct', at: new Date() }, - ) - } - : undefined - } - alreadySelectedPreviously={alreadySelectedPreviously} - profiles={friends} - whoAmI={whoAmI ? whoAmI.identifier : ProfileIdentifier.unknown} - />, - ) - } else if (provePost.length) { - return withAdditionalContent() } - return withAdditionalContent(null) + return withAdditionalContent( + { + // TODO + }} + alreadySelectedPreviously={alreadySelectedPreviously} + profiles={friends} + whoAmI={whoAmI ? whoAmI.identifier : ProfileIdentifier.unknown} + />, + ) + // else if (provePost.length) { + // // TODO: + // return null + // // return withAdditionalContent() + // } function withAdditionalContent(x: JSX.Element | null) { const slot = encryptedPost.ok ? null : return ( diff --git a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx index 4a8f9893f5d8..c781fe304051 100644 --- a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx +++ b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx @@ -32,7 +32,7 @@ export const injectCommentBoxDefaultFactory = function ( const payload = usePostInfoDetails.containingMaskPayload() const postContent = usePostInfoDetails.rawMessagePiped() const { classes } = useCustomStyles() - const iv = usePostInfoDetails.iv() + const iv = null as null | string const props = additionPropsToCommentBox(classes) const onCallback = useCallback( async (content) => { diff --git a/packages/mask/src/social-network/defaults/inject/PostInspector.tsx b/packages/mask/src/social-network/defaults/inject/PostInspector.tsx index 4688fa46a6fc..838f58e86777 100644 --- a/packages/mask/src/social-network/defaults/inject/PostInspector.tsx +++ b/packages/mask/src/social-network/defaults/inject/PostInspector.tsx @@ -28,9 +28,7 @@ export function injectPostInspectorDefault( const jsx = ( { - current.rawMessagePiped.value = typed - }} + onDecrypted={() => {}} zipPost={() => zipPostF(current.rootElement)} {...current} /> diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index cab470ee8326..589311f048b3 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -124,28 +124,12 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct }), mentionedLinks: linksSubscribe, - postMetadataImages: - opt.postImagesProvider || - debug({ - getCurrentValue: () => [], - subscribe: () => () => {}, - }), postMetadataMentionedLinks: linksSubscribe, rawMessage: opt.rawMessage, rawMessagePiped: transformedPostContent, - postContent: SubscriptionFromValueRef(postContent), containingMaskPayload: SubscriptionFromValueRef(postPayload), - decryptedPayloadForImage: new ValueRef(null), - iv: new ValueRef(null), - publicShared: debug({ - getCurrentValue: () => - postPayload.value - .map((val) => val.version === -38 && val.sharedPublic) - .unwrapOr(undefined), - subscribe: (sub) => postPayload.addListener(sub), - }), } } } @@ -169,10 +153,6 @@ export function createRefsForCreatePostContext() { author: SubscriptionFromValueRef(postBy), snsID: SubscriptionFromValueRef(postID), rawMessage: SubscriptionFromValueRef(postMessage), - postImagesProvider: debug({ - getCurrentValue: () => [...postMetadataImages], - subscribe: (sub) => postMetadataImages.event.on(ALL_EVENTS, sub), - }), postMentionedLinksProvider: debug({ getCurrentValue: () => [...postMetadataMentionedLinks.values()], subscribe: (sub) => postMetadataMentionedLinks.event.on(ALL_EVENTS, sub), diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index 2e377f46a6d4..6f4f0aa0e53c 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -42,8 +42,6 @@ export interface PostContextCreation extends PostContextAuthor { * You don't need to provide this to resolve links in `PostContext.postContent`. */ readonly postMentionedLinksProvider?: Subscription - /** @deprecated It should parse image into rawMessage */ - readonly postImagesProvider?: Subscription /** * The raw TypedMessage that the SNS gives. */ @@ -63,8 +61,6 @@ export interface PostContext extends PostContextAuthor { readonly url: Subscription // Meta readonly mentionedLinks: Subscription - /** @deprecated It should appears in rawMessage */ - readonly postMetadataImages: Subscription /** @deprecated Use postMentionedLinks instead */ readonly postMetadataMentionedLinks: Subscription //#endregion @@ -72,19 +68,10 @@ export interface PostContext extends PostContextAuthor { readonly rawMessage: Subscription // TODO: should be a Subscription readonly rawMessagePiped: ValueRef - /** @deprecated Use postMessage or transformedPostContent (depends on your usage) instead */ - readonly postContent: Subscription //#endregion //#region Post payload discovered in the rawMessage readonly containingMaskPayload: Subscription> - /** @deprecated Use postPayload instead */ - readonly decryptedPayloadForImage: ValueRef - // TODO: should be a Subscription - readonly iv: ValueRef - /** - * undefined => payload not found - */ - readonly publicShared: Subscription + //#endregion } export type PostInfo = PostContext @@ -136,11 +123,3 @@ function isSubscription(x: any): x is Subscription { Boolean((x as Subscription).getCurrentValue && (x as Subscription).subscribe) ) } - -export function usePostInfoSharedPublic(): boolean { - const info = usePostInfoDetails.containingMaskPayload() - if (info.err) return false - const payload = info.val - if (payload.version !== -38) return false - return !!payload.sharedPublic -} From e39ef694ac0e26722eca0e09b6f0793c3f33e063 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Fri, 31 Dec 2021 18:46:51 +0800 Subject: [PATCH 16/85] chore: wip --- .../DecryptedPost/DecryptedPost.tsx | 21 +++++++++--- packages/shared-base/src/TypedMessage/core.ts | 8 +++-- .../src/TypedMessage/transforms/flatten.ts | 33 +++++++++++++++++++ .../src/TypedMessage/transforms/promise.ts | 23 +++++++++++++ .../src/TypedMessage/utils/index.ts | 22 ++++++++----- 5 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 packages/shared-base/src/TypedMessage/transforms/flatten.ts create mode 100644 packages/shared-base/src/TypedMessage/transforms/promise.ts diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index 0ee975ce8013..2d89b3fb7154 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -1,5 +1,12 @@ import { useEffect } from 'react' -import { TypedMessageTuple, ProfileIdentifier, extractTextFromTypedMessage } from '@masknet/shared-base' +import { + TypedMessageTuple, + ProfileIdentifier, + extractTextFromTypedMessage, + waitTypedMessage, + flattenTypedMessage, + extractImageFromTypedMessage, +} from '@masknet/shared-base' import type { Profile } from '../../../database' import { usePostInfoDetails } from '@masknet/plugin-infra' @@ -16,16 +23,20 @@ export interface DecryptPostProps { } export function DecryptPost(props: DecryptPostProps) { const { whoAmI } = props - const raw = extractTextFromTypedMessage(usePostInfoDetails.rawMessage()) + const raw = usePostInfoDetails.rawMessage() const author = usePostInfoDetails.author() const url = usePostInfoDetails.url() useEffect(() => { const abort = new AbortController() async function main() { - const x: SocialNetworkEncodedPayload = [].length - ? { type: 'image-url', url: [].at(0)! } - : { type: 'text', text: raw.unwrapOr('') } + const m = flattenTypedMessage(await waitTypedMessage(raw)) + const t = extractTextFromTypedMessage(m).unwrapOr('') + const i = extractImageFromTypedMessage(m).filter((x): x is string => typeof x === 'string') + console.log(m, t, i) + const x: SocialNetworkEncodedPayload = i.length + ? { type: 'image-url', url: i.at(0)! } + : { type: 'text', text: t } const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(x, { authorHint: author, currentProfile: whoAmI, diff --git a/packages/shared-base/src/TypedMessage/core.ts b/packages/shared-base/src/TypedMessage/core.ts index d2692cedadb1..e46700e3e935 100644 --- a/packages/shared-base/src/TypedMessage/core.ts +++ b/packages/shared-base/src/TypedMessage/core.ts @@ -41,7 +41,7 @@ export interface TypedMessageImageV1 extends NonSerializableTypedMessage { readonly width?: number readonly height?: number } -export const isTypedMessageImageV1 = createIsType('image', 1) +export const isTypedMessageImageV1 = createIsType('image') export const isTypedMessageImage = composeSome(isTypedMessageImageV1) as (x: TypedMessage) => x is TypedMessageImage export function makeTypedMessageImage( image: TypedMessageImageV1['image'], @@ -115,13 +115,15 @@ export function makeTypedMessageEmpty(meta?: Meta): TypedMessageEmpty { export interface TypedMessagePromise extends NonSerializableTypedMessage { readonly type: 'promise' readonly promise: Promise + readonly value?: T } export const isTypedMessagePromise = createIsType('promise') export function makeTypedMessagePromise( promise: Promise, - meta?: Meta, ): TypedMessagePromise { - return { type: 'promise', serializable: false, promise, meta } + const x: TypedMessagePromise = { type: 'promise', serializable: false, promise } + promise.then((y) => ((x as any).value = y)) + return x } //#endregion export const isWellKnownCoreTypedMessages = composeSome( diff --git a/packages/shared-base/src/TypedMessage/transforms/flatten.ts b/packages/shared-base/src/TypedMessage/transforms/flatten.ts new file mode 100644 index 000000000000..678a5d658790 --- /dev/null +++ b/packages/shared-base/src/TypedMessage/transforms/flatten.ts @@ -0,0 +1,33 @@ +import { + isTypedMessagePromise, + isTypedMessageText, + isTypedMessageTuple, + makeTypedMessageEmpty, + makeTypedMessageText, + makeTypedMessageTuple, + TypedMessage, +} from '..' +export function flattenTypedMessage(x: TypedMessage): TypedMessage { + // Promise are always dropped after resolve even it has meta. + if (isTypedMessagePromise(x) && x.value) return flattenTypedMessage(x.value) + if (isTypedMessageTuple(x)) { + if (x.meta) return x + if (x.items.length === 0) return makeTypedMessageEmpty() + if (x.items.length === 1) return flattenTypedMessage(x.items[0]) + const nextItem = x.items + .map(flattenTypedMessage) + .flatMap((x) => (isTypedMessageTuple(x) && !x.meta ? x.items : x)) + .reduce((prev, current) => { + const lastItem = prev.at(-1) + if (!lastItem || lastItem.meta || current.meta) return prev.concat(current) + if (!isTypedMessageText(current) || !isTypedMessageText(lastItem)) return prev.concat(current) + // Only concat when last one and current one are both text and have no meta. + prev.pop() + prev.push(makeTypedMessageText(`${lastItem.content} ${current.content}`)) + return prev + }, []) + if (nextItem.length === 1) return nextItem[0] + return makeTypedMessageTuple(nextItem) + } + return x +} diff --git a/packages/shared-base/src/TypedMessage/transforms/promise.ts b/packages/shared-base/src/TypedMessage/transforms/promise.ts new file mode 100644 index 000000000000..6fb6be512498 --- /dev/null +++ b/packages/shared-base/src/TypedMessage/transforms/promise.ts @@ -0,0 +1,23 @@ +import { isTypedMessagePromise, isTypedMessageTuple, TypedMessage } from '..' +import { flattenTypedMessage } from './flatten' + +export function hasPromise(x: TypedMessage) { + if (isTypedMessagePromise(x)) return true + if (isTypedMessageTuple(x)) return x.items.some(hasPromise) + return false +} + +export async function waitTypedMessage(x: TypedMessage): Promise { + if (!hasPromise(x)) return x + const promise = collectPromise(x) + await Promise.allSettled(promise) + return flattenTypedMessage(x) +} + +function collectPromise(x: TypedMessage, result: Promise[] = []): Promise[] { + if (isTypedMessagePromise(x)) return result.concat(x.promise) + if (isTypedMessageTuple(x)) { + return result.concat(x.items.flatMap((x) => collectPromise(x))) + } + return result +} diff --git a/packages/shared-base/src/TypedMessage/utils/index.ts b/packages/shared-base/src/TypedMessage/utils/index.ts index e2ba93be649c..0aebc874a606 100644 --- a/packages/shared-base/src/TypedMessage/utils/index.ts +++ b/packages/shared-base/src/TypedMessage/utils/index.ts @@ -1,7 +1,6 @@ import { isTypedMessageAnchor } from '../extension' import { isTypedMessageText, isTypedMessageTuple, TypedMessageTuple } from '../core' import type { - NonSerializableTypedMessage, NonSerializableWithAltTypedMessage, SerializableTypedMessage, SerializableTypedMessages, @@ -9,6 +8,7 @@ import type { } from '../base' import { eq } from 'lodash-unified' import { Err, Ok, Result } from 'ts-results' +import { isTypedMessageImage } from '..' export function isSerializableTypedMessage(x: TypedMessage): x is SerializableTypedMessages { if ((x as SerializableTypedMessage).serializable) return true @@ -17,12 +17,6 @@ export function isSerializableTypedMessage(x: TypedMessage): x is SerializableTy return false } -export function normalizeTypedMessage(x: SerializableTypedMessages): SerializableTypedMessages -export function normalizeTypedMessage(x: NonSerializableTypedMessage): NonSerializableTypedMessage -export function normalizeTypedMessage(x: TypedMessage): TypedMessage { - return x -} - /** * This is a tree compare algorithm, may need to find a more efficient one from NPM */ @@ -43,7 +37,7 @@ export function isTypedMessageEqual(message1: TypedMessage, message2: TypedMessa * @param message message */ export function extractTextFromTypedMessage(message: TypedMessage | null): Result { - if (message === null) return Err.EMPTY + if (!message) return Err.EMPTY if (isTypedMessageText(message)) return Ok(message.content) if (isTypedMessageAnchor(message)) return Ok(message.content) if (isTypedMessageTuple(message)) { @@ -57,3 +51,15 @@ export function extractTextFromTypedMessage(message: TypedMessage | null): Resul } return Err.EMPTY } +export function extractImageFromTypedMessage( + message: TypedMessage | null, + result: (string | Blob)[] = [], +): (string | Blob)[] { + if (!message) return result + if (isTypedMessageImage(message)) return result.concat(message.image) + if (isTypedMessageTuple(message)) + return result.concat(message.items.flatMap((x) => extractImageFromTypedMessage(x))) + return result +} +export * from '../transforms/flatten' +export * from '../transforms/promise' From 9c0a4eeb8a6d4c9f5df4d0f9fea5573e51758cb2 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Fri, 31 Dec 2021 22:09:32 +0800 Subject: [PATCH 17/85] feat: multiple decryption --- .../background/services/crypto/decryption.ts | 53 ++++++++++++------- .../DecryptedPost/DecryptedPost.tsx | 15 +++--- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 0e72731ed520..aa3baa1beb36 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -20,6 +20,7 @@ import { PostIVIdentifier, ProfileIdentifier, } from '@masknet/shared-base' +import EventIterator from 'event-iterator' import { queryPersonaByProfileDB } from '../../database/persona/db' import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../database/persona/helper' import { queryPostDB } from '../../database/post' @@ -30,6 +31,8 @@ import { getGunInstance } from '../../network/gun/instance' export type DecryptionInfo = { type: DecryptProgressKind.Info iv?: Uint8Array + /** Progress and failure should be silent. */ + implicit?: boolean } export type DecryptionProgress = DecryptProgress | DecryptionInfo @@ -43,28 +46,42 @@ export type SocialNetworkEncodedPayload = | { type: 'text'; text: string } // | { type: 'image'; image: Uint8Array } | { type: 'image-url'; url: string } - +const downloadImage = (url: string): Promise => fetch(url).then((x) => x.arrayBuffer()) export async function* decryptionWithSocialNetworkDecoding( - encoded: SocialNetworkEncodedPayload, + encoded: SocialNetworkEncodedPayload[], context: DecryptionContext, ) { - let decoded!: string | Uint8Array - if (encoded.type === 'text') { - decoded = socialNetworkDecoder(context.currentSocialNetwork, encoded.text).unwrapOr(encoded.text) - } else if (encoded.type === 'image-url') { - if (!context.authorHint || context.authorHint.isUnknown) { - yield new DecryptError(ErrorReasons.UnrecognizedAuthor, undefined) - return + yield* new EventIterator<[id: number, progress: DecryptProgress | DecryptionInfo]>((flow) => { + for (const e of encoded) { + async function main() { + const id = Math.random() + let decoded!: string | Uint8Array + if (e.type === 'text') { + // TODO: socialNetworkDecoder should emit multiple results if the text contains multiple payload. + decoded = socialNetworkDecoder(context.currentSocialNetwork, e.text).unwrapOr(e.text) + } else if (e.type === 'image-url') { + flow.push([id, { type: DecryptProgressKind.Info, implicit: true }]) + if (!context.authorHint || context.authorHint.isUnknown) { + flow.push([id, new DecryptError(ErrorReasons.UnrecognizedAuthor, undefined)]) + return + } + const result = socialNetworkDecoder( + context.currentSocialNetwork, + await steganographyDecodeImageUrl(e.url, { + pass: context.authorHint.toText(), + downloadImage, + }), + ) + if (result.none) return + decoded = result.val + } + for await (const x of decryption(decoded, context)) { + flow.push([id, x]) + } + } + main() } - decoded = socialNetworkDecoder( - context.currentSocialNetwork, - await steganographyDecodeImageUrl(encoded.url, { - pass: context.authorHint.toText(), - downloadImage: (url) => fetch(url).then((x) => x.arrayBuffer()), - }), - ).unwrapOr('') - } - return yield* decryption(decoded, context) + }) } export async function* decryption(payload: string | Uint8Array, context: DecryptionContext) { const { currentSocialNetwork, postURL } = context diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index 2d89b3fb7154..eb35ff2e1791 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -30,14 +30,13 @@ export function DecryptPost(props: DecryptPostProps) { useEffect(() => { const abort = new AbortController() async function main() { - const m = flattenTypedMessage(await waitTypedMessage(raw)) - const t = extractTextFromTypedMessage(m).unwrapOr('') - const i = extractImageFromTypedMessage(m).filter((x): x is string => typeof x === 'string') - console.log(m, t, i) - const x: SocialNetworkEncodedPayload = i.length - ? { type: 'image-url', url: i.at(0)! } - : { type: 'text', text: t } - const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(x, { + const message = flattenTypedMessage(await waitTypedMessage(raw)) + const text = extractTextFromTypedMessage(message).unwrapOr('') + const images = extractImageFromTypedMessage(message).filter((x): x is string => typeof x === 'string') + const payloads: SocialNetworkEncodedPayload[] = [] + payloads.push({ type: 'text', text }) + payloads.push(...images.map((url) => ({ type: 'image-url', url }))) + const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(payloads, { authorHint: author, currentProfile: whoAmI, currentSocialNetwork: SocialNetworkEnum.Twitter, From fc01bd5682f5b8540be1780bbf15bc4d6ce6b4f7 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 4 Jan 2022 21:16:37 +0800 Subject: [PATCH 18/85] feat: pub dec works --- .../encryption/src/encryption/Decryption.ts | 11 +- .../src/encryption/DecryptionTypes.ts | 12 ++- packages/encryption/src/index.ts | 2 + .../network/gun/encryption/queryPostKey.ts | 1 + .../background/services/crypto/decryption.ts | 67 +++++++----- packages/mask/shared-ui/locales/en-US.json | 3 +- .../DecryptedPost/DecryptPostAwaiting.tsx | 51 ++++----- .../DecryptedPost/DecryptedPost.tsx | 101 ++++++++++++++---- packages/mask/src/social-network/types.ts | 2 +- .../utils/create-post-context.ts | 14 +++ packages/plugin-infra/src/PostContext.ts | 3 + .../types/lib.esnext.Array.findFromLast.d.ts | 48 +++++---- .../shared-base/src/TypedMessage/metadata.ts | 4 +- 13 files changed, 217 insertions(+), 102 deletions(-) diff --git a/packages/encryption/src/encryption/Decryption.ts b/packages/encryption/src/encryption/Decryption.ts index 34236893918c..b2fe8dc2f4e8 100644 --- a/packages/encryption/src/encryption/Decryption.ts +++ b/packages/encryption/src/encryption/Decryption.ts @@ -7,6 +7,7 @@ import { andThenAsync, } from '@masknet/shared-base' import { None, Result } from 'ts-results' +import { DecryptIntermediateProgress, DecryptIntermediateProgressKind } from '.' import { AESAlgorithmEnum, PayloadParseResult } from '../payload' import { decryptWithAES, importAESFromJWK } from '../utils' import { @@ -22,8 +23,6 @@ export * from './DecryptionTypes' const ErrorReasons = DecryptError.Reasons type Version = PayloadParseResult.Payload['version'] export async function* decrypt(options: DecryptOptions, io: DecryptIO): AsyncIterableIterator { - yield progress(DecryptProgressKind.Started) - const { author: _author, encrypted: _encrypted, encryption: _encryption, version } = options.message const { authorPublicKey: _authorPublicKey } = options.message @@ -79,6 +78,7 @@ export async function* decrypt(options: DecryptOptions, io: DecryptIO): AsyncIte //#endregion // ! Try to decrypt this post via ECDH + yield { type: DecryptProgressKind.Progress, event: DecryptIntermediateProgressKind.TryDecryptByE2E } //#region const authorPublicKey = _authorPublicKey.unwrapOr(None) if (version === -37) { @@ -183,7 +183,10 @@ async function* decryptByECDH( decryptWithAES(AESAlgorithmEnum.A256GCM, derivedKey, postKeyIV, encryptedPostKey), (postKeyRaw) => Result.wrapAsync(() => importAESKeyFromJWKFromTextEncoder(postKeyRaw)), ) - if (possiblePostKey.err) continue + if (possiblePostKey.err) { + console.debug(possiblePostKey.val) + continue + } const decrypted = await decryptWithAES(AESAlgorithmEnum.A256GCM, possiblePostKey.val, iv, encrypted) if (decrypted.err) continue @@ -222,7 +225,7 @@ async function importAESKeyFromJWKFromTextEncoder(aes_raw: Uint8Array) { } function progress(kind: DecryptProgressKind.Success, rest: Omit): DecryptSuccess -function progress(kind: DecryptProgressKind.Started): DecryptProgress +function progress(kind: DecryptProgressKind.Progress, rest: Omit): DecryptProgress function progress(kind: DecryptProgressKind, rest?: object): DecryptProgress { return { type: kind, ...rest } as any } diff --git a/packages/encryption/src/encryption/DecryptionTypes.ts b/packages/encryption/src/encryption/DecryptionTypes.ts index 6fd3336dba68..1b1df87c063e 100644 --- a/packages/encryption/src/encryption/DecryptionTypes.ts +++ b/packages/encryption/src/encryption/DecryptionTypes.ts @@ -89,12 +89,19 @@ export interface DecryptEphemeralECDH_PostKey extends DecryptStaticECDH_PostKey ephemeralPublicKeySignature?: Uint8Array } export enum DecryptProgressKind { - Started = 'started', Success = 'success', Error = 'error', Info = 'info', + Progress = 'progress', +} +export type DecryptProgress = DecryptSuccess | DecryptError | DecryptIntermediateProgress +export interface DecryptIntermediateProgress { + type: DecryptProgressKind.Progress + event: DecryptIntermediateProgressKind +} +export enum DecryptIntermediateProgressKind { + TryDecryptByE2E = 'E2E', } -export type DecryptProgress = { type: DecryptProgressKind.Started } | DecryptSuccess | DecryptError export interface DecryptSuccess { type: DecryptProgressKind.Success content: TypedMessage @@ -109,6 +116,7 @@ export enum ErrorReasons { NotShareTarget = '[@masknet/encryption] No valid key is found. Likely this post is not shared with you', // Not used in this library. UnrecognizedAuthor = '[@masknet/encryption] No author is recognized which is required for the image steganography decoding.', + CurrentProfileDoesNotConnectedToPersona = '[@masknet/encryption] Cannot get the public key of the current profile therefore cannot decrypt by E2E.', } export class DecryptError extends Error { static Reasons = ErrorReasons diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index 739dfec3c952..13642e4efdf4 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -16,6 +16,8 @@ export { DecryptError, DecryptProgressKind, ErrorReasons, + DecryptIntermediateProgressKind, + type DecryptIntermediateProgress, type DecryptOptions, type DecryptIO, type DecryptEphemeralECDH_PostKey, diff --git a/packages/mask/background/network/gun/encryption/queryPostKey.ts b/packages/mask/background/network/gun/encryption/queryPostKey.ts index 16a4d2d949e2..d583a5819c97 100644 --- a/packages/mask/background/network/gun/encryption/queryPostKey.ts +++ b/packages/mask/background/network/gun/encryption/queryPostKey.ts @@ -46,6 +46,7 @@ namespace Version38Or39 { /* cspell:disable-next-line */ // ? In this step we get something like ["jzarhbyjtexiE7aB1DvQ", "jzarhuse6xlTAtblKRx9"] + console.log(`[@masknet/encryption] Reading key partition [${postHash}][${keyHash}]`) const internalNodeNames = Object.keys((await getGunData(gun, postHash, keyHash)) || {}).filter((x) => x !== '_') // ? In this step we get all keys in this category (gun2[postHash][keyHash]) const resultPromise = internalNodeNames.map((key) => getGunData(gun, key)) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index aa3baa1beb36..5305df3b38fc 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -1,4 +1,4 @@ -import { encodeArrayBuffer } from '@dimensiondev/kit' +import { encodeArrayBuffer, unreachable } from '@dimensiondev/kit' import { decrypt, parsePayload, @@ -31,8 +31,6 @@ import { getGunInstance } from '../../network/gun/instance' export type DecryptionInfo = { type: DecryptProgressKind.Info iv?: Uint8Array - /** Progress and failure should be silent. */ - implicit?: boolean } export type DecryptionProgress = DecryptProgress | DecryptionInfo @@ -51,16 +49,15 @@ export async function* decryptionWithSocialNetworkDecoding( encoded: SocialNetworkEncodedPayload[], context: DecryptionContext, ) { - yield* new EventIterator<[id: number, progress: DecryptProgress | DecryptionInfo]>((flow) => { + yield* new EventIterator<[id: string, progress: DecryptProgress | DecryptionInfo]>((flow) => { for (const e of encoded) { async function main() { - const id = Math.random() + const id = Math.random() + '' let decoded!: string | Uint8Array if (e.type === 'text') { // TODO: socialNetworkDecoder should emit multiple results if the text contains multiple payload. decoded = socialNetworkDecoder(context.currentSocialNetwork, e.text).unwrapOr(e.text) } else if (e.type === 'image-url') { - flow.push([id, { type: DecryptProgressKind.Info, implicit: true }]) if (!context.authorHint || context.authorHint.isUnknown) { flow.push([id, new DecryptError(ErrorReasons.UnrecognizedAuthor, undefined)]) return @@ -75,23 +72,21 @@ export async function* decryptionWithSocialNetworkDecoding( if (result.none) return decoded = result.val } - for await (const x of decryption(decoded, context)) { - flow.push([id, x]) - } + for await (const x of decryption(decoded, context)) flow.push([id, x]) } main() } }) } export async function* decryption(payload: string | Uint8Array, context: DecryptionContext) { + const parse = await parsePayload(payload) + if (parse.err) return null + const { currentSocialNetwork, postURL } = context let { currentProfile, authorHint } = context if (currentProfile?.isUnknown) currentProfile = null if (authorHint?.isUnknown) authorHint = null - const parse = await parsePayload(payload) - if (parse.err) return null - //#region Identify the PostIdentifier const iv = parse.val.encryption.unwrapOr(null)?.iv.unwrapOr(null) { @@ -137,40 +132,34 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt async deriveAESKey(pub) { return Array.from((await deriveAESByECDH(pub)).values()) }, - async queryAuthorPublicKey(author, signal) { - author ||= authorHint - if (!author) return null - const persona = await queryPersonaByProfileDB(author) - if (!persona) return null - return (await crypto.subtle.importKey( - 'jwk', - persona.publicKey, - { name: 'ECDH', namedCurve: persona.publicKey.crv! } as EcKeyAlgorithm, - false, - ['deriveKey'], - )) as EC_Public_CryptoKey + queryAuthorPublicKey(author, signal) { + return queryPublicKey(author || authorHint, false, signal) }, // TODO: get a gun instance async *queryPostKey_version37() { throw 'TODO' }, async *queryPostKey_version38(iv, signal) { + const author = await queryPublicKey(context.currentProfile, true) + if (!author) throw new DecryptError(ErrorReasons.CurrentProfileDoesNotConnectedToPersona, undefined) yield* GUN_queryPostKey_version39Or38( getGunInstance(), -38, iv, - {} as any, - '', + author, + getNetworkHint(context.currentSocialNetwork), signal || new AbortController().signal, ) }, async *queryPostKey_version39(iv, signal) { + const author = await queryPublicKey(context.currentProfile, true) + if (!author) throw new DecryptError(ErrorReasons.CurrentProfileDoesNotConnectedToPersona, undefined) yield* GUN_queryPostKey_version39Or38( getGunInstance(), -39, iv, - {} as any, - '', + author, + getNetworkHint(context.currentSocialNetwork), signal || new AbortController().signal, ) }, @@ -184,6 +173,15 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt yield* progress return null } + +function getNetworkHint(x: SocialNetworkEnum) { + if (x === SocialNetworkEnum.Facebook) return '' + if (x === SocialNetworkEnum.Twitter) return 'twitter-' + if (x === SocialNetworkEnum.Minds) return 'minds-' + if (x === SocialNetworkEnum.Instagram) return 'instagram-' + unreachable(x) +} + async function getPostKeyCache(id: PostIVIdentifier) { const post = await queryPostDB(id) if (!post?.postCryptoKey) return null @@ -206,3 +204,16 @@ async function storeAuthorPublicKey( } // ! Author detected is not equal to AuthorHint. Skip store the public key because it might be a security problem. } + +async function queryPublicKey(author: ProfileIdentifier | null, extractable = false, signal?: AbortSignal) { + if (!author) return null + const persona = await queryPersonaByProfileDB(author) + if (!persona) return null + return (await crypto.subtle.importKey( + 'jwk', + persona.publicKey, + { name: 'ECDH', namedCurve: persona.publicKey.crv! } as EcKeyAlgorithm, + extractable, + ['deriveKey'], + )) as EC_Public_CryptoKey +} diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 439d23559200..8dbc1f4e6863 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -69,7 +69,8 @@ "decrypted_postbox_decrypting": "Mask decryptingโ€ฆ", "decrypted_postbox_decoding": "Mask decodingโ€ฆ", "decrypted_postbox_decrypting_finding_person_key": "Mask is looking for the public key of the authorโ€ฆ", - "decrypted_postbox_decrypting_finding_post_key": "Mask is retrieving the post key to decrypt this postโ€ฆ", + "decrypted_postbox_decrypting_finding_post_key": "Mask is looking for the key to decrypt this postโ€ฆ", + "decrypted_postbox_decrypting_finding_post_key_long": "Mask is still trying to decrypt this post, but it seems like the post is not shared with you.", "decrypted_postbox_author_mismatch": "Originally posted by {{name}}", "decrypted_postbox_title": "Decrypted by Mask:", "dismiss": "Dismiss", diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx index b8ff90986359..ab5d03fd8f92 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx @@ -1,36 +1,39 @@ -import { memo } from 'react' -import { useI18N } from '../../../utils' -import { AdditionalContent, AdditionalContentProps } from '../AdditionalPostContent' -import type { DecryptionProgress } from '../../../extension/background-script/CryptoServices/decryptFrom' +import { memo, useState } from 'react' +import { ShadowRootTooltip, useI18N } from '../../../utils' +import { AdditionalContent } from '../AdditionalPostContent' import type { ProfileIdentifier } from '@masknet/shared-base' import { wrapAuthorDifferentMessage } from './authorDifferentMessage' +import { DecryptIntermediateProgress, DecryptIntermediateProgressKind } from '@masknet/encryption' +import { useTimeout } from 'react-use' +import { Typography } from '@mui/material' export interface DecryptPostAwaitingProps { - type?: DecryptionProgress - AdditionalContentProps?: Partial + progress?: DecryptIntermediateProgress /** The author in the payload */ author?: ProfileIdentifier /** The author of the encrypted post */ postedBy?: ProfileIdentifier } export const DecryptPostAwaiting = memo(function DecryptPostAwaiting(props: DecryptPostAwaitingProps) { - const { AdditionalContentProps, author, postedBy, type } = props + const { author, postedBy, progress: type } = props const { t } = useI18N() - const key = { - finding_post_key: t('decrypted_postbox_decrypting_finding_post_key'), - finding_person_public_key: t('decrypted_postbox_decrypting_finding_person_key'), - init: t('decrypted_postbox_decrypting'), - decode_post: t('decrypted_postbox_decoding'), - iv_decrypted: t('decrypted_postbox_decoding'), - payload_decrypted: t('decrypted_postbox_decoding'), - intermediate_success: 'unreachable case. it should display success UI', - undefined: t('decrypted_postbox_decrypting'), - } as const - return ( - + + const [isReady] = useTimeout(5000) + + let text = t('decrypted_postbox_decrypting') + if (type?.event === DecryptIntermediateProgressKind.TryDecryptByE2E) { + text = t('decrypted_postbox_decrypting_finding_post_key') + if (isReady()) text = t('decrypted_postbox_decrypting_finding_post_key_long') + } + // unused + t('decrypted_postbox_decrypting_finding_person_key') + + const header = wrapAuthorDifferentMessage( + author, + postedBy, + + Can't decrypt? + , ) + const shouldShowProgress = type?.event !== DecryptIntermediateProgressKind.TryDecryptByE2E || !isReady() + return }) diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index eb35ff2e1791..32cce273ccc4 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -1,18 +1,27 @@ -import { useEffect } from 'react' +import { useEffect, useMemo, useState } from 'react' import { TypedMessageTuple, ProfileIdentifier, extractTextFromTypedMessage, - waitTypedMessage, - flattenTypedMessage, extractImageFromTypedMessage, } from '@masknet/shared-base' import type { Profile } from '../../../database' import { usePostInfoDetails } from '@masknet/plugin-infra' import { ServicesWithProgress } from '../../../extension/service' -import { SocialNetworkEnum } from '@masknet/encryption' -import type { SocialNetworkEncodedPayload } from '../../../../background/services/crypto/decryption' +import { + DecryptError, + DecryptProgress, + DecryptProgressKind, + DecryptSuccess, + SocialNetworkEnum, +} from '@masknet/encryption' +import type { DecryptionInfo, SocialNetworkEncodedPayload } from '../../../../background/services/crypto/decryption' +import { DecryptPostSuccess } from './DecryptedPostSuccess' +import type { DecryptIntermediateProgress } from '@masknet/encryption/src/encryption' +import { DecryptPostAwaiting } from './DecryptPostAwaiting' +import { DecryptPostFailed } from './DecryptPostFailed' +import draft from 'immer' export interface DecryptPostProps { onDecrypted: (post: TypedMessageTuple) => void @@ -23,34 +32,90 @@ export interface DecryptPostProps { } export function DecryptPost(props: DecryptPostProps) { const { whoAmI } = props - const raw = usePostInfoDetails.rawMessage() + const decryptions = useDecryption(whoAmI) + const jsx = Object.entries(decryptions).map(([key, value]) => { + const isExplicit = value.some((x) => x.type === DecryptProgressKind.Info && x.iv) + const result = value.find(isDecryptSuccess) + if (!isExplicit && !result) return null + + if (result) { + return ( + + ) + } + const failed = value.find(isDecryptError) + if (failed) return + const process = value.find(isDecryptProgress) + if (process) return + + return null + }) + return <>{jsx} +} + +function useSocialNetworkEncodedPayload(): SocialNetworkEncodedPayload[] { + const message = usePostInfoDetails.rawMessageResolved() + const mentionedLinks = usePostInfoDetails.mentionedLinks() + return useMemo(() => { + const text = extractTextFromTypedMessage(message) + const images = extractImageFromTypedMessage(message) + const payloads: SocialNetworkEncodedPayload[] = [] + if (text.ok && text.val.length > 10) payloads.push({ type: 'text', text: text.val }) + for (const text of mentionedLinks) payloads.push({ type: 'text', text }) + for (const image of images) if (typeof image === 'string') payloads.push({ type: 'image-url', url: image }) + return payloads + }, [message, mentionedLinks.join(' ')]) +} + +function useDecryption(whoAmI: ProfileIdentifier) { + const payloads = useSocialNetworkEncodedPayload() const author = usePostInfoDetails.author() - const url = usePostInfoDetails.url() + const url = usePostInfoDetails.url()?.toString() + const [decryptions, _setDecryption] = useState>({}) + function setDecryption(f: (val: typeof decryptions) => void) { + _setDecryption((val) => draft(val, f)) + } useEffect(() => { const abort = new AbortController() async function main() { - const message = flattenTypedMessage(await waitTypedMessage(raw)) - const text = extractTextFromTypedMessage(message).unwrapOr('') - const images = extractImageFromTypedMessage(message).filter((x): x is string => typeof x === 'string') - const payloads: SocialNetworkEncodedPayload[] = [] - payloads.push({ type: 'text', text }) - payloads.push(...images.map((url) => ({ type: 'image-url', url }))) const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(payloads, { authorHint: author, currentProfile: whoAmI, currentSocialNetwork: SocialNetworkEnum.Twitter, - postURL: url?.toString(), + postURL: url, }) - for await (const x of process) { + const currentPassIDs = new Set() + abort.signal.addEventListener('abort', () => { + setDecryption((val) => { + for (const id of currentPassIDs) delete val[id] + }) + }) + for await (const [id, item] of process) { if (abort.signal.aborted) return - console.log(x, 1) + currentPassIDs.add(id) + setDecryption((val) => { + val[id] ||= [] + val[id] = [item, ...val[id]] + }) } } main() return () => abort.abort() - }) + }, [payloads, author.toText(), whoAmI.toText(), url]) + + return decryptions +} + +function isDecryptSuccess(value: DecryptProgress | DecryptionInfo): value is DecryptSuccess { + return value.type === DecryptProgressKind.Success +} + +function isDecryptError(value: DecryptProgress | DecryptionInfo): value is DecryptError { + return value.type === DecryptProgressKind.Error +} - return <>In progress... +function isDecryptProgress(value: DecryptProgress | DecryptionInfo): value is DecryptIntermediateProgress { + return value.type === DecryptProgressKind.Progress } diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index 8fa395669d45..0101eb7867a9 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -12,7 +12,7 @@ import type { PaletteMode, Theme } from '@mui/material' import type { InjectedDialogClassKey, InjectedDialogProps } from '../components/shared/InjectedDialog' import type { Profile } from '../database' import type { PostInfo } from './PostInfo' -import type { GrayscaleAlgorithm } from '@masknet/encryption' +import type { GrayscaleAlgorithm, SocialNetworkEnum } from '@masknet/encryption' import type { createSNSAdaptorSpecializedPostContext } from './utils/create-post-context' import type { Subscription } from 'use-subscription' diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index 589311f048b3..df92345635d2 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -16,6 +16,10 @@ import { SubscriptionFromValueRef, SubscriptionDebug as debug, mapSubscription, + createSubscriptionFromAsync, + waitTypedMessage, + makeTypedMessageEmpty, + flattenTypedMessage, } from '@masknet/shared-base' import { Err, Result } from 'ts-results' import type { Subscription } from 'use-subscription' @@ -102,6 +106,15 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct return () => void [a(), b()] }, }) + let initialTrigger = false + const rawMessageResolved = createSubscriptionFromAsync( + () => waitTypedMessage(opt.rawMessage.getCurrentValue()).then(flattenTypedMessage), + makeTypedMessageEmpty(), + (e) => { + !initialTrigger && e() && (initialTrigger = true) + return opt.rawMessage.subscribe(e) + }, + ) return { ...author, @@ -127,6 +140,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct postMetadataMentionedLinks: linksSubscribe, rawMessage: opt.rawMessage, + rawMessageResolved, rawMessagePiped: transformedPostContent, containingMaskPayload: SubscriptionFromValueRef(postPayload), diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index 6f4f0aa0e53c..da95d8f5d9ff 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -2,6 +2,7 @@ import { useObservableValues, useValueRef } from '@masknet/shared' import { ObservableMap, ObservableSet, + TypedMessage, type Payload, type PostIdentifier, type ProfileIdentifier, @@ -66,6 +67,8 @@ export interface PostContext extends PostContextAuthor { //#endregion //#region Raw post content (not decrypted) readonly rawMessage: Subscription + /** rawMessage but with no TypedMessagePromise inside */ + readonly rawMessageResolved: Subscription // TODO: should be a Subscription readonly rawMessagePiped: ValueRef //#endregion diff --git a/packages/polyfills/types/lib.esnext.Array.findFromLast.d.ts b/packages/polyfills/types/lib.esnext.Array.findFromLast.d.ts index fb5fa05a7a24..e13b79abd9d5 100644 --- a/packages/polyfills/types/lib.esnext.Array.findFromLast.d.ts +++ b/packages/polyfills/types/lib.esnext.Array.findFromLast.d.ts @@ -1,44 +1,48 @@ interface Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast( + predicate: (this: void, value: T, index: number, obj: T[]) => value is S, + thisArg?: any, + ): S | undefined + findFromLast(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface ReadonlyArray { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Int8Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Uint8Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Uint8ClampedArray { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Int16Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Uint16Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Int32Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Uint32Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Float32Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } interface Float64Array { - findFromLast(predicate, thisArg: any): T | undefined - findFromLastIndex(predicate, thisArg: any): number + findFromLast(predicate: (value: T) => boolean, thisArg?: any): T | undefined + findFromLastIndex(predicate, thisArg?: any): number } diff --git a/packages/shared-base/src/TypedMessage/metadata.ts b/packages/shared-base/src/TypedMessage/metadata.ts index 055fc92fb74e..731bcc89cd43 100644 --- a/packages/shared-base/src/TypedMessage/metadata.ts +++ b/packages/shared-base/src/TypedMessage/metadata.ts @@ -1,7 +1,7 @@ import { Result, Ok, Err, Some, Option, None } from 'ts-results' import type { TypedMessage } from './base' import z_schema from 'z-schema' -import produce, { enableMapSet, Draft } from 'immer' +import draft, { enableMapSet, Draft } from 'immer' enableMapSet() const metadataSchemaStore = new Map() @@ -82,7 +82,7 @@ export function editMetadata( metadata: TypedMessage['meta'], edit: (meta: NonNullable>) => void, ): NonNullable { - return produce(metadata || new Map(), (e) => void edit(e)) + return draft(metadata || new Map(), (e) => void edit(e)) } export function editTypedMessageMeta( typedMessage: T, From 1d187fa3c43c6096b548cf4b476e61c6e13070bc Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 13:07:34 +0800 Subject: [PATCH 19/85] feat: wip --- .../src/components/DataSource/usePostInfo.ts | 12 +- .../DecryptedPost/DecryptedPost.tsx | 125 ++++++++---------- .../DisabledPluginSuggestion.tsx | 2 +- .../InjectedComponents/PostComments.tsx | 6 +- .../InjectedComponents/PostInspector.tsx | 18 +-- .../InjectedComponents/PostReplacer.tsx | 4 +- .../plugins/Collectible/SNSAdaptor/index.tsx | 2 +- .../plugins/CryptoartAI/SNSAdaptor/index.tsx | 2 +- .../plugins/FindTruman/SNSAdaptor/index.tsx | 2 +- .../plugins/Furucombo/SNSAdaptor/index.tsx | 5 +- .../src/plugins/Gitcoin/SNSAdaptor/index.tsx | 2 +- .../plugins/GoodGhosting/SNSAdaptor/index.tsx | 2 +- .../src/plugins/MaskBox/SNSAdaptor/index.tsx | 5 +- .../plugins/PoolTogether/SNSAdaptor/index.tsx | 2 +- .../src/plugins/Snapshot/SNSAdaptor/index.tsx | 2 +- .../src/plugins/dHEDGE/SNSAdaptor/index.tsx | 2 +- .../facebook.com/collecting/posts.tsx | 5 +- .../facebook.com/utils/resolveFacebookLink.ts | 6 +- .../twitter.com/collecting/post.ts | 6 +- .../defaults/inject/CommentBox.tsx | 7 +- .../utils/create-post-context.ts | 73 +++------- packages/plugin-infra/package.json | 1 + packages/plugin-infra/src/PostContext.ts | 14 +- packages/plugin-infra/tsconfig.json | 7 +- pnpm-lock.yaml | 2 + 25 files changed, 128 insertions(+), 186 deletions(-) diff --git a/packages/mask/src/components/DataSource/usePostInfo.ts b/packages/mask/src/components/DataSource/usePostInfo.ts index 43fdc666e360..7f5854c999ad 100644 --- a/packages/mask/src/components/DataSource/usePostInfo.ts +++ b/packages/mask/src/components/DataSource/usePostInfo.ts @@ -1,17 +1,11 @@ import { activatedSocialNetworkUI } from '../../social-network' import { usePostInfoDetails } from '@masknet/plugin-infra' -import type { ProfileIdentifier } from '@masknet/shared-base' export { usePostInfo, PostInfoProvider, usePostInfoDetails } from '@masknet/plugin-infra' export function usePostLink() { - const id = usePostInfoDetails.snsID() + const url = usePostInfoDetails.url() const identifier = usePostInfoDetails.identifier() - if (!id || !identifier) return '' + if (url) return url.toString() + if (!identifier) return '' return activatedSocialNetworkUI.utils.getPostURL?.(identifier) ?? '' } -export function usePostClaimedAuthor(): ProfileIdentifier | undefined { - const payload = usePostInfoDetails.containingMaskPayload().unwrapOr(undefined) - if (!payload) return undefined - if (payload.version !== -38) return undefined - return payload.authorUserID -} diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index 32cce273ccc4..2cec06b662b5 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -1,27 +1,13 @@ -import { useEffect, useMemo, useState } from 'react' -import { - TypedMessageTuple, - ProfileIdentifier, - extractTextFromTypedMessage, - extractImageFromTypedMessage, -} from '@masknet/shared-base' +import type { TypedMessageTuple, ProfileIdentifier } from '@masknet/shared-base' import type { Profile } from '../../../database' import { usePostInfoDetails } from '@masknet/plugin-infra' -import { ServicesWithProgress } from '../../../extension/service' -import { - DecryptError, - DecryptProgress, - DecryptProgressKind, - DecryptSuccess, - SocialNetworkEnum, -} from '@masknet/encryption' -import type { DecryptionInfo, SocialNetworkEncodedPayload } from '../../../../background/services/crypto/decryption' +import { DecryptError, DecryptProgress, DecryptProgressKind, DecryptSuccess } from '@masknet/encryption' +import type { DecryptionInfo } from '../../../../background/services/crypto/decryption' import { DecryptPostSuccess } from './DecryptedPostSuccess' import type { DecryptIntermediateProgress } from '@masknet/encryption/src/encryption' import { DecryptPostAwaiting } from './DecryptPostAwaiting' import { DecryptPostFailed } from './DecryptPostFailed' -import draft from 'immer' export interface DecryptPostProps { onDecrypted: (post: TypedMessageTuple) => void @@ -31,12 +17,9 @@ export interface DecryptPostProps { requestAppendRecipients?(to: Profile[]): Promise } export function DecryptPost(props: DecryptPostProps) { - const { whoAmI } = props - const decryptions = useDecryption(whoAmI) + const decryptions = usePostInfoDetails.decryptedMaskPayload() const jsx = Object.entries(decryptions).map(([key, value]) => { - const isExplicit = value.some((x) => x.type === DecryptProgressKind.Info && x.iv) const result = value.find(isDecryptSuccess) - if (!isExplicit && !result) return null if (result) { return ( @@ -53,60 +36,60 @@ export function DecryptPost(props: DecryptPostProps) { return <>{jsx} } -function useSocialNetworkEncodedPayload(): SocialNetworkEncodedPayload[] { - const message = usePostInfoDetails.rawMessageResolved() - const mentionedLinks = usePostInfoDetails.mentionedLinks() - return useMemo(() => { - const text = extractTextFromTypedMessage(message) - const images = extractImageFromTypedMessage(message) - const payloads: SocialNetworkEncodedPayload[] = [] - if (text.ok && text.val.length > 10) payloads.push({ type: 'text', text: text.val }) - for (const text of mentionedLinks) payloads.push({ type: 'text', text }) - for (const image of images) if (typeof image === 'string') payloads.push({ type: 'image-url', url: image }) - return payloads - }, [message, mentionedLinks.join(' ')]) -} +// function useSocialNetworkEncodedPayload(): SocialNetworkEncodedPayload[] { +// const message = usePostInfoDetails.rawMessageResolved() +// const mentionedLinks = usePostInfoDetails.mentionedLinks() +// return useMemo(() => { +// const text = extractTextFromTypedMessage(message) +// const images = extractImageFromTypedMessage(message) +// const payloads: SocialNetworkEncodedPayload[] = [] +// if (text.ok && text.val.length > 10) payloads.push({ type: 'text', text: text.val }) +// for (const text of mentionedLinks) payloads.push({ type: 'text', text }) +// for (const image of images) if (typeof image === 'string') payloads.push({ type: 'image-url', url: image }) +// return payloads +// }, [message, mentionedLinks.join(' ')]) +// } -function useDecryption(whoAmI: ProfileIdentifier) { - const payloads = useSocialNetworkEncodedPayload() - const author = usePostInfoDetails.author() - const url = usePostInfoDetails.url()?.toString() - const [decryptions, _setDecryption] = useState>({}) - function setDecryption(f: (val: typeof decryptions) => void) { - _setDecryption((val) => draft(val, f)) - } +// function useDecryption(whoAmI: ProfileIdentifier) { +// const payloads = useSocialNetworkEncodedPayload() +// const author = usePostInfoDetails.author() +// const url = usePostInfoDetails.url()?.toString() +// const [decryptions, _setDecryption] = useState>({}) +// function setDecryption(f: (val: typeof decryptions) => void) { +// _setDecryption((val) => draft(val, f)) +// } - useEffect(() => { - const abort = new AbortController() - async function main() { - const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(payloads, { - authorHint: author, - currentProfile: whoAmI, - currentSocialNetwork: SocialNetworkEnum.Twitter, - postURL: url, - }) - const currentPassIDs = new Set() - abort.signal.addEventListener('abort', () => { - setDecryption((val) => { - for (const id of currentPassIDs) delete val[id] - }) - }) - for await (const [id, item] of process) { - if (abort.signal.aborted) return - currentPassIDs.add(id) - setDecryption((val) => { - val[id] ||= [] - val[id] = [item, ...val[id]] - }) - } - } - main() +// useEffect(() => { +// const abort = new AbortController() +// async function main() { +// const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(payloads, { +// authorHint: author, +// currentProfile: whoAmI, +// currentSocialNetwork: SocialNetworkEnum.Twitter, +// postURL: url, +// }) +// const currentPassIDs = new Set() +// abort.signal.addEventListener('abort', () => { +// setDecryption((val) => { +// for (const id of currentPassIDs) delete val[id] +// }) +// }) +// for await (const [id, item] of process) { +// if (abort.signal.aborted) return +// currentPassIDs.add(id) +// setDecryption((val) => { +// val[id] ||= [] +// val[id] = [item, ...val[id]] +// }) +// } +// } +// main() - return () => abort.abort() - }, [payloads, author.toText(), whoAmI.toText(), url]) +// return () => abort.abort() +// }, [payloads, author.toText(), whoAmI.toText(), url]) - return decryptions -} +// return decryptions +// } function isDecryptSuccess(value: DecryptProgress | DecryptionInfo): value is DecryptSuccess { return value.type === DecryptProgressKind.Success diff --git a/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx b/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx index 782b76887c99..5e9074225a0e 100644 --- a/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx +++ b/packages/mask/src/components/InjectedComponents/DisabledPluginSuggestion.tsx @@ -45,7 +45,7 @@ export function useDisabledPluginSuggestionFromMeta(meta: ReadonlyMap } diff --git a/packages/mask/src/components/InjectedComponents/PostComments.tsx b/packages/mask/src/components/InjectedComponents/PostComments.tsx index a2444c758e23..0d9af9df435c 100644 --- a/packages/mask/src/components/InjectedComponents/PostComments.tsx +++ b/packages/mask/src/components/InjectedComponents/PostComments.tsx @@ -50,11 +50,9 @@ export interface PostCommentProps { export function PostComment(props: PostCommentProps) { const { failedComponent: Fail, waitingComponent: Wait, needZip } = props const comment = useValueRef(props.comment) + // TODO: const postContent = usePostInfoDetails.rawMessagePiped() - const containingPayload = usePostInfoDetails.containingMaskPayload() - // TODO - const iv = null as string | null - const postIV = containingPayload.map((x) => x.iv).unwrapOr(iv) + const postIV = usePostInfoDetails.commentEncryptionIV() const dec = useAsync(async () => { const decryptedText = extractTextFromTypedMessage(postContent).unwrap() diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index 84c1e28267f1..7fa91f3670e9 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -22,21 +22,23 @@ export interface PostInspectorProps { } export function PostInspector(props: PostInspectorProps) { const postBy = usePostInfoDetails.author() - const encryptedPost = usePostInfoDetails.containingMaskPayload() + const encryptedPost = { ok: false } // usePostInfoDetails.containingMaskPayload() + const version = usePostInfoDetails.containsMaskPayloadVersion() const whoAmI = useCurrentIdentity() const friends = useFriendsList() const [alreadySelectedPreviously, setAlreadySelectedPreviously] = useState([]) const { value: sharedListOfPost } = useAsync(async () => { - if (!whoAmI || !whoAmI.identifier.equals(postBy) || !encryptedPost.ok) return [] - const { iv, version } = encryptedPost.val - return Services.Crypto.getPartialSharedListOfPost(version, iv, postBy) - }, [postBy, whoAmI, encryptedPost]) + return [] + // if (!whoAmI || !whoAmI.identifier.equals(postBy) || !encryptedPost.ok) return [] + // const { iv, version } = encryptedPost.val + // return Services.Crypto.getPartialSharedListOfPost(version, iv, postBy) + }, []) useEffect(() => setAlreadySelectedPreviously(sharedListOfPost ?? []), [sharedListOfPost]) - if (encryptedPost.ok) { - props.needZip() - } + // if (encryptedPost.ok) { + // props.needZip() + // } return withAdditionalContent( !isWellKnownTypedMessages(x)) || // replace posts which encrypted by Mask - postPayload.ok + hasMaskPayload // zip/unzip original post useEffect(() => { diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx index c379ebe1d460..be67a0935d55 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx @@ -14,7 +14,7 @@ const sns: Plugin.SNSAdaptor.Definition = { ...base, init(signal) {}, PostInspector: function Component() { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const link = uniq(links).find(checkUrl) const asset = getAssetInfoFromURL(link) diff --git a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/index.tsx b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/index.tsx index ee92bf8a04e4..cdf689f9f321 100644 --- a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/index.tsx @@ -12,7 +12,7 @@ const sns: Plugin.SNSAdaptor.Definition = { ...base, init(signal) {}, PostInspector: function Component() { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const link = uniq(links).find(checkUrl) const asset = getAssetInfoFromURL(link) diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx index 5c35f9ad515b..acc0f07941ad 100644 --- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx @@ -68,7 +68,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector: function Component(): JSX.Element | null { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const link = links.find(isFindTrumanURL) if (!link) return null return diff --git a/packages/mask/src/plugins/Furucombo/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Furucombo/SNSAdaptor/index.tsx index d59eac4b6889..1b1ed2e91bc6 100644 --- a/packages/mask/src/plugins/Furucombo/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Furucombo/SNSAdaptor/index.tsx @@ -21,10 +21,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector: function Component() { - const link = usePostInfoDetails - .postMetadataMentionedLinks() - .concat(usePostInfoDetails.mentionedLinks()) - .find(isFurucomboLink) + const link = usePostInfoDetails.mentionedLinks().find(isFurucomboLink) if (!link) return null return }, diff --git a/packages/mask/src/plugins/Gitcoin/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Gitcoin/SNSAdaptor/index.tsx index de87664b2af0..ae23f377a07f 100644 --- a/packages/mask/src/plugins/Gitcoin/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Gitcoin/SNSAdaptor/index.tsx @@ -29,7 +29,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector() { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const link = links.find(isGitcoin) if (!link) return null diff --git a/packages/mask/src/plugins/GoodGhosting/SNSAdaptor/index.tsx b/packages/mask/src/plugins/GoodGhosting/SNSAdaptor/index.tsx index 5d42e5d84b09..d97783a4bb9b 100644 --- a/packages/mask/src/plugins/GoodGhosting/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/GoodGhosting/SNSAdaptor/index.tsx @@ -22,7 +22,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector: function Component() { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const link = links.find(isGoodGhosting) if (!link) return null diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx index aa79198dcb17..9045b39cbc13 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx @@ -21,10 +21,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector: function Component() { - const link = usePostInfoDetails - .postMetadataMentionedLinks() - .concat(usePostInfoDetails.mentionedLinks()) - .find(isMaskBox) + const link = usePostInfoDetails.mentionedLinks().find(isMaskBox) if (!link) return null return }, diff --git a/packages/mask/src/plugins/PoolTogether/SNSAdaptor/index.tsx b/packages/mask/src/plugins/PoolTogether/SNSAdaptor/index.tsx index fa3ebdd1559c..2a4bc27263e7 100644 --- a/packages/mask/src/plugins/PoolTogether/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/PoolTogether/SNSAdaptor/index.tsx @@ -23,7 +23,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector: function Component() { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const link = links.find(isPoolTogetherUrl) if (!link) return null diff --git a/packages/mask/src/plugins/Snapshot/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Snapshot/SNSAdaptor/index.tsx index 02b2b6523b4b..9fac1c2bc82d 100644 --- a/packages/mask/src/plugins/Snapshot/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Snapshot/SNSAdaptor/index.tsx @@ -57,7 +57,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector: function Component(): JSX.Element | null { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const link = links.find(isSnapshotURL) if (!link) return null diff --git a/packages/mask/src/plugins/dHEDGE/SNSAdaptor/index.tsx b/packages/mask/src/plugins/dHEDGE/SNSAdaptor/index.tsx index f25f766efebb..a6a96c2088e6 100644 --- a/packages/mask/src/plugins/dHEDGE/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/dHEDGE/SNSAdaptor/index.tsx @@ -33,7 +33,7 @@ const sns: Plugin.SNSAdaptor.Definition = { return }, PostInspector: function Component() { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.mentionedLinks()) + const links = usePostInfoDetails.mentionedLinks() const pool = getPoolFromLinks(links) if (!pool?.address) return null return diff --git a/packages/mask/src/social-network-adaptor/facebook.com/collecting/posts.tsx b/packages/mask/src/social-network-adaptor/facebook.com/collecting/posts.tsx index 98f7d681ad29..bc9843ef5349 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/collecting/posts.tsx +++ b/packages/mask/src/social-network-adaptor/facebook.com/collecting/posts.tsx @@ -82,10 +82,7 @@ function collectPostsFacebookInner(store: Next.CollectingCapabilities.PostsProvi function collectPostInfo() { rootProxy.realCurrent = root.evaluate()[0] const nextTypedMessage: TypedMessage[] = [] - info.postBy.value = getPostBy( - metadata, - postInfo.containingMaskPayload.getCurrentValue() !== null, - ).identifier + info.postBy.value = getPostBy(metadata, postInfo.containsMaskPayload.getCurrentValue()).identifier info.postID.value = getPostID(metadata, rootProxy.realCurrent) // parse text const text = collectNodeText(node, { diff --git a/packages/mask/src/social-network-adaptor/facebook.com/utils/resolveFacebookLink.ts b/packages/mask/src/social-network-adaptor/facebook.com/utils/resolveFacebookLink.ts index 3acccb7bccd6..abc662cd7634 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/utils/resolveFacebookLink.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/utils/resolveFacebookLink.ts @@ -1,5 +1,3 @@ -import { FACEBOOK_ID } from '../base' - -export function resolveFacebookLink(link: string, id: string) { - return id === FACEBOOK_ID ? link.replace(/\?fbclid=[\S\s]*#/, '#') : link +export function resolveFacebookLink(link: string) { + return link.replace(/\?fbclid=[\S\s]*#/, '#') } diff --git a/packages/mask/src/social-network-adaptor/twitter.com/collecting/post.ts b/packages/mask/src/social-network-adaptor/twitter.com/collecting/post.ts index c0d8eba48e18..7d34612f6c1f 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/collecting/post.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/collecting/post.ts @@ -85,9 +85,9 @@ function registerPostCollectorInner( run() cancel.addEventListener( 'abort', - info.containingMaskPayload.subscribe(() => { - const payload = info.containingMaskPayload.getCurrentValue() - if (payload.err && refs.postMetadataImages.size === 0) return + info.containsMaskPayload.subscribe(() => { + const noMaskPayload = !info.containsMaskPayload.getCurrentValue() + if (noMaskPayload && refs.postMetadataImages.size === 0) return updateProfileInfo(info) }), ) diff --git a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx index c781fe304051..a73a284165ff 100644 --- a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx +++ b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx @@ -29,19 +29,18 @@ export const injectCommentBoxDefaultFactory = function ( ) { const CommentBoxUI = memo(function CommentBoxUI({ dom }: { dom: HTMLElement | null }) { const info = usePostInfo() - const payload = usePostInfoDetails.containingMaskPayload() + const postIV = usePostInfoDetails.commentEncryptionIV() const postContent = usePostInfoDetails.rawMessagePiped() const { classes } = useCustomStyles() - const iv = null as null | string const props = additionPropsToCommentBox(classes) const onCallback = useCallback( async (content) => { - const postIV = iv || payload.unwrap().iv const decryptedText = extractTextFromTypedMessage(postContent).unwrap() + if (!postIV) throw new TypeError('[@masknet/encryption] No IV, cannot encrypt/decrypt comment.') const encryptedComment = await Services.Crypto.encryptComment(postIV, decryptedText, content) onPasteToCommentBox(encryptedComment, info!, dom).catch(console.error) }, - [payload, postContent, info, dom, iv], + [postIV, postContent, info, dom, postIV], ) if (!postContent.items.length) return null diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index df92345635d2..ea3ac83b1ac7 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -8,7 +8,6 @@ import { ObservableMap, ObservableSet, parseURL, - Payload, PostIdentifier, ProfileIdentifier, TypedMessage, @@ -20,10 +19,11 @@ import { waitTypedMessage, makeTypedMessageEmpty, flattenTypedMessage, + createConstantSubscription, } from '@masknet/shared-base' -import { Err, Result } from 'ts-results' import type { Subscription } from 'use-subscription' import { activatedSocialNetworkUI } from '../' +import { FACEBOOK_ID } from '../../social-network-adaptor/facebook.com/base' import { resolveFacebookLink } from '../../social-network-adaptor/facebook.com/utils/resolveFacebookLink' export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSActions) { @@ -31,61 +31,28 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct const cancel: (Function | undefined)[] = [] opt.signal?.addEventListener('abort', () => cancel.forEach((fn) => fn?.())) - //#region Post text content - const postContent = new ValueRef(extractText()) - cancel.push(opt.rawMessage.subscribe(() => (postContent.value = extractText()))) - function extractText() { - return extractTextFromTypedMessage(opt.rawMessage.getCurrentValue()).unwrapOr('') - } - //#endregion - //#region Mentioned links const links = new ObservableSet() - cancel.push( - postContent.addListener((post) => { + { + async function fillLinks() { + const message = await waitTypedMessage(opt.rawMessage.getCurrentValue()) + const text = extractTextFromTypedMessage(message).unwrapOr('') + const link = parseURL(text).concat(opt.postMentionedLinksProvider?.getCurrentValue() || []) links.clear() - parseURL(post).forEach((link) => - links.add(resolveFacebookLink(link, activatedSocialNetworkUI.networkIdentifier)), - ) - opt.postMentionedLinksProvider - ?.getCurrentValue() - .forEach((link) => links.add(resolveFacebookLink(link, activatedSocialNetworkUI.networkIdentifier))) - }), - ) - cancel.push( - opt.postMentionedLinksProvider?.subscribe(() => { - // Not clean old links cause post content not changed - opt.postMentionedLinksProvider - ?.getCurrentValue() - .forEach((link) => links.add(resolveFacebookLink(link, activatedSocialNetworkUI.networkIdentifier))) - }), - ) + for (const x of link) { + if (activatedSocialNetworkUI.networkIdentifier === FACEBOOK_ID) { + links.add(resolveFacebookLink(x)) + } else links.add(x) + } + } + fillLinks() + cancel.push(opt.rawMessage.subscribe(fillLinks)) + } const linksSubscribe: Subscription = debug({ getCurrentValue: () => [...links], subscribe: (sub) => links.event.on(ALL_EVENTS, sub), }) //#endregion - - //#region Parse payload - const postPayload = new ValueRef>(Err(new Error('Empty'))) - parsePayload() - cancel.push(postContent.addListener(parsePayload)) - cancel.push(linksSubscribe.subscribe(parsePayload)) - function parsePayload() { - // TODO: Also parse for payload in the image. - let lastResult: Result = Err(new Error('No candidate')) - for (const each of (create.payloadDecoder || ((x) => [x]))( - postContent.value + linksSubscribe.getCurrentValue().join('\n'), - )) { - lastResult = create.payloadParser(each) - if (lastResult.ok) { - postPayload.value = lastResult - return - } - } - if (postPayload.value.err) postPayload.value = lastResult - } - //#endregion const author: PostContextAuthor = { avatarURL: opt.avatarURL, nickname: opt.nickname, @@ -115,6 +82,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct return opt.rawMessage.subscribe(e) }, ) + // @ts-ignore return { ...author, @@ -137,13 +105,14 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct }), mentionedLinks: linksSubscribe, - postMetadataMentionedLinks: linksSubscribe, rawMessage: opt.rawMessage, - rawMessageResolved, rawMessagePiped: transformedPostContent, - containingMaskPayload: SubscriptionFromValueRef(postPayload), + // TODO + commentEncryptionIV: createConstantSubscription(''), + containsMaskPayload: createConstantSubscription(false), + payloadClaimedAuthor: createConstantSubscription(undefined), } } } diff --git a/packages/plugin-infra/package.json b/packages/plugin-infra/package.json index 2f1609320c46..385092d5ba63 100644 --- a/packages/plugin-infra/package.json +++ b/packages/plugin-infra/package.json @@ -7,6 +7,7 @@ "scripts": {}, "dependencies": { "@dimensiondev/holoflows-kit": "^0.9.0-20210902104757-7c3d0d0", + "@masknet/encryption": "workspace:^0.0.0", "@masknet/shared": "workspace:*", "@masknet/shared-base": "workspace:*", "@masknet/theme": "workspace:*", diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index da95d8f5d9ff..08a3c1c8a498 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -12,6 +12,7 @@ import { ValueRef, LiveSelector, DOMProxy } from '@dimensiondev/holoflows-kit' import type { Result } from 'ts-results' import { Context, createContext, createElement, memo, useContext } from 'react' import { Subscription, useSubscription } from 'use-subscription' +import type { DecryptProgress } from '@masknet/encryption' export interface PostContextSNSActions { /** Parse payload into Payload */ payloadParser(raw: string): Result @@ -62,19 +63,18 @@ export interface PostContext extends PostContextAuthor { readonly url: Subscription // Meta readonly mentionedLinks: Subscription - /** @deprecated Use postMentionedLinks instead */ - readonly postMetadataMentionedLinks: Subscription //#endregion //#region Raw post content (not decrypted) readonly rawMessage: Subscription - /** rawMessage but with no TypedMessagePromise inside */ - readonly rawMessageResolved: Subscription // TODO: should be a Subscription readonly rawMessagePiped: ValueRef //#endregion - //#region Post payload discovered in the rawMessage - readonly containingMaskPayload: Subscription> - + //#region Information revealed in the Mask Payload + readonly containsMaskPayload: Subscription + readonly containsMaskPayloadVersion: Subscription> + readonly payloadClaimedAuthor: Subscription + readonly commentEncryptionIV: Subscription + readonly decryptedMaskPayload: Subscription> //#endregion } export type PostInfo = PostContext diff --git a/packages/plugin-infra/tsconfig.json b/packages/plugin-infra/tsconfig.json index b9877d62ccd3..98a9afdc236c 100644 --- a/packages/plugin-infra/tsconfig.json +++ b/packages/plugin-infra/tsconfig.json @@ -6,7 +6,12 @@ "tsBuildInfoFile": "./dist/.tsbuildinfo" }, "include": ["./src"], - "references": [{ "path": "../shared/" }, { "path": "../shared-base" }, { "path": "../theme" }], + "references": [ + { "path": "../shared/" }, + { "path": "../shared-base" }, + { "path": "../theme" }, + { "path": "../encryption" } + ], "ts-node": { "transpileOnly": true, "compilerOptions": { "module": "CommonJS" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bad111c8fe8a..c226df06570b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -565,6 +565,7 @@ importers: packages/plugin-infra: specifiers: '@dimensiondev/holoflows-kit': ^0.9.0-20210902104757-7c3d0d0 + '@masknet/encryption': workspace:^0.0.0 '@masknet/shared': workspace:* '@masknet/shared-base': workspace:* '@masknet/theme': workspace:* @@ -577,6 +578,7 @@ importers: use-subscription: ^1.5.1 dependencies: '@dimensiondev/holoflows-kit': 0.9.0-20210902104757-7c3d0d0 + '@masknet/encryption': link:../encryption '@masknet/shared': link:../shared '@masknet/shared-base': link:../shared-base '@masknet/theme': link:../theme From 6980b253bf00c625db2ba1da11214f4a5fbdc148 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 13:10:44 +0800 Subject: [PATCH 20/85] fix: eslint --- .../background/network/gun/encryption/queryPostKey.ts | 8 ++++---- packages/mask/background/services/crypto/decryption.ts | 2 +- .../DecryptedPost/DecryptPostAwaiting.tsx | 2 +- .../src/components/InjectedComponents/PostInspector.tsx | 1 - packages/mask/src/social-network/types.ts | 2 +- packages/plugin-infra/src/PostContext.ts | 1 - 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/mask/background/network/gun/encryption/queryPostKey.ts b/packages/mask/background/network/gun/encryption/queryPostKey.ts index d583a5819c97..ae37445bbba5 100644 --- a/packages/mask/background/network/gun/encryption/queryPostKey.ts +++ b/packages/mask/background/network/gun/encryption/queryPostKey.ts @@ -95,7 +95,7 @@ namespace Version38Or39 { // Store AES key to gun receiversKeys.forEach(async ({ aesKey, receiverKey }) => { const keyHash = await (version === -38 ? hashKey38 : hashKey39)(receiverKey) - console.log(`gun[${postHash}][${keyHash}].push(`, aesKey, `)`) + console.log(`gun[${postHash}][${keyHash}].push(`, aesKey, ')') pushToGunDataArray(gun, [postHash, keyHash], aesKey) }) } @@ -128,7 +128,7 @@ namespace Version38Or39 { } async function hashIV(networkHint: string, iv: Uint8Array) { - const hashPair = `9283464d-ee4e-4e8d-a7f3-cf392a88133f` + const hashPair = '9283464d-ee4e-4e8d-a7f3-cf392a88133f' const N = 2 const hash = await work(encodeArrayBuffer(iv), hashPair) @@ -139,7 +139,7 @@ namespace Version38Or39 { // it's an implementation bug but for backward compatibility, it cannot be changed. // Therefore we upgraded the version and use a stable hash. async function hashKey39(key: EC_Public_JsonWebKey) { - const hashPair = `10198a2f-205f-45a6-9987-3488c80113d0` + const hashPair = '10198a2f-205f-45a6-9987-3488c80113d0' const N = 2 const jwk = JSON.stringify(key) @@ -148,7 +148,7 @@ namespace Version38Or39 { } async function hashKey38(jwk: EC_Public_JsonWebKey) { - const hashPair = `10198a2f-205f-45a6-9987-3488c80113d0` + const hashPair = '10198a2f-205f-45a6-9987-3488c80113d0' const N = 2 const hash = await work(jwk.x! + jwk.y!, hashPair) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 5305df3b38fc..5bacaf8e20ea 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -137,7 +137,7 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt }, // TODO: get a gun instance async *queryPostKey_version37() { - throw 'TODO' + throw new Error('TODO') }, async *queryPostKey_version38(iv, signal) { const author = await queryPublicKey(context.currentProfile, true) diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx index ab5d03fd8f92..75c311b6723e 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx @@ -1,4 +1,4 @@ -import { memo, useState } from 'react' +import { memo } from 'react' import { ShadowRootTooltip, useI18N } from '../../../utils' import { AdditionalContent } from '../AdditionalPostContent' import type { ProfileIdentifier } from '@masknet/shared-base' diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index 7fa91f3670e9..c4d9d85f98e8 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -1,7 +1,6 @@ import { useState, useEffect } from 'react' import { useAsync } from 'react-use' import { DecryptPost } from './DecryptedPost/DecryptedPost' -import Services from '../../extension/service' import { ProfileIdentifier, type TypedMessageTuple } from '@masknet/shared-base' import type { Profile } from '../../database' import { useCurrentIdentity, useFriendsList } from '../DataSource/useActivatedUI' diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index 0101eb7867a9..8fa395669d45 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -12,7 +12,7 @@ import type { PaletteMode, Theme } from '@mui/material' import type { InjectedDialogClassKey, InjectedDialogProps } from '../components/shared/InjectedDialog' import type { Profile } from '../database' import type { PostInfo } from './PostInfo' -import type { GrayscaleAlgorithm, SocialNetworkEnum } from '@masknet/encryption' +import type { GrayscaleAlgorithm } from '@masknet/encryption' import type { createSNSAdaptorSpecializedPostContext } from './utils/create-post-context' import type { Subscription } from 'use-subscription' diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index 08a3c1c8a498..85c8284041c5 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -2,7 +2,6 @@ import { useObservableValues, useValueRef } from '@masknet/shared' import { ObservableMap, ObservableSet, - TypedMessage, type Payload, type PostIdentifier, type ProfileIdentifier, From 46c7815f44c92eadc58836c42ecf770f5d2bd6e8 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 13:13:25 +0800 Subject: [PATCH 21/85] fix: eslint --- cspell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cspell.json b/cspell.json index 559697c3796f..14d007050c02 100644 --- a/cspell.json +++ b/cspell.json @@ -361,7 +361,8 @@ "poap", "irss", "CELO", - "flac" + "flac", + "plusplus" ], "ignoreRegExpList": ["/@servie/"], "overrides": [ From 7e5e1189d26b77cd323482b71218f7314394fe77 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 15:08:44 +0800 Subject: [PATCH 22/85] feat: use new api --- .../DecryptedPost/DecryptedPost.tsx | 59 +++++++++++-------- .../InjectedComponents/PostComments.tsx | 25 ++------ .../InjectedComponents/PostInspector.tsx | 37 +----------- .../defaults/inject/CommentBox.tsx | 17 +++--- .../defaults/inject/PostInspector.tsx | 15 ++--- .../utils/create-post-context.ts | 41 ++++++++----- packages/plugin-infra/src/PostContext.ts | 14 +++-- 7 files changed, 88 insertions(+), 120 deletions(-) diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index 2cec06b662b5..c4b70c80b371 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -1,39 +1,46 @@ -import type { TypedMessageTuple, ProfileIdentifier } from '@masknet/shared-base' - -import type { Profile } from '../../../database' -import { usePostInfoDetails } from '@masknet/plugin-infra' +import { MaskPayloadContext, usePostInfoDetails } from '@masknet/plugin-infra' import { DecryptError, DecryptProgress, DecryptProgressKind, DecryptSuccess } from '@masknet/encryption' import type { DecryptionInfo } from '../../../../background/services/crypto/decryption' import { DecryptPostSuccess } from './DecryptedPostSuccess' import type { DecryptIntermediateProgress } from '@masknet/encryption/src/encryption' import { DecryptPostAwaiting } from './DecryptPostAwaiting' import { DecryptPostFailed } from './DecryptPostFailed' +import { useSubscription } from 'use-subscription' +import { findLast } from 'lodash-unified' -export interface DecryptPostProps { - onDecrypted: (post: TypedMessageTuple) => void - whoAmI: ProfileIdentifier - profiles: Profile[] - alreadySelectedPreviously: Profile[] - requestAppendRecipients?(to: Profile[]): Promise +export interface DecryptedPostsProps {} +export function DecryptedPosts(props: DecryptedPostsProps) { + const decryptions = usePostInfoDetails.maskPayloads() + return ( + <> + {decryptions.map((context) => ( + + ))} + + ) } -export function DecryptPost(props: DecryptPostProps) { - const decryptions = usePostInfoDetails.decryptedMaskPayload() - const jsx = Object.entries(decryptions).map(([key, value]) => { - const result = value.find(isDecryptSuccess) - if (result) { - return ( - - ) - } - const failed = value.find(isDecryptError) - if (failed) return - const process = value.find(isDecryptProgress) - if (process) return +function DecryptedPost(props: MaskPayloadContext) { + const decryption = useSubscription(props.decrypted) + const publicShared = useSubscription(props.publicShared) - return null - }) - return <>{jsx} + const success = findLast(decryption, isDecryptSuccess) + if (success) { + return ( + + ) + } + const failed = findLast(decryption, isDecryptError) + if (failed) return + const progress = findLast(decryption, isDecryptProgress) + if (progress) return + return null } // function useSocialNetworkEncodedPayload(): SocialNetworkEncodedPayload[] { diff --git a/packages/mask/src/components/InjectedComponents/PostComments.tsx b/packages/mask/src/components/InjectedComponents/PostComments.tsx index 0d9af9df435c..88d339d577e6 100644 --- a/packages/mask/src/components/InjectedComponents/PostComments.tsx +++ b/packages/mask/src/components/InjectedComponents/PostComments.tsx @@ -6,9 +6,7 @@ import type { ChipProps } from '@mui/material/Chip' import Lock from '@mui/icons-material/Lock' import { useEffect } from 'react' import { useAsync } from 'react-use' -import Services from '../../extension/service' -import { extractTextFromTypedMessage } from '@masknet/shared-base' -import { usePostInfoDetails } from '../DataSource/usePostInfo' +import { usePostInfo } from '../DataSource/usePostInfo' const useStyle = makeStyles()({ root: { @@ -42,30 +40,19 @@ export function PostCommentDecrypted(props: PostCommentDecryptedProps) { export interface PostCommentProps { comment: ValueRef needZip(): void - successComponentProps?: PostCommentDecryptedProps - successComponent?: React.ComponentType - waitingComponent?: React.ComponentType - failedComponent?: React.ComponentType<{ error: Error }> } export function PostComment(props: PostCommentProps) { - const { failedComponent: Fail, waitingComponent: Wait, needZip } = props + const { needZip } = props const comment = useValueRef(props.comment) - // TODO: - const postContent = usePostInfoDetails.rawMessagePiped() - const postIV = usePostInfoDetails.commentEncryptionIV() + const { decryptPostComment } = usePostInfo()! const dec = useAsync(async () => { - const decryptedText = extractTextFromTypedMessage(postContent).unwrap() - if (!postIV || !decryptedText) throw new Error('Decrypt comment failed') - const result = await Services.Crypto.decryptComment(postIV, decryptedText, comment) + const result = await decryptPostComment(comment) if (result === null) throw new Error('Decrypt result empty') return result - }, [postIV, postContent, comment]) + }, [comment]) - const Success = props.successComponent || PostCommentDecrypted useEffect(() => void (dec.value && needZip()), [dec.value, needZip]) - if (dec.error) return Fail ? : null - if (dec.loading) return Wait ? : null - if (dec.value) return {dec.value} + if (dec.value) return {dec.value} return null } diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index c4d9d85f98e8..4c619b43a697 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -1,9 +1,4 @@ -import { useState, useEffect } from 'react' -import { useAsync } from 'react-use' -import { DecryptPost } from './DecryptedPost/DecryptedPost' -import { ProfileIdentifier, type TypedMessageTuple } from '@masknet/shared-base' -import type { Profile } from '../../database' -import { useCurrentIdentity, useFriendsList } from '../DataSource/useActivatedUI' +import { DecryptedPosts } from './DecryptedPost/DecryptedPost' import { usePostInfoDetails } from '../DataSource/usePostInfo' import { createInjectHooksRenderer, useActivatedPluginsSNSAdaptor } from '@masknet/plugin-infra' import { PossiblePluginSuggestionPostInspector } from './DisabledPluginSuggestion' @@ -14,7 +9,6 @@ const PluginHooksRenderer = createInjectHooksRenderer( ) export interface PostInspectorProps { - onDecrypted(post: TypedMessageTuple): void needZip(): void /** @default 'before' */ slotPosition?: 'before' | 'after' @@ -22,38 +16,11 @@ export interface PostInspectorProps { export function PostInspector(props: PostInspectorProps) { const postBy = usePostInfoDetails.author() const encryptedPost = { ok: false } // usePostInfoDetails.containingMaskPayload() - const version = usePostInfoDetails.containsMaskPayloadVersion() - const whoAmI = useCurrentIdentity() - const friends = useFriendsList() - const [alreadySelectedPreviously, setAlreadySelectedPreviously] = useState([]) - - const { value: sharedListOfPost } = useAsync(async () => { - return [] - // if (!whoAmI || !whoAmI.identifier.equals(postBy) || !encryptedPost.ok) return [] - // const { iv, version } = encryptedPost.val - // return Services.Crypto.getPartialSharedListOfPost(version, iv, postBy) - }, []) - useEffect(() => setAlreadySelectedPreviously(sharedListOfPost ?? []), [sharedListOfPost]) // if (encryptedPost.ok) { // props.needZip() // } - return withAdditionalContent( - { - // TODO - }} - alreadySelectedPreviously={alreadySelectedPreviously} - profiles={friends} - whoAmI={whoAmI ? whoAmI.identifier : ProfileIdentifier.unknown} - />, - ) - // else if (provePost.length) { - // // TODO: - // return null - // // return withAdditionalContent() - // } + return withAdditionalContent() function withAdditionalContent(x: JSX.Element | null) { const slot = encryptedPost.ok ? null : return ( diff --git a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx index a73a284165ff..5d2a77466f23 100644 --- a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx +++ b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx @@ -2,18 +2,17 @@ import { memo, useCallback } from 'react' import type { PostInfo } from '../../PostInfo' import { DOMProxy, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' import { CommentBox, CommentBoxProps } from '../../../components/InjectedComponents/CommentBox' -import Services from '../../../extension/service' import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot' import { makeStyles } from '@masknet/theme' import { usePostInfoDetails, usePostInfo, PostInfoProvider } from '../../../components/DataSource/usePostInfo' import { noop } from 'lodash-unified' import { MaskMessages } from '../../../utils/messages' import { startWatch } from '../../../utils/watcher' -import { extractTextFromTypedMessage } from '@masknet/shared-base' +import type { PostContext } from '@masknet/plugin-infra/src' const defaultOnPasteToCommentBox = async ( encryptedComment: string, - _current: PostInfo, + _postContext: PostContext, _realCurrent: HTMLElement | null, ) => { MaskMessages.events.autoPasteFailed.sendToLocal({ text: encryptedComment }) @@ -28,19 +27,17 @@ export const injectCommentBoxDefaultFactory = function ( mountPointCallback?: (node: DOMProxy) => void, ) { const CommentBoxUI = memo(function CommentBoxUI({ dom }: { dom: HTMLElement | null }) { - const info = usePostInfo() - const postIV = usePostInfoDetails.commentEncryptionIV() + const info = usePostInfo()! + const { encryptPostComment } = usePostInfo()! const postContent = usePostInfoDetails.rawMessagePiped() const { classes } = useCustomStyles() const props = additionPropsToCommentBox(classes) const onCallback = useCallback( async (content) => { - const decryptedText = extractTextFromTypedMessage(postContent).unwrap() - if (!postIV) throw new TypeError('[@masknet/encryption] No IV, cannot encrypt/decrypt comment.') - const encryptedComment = await Services.Crypto.encryptComment(postIV, decryptedText, content) - onPasteToCommentBox(encryptedComment, info!, dom).catch(console.error) + const encryptedComment = await encryptPostComment(content) + onPasteToCommentBox(encryptedComment, info, dom).catch(console.error) }, - [postIV, postContent, info, dom, postIV], + [encryptPostComment, postContent, info, dom], ) if (!postContent.items.length) return null diff --git a/packages/mask/src/social-network/defaults/inject/PostInspector.tsx b/packages/mask/src/social-network/defaults/inject/PostInspector.tsx index 838f58e86777..3c7ac4218ed0 100644 --- a/packages/mask/src/social-network/defaults/inject/PostInspector.tsx +++ b/packages/mask/src/social-network/defaults/inject/PostInspector.tsx @@ -12,14 +12,11 @@ export function injectPostInspectorDefault( additionalPropsToPostInspector: (classes: Record) => Partial = () => ({}), useCustomStyles: (props?: any) => { classes: Record } = makeStyles()({}) as any, ) { - const PostInspectorDefault = memo(function PostInspectorDefault(props: { - onDecrypted: PostInspectorProps['onDecrypted'] - zipPost: PostInspectorProps['needZip'] - }) { - const { onDecrypted, zipPost } = props + const PostInspectorDefault = memo(function PostInspectorDefault(props: { zipPost: PostInspectorProps['needZip'] }) { + const { zipPost } = props const { classes } = useCustomStyles() const additionalProps = additionalPropsToPostInspector(classes) - return + return }) const { zipPost, injectionPoint } = config @@ -27,11 +24,7 @@ export function injectPostInspectorDefault( return function injectPostInspector(current: PostInfo, signal: AbortSignal) { const jsx = ( - {}} - zipPost={() => zipPostF(current.rootElement)} - {...current} - /> + zipPostF(current.rootElement)} {...current} /> ) const root = createReactRootShadowed(injectionPoint?.(current) ?? current.rootElement.afterShadow, { diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index ea3ac83b1ac7..ffb009da0e80 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -1,4 +1,5 @@ import { ValueRef } from '@dimensiondev/holoflows-kit' +import { encodeArrayBuffer } from '@dimensiondev/kit' import type { PostContext, PostContextAuthor, PostContextCreation, PostContextSNSActions } from '@masknet/plugin-infra' import { ALL_EVENTS, @@ -23,6 +24,7 @@ import { } from '@masknet/shared-base' import type { Subscription } from 'use-subscription' import { activatedSocialNetworkUI } from '../' +import { nonNullable } from '../../../utils-pure' import { FACEBOOK_ID } from '../../social-network-adaptor/facebook.com/base' import { resolveFacebookLink } from '../../social-network-adaptor/facebook.com/utils/resolveFacebookLink' @@ -73,18 +75,20 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct return () => void [a(), b()] }, }) - let initialTrigger = false - const rawMessageResolved = createSubscriptionFromAsync( - () => waitTypedMessage(opt.rawMessage.getCurrentValue()).then(flattenTypedMessage), - makeTypedMessageEmpty(), - (e) => { - !initialTrigger && e() && (initialTrigger = true) - return opt.rawMessage.subscribe(e) - }, - ) - // @ts-ignore + const MaskPayloads: PostContext['maskPayloads'] = new ObservableMap() + function calculateCommentEncryptionIV() { + const all = [...MaskPayloads.values()] + .map((x) => x.iv.getCurrentValue()?.buffer) + .filter(nonNullable) + .map(encodeArrayBuffer) + .sort() + return all[0] as string | null + } return { - ...author, + author: author.author, + avatarURL: author.avatarURL, + nickname: author.nickname, + snsID: author.snsID, get rootNode() { return opt.rootElement.realCurrent @@ -109,10 +113,17 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct rawMessage: opt.rawMessage, rawMessagePiped: transformedPostContent, - // TODO - commentEncryptionIV: createConstantSubscription(''), - containsMaskPayload: createConstantSubscription(false), - payloadClaimedAuthor: createConstantSubscription(undefined), + async decryptPostComment() { + throw new Error('TODO') + }, + async encryptPostComment() { + throw new Error('TODO') + }, + containsMaskPayload: { + getCurrentValue: () => MaskPayloads.size !== 0, + subscribe: (sub) => MaskPayloads.event.on(ALL_EVENTS, sub), + }, + maskPayloads: MaskPayloads, } } } diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index 85c8284041c5..e62af6250569 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -70,12 +70,18 @@ export interface PostContext extends PostContextAuthor { //#endregion //#region Information revealed in the Mask Payload readonly containsMaskPayload: Subscription - readonly containsMaskPayloadVersion: Subscription> - readonly payloadClaimedAuthor: Subscription - readonly commentEncryptionIV: Subscription - readonly decryptedMaskPayload: Subscription> + encryptPostComment(comment: string): Promise + decryptPostComment(comment: string): Promise + readonly maskPayloads: ObservableMap //#endregion } +export interface MaskPayloadContext { + readonly claimedAuthor: Subscription + readonly decrypted: Subscription + readonly iv: Subscription + readonly publicShared: Subscription + readonly id: string +} export type PostInfo = PostContext const Context = createContext(null) From 4accf1d44f3771aa3283f6cee8c98c3ad7376435 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 16:35:53 +0800 Subject: [PATCH 23/85] feat: add decryption in post context --- .../background/services/crypto/decryption.ts | 7 +- .../components/DataSource/useActivatedUI.ts | 20 +- .../DecryptedPost/DecryptedPost.tsx | 58 +----- .../DecryptedPost/DecryptedPostSuccess.tsx | 4 +- .../utils/create-post-context.ts | 183 ++++++++++++++++-- packages/plugin-infra/src/PostContext.ts | 2 +- 6 files changed, 195 insertions(+), 79 deletions(-) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 5bacaf8e20ea..b0cadcc14bfe 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -31,6 +31,8 @@ import { getGunInstance } from '../../network/gun/instance' export type DecryptionInfo = { type: DecryptProgressKind.Info iv?: Uint8Array + claimedAuthor?: ProfileIdentifier + publicShared?: boolean } export type DecryptionProgress = DecryptProgress | DecryptionInfo @@ -50,10 +52,11 @@ export async function* decryptionWithSocialNetworkDecoding( context: DecryptionContext, ) { yield* new EventIterator<[id: string, progress: DecryptProgress | DecryptionInfo]>((flow) => { + let globalID = 0 for (const e of encoded) { + const id = (globalID += 1) + '' + let decoded!: string | Uint8Array async function main() { - const id = Math.random() + '' - let decoded!: string | Uint8Array if (e.type === 'text') { // TODO: socialNetworkDecoder should emit multiple results if the text contains multiple payload. decoded = socialNetworkDecoder(context.currentSocialNetwork, e.text).unwrapOr(e.text) diff --git a/packages/mask/src/components/DataSource/useActivatedUI.ts b/packages/mask/src/components/DataSource/useActivatedUI.ts index 915a09385854..b9162718bfc5 100644 --- a/packages/mask/src/components/DataSource/useActivatedUI.ts +++ b/packages/mask/src/components/DataSource/useActivatedUI.ts @@ -5,6 +5,7 @@ import { ProfileIdentifier } from '@masknet/shared-base' import type { Profile } from '../../database' import type { SocialNetworkUI } from '../../social-network' import { activatedSocialNetworkUI, globalUIState } from '../../social-network' +import { Subscription, useSubscription } from 'use-subscription' export function useFriendsList() { const ref = useValueRef(globalUIState.friends) @@ -25,8 +26,19 @@ export function useCurrentVisitingIdentity() { export function useMyIdentities() { return useValueRef(globalUIState.profiles) } -export function useCurrentIdentity(noDefault?: boolean): Profile | null { - const all = useMyIdentities() - const current = useLastRecognizedIdentity() - return all.find((i) => i.identifier.toText() === current.identifier.toText()) || (noDefault ? null : all[0]) +export function useCurrentIdentity(): Profile | null { + return useSubscription(CurrentIdentitySubscription) +} + +export const CurrentIdentitySubscription: Subscription = { + getCurrentValue() { + const all = globalUIState.profiles.value + const current = (activatedSocialNetworkUI.collecting.identityProvider?.recognized || default_).value.identifier + return all.find((i) => i.identifier.toText() === current.toText()) || all[0] + }, + subscribe(sub) { + const a = globalUIState.profiles.addListener(sub) + const b = activatedSocialNetworkUI.collecting.identityProvider?.recognized.addListener(sub) + return () => [a(), b?.()] + }, } diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index c4b70c80b371..9342e3ccb5e5 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -8,8 +8,7 @@ import { DecryptPostFailed } from './DecryptPostFailed' import { useSubscription } from 'use-subscription' import { findLast } from 'lodash-unified' -export interface DecryptedPostsProps {} -export function DecryptedPosts(props: DecryptedPostsProps) { +export function DecryptedPosts() { const decryptions = usePostInfoDetails.maskPayloads() return ( <> @@ -43,61 +42,6 @@ function DecryptedPost(props: MaskPayloadContext) { return null } -// function useSocialNetworkEncodedPayload(): SocialNetworkEncodedPayload[] { -// const message = usePostInfoDetails.rawMessageResolved() -// const mentionedLinks = usePostInfoDetails.mentionedLinks() -// return useMemo(() => { -// const text = extractTextFromTypedMessage(message) -// const images = extractImageFromTypedMessage(message) -// const payloads: SocialNetworkEncodedPayload[] = [] -// if (text.ok && text.val.length > 10) payloads.push({ type: 'text', text: text.val }) -// for (const text of mentionedLinks) payloads.push({ type: 'text', text }) -// for (const image of images) if (typeof image === 'string') payloads.push({ type: 'image-url', url: image }) -// return payloads -// }, [message, mentionedLinks.join(' ')]) -// } - -// function useDecryption(whoAmI: ProfileIdentifier) { -// const payloads = useSocialNetworkEncodedPayload() -// const author = usePostInfoDetails.author() -// const url = usePostInfoDetails.url()?.toString() -// const [decryptions, _setDecryption] = useState>({}) -// function setDecryption(f: (val: typeof decryptions) => void) { -// _setDecryption((val) => draft(val, f)) -// } - -// useEffect(() => { -// const abort = new AbortController() -// async function main() { -// const process = ServicesWithProgress.decryptionWithSocialNetworkDecoding(payloads, { -// authorHint: author, -// currentProfile: whoAmI, -// currentSocialNetwork: SocialNetworkEnum.Twitter, -// postURL: url, -// }) -// const currentPassIDs = new Set() -// abort.signal.addEventListener('abort', () => { -// setDecryption((val) => { -// for (const id of currentPassIDs) delete val[id] -// }) -// }) -// for await (const [id, item] of process) { -// if (abort.signal.aborted) return -// currentPassIDs.add(id) -// setDecryption((val) => { -// val[id] ||= [] -// val[id] = [item, ...val[id]] -// }) -// } -// } -// main() - -// return () => abort.abort() -// }, [payloads, author.toText(), whoAmI.toText(), url]) - -// return decryptions -// } - function isDecryptSuccess(value: DecryptProgress | DecryptionInfo): value is DecryptSuccess { return value.type === DecryptProgressKind.Success } diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx index 09c66ad4e604..156446d625d7 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx @@ -37,7 +37,7 @@ export interface DecryptPostSuccessProps extends withClasses { requestAppendRecipients?(to: Profile[]): Promise alreadySelectedPreviously: Profile[] profiles: Profile[] - sharedPublic?: boolean + sharedPublic: boolean | null AdditionalContentProps?: Partial /** The author in the payload */ author?: ProfileIdentifier @@ -68,7 +68,7 @@ export const DecryptPostSuccess = memo(function DecryptPostSuccess(props: Decryp props.requestAppendRecipients || (async () => {}), props.alreadySelectedPreviously, ) - const rightActions = props.requestAppendRecipients && !props.sharedPublic && ( + const rightActions = props.requestAppendRecipients && props.sharedPublic === false && ( {t('decrypted_postbox_add_recipients')} diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index ffb009da0e80..6e3f9b681d05 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -1,6 +1,13 @@ import { ValueRef } from '@dimensiondev/holoflows-kit' -import { encodeArrayBuffer } from '@dimensiondev/kit' -import type { PostContext, PostContextAuthor, PostContextCreation, PostContextSNSActions } from '@masknet/plugin-infra' +import { encodeArrayBuffer, encodeText } from '@dimensiondev/kit' +import { DecryptProgress, DecryptProgressKind, SocialNetworkEnum } from '@masknet/encryption' +import type { + MaskPayloadContext, + PostContext, + PostContextAuthor, + PostContextCreation, + PostContextSNSActions, +} from '@masknet/plugin-infra' import { ALL_EVENTS, extractTextFromTypedMessage, @@ -21,10 +28,15 @@ import { makeTypedMessageEmpty, flattenTypedMessage, createConstantSubscription, + combineAbortSignal, + extractImageFromTypedMessage, } from '@masknet/shared-base' import type { Subscription } from 'use-subscription' import { activatedSocialNetworkUI } from '../' +import type { SocialNetworkEncodedPayload } from '../../../background/services/crypto/decryption' import { nonNullable } from '../../../utils-pure' +import { CurrentIdentitySubscription } from '../../components/DataSource/useActivatedUI' +import { ServicesWithProgress } from '../../extension/service' import { FACEBOOK_ID } from '../../social-network-adaptor/facebook.com/base' import { resolveFacebookLink } from '../../social-network-adaptor/facebook.com/utils/resolveFacebookLink' @@ -50,7 +62,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct fillLinks() cancel.push(opt.rawMessage.subscribe(fillLinks)) } - const linksSubscribe: Subscription = debug({ + const mentionedLinks: Subscription = debug({ getCurrentValue: () => [...links], subscribe: (sub) => links.event.on(ALL_EVENTS, sub), }) @@ -75,7 +87,23 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct return () => void [a(), b()] }, }) - const MaskPayloads: PostContext['maskPayloads'] = new ObservableMap() + const url = debug({ + getCurrentValue: () => { + const id = postIdentifier.getCurrentValue() + if (id) return create.getURLFromPostIdentifier?.(id) || null + return null + }, + subscribe: (sub) => postIdentifier.subscribe(sub), + }) + const MaskPayloads = decryptionContext( + { + mentionedLinks, + rawMessage: opt.rawMessage, + author: author.author, + url, + }, + opt.signal, + ) function calculateCommentEncryptionIV() { const all = [...MaskPayloads.values()] .map((x) => x.iv.getCurrentValue()?.buffer) @@ -99,16 +127,9 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct comment: opt.comments, identifier: postIdentifier, - url: debug({ - getCurrentValue: () => { - const id = postIdentifier.getCurrentValue() - if (id) return create.getURLFromPostIdentifier?.(id) || null - return null - }, - subscribe: (sub) => postIdentifier.subscribe(sub), - }), + url, - mentionedLinks: linksSubscribe, + mentionedLinks: mentionedLinks, rawMessage: opt.rawMessage, rawMessagePiped: transformedPostContent, @@ -163,3 +184,139 @@ export function createRefsForCreatePostContext() { postMetadataImages, } } + +function decryptionContext( + context: Pick, + signal?: AbortSignal, +): PostContext['maskPayloads'] { + const { author, mentionedLinks, rawMessage, url } = context + const map: PostContext['maskPayloads'] = new ObservableMap() + const providerMap = new Map() + + { + let abort = new AbortController() + async function decryptByLinks() { + const links = [...mentionedLinks.getCurrentValue()].sort() + const key = await hash(links.join(';')) + await decryption( + key, + combineAbortSignal(signal, abort.signal), + links.map((link) => ({ type: 'text', text: link })), + ) + } + + decryptByLinks() + const f = mentionedLinks.subscribe(() => { + abort.abort() + abort = new AbortController() + decryptByLinks() + }) + signal?.addEventListener('abort', f) + } + + { + let abort = new AbortController() + async function decryptByPost() { + const message = await waitTypedMessage(rawMessage.getCurrentValue()) + let text = extractTextFromTypedMessage(message).unwrapOr('') + if (text.length < 10) text = '' + const images = extractImageFromTypedMessage(message) + .filter((x): x is string => typeof x === 'string') + .sort() + const key = await hash(text + images.join(';')) + + const payloads: SocialNetworkEncodedPayload[] = [] + + if (text) payloads.push({ type: 'text', text }) + payloads.push(...images.map((url) => ({ type: 'image-url', url }))) + await decryption(key, combineAbortSignal(signal, abort.signal), payloads) + } + + decryptByPost() + const f = mentionedLinks.subscribe(() => { + abort.abort() + abort = new AbortController() + decryptByPost() + }) + signal?.addEventListener('abort', f) + } + + async function decryption(key: string, signal: AbortSignal, payloads: SocialNetworkEncodedPayload[]) { + const decryption = ServicesWithProgress.decryptionWithSocialNetworkDecoding(payloads, { + // TODO: + currentSocialNetwork: SocialNetworkEnum.Twitter, + authorHint: author.getCurrentValue(), + currentProfile: CurrentIdentitySubscription.getCurrentValue()?.identifier, + postURL: url.getCurrentValue()?.toString(), + }) + const currentPassIDs = new Set() + signal.addEventListener('abort', () => { + currentPassIDs.forEach((id) => { + providerMap.delete(id) + map.delete(id) + }) + decryption.return() + }) + + for await (const [decryptionID, progress] of decryption) { + if (signal.aborted) return + const currentID = `${key}-${decryptionID}` + currentPassIDs.add(currentID) + if (!map.has(decryptionID)) { + const provider = new MaskPayloadContextProvider(decryptionID) + providerMap.set(currentID, provider) + map.set(currentID, provider.context) + } + + const provider = providerMap.get(currentID)! + if ( + progress.type === DecryptProgressKind.Success || + progress.type === DecryptProgressKind.Error || + progress.type === DecryptProgressKind.Progress + ) { + provider.pushDecryptionProgress(progress) + } else { + const { iv, claimedAuthor, publicShared } = progress + if (typeof publicShared === 'boolean') provider.setPublicShared(publicShared) + if (iv) provider.setIV(iv) + if (claimedAuthor) provider.setAuthor(claimedAuthor) + } + } + } + + return map +} + +class MaskPayloadContextProvider { + constructor(public id: string) {} + private claimedAuthor = new ValueRef(null, ProfileIdentifier.equals) + private decrypted = new ValueRef([]) + private iv = new ValueRef(null) + private publicShared = new ValueRef(null) + context: MaskPayloadContext = { + id: this.id, + claimedAuthor: SubscriptionFromValueRef(this.claimedAuthor), + decrypted: SubscriptionFromValueRef(this.decrypted), + iv: SubscriptionFromValueRef(this.iv), + publicShared: SubscriptionFromValueRef(this.publicShared), + } + setAuthor(profile: ProfileIdentifier) { + this.claimedAuthor.value = profile + } + pushDecryptionProgress(progress: DecryptProgress) { + this.decrypted.value = this.decrypted.value.concat(progress) + } + setIV(val: Uint8Array) { + this.iv.value = val + } + setPublicShared(val: boolean) { + this.publicShared.value = val + } +} + +// hash for a stable key of rendering UI, therefore SHA-1 is acceptable +async function hash(val: string | Uint8Array) { + if (typeof val === 'string') val = encodeText(val) + const buf = await crypto.subtle.digest({ name: 'SHA-1' }, val) + return encodeArrayBuffer(buf) +} diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index e62af6250569..e1bab6dc497a 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -79,7 +79,7 @@ export interface MaskPayloadContext { readonly claimedAuthor: Subscription readonly decrypted: Subscription readonly iv: Subscription - readonly publicShared: Subscription + readonly publicShared: Subscription readonly id: string } export type PostInfo = PostContext From ee0960f2eb49b47a317c93506ca1fe732294e60f Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 18:21:19 +0800 Subject: [PATCH 24/85] feat: make it work --- .../defaults/inject/CommentBox.tsx | 9 +- .../utils/create-post-context.ts | 83 +++++++++++++------ packages/plugin-infra/src/PostContext.ts | 2 - .../src/TypedMessage/transforms/promise.ts | 19 ++--- packages/shared-base/src/collections/index.ts | 19 +++-- .../shared/src/hooks/useObservableMapSet.ts | 5 +- 6 files changed, 82 insertions(+), 55 deletions(-) diff --git a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx index 5d2a77466f23..e80e7caaef4e 100644 --- a/packages/mask/src/social-network/defaults/inject/CommentBox.tsx +++ b/packages/mask/src/social-network/defaults/inject/CommentBox.tsx @@ -28,8 +28,8 @@ export const injectCommentBoxDefaultFactory = function ( ) { const CommentBoxUI = memo(function CommentBoxUI({ dom }: { dom: HTMLElement | null }) { const info = usePostInfo()! - const { encryptPostComment } = usePostInfo()! - const postContent = usePostInfoDetails.rawMessagePiped() + const hasMask = usePostInfoDetails.containsMaskPayload() + const { encryptPostComment } = info const { classes } = useCustomStyles() const props = additionPropsToCommentBox(classes) const onCallback = useCallback( @@ -37,10 +37,9 @@ export const injectCommentBoxDefaultFactory = function ( const encryptedComment = await encryptPostComment(content) onPasteToCommentBox(encryptedComment, info, dom).catch(console.error) }, - [encryptPostComment, postContent, info, dom], + [encryptPostComment, info, dom], ) - - if (!postContent.items.length) return null + if (!hasMask) return null return }) return (signal: AbortSignal, current: PostInfo) => { diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index 6e3f9b681d05..dae82ae59b73 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -11,7 +11,6 @@ import type { import { ALL_EVENTS, extractTextFromTypedMessage, - isTypedMessageEqual, makeTypedMessageTupleFromList, ObservableMap, ObservableSet, @@ -23,13 +22,10 @@ import { SubscriptionFromValueRef, SubscriptionDebug as debug, mapSubscription, - createSubscriptionFromAsync, - waitTypedMessage, - makeTypedMessageEmpty, - flattenTypedMessage, - createConstantSubscription, + collectTypedMessagePromise, combineAbortSignal, extractImageFromTypedMessage, + flattenTypedMessage, } from '@masknet/shared-base' import type { Subscription } from 'use-subscription' import { activatedSocialNetworkUI } from '../' @@ -48,19 +44,36 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct //#region Mentioned links const links = new ObservableSet() { - async function fillLinks() { - const message = await waitTypedMessage(opt.rawMessage.getCurrentValue()) + const isFB = activatedSocialNetworkUI.networkIdentifier === FACEBOOK_ID + + let controller = new AbortController() + function fillLinks() { + const signal = controller.signal + function addLink(x: string) { + if (signal.aborted) return + if (isFB) links.add(resolveFacebookLink(x)) + else links.add(x) + } + // Instant values + const message = flattenTypedMessage(opt.rawMessage.getCurrentValue()) const text = extractTextFromTypedMessage(message).unwrapOr('') const link = parseURL(text).concat(opt.postMentionedLinksProvider?.getCurrentValue() || []) + link.forEach(addLink) + + // Deferred values + collectTypedMessagePromise(message).forEach((x) => + x.then(extractTextFromTypedMessage).then((x) => x.map(parseURL).map((val) => val.forEach(addLink))), + ) + } + const retry = () => { + controller.abort() + controller = new AbortController() links.clear() - for (const x of link) { - if (activatedSocialNetworkUI.networkIdentifier === FACEBOOK_ID) { - links.add(resolveFacebookLink(x)) - } else links.add(x) - } + fillLinks() } fillLinks() - cancel.push(opt.rawMessage.subscribe(fillLinks)) + cancel.push(opt.rawMessage.subscribe(retry)) + cancel.push(opt.postMentionedLinksProvider?.subscribe(retry)) } const mentionedLinks: Subscription = debug({ getCurrentValue: () => [...links], @@ -73,7 +86,6 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct author: opt.author, snsID: opt.snsID, } - const transformedPostContent = new ValueRef(makeTypedMessageTupleFromList(), isTypedMessageEqual) const postIdentifier = debug({ getCurrentValue: () => { const by = opt.author.getCurrentValue() @@ -132,7 +144,6 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct mentionedLinks: mentionedLinks, rawMessage: opt.rawMessage, - rawMessagePiped: transformedPostContent, async decryptPostComment() { throw new Error('TODO') @@ -197,6 +208,8 @@ function decryptionContext( let abort = new AbortController() async function decryptByLinks() { const links = [...mentionedLinks.getCurrentValue()].sort() + console.log('start by link', ...links) + if (links.length === 0) return const key = await hash(links.join(';')) await decryption( key, @@ -216,29 +229,47 @@ function decryptionContext( { let abort = new AbortController() - async function decryptByPost() { - const message = await waitTypedMessage(rawMessage.getCurrentValue()) + async function decryptByPost( + message = flattenTypedMessage(rawMessage.getCurrentValue()), + signal = abort.signal, + ) { + // Instant values let text = extractTextFromTypedMessage(message).unwrapOr('') + { + // Because all links are in the mentionedLinks, we should remove them to avoid duplicate + const links = mentionedLinks.getCurrentValue() + links.forEach((link) => (text = text.replace(link, ''))) + } if (text.length < 10) text = '' const images = extractImageFromTypedMessage(message) .filter((x): x is string => typeof x === 'string') .sort() - const key = await hash(text + images.join(';')) - const payloads: SocialNetworkEncodedPayload[] = [] - if (text) payloads.push({ type: 'text', text }) payloads.push(...images.map((url) => ({ type: 'image-url', url }))) + if (payloads.length === 0) return + + // Deferred values + const promises = collectTypedMessagePromise(message) + promises.forEach((promise) => { + promise.then((message) => decryptByPost(message, signal)) + }) + + // Instant value parse + const key = await hash(text + images.join(';')) await decryption(key, combineAbortSignal(signal, abort.signal), payloads) } decryptByPost() - const f = mentionedLinks.subscribe(() => { + const reload = () => { abort.abort() abort = new AbortController() decryptByPost() - }) + } + const f = rawMessage.subscribe(reload) + const f2 = mentionedLinks.subscribe(reload) signal?.addEventListener('abort', f) + signal?.addEventListener('abort', f2) } async function decryption(key: string, signal: AbortSignal, payloads: SocialNetworkEncodedPayload[]) { @@ -263,7 +294,7 @@ function decryptionContext( const currentID = `${key}-${decryptionID}` currentPassIDs.add(currentID) if (!map.has(decryptionID)) { - const provider = new MaskPayloadContextProvider(decryptionID) + const provider = new MaskPayloadContextProvider(currentID) providerMap.set(currentID, provider) map.set(currentID, provider.context) } @@ -315,8 +346,8 @@ class MaskPayloadContextProvider { } // hash for a stable key of rendering UI, therefore SHA-1 is acceptable -async function hash(val: string | Uint8Array) { - if (typeof val === 'string') val = encodeText(val) +async function hash(text: string) { + const val = encodeText(text) const buf = await crypto.subtle.digest({ name: 'SHA-1' }, val) return encodeArrayBuffer(buf) } diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index e1bab6dc497a..2bc2608e4235 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -65,8 +65,6 @@ export interface PostContext extends PostContextAuthor { //#endregion //#region Raw post content (not decrypted) readonly rawMessage: Subscription - // TODO: should be a Subscription - readonly rawMessagePiped: ValueRef //#endregion //#region Information revealed in the Mask Payload readonly containsMaskPayload: Subscription diff --git a/packages/shared-base/src/TypedMessage/transforms/promise.ts b/packages/shared-base/src/TypedMessage/transforms/promise.ts index 6fb6be512498..26a225d06f4a 100644 --- a/packages/shared-base/src/TypedMessage/transforms/promise.ts +++ b/packages/shared-base/src/TypedMessage/transforms/promise.ts @@ -1,5 +1,4 @@ -import { isTypedMessagePromise, isTypedMessageTuple, TypedMessage } from '..' -import { flattenTypedMessage } from './flatten' +import { flattenTypedMessage, isTypedMessagePromise, isTypedMessageTuple, TypedMessage } from '..' export function hasPromise(x: TypedMessage) { if (isTypedMessagePromise(x)) return true @@ -7,17 +6,13 @@ export function hasPromise(x: TypedMessage) { return false } -export async function waitTypedMessage(x: TypedMessage): Promise { - if (!hasPromise(x)) return x - const promise = collectPromise(x) - await Promise.allSettled(promise) - return flattenTypedMessage(x) -} - -function collectPromise(x: TypedMessage, result: Promise[] = []): Promise[] { - if (isTypedMessagePromise(x)) return result.concat(x.promise) +export function collectTypedMessagePromise( + x: TypedMessage, + result: Promise[] = [], +): Promise[] { + if (isTypedMessagePromise(x)) return result.concat(x.promise.then(flattenTypedMessage)) if (isTypedMessageTuple(x)) { - return result.concat(x.items.flatMap((x) => collectPromise(x))) + return result.concat(x.items.flatMap((x) => collectTypedMessagePromise(x))) } return result } diff --git a/packages/shared-base/src/collections/index.ts b/packages/shared-base/src/collections/index.ts index b77f88efefc1..fde607b73e3a 100644 --- a/packages/shared-base/src/collections/index.ts +++ b/packages/shared-base/src/collections/index.ts @@ -1,18 +1,21 @@ import { Emitter } from '@servie/events' export { ALL_EVENTS } from '@servie/events' -// Consider switch to libraries like Mobx if this file become too complex. + +function tick(callback: () => void) { + Promise.resolve().then(callback) +} export class ObservableWeakMap extends WeakMap { declare __brand: 'Map' event = new Emitter<{ delete: [K]; set: [K, V] }>() override delete(key: K) { const _ = super.delete(key) - this.event.emit('delete', key) + tick(() => this.event.emit('delete', key)) return _ } override set(key: K, value: V) { const _ = super.set(key, value) - this.event.emit('set', key, value) + tick(() => this.event.emit('set', key, value)) return _ } } @@ -22,15 +25,16 @@ export class ObservableMap extends Map { event = new Emitter<{ delete: [K]; set: [K, V]; clear: [] }>() override clear() { super.clear() - this.event.emit('clear') + this.size && tick(() => this.event.emit('clear')) } override delete(key: K) { const _ = super.delete(key) - this.event.emit('delete', key) + tick(() => this.event.emit('delete', key)) return _ } override set(key: K, value: V) { const _ = super.set(key, value) + tick(() => this.event.emit('set', key, value)) this.event.emit('set', key, value) return _ } @@ -41,16 +45,17 @@ export class ObservableSet extends Set { event = new Emitter<{ delete: [T]; add: [T[]]; clear: [] }>() override clear() { super.clear() - this.event.emit('clear') + this.size && tick(() => this.event.emit('clear')) } override delete(key: T) { const _ = super.delete(key) - this.event.emit('delete', key) + tick(() => this.event.emit('delete', key)) return _ } override add(...value: T[]) { value.forEach((x) => super.add(x)) this.event.emit('add', value) + tick(() => this.event.emit('add', value)) return this } } diff --git a/packages/shared/src/hooks/useObservableMapSet.ts b/packages/shared/src/hooks/useObservableMapSet.ts index 3caab505ea8c..f8a44be6be66 100644 --- a/packages/shared/src/hooks/useObservableMapSet.ts +++ b/packages/shared/src/hooks/useObservableMapSet.ts @@ -1,14 +1,13 @@ import { useSubscription, Subscription } from 'use-subscription' import { useMemo } from 'react' -import type { ObservableMap, ObservableSet } from '@masknet/shared-base' +import { ALL_EVENTS, ObservableMap, ObservableSet } from '@masknet/shared-base' export function useObservableValues(map: ObservableMap | ObservableSet) { const subscription = useMemo>( () => ({ getCurrentValue: () => [...map.values()], subscribe: (callback) => { - // @ts-ignore - return map.event.on(ALL_EVENTS, callback) + return (map.event.on as any)(ALL_EVENTS, callback) }, }), [map], From f5ee815db8b30f3447d216a8cdf615bf2da724db Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 19:31:17 +0800 Subject: [PATCH 25/85] refactor: remove unused code --- packages/encryption/src/index.ts | 2 + .../InjectedComponents/AddToKeyStore.tsx | 30 -- .../DecryptedPost/DecryptPostFailed.tsx | 4 +- .../TypedMessageRenderer.tsx | 25 +- .../background-script/CryptoService.ts | 1 - .../CryptoServices/decryptFrom.ts | 344 ------------------ .../CryptoServices/encryptTo.ts | 74 ++-- .../CryptoServices/verifyOthersProve.ts | 28 -- .../mask/src/extension/service-generator.ts | 1 - .../facebook.com/shared.ts | 4 - .../instagram.com/shared.ts | 5 +- .../minds.com/shared.ts | 4 - .../twitter.com/encoding.ts | 76 ---- .../twitter.com/injection/PostInspector.tsx | 4 +- .../twitter.com/shared.ts | 20 - packages/mask/src/social-network/types.ts | 8 - .../social-network/utils/text-payload-ui.ts | 21 -- .../utils/text-payload-worker.ts | 19 - packages/mask/src/utils/constants.ts | 3 - packages/mask/src/utils/index.ts | 6 +- .../mask/src/utils/type-transform/Payload.ts | 110 ------ .../type-transform/asyncIteratorHelpers.ts | 63 ---- .../mask/src/utils/type-transform/index.ts | 2 - packages/plugin-infra/src/PostContext.ts | 9 - packages/shared-base/src/MaskPayload/index.ts | 33 -- 25 files changed, 72 insertions(+), 824 deletions(-) delete mode 100644 packages/mask/src/components/InjectedComponents/AddToKeyStore.tsx delete mode 100644 packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts delete mode 100644 packages/mask/src/extension/background-script/CryptoServices/verifyOthersProve.ts delete mode 100644 packages/mask/src/social-network-adaptor/twitter.com/encoding.ts delete mode 100644 packages/mask/src/social-network/utils/text-payload-ui.ts delete mode 100644 packages/mask/src/social-network/utils/text-payload-worker.ts delete mode 100644 packages/mask/src/utils/constants.ts delete mode 100644 packages/mask/src/utils/type-transform/Payload.ts delete mode 100644 packages/mask/src/utils/type-transform/asyncIteratorHelpers.ts delete mode 100644 packages/shared-base/src/MaskPayload/index.ts diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index d64bb3a7ade9..d0b93f54dfd6 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -41,3 +41,5 @@ export { steganographyEncodeImage, GrayscaleAlgorithm, } from './image-steganography' +// TODO: remove them in the future +export { importAsymmetryKeyFromJsonWebKeyOrSPKI, importAESFromJWK } from './utils' diff --git a/packages/mask/src/components/InjectedComponents/AddToKeyStore.tsx b/packages/mask/src/components/InjectedComponents/AddToKeyStore.tsx deleted file mode 100644 index d1f5a519fcdb..000000000000 --- a/packages/mask/src/components/InjectedComponents/AddToKeyStore.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useAsync } from 'react-use' -import { createElement, memo } from 'react' -import { useI18N } from '../../utils' -import { AdditionalContent, AdditionalContentProps } from './AdditionalPostContent' -import Services from '../../extension/service' -import type { ProfileIdentifier } from '@masknet/shared-base' - -export interface AddToKeyStoreProps { - provePost: string - postBy: ProfileIdentifier - completeComponentProps?: Partial - completeComponent?: React.ComponentType<{ data: boolean }> | null -} -export function AddToKeyStore({ provePost, postBy, ...props }: AddToKeyStoreProps) { - const state = useAsync(() => Services.Crypto.verifyOthersProve(provePost, postBy), [provePost, postBy]) - const { completeComponent: Success, completeComponentProps } = props - if (state.value) return render(Success, AddToKeyStoreUI.success, completeComponentProps) - else return null - function render(outer: React.ComponentType | null | undefined, def: React.ComponentType, props?: {}) { - if (outer === null) return null - return createElement(outer || def, props) - } -} -type SuccessProps = Partial -export const AddToKeyStoreUI = { - success: memo((props: SuccessProps) => { - const { t } = useI18N() - return - }), -} diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx index 849d15a05539..33967cdf68f4 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostFailed.tsx @@ -1,7 +1,7 @@ import { memo } from 'react' import { useI18N } from '../../../utils' import { AdditionalContent } from '../AdditionalPostContent' -import { DecryptFailedReason } from '../../../utils/constants' +import { ErrorReasons } from '@masknet/encryption' import type { ProfileIdentifier } from '@masknet/shared-base' import { wrapAuthorDifferentMessage } from './authorDifferentMessage' import MaskPluginWrapper from '../../../plugins/MaskPluginWrapper' @@ -17,7 +17,7 @@ export const DecryptPostFailed = memo(function DecryptPostFailed(props: DecryptP const { author, postedBy, error } = props const { t } = useI18N() - if (error?.message === DecryptFailedReason.MyCryptoKeyNotFound) { + if (error?.message === ErrorReasons.CurrentProfileDoesNotConnectedToPersona) { return } return ( diff --git a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx index 9980572d87b9..f659abf69525 100644 --- a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx +++ b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx @@ -17,7 +17,6 @@ import { import { Image } from '../shared/Image' import { useAsync } from 'react-use' import { getRendererOfTypedMessage, registerTypedMessageRenderer } from '../../protocols/typed-message' -import { deconstructPayload } from '../../utils/type-transform/Payload' import { PayloadReplacer } from './PayloadReplacer' import { useI18N } from '../../utils' @@ -50,15 +49,12 @@ export const DefaultTypedMessageTextRenderer = memo(function DefaultTypedMessage props: TypedMessageRendererProps, ) { const { content } = props.message - const deconstructed = deconstructPayload(content, null) return renderWithMetadata( props, - {deconstructed.ok ? ( - - ) : ( - - )} + {/* // TODO: */} + + , ) }) @@ -72,18 +68,15 @@ export const DefaultTypedMessageAnchorRenderer = memo(function DefaultTypedMessa props: TypedMessageRendererProps, ) { const { content, href } = props.message - const deconstructed = deconstructPayload(content, null) return renderWithMetadata( props, - {deconstructed.ok ? ( - - ) : ( - // TODO: shrink link size - - {content} - - )} + {/* // TODO: */} + + // TODO: shrink link size + + {content} + , ) }) diff --git a/packages/mask/src/extension/background-script/CryptoService.ts b/packages/mask/src/extension/background-script/CryptoService.ts index 272f5d35970b..34b5aec1ce7e 100644 --- a/packages/mask/src/extension/background-script/CryptoService.ts +++ b/packages/mask/src/extension/background-script/CryptoService.ts @@ -7,7 +7,6 @@ export { encryptComment, decryptComment } from '../../crypto/crypto-alpha-40' export { encryptTo, publishPostAESKey } from './CryptoServices/encryptTo' export { appendShareTarget } from './CryptoServices/appendShareTarget' export { getPartialSharedListOfPost } from './CryptoServices/getPartialSharedListOfPost' -export { verifyOthersProve } from './CryptoServices/verifyOthersProve' export function steganographyEncodeImage(buf: ArrayBuffer, options: Omit) { return __steganographyEncodeImage(buf, { ...options, downloadImage: steganographyDownloadImage }) diff --git a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts b/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts deleted file mode 100644 index b3d5a4df9f67..000000000000 --- a/packages/mask/src/extension/background-script/CryptoServices/decryptFrom.ts +++ /dev/null @@ -1,344 +0,0 @@ -import * as Alpha40 from '../../../crypto/crypto-alpha-40' -import * as Alpha39 from '../../../crypto/crypto-alpha-39' -import { decodeText } from '@dimensiondev/kit' -import { deconstructPayload } from '../../../utils/type-transform/Payload' -import { i18n } from '../../../../shared-ui/locales_legacy' -import { queryPersonaRecord, queryLocalKey } from '../../../database' -import { ProfileIdentifier, PostIVIdentifier, delay } from '@masknet/shared-base' -import { PostRecord, queryPostDB, updatePostDB } from '../../../../background/database/post' -import { getNetworkWorker, getNetworkWorkerUninitialized } from '../../../social-network/worker' -import { cryptoProviderTable } from './cryptoProviderTable' -import type { PersonaRecord } from '../../../../background/database/persona/db' -import { verifyOthersProve } from './verifyOthersProve' -import { publicSharedAESKey } from '../../../crypto/crypto-alpha-38' -import { DecryptFailedReason } from '../../../utils/constants' -import { asyncIteratorWithResult, memorizeAsyncGenerator } from '../../../utils/type-transform/asyncIteratorHelpers' -import { steganographyDecodeImageUrl } from '@masknet/encryption' -import type { TypedMessage, AESJsonWebKey, Payload } from '@masknet/shared-base' -import stringify from 'json-stable-stringify' -import { MaskMessages } from '../../../utils/messages' -import { Err, Ok, Result } from 'ts-results' -import { decodeTextPayloadWorker } from '../../../social-network/utils/text-payload-worker' -import { steganographyDownloadImage } from './utils' - -type Progress = ( - | { progress: 'finding_person_public_key' | 'finding_post_key' | 'init' | 'decode_post' } - | { progress: 'intermediate_success'; data: Success } - | { progress: 'iv_decrypted'; iv: string } - | { progress: 'payload_decrypted'; decryptedPayloadForImage: Payload } -) & { - type: 'progress' - /** if this is true, this progress should not cause UI change. */ - internal: boolean -} -type DebugInfo = { - debug: 'debug_finding_hash' - hash: [string, string] - type: 'debug' -} -type SuccessThrough = 'author_key_not_found' | 'post_key_cached' | 'normal_decrypted' -type Success = { - type: 'success' - iv: string - decryptedPayloadForImage: Payload - content: TypedMessage - through: SuccessThrough[] - internal: boolean -} -type Failure = { - error: string - type: 'error' - internal: boolean -} -export type SuccessDecryption = Success -export type FailureDecryption = Failure -export type DecryptionProgress = Progress -type ReturnOfDecryptPostContentWithProgress = AsyncGenerator - -const successDecryptionCache = new Map() -const makeSuccessResultF = - ( - cacheKey: string, - iv: string, - decryptedPayloadForImage: Payload, - cryptoProvider: typeof cryptoProviderTable[keyof typeof cryptoProviderTable], - ) => - (rawEncryptedContent: string, through: Success['through']): Success => { - const success: Success = { - through, - iv, - decryptedPayloadForImage, - content: cryptoProvider.typedMessageParse(rawEncryptedContent), - type: 'success', - internal: false, - } - successDecryptionCache.set(cacheKey, success) - return success - } - -function makeProgress( - progress: Exclude | Success, - internal = false, -): Progress { - if (typeof progress === 'string') return { type: 'progress', progress, internal } - return { type: 'progress', progress: 'intermediate_success', data: progress, internal } -} -function makeError(error: string | Error, internal = false): Failure { - if (typeof error === 'string') return { type: 'error', error, internal } - return makeError(error.message, internal) -} -/** - * Decrypt message from a user - * @param post post - * @param author Post by - * @param authorNetworkHint When the author is unknown, the decryption (to public) won't die - * @param whoAmI My username - * @param publicShared Is this post public shared - * - * @description - * The decrypt process: - * - * ## Prepare - * a. if unknown payload, throw - * b. if unknown payload version, throw - * - * ## Decrypt for version -38, -39 and -40 - * a. read the cache `cachedPostResult` - * b. find author's public key (See: `findAuthorPublicKey` function) - * 0. if it is in the payload, use the key in the payload. - * 1. if there is cache, return the cache - * 2. if try N times but not finding the key, throw - * c. if there is cache, return the cache - * d. try to decrypt by `author` with `decryptAsAuthor` - * e. try to decrypt by `whoAmI` with `decryptAsAuthor` - * f. if `author` === `whoAmI`, throw - * g. find key for `whoAmI` on Gun - * h. try to decrypt by the key on Gun - * i. return a Promise - * 0. if version === -40, throws - * 1. listen to future new keys on Gun - * 2. try to decrypt with that key - */ -async function* decryptFromPayloadWithProgress_raw( - post: Payload, - author: ProfileIdentifier, - authorNetworkHint: string, - whoAmI: ProfileIdentifier, - discoverURL: string | undefined, -): ReturnOfDecryptPostContentWithProgress { - const cacheKey = stringify(post) - if (successDecryptionCache.has(cacheKey)) return successDecryptionCache.get(cacheKey)! - yield makeProgress('init') - - const authorNetworkWorker = Result.wrap(() => - getNetworkWorkerUninitialized(author.isUnknown ? authorNetworkHint : author.network), - ).andThen((x) => (x ? Ok(x) : Err(new Error('Worker not found')))) - if (authorNetworkWorker.err) return makeError(authorNetworkWorker.val as Error) - - const data = post - const { version } = data - const sharePublic = data.version === -38 ? data.sharedPublic ?? false : false - - if (version === -40 || version === -39 || version === -38) { - const { encryptedText, iv, version } = data - const cryptoProvider = cryptoProviderTable[version] - const makeSuccessResult = makeSuccessResultF(cacheKey, iv, data, cryptoProvider) - const ownersAESKeyEncrypted = data.version === -38 ? data.AESKeyEncrypted : data.ownersAESKeyEncrypted - - yield { type: 'progress', progress: 'payload_decrypted', decryptedPayloadForImage: data, internal: true } - yield { type: 'progress', progress: 'iv_decrypted', iv: iv, internal: true } - // ? Early emit the cache. - const [cachedPostResult, setPostCache] = await decryptFromCache(data, author, discoverURL) - if (cachedPostResult) { - yield makeProgress(makeSuccessResult(cachedPostResult, ['post_key_cached'])) - } - - // ? If the author's key is in the payload, store it. - if (data.version === -38 && data.authorPublicKey && !author.isUnknown) { - await verifyOthersProve({ raw: data.authorPublicKey }, author).catch(console.error) - } - // ? Find author's public key. - let authorPersona!: PersonaRecord - for await (const _ of asyncIteratorWithResult(findAuthorPublicKey(author, !!cachedPostResult))) { - if (author.isUnknown) break - if (!_.done) { - yield _.value - continue - } - const result = _.value - if (result === 'out of chance') - return makeError(i18n.t('service_others_key_not_found', { name: author.userId })) - else if (result === 'use cache') - return makeSuccessResult(cachedPostResult!, ['author_key_not_found', 'post_key_cached']) - else authorPersona = result - } - - // ? Get my public & private key. - const queryWhoAmI = () => queryPersonaRecord(whoAmI) - const mine = await queryWhoAmI().then((x) => x || delay(1000).then(queryWhoAmI)) - if (!mine?.privateKey) return makeError(DecryptFailedReason.MyCryptoKeyNotFound) - - const { publicKey: minePublic, privateKey: minePrivate } = mine - if (cachedPostResult) return makeSuccessResult(cachedPostResult, ['post_key_cached']) - - let lastError: unknown - /** - * ? try to decrypt as I am the author - * ? then try to decrypt as whoAmI - * ? then try to go through a normal decrypt process - */ - try { - const a = decryptAsAuthor(author) - const b = decryptAsAuthor(whoAmI) - // ! Don't remove the await - return await a.catch(() => b) - } catch (error) { - lastError = error - } - - if (author.equals(whoAmI)) { - // if the decryption process goes here, - // that means it is failed to decrypt by local identities. - // If remove this if block, Mask will search the key - // for the post even that post by myself. - if (lastError instanceof DOMException) return handleDOMException(lastError) - console.error(lastError) - return makeError(i18n.t('service_self_key_decryption_failed')) - } - - yield makeProgress('finding_post_key') - return makeError(i18n.t('service_not_share_target')) - - async function decryptAsAuthor(authorIdentifier: ProfileIdentifier) { - const localKey = sharePublic ? publicSharedAESKey : await queryLocalKey(authorIdentifier) - if (!localKey) throw new Error(`Local key for identity ${authorIdentifier.toText()} not found`) - const [contentArrayBuffer, postAESKey] = await cryptoProvider.decryptMessage1ToNByMyself({ - version, - encryptedAESKey: ownersAESKeyEncrypted, - encryptedContent: encryptedText, - myLocalKey: localKey, - iv, - }) - // Store the key to speed up next time decrypt - setPostCache(postAESKey) - const content = decodeText(contentArrayBuffer) - return makeSuccessResult(content, ['normal_decrypted']) - } - } - return makeError(i18n.t('service_unknown_payload')) -} - -async function* decryptFromImageUrlWithProgress_raw( - url: string, - author: ProfileIdentifier, - authorNetworkHint: string, - whoAmI: ProfileIdentifier, - discoverURL: string | undefined, -): ReturnOfDecryptPostContentWithProgress { - if (successDecryptionCache.has(url)) return successDecryptionCache.get(url)! - yield makeProgress('decode_post', true) - const post = await steganographyDecodeImageUrl(url, { - pass: author.toText(), - downloadImage: steganographyDownloadImage, - }) - if (!post.startsWith('๐ŸŽผ') && !/https:\/\/.+\..+\/(\?PostData_v\d=)?%20(.+)%40/.test(post)) - return makeError(i18n.t('service_decode_image_payload_failed'), true) - const worker = await Result.wrapAsync(() => getNetworkWorker(author)) - if (worker.err) return makeError(worker.val as Error) - const payload = deconstructPayload(post, await decodeTextPayloadWorker(author)) - if (payload.err) return makeError(payload.val) - return yield* decryptFromText(payload.val, author, authorNetworkHint, whoAmI, discoverURL) -} - -export const decryptFromText = memorizeAsyncGenerator( - decryptFromPayloadWithProgress_raw, - (encrypted, author, _, whoAmI) => - JSON.stringify([encrypted.iv, encrypted.encryptedText, author.toText(), whoAmI.toText()]), - 1000 * 30, -) - -export const decryptFromImageUrl = memorizeAsyncGenerator( - decryptFromImageUrlWithProgress_raw, - (url, author, _, whoAmI) => JSON.stringify([url, author.toText(), whoAmI.toText()]), - 1000 * 30, -) - -function handleDOMException(e: unknown) { - if (e instanceof DOMException) { - return makeError(i18n.t('service_decryption_failed')) - } else throw e -} -async function* findAuthorPublicKey( - by: ProfileIdentifier, - hasCache: boolean, - maxIteration = 10, -): AsyncGenerator { - let author = await queryPersonaRecord(by) - let iterations = 0 - while (!author?.publicKey) { - iterations += 1 - if (iterations < maxIteration) yield makeProgress('finding_person_public_key') - else return 'out of chance' as const - - author = await queryPersonaRecord(by).catch(() => null) - - if (!author?.publicKey) { - if (hasCache) return 'use cache' as const - const abort = new AbortController() - const databasePromise = new Promise((resolve, reject) => { - abort.signal.addEventListener('abort', () => { - undo() - reject() - }) - const undo = MaskMessages.events.profilesChanged.on((data) => { - for (const x of data) { - if (x.reason === 'delete') continue - if (x.of.equals(by)) { - undo() - resolve() - break - } - } - }) - }) - await Promise.race([databasePromise]) - .then(() => abort.abort()) - .catch(() => null) - } - } - if (author?.publicKey) return author - return 'out of chance' -} - -async function decryptFromCache(postPayload: Payload, by: ProfileIdentifier, discoverURL: string | undefined) { - const { encryptedText, iv, version } = postPayload - const cryptoProvider = version === -40 ? Alpha40 : Alpha39 - - const postIdentifier = new PostIVIdentifier(by.network, iv) - const cachedKey = await queryPostDB(postIdentifier) - if (cachedKey && !cachedKey.url && discoverURL) { - await updatePostDB({ identifier: postIdentifier, url: discoverURL }, 'append') - } - const setCache = (postAESKey: AESJsonWebKey) => { - const postUpdate: Partial & Pick = { - identifier: postIdentifier, - postCryptoKey: postAESKey, - postBy: by, - } - if (discoverURL) postUpdate.url = discoverURL - updatePostDB(postUpdate, 'append') - } - if (cachedKey?.postCryptoKey) { - try { - const result = decodeText( - await cryptoProvider.decryptWithAES({ - aesKey: cachedKey.postCryptoKey, - encrypted: encryptedText, - iv: iv, - }), - ) - return [result, setCache] as const - } catch {} - } - return [undefined, setCache] as const -} diff --git a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts index d10e57084c13..65b1b4297200 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts @@ -1,6 +1,5 @@ import * as Alpha38 from '../../../crypto/crypto-alpha-38' import { decodeArrayBuffer, encodeArrayBuffer } from '@dimensiondev/kit' -import { constructAlpha38 } from '../../../utils/type-transform/Payload' import { queryPrivateKey, queryLocalKey, queryProfile } from '../../../database' import { ProfileIdentifier, PostIVIdentifier } from '@masknet/shared-base' import { prepareRecipientDetail } from './prepareRecipientDetail' @@ -8,16 +7,18 @@ import { getNetworkWorker } from '../../../social-network/worker' import { createPostDB, PostRecord } from '../../../../background/database/post' import { queryPersonaByProfileDB } from '../../../../background/database/persona/db' import { i18n } from '../../../../shared-ui/locales_legacy' -import { - isTypedMessageText, - TypedMessage, - TypedMessageText, - PayloadLatest, - compressSecp256k1Key, -} from '@masknet/shared-base' -import { encodeTextPayloadWorker } from '../../../social-network/utils/text-payload-worker' +import { isTypedMessageText, TypedMessage, TypedMessageText } from '@masknet/shared-base' import { publishPostAESKey_version39Or38 } from '../../../../background/network/gun/encryption/queryPostKey' import { getGunInstance } from '../../../../background/network/gun/instance' +import { + PayloadWellFormed, + PublicKeyAlgorithmEnum, + importAsymmetryKeyFromJsonWebKeyOrSPKI, + AESAlgorithmEnum, + importAESFromJWK, + encodePayload, +} from '@masknet/encryption' +import { None, Some } from 'ts-results' type EncryptedText = string type OthersAESKeyEncryptedToken = string @@ -67,26 +68,51 @@ export async function encryptTo( iv: crypto.getRandomValues(new Uint8Array(16)), }) - const payload: PayloadLatest = { - AESKeyEncrypted: encodeArrayBuffer(ownersAESKeyEncrypted), - encryptedText: encodeArrayBuffer(encryptedText), - iv: encodeArrayBuffer(iv), - signature: '', - sharedPublic: publicShared, - version: -38, - authorUserID: whoAmI, - } + let authorPublicKey: PayloadWellFormed.Payload['authorPublicKey'] = None try { - const publicKey = (await queryPersonaByProfileDB(whoAmI))?.publicKey - if (publicKey) payload.authorPublicKey = compressSecp256k1Key(publicKey) + const jwk = (await queryPersonaByProfileDB(whoAmI))?.publicKey + if (jwk) { + authorPublicKey = Some({ + algr: PublicKeyAlgorithmEnum.secp256k1, + key: (await importAsymmetryKeyFromJsonWebKeyOrSPKI(jwk, PublicKeyAlgorithmEnum.secp256k1)).unwrap(), + }) + } } catch { // ignore } - payload.signature = '_' + const postCryptoKey = (await importAESFromJWK(postAESKey, AESAlgorithmEnum.A256GCM)).unwrap() + let encryption: PayloadWellFormed.EndToEndEncryption | PayloadWellFormed.PublicEncryption + if (publicShared) { + encryption = { + type: 'public', + iv: new Uint8Array(iv), + AESKey: { + algr: AESAlgorithmEnum.A256GCM, + key: postCryptoKey, + }, + } + } else { + encryption = { + type: 'E2E', + iv: new Uint8Array(iv), + ephemeralPublicKey: new Map(), + ownersAESKeyEncrypted: new Uint8Array(ownersAESKeyEncrypted), + } + } + + const payload: { -readonly [key in keyof PayloadWellFormed.Payload]: PayloadWellFormed.Payload[key] } = { + version: -38, + author: Some(whoAmI), + encryption, + encrypted: new Uint8Array(encryptedText), + authorPublicKey, + // not supported + signature: None, + } const newPostRecord: PostRecord = { - identifier: new PostIVIdentifier(whoAmI.network, payload.iv), + identifier: new PostIVIdentifier(whoAmI.network, encodeArrayBuffer(iv)), postBy: whoAmI, postCryptoKey: postAESKey, recipients: publicShared ? 'everyone' : recipients, @@ -102,7 +128,9 @@ export async function encryptTo( const postAESKeyToken = encodeArrayBuffer(iv) const worker = await getNetworkWorker(whoAmI)! OthersAESKeyEncryptedMap.set(postAESKeyToken, [worker.gunNetworkHint, othersAESKeyEncrypted]) - return [constructAlpha38(payload, await encodeTextPayloadWorker(whoAmI)), postAESKeyToken] + const payloadEncoded = (await encodePayload.NoSign(payload)).unwrap() + if (typeof payloadEncoded !== 'string') throw new TypeError('') + return [payloadEncoded, postAESKeyToken] } /** diff --git a/packages/mask/src/extension/background-script/CryptoServices/verifyOthersProve.ts b/packages/mask/src/extension/background-script/CryptoServices/verifyOthersProve.ts deleted file mode 100644 index 47745da75604..000000000000 --- a/packages/mask/src/extension/background-script/CryptoServices/verifyOthersProve.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type ProfileIdentifier, decompressSecp256k1Key, ECKeyIdentifierFromJsonWebKey } from '@masknet/shared-base' -import { decodePublicKeyWorker } from '../../../social-network/utils/text-payload-worker' -import { createProfileWithPersona, queryPersonaRecord } from '../../../database' - -export async function verifyOthersProve(bio: string | { raw: string }, others: ProfileIdentifier): Promise { - const compressedX = typeof bio === 'string' ? (await decodePublicKeyWorker(others.network))(bio) : [bio.raw] - if (!compressedX) return false - const publicKey = compressedX - .map((x) => { - try { - return decompressSecp256k1Key(x) - } catch { - return null - } - }) - .filter((x) => x)[0] - if (!publicKey) return false - // TODO: use json schema / other ways to verify the JWK - // or - // throw new Error(i18n.t('service_key_parse_failed')) - // if privateKey, we should possibly not recreate it - const hasPrivate = - (await queryPersonaRecord(ECKeyIdentifierFromJsonWebKey(publicKey)))?.privateKey || - (await queryPersonaRecord(others))?.privateKey - if (!hasPrivate) await createProfileWithPersona(others, { connectionConfirmState: 'pending' }, { publicKey }) - // TODO: unhandled case: if the profile is connected but a different key. - return true -} diff --git a/packages/mask/src/extension/service-generator.ts b/packages/mask/src/extension/service-generator.ts index 4b378c7a13c8..8daf0b9125b8 100644 --- a/packages/mask/src/extension/service-generator.ts +++ b/packages/mask/src/extension/service-generator.ts @@ -1,2 +1 @@ -export { decryptFromText, decryptFromImageUrl } from './background-script/CryptoServices/decryptFrom' export { decryptionWithSocialNetworkDecoding } from '../../background/services/crypto/decryption' diff --git a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts index f181f19d02b4..087174a847b0 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts @@ -2,7 +2,6 @@ import type { SocialNetwork } from '../../social-network/types' import { facebookBase } from './base' import { getPostUrlAtFacebook, isValidFacebookUsername } from './utils/parse-username' import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' -import { deconstructPayload } from '../../utils' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' const getPostURL = (post: PostIdentifier): URL | null => { @@ -16,8 +15,6 @@ export const facebookShared: SocialNetwork.Shared & SocialNetwork.Base = { getHomePage: () => 'https://www.facebook.com', getProfilePage: (userId: string) => 'https://www.facebook.com', isValidUsername: (v) => !!isValidFacebookUsername(v), - publicKeyEncoding: undefined, - textPayloadPostProcessor: undefined, getPostURL, getShareLinkURL(message) { const url = new URL('https://www.facebook.com/sharer/sharer.php') @@ -26,7 +23,6 @@ export const facebookShared: SocialNetwork.Shared & SocialNetwork.Base = { return url }, createPostContext: createSNSAdaptorSpecializedPostContext({ - payloadParser: deconstructPayload, getURLFromPostIdentifier: getPostURL, }), }, diff --git a/packages/mask/src/social-network-adaptor/instagram.com/shared.ts b/packages/mask/src/social-network-adaptor/instagram.com/shared.ts index 4a415b1018c7..e7bf44d77470 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/instagram.com/shared.ts @@ -1,6 +1,5 @@ import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' -import { deconstructPayload } from '../../utils' import { instagramBase } from './base' export const instagramShared: SocialNetwork.Shared & SocialNetwork.Base = { @@ -8,8 +7,6 @@ export const instagramShared: SocialNetwork.Shared & SocialNetwork.Base = { utils: { getHomePage: () => 'https://www.instagram.com/', getProfilePage: () => 'https://www.instagram.com/', - createPostContext: createSNSAdaptorSpecializedPostContext({ - payloadParser: deconstructPayload, - }), + createPostContext: createSNSAdaptorSpecializedPostContext({}), }, } diff --git a/packages/mask/src/social-network-adaptor/minds.com/shared.ts b/packages/mask/src/social-network-adaptor/minds.com/shared.ts index 7272a9f5fc1a..01d78d4705ef 100644 --- a/packages/mask/src/social-network-adaptor/minds.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/minds.com/shared.ts @@ -1,7 +1,6 @@ import type { PostIdentifier } from '@masknet/shared-base' import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' -import { deconstructPayload } from '../../utils' import { mindsBase } from './base' import { usernameValidator } from './utils/user' @@ -14,14 +13,11 @@ export const mindsShared: SocialNetwork.Shared & SocialNetwork.Base = { getHomePage: () => 'https://www.minds.com', getProfilePage: () => 'https://www.minds.com', isValidUsername: usernameValidator, - publicKeyEncoding: undefined, - textPayloadPostProcessor: undefined, getPostURL, getShareLinkURL(message) { return new URL(`https://www.minds.com/newsfeed/subscriptions?intentUrl=${encodeURIComponent(message)}`) }, createPostContext: createSNSAdaptorSpecializedPostContext({ - payloadParser: deconstructPayload, getURLFromPostIdentifier: getPostURL, }), }, diff --git a/packages/mask/src/social-network-adaptor/twitter.com/encoding.ts b/packages/mask/src/social-network-adaptor/twitter.com/encoding.ts deleted file mode 100644 index 1d49abb23240..000000000000 --- a/packages/mask/src/social-network-adaptor/twitter.com/encoding.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { TwitterDecoder, __TwitterEncoder } from '@masknet/encryption' -import { isNull } from 'lodash-unified' -const ICAO9303Checksum = { - encode: (input: string) => { - return `${input}${( - input - .toUpperCase() - .replace(/[+/=]/g, '0') - .split('') - .map((d, i) => Number.parseInt(d, 36) * [7, 3, 1][i % 3]) - .reduce((l, r) => l + r, 0) % 19 - ) - .toString(19) - .toUpperCase()}` - }, - decode: (input: string) => { - const content = input.slice(0, input.length - 1) - const checksum = input.slice(input.length - 1) - const r = ICAO9303Checksum.encode(content) - if (checksum === r.slice(r.length - 1)) { - return content - } else { - return null - } - }, -} - -export const twitterEncoding = { - /** - * @link https://github.com/DimensionDev/Maskbook/issues/191 - */ - publicKeyEncoder: (text: string) => `๐ŸŽญ${ICAO9303Checksum.encode(text)}๐ŸŽญ`, - publicKeyDecoder: (text: string): string[] => { - const r = regexMatchAll(text, /([\d+/=A-Za-z]{20,60})/) - if (isNull(r)) { - return [] - } - for (const v of r) { - const retV = ICAO9303Checksum.decode(v) - if (isNull(retV)) { - continue - } - return [retV] - } - return [] - }, - payloadEncoder: __TwitterEncoder, - payloadDecoder: (text: string): string[] => { - return TwitterDecoder(text) - .map((x) => [x]) - .unwrapOr([]) - }, -} - -function regexMatchAll(str: string, regexp: RegExp, index = 1) { - const gPos = regexp.flags.indexOf('g') - const withoutG = gPos >= 0 ? `${regexp.flags.slice(0, gPos)}${regexp.flags.slice(gPos + 1)}` : regexp.flags - const o = new RegExp(regexp.source, withoutG) - const g = new RegExp(regexp.source, `${withoutG}g`) - const r = str.match(g) - if (isNull(r)) { - return null - } - const sto = [] - for (const v of r) { - const retV = v.match(o) - if (isNull(retV)) { - continue - } - sto.push(retV[index]) - } - if (sto.length === 0) { - return null - } - return sto -} diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx index ca1714fd5891..3a416187f469 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx @@ -1,6 +1,6 @@ import type { PostInfo } from '../../../social-network/PostInfo' import { injectPostInspectorDefault } from '../../../social-network/defaults/inject/PostInspector' -import { twitterEncoding } from '../encoding' +import { socialNetworkDecoder, SocialNetworkEnum } from '@masknet/encryption' export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostInfo) { return injectPostInspectorDefault({ @@ -12,7 +12,7 @@ export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostI if (!content) return for (const a of content.querySelectorAll('a')) { - if (twitterEncoding.payloadDecoder(a.title).length) hideDOM(a) + if (socialNetworkDecoder(SocialNetworkEnum.Twitter, a.title).some) hideDOM(a) if (/^https?:\/\/mask(\.io|book\.com)$/i.test(a.title)) hideDOM(a) } diff --git a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts index 015ae96e54a3..dfce4e69d632 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts @@ -1,9 +1,7 @@ import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' -import { deconstructPayload } from '../../utils' import { twitterBase } from './base' -import { twitterEncoding } from './encoding' import { usernameValidator } from './utils/user' const getPostURL = (post: PostIdentifier): URL | null => { @@ -16,29 +14,11 @@ export const twitterShared: SocialNetwork.Shared & SocialNetwork.Base = { getHomePage: () => 'https://twitter.com', getProfilePage: (userId) => `https://twitter.com/${userId}`, isValidUsername: usernameValidator, - publicKeyEncoding: { - encoder(text) { - return twitterEncoding.publicKeyEncoder(text) - }, - decoder(text) { - return twitterEncoding.publicKeyDecoder(text) - }, - }, - textPayloadPostProcessor: { - encoder(text) { - return twitterEncoding.payloadEncoder(text) - }, - decoder(text) { - return twitterEncoding.payloadDecoder(text) - }, - }, getPostURL, getShareLinkURL(message) { return new URL(`https://twitter.com/intent/tweet?text=${encodeURIComponent(message)}`) }, createPostContext: createSNSAdaptorSpecializedPostContext({ - payloadParser: deconstructPayload, - payloadDecoder: twitterEncoding.payloadDecoder, getURLFromPostIdentifier: getPostURL, }), }, diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index 8fa395669d45..ec631e2a68d1 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -20,10 +20,6 @@ type ClassNameMap = { [P in ClassKey]: string // Don't define values in namespaces export namespace SocialNetwork { export interface PayloadEncoding { - encoder(text: string): string - /** - * the result is candidates - */ decoder(text: string): string[] } @@ -36,10 +32,6 @@ export namespace SocialNetwork { getPostURL?(post: PostIdentifier): URL | null /** Is this username valid in this network */ isValidUsername?(username: string): boolean - /** How to encode/decode public keys when it is put in the bio. */ - publicKeyEncoding?: PayloadEncoding - /** How to encode/decode text payload (e.g. make it into a link so it will be shortened by SNS). */ - textPayloadPostProcessor?: PayloadEncoding /** Given a text, return a URL that will allow user to share this text */ getShareLinkURL?(text: string): URL createPostContext: ReturnType diff --git a/packages/mask/src/social-network/utils/text-payload-ui.ts b/packages/mask/src/social-network/utils/text-payload-ui.ts deleted file mode 100644 index bcfc6cb4e605..000000000000 --- a/packages/mask/src/social-network/utils/text-payload-ui.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { activatedSocialNetworkUI } from '../ui' -export function encodePublicKeyUI(x: string): string { - const f = activatedSocialNetworkUI.utils.publicKeyEncoding?.encoder - if (f) return f(x) || x - return x -} -export function decodePublicKeyUI(x: string): string[] { - const f = activatedSocialNetworkUI.utils.publicKeyEncoding?.decoder - if (f) return f(x).concat(x) - return [x] -} -export function encodeTextPayloadUI(x: string): string { - const f = activatedSocialNetworkUI.utils.textPayloadPostProcessor?.encoder - if (f) return f(x) || x - return x -} -export function decodeTextPayloadUI(x: string): string[] { - const f = activatedSocialNetworkUI.utils.textPayloadPostProcessor?.decoder - if (f) return f(x).concat(x) - return [x] -} diff --git a/packages/mask/src/social-network/utils/text-payload-worker.ts b/packages/mask/src/social-network/utils/text-payload-worker.ts deleted file mode 100644 index c7f1c0ac3209..000000000000 --- a/packages/mask/src/social-network/utils/text-payload-worker.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getNetworkWorker, NetworkWorkerQuery } from '../worker' -export async function encodePublicKeyWorker(query: NetworkWorkerQuery): Promise<(text: string) => string> { - const f = (await getNetworkWorker(query)).utils.publicKeyEncoding?.encoder - return (x) => f?.(x) || x -} -export async function decodePublicKeyWorker(query: NetworkWorkerQuery): Promise<(text: string) => string[]> { - const f = (await getNetworkWorker(query)).utils.publicKeyEncoding?.decoder - if (f) return (x) => f(x).concat(x) - return (x) => [x] -} -export async function encodeTextPayloadWorker(query: NetworkWorkerQuery): Promise<(text: string) => string> { - const f = (await getNetworkWorker(query)).utils.textPayloadPostProcessor?.encoder - return (x) => f?.(x) || x -} -export async function decodeTextPayloadWorker(query: NetworkWorkerQuery): Promise<(text: string) => string[]> { - const f = (await getNetworkWorker(query)).utils.textPayloadPostProcessor?.decoder - if (f) return (x) => f(x).concat(x) - return (x) => [x] -} diff --git a/packages/mask/src/utils/constants.ts b/packages/mask/src/utils/constants.ts deleted file mode 100644 index 090383f89115..000000000000 --- a/packages/mask/src/utils/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum DecryptFailedReason { - MyCryptoKeyNotFound = 'MyCryptoKeyNotFound', -} diff --git a/packages/mask/src/utils/index.ts b/packages/mask/src/utils/index.ts index c7f0facb60e6..944c1a32f7d6 100644 --- a/packages/mask/src/utils/index.ts +++ b/packages/mask/src/utils/index.ts @@ -6,7 +6,6 @@ export * from './shadow-root' export * from './suspends' export * from './type-transform' export * from './comparer' -export * from './constants' export * from './dom' export * from './getTextUILength' export * from './i18n-next-ui' @@ -18,3 +17,8 @@ export * from './theme' export * from './utils' export * from './watcher' export * from './collectNodeText' +export async function asyncIteratorToArray(iter: AsyncIterable): Promise { + const x: T[] = [] + for await (const y of x) x.push(y) + return x +} diff --git a/packages/mask/src/utils/type-transform/Payload.ts b/packages/mask/src/utils/type-transform/Payload.ts deleted file mode 100644 index 0605676ea5ee..000000000000 --- a/packages/mask/src/utils/type-transform/Payload.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { SocialNetwork } from '../../social-network' -import { isEnvironment, Environment } from '@dimensiondev/holoflows-kit' -import { i18n } from '../../../shared-ui/locales_legacy' -import { Result, Ok, Err } from 'ts-results' -import { decodeTextPayloadUI, encodeTextPayloadUI } from '../../social-network/utils/text-payload-ui' - -import { Identifier, Payload, PayloadAlpha38, ProfileIdentifier } from '@masknet/shared-base' - -/** - * Detect if there is version -40, -39 or -38 payload - */ -function deconstructAlpha40_Or_Alpha39_Or_Alpha38(str: string, throws = false): Payload | null { - // ? payload is ๐ŸŽผ2/4|ownersAESKeyEncrypted|iv|encryptedText|signature:|| - // ? payload is ๐ŸŽผ3/4|ownersAESKeyEncrypted|iv|encryptedText|signature:|| - // ? payload is ๐ŸŽผ4/4|AESKeyEncrypted|iv|encryptedText|signature|authorPublicKey?|publicShared?|authorIdentifier:|| - // ? if publicShared is true, that means AESKeyEncrypted is shared with public - // ? "1" treated as true, "0" or not defined treated as false - // ? authorIdentifier is encoded as `${network}/${id}` - const isVersion40 = str.includes('๐ŸŽผ2/4') - const isVersion39 = str.includes('๐ŸŽผ3/4') - const isVersion38 = str.includes('๐ŸŽผ4/4') - str = str.replace('๐ŸŽผ2/4', '๐ŸŽผ3/4') - str = str.replace('๐ŸŽผ3/4', '๐ŸŽผ4/4') - const [_, payloadStart] = str.split('๐ŸŽผ4/4|') - if (!payloadStart) - if (throws) throw new Error(i18n.t('payload_not_found')) - else return null - const [payload, rest] = payloadStart.split(':||') - if (rest === undefined) - if (throws) throw new Error(i18n.t('payload_incomplete')) - else return null - const [AESKeyEncrypted, iv, encryptedText, signature, ...optional] = payload.split('|') - const [authorPublicKey, publicShared, authorID, ...extra] = optional - if (!(AESKeyEncrypted && iv && encryptedText)) - if (throws) throw new Error(i18n.t('payload_bad')) - else return null - if (extra.length) console.warn('Found extra payload', extra) - if (isVersion38) { - if (!signature) throw new Error(i18n.t('payload_bad')) - return { - version: -38, - AESKeyEncrypted, - iv, - encryptedText, - signature, - authorPublicKey, - sharedPublic: publicShared === '1', - authorUserID: Result.wrap(() => - Identifier.fromString('person:' + atob(authorID), ProfileIdentifier).unwrap(), - ).unwrapOr(undefined), - } - } - return { - ownersAESKeyEncrypted: AESKeyEncrypted, - iv, - encryptedText, - signature, - version: isVersion39 ? -39 : -40, - } -} - -function deconstructAlpha41(str: string, throws = false): null | never { - // ๐ŸŽผ1/4|ownersAESKeyEncrypted|iv|encryptedText|signature:|| - if (str.includes('๐ŸŽผ1/4') && str.includes(':||')) - if (throws) throw new Error(i18n.t('payload_throw_in_alpha41')) - else return null - return null -} - -const versions = new Set([deconstructAlpha40_Or_Alpha39_Or_Alpha38, deconstructAlpha41]) - -/** - * @type Decoder - defines decoder. if null, auto select decoder. - */ -type Decoder = SocialNetwork.PayloadEncoding['decoder'] | null -type Encoder = SocialNetwork.PayloadEncoding['encoder'] - -export function deconstructPayload(str: string, networkDecoder?: Decoder): Result { - if (!networkDecoder) { - networkDecoder = isEnvironment(Environment.ContentScript) ? decodeTextPayloadUI : (x) => [x] - } - - for (const versionDecoder of versions) { - const results = networkDecoder(str) - for (const result of results) { - if (!result) continue - const payload = versionDecoder(result, false) - if (payload) return Ok(payload) - } - } - if (str.includes('๐ŸŽผ') && str.includes(':||')) return Err(new TypeError(i18n.t('service_unknown_payload'))) - return Err(new TypeError(i18n.t('payload_not_found'))) -} - -export function constructAlpha38(data: PayloadAlpha38, encoder?: Encoder) { - if (!encoder) { - encoder = isEnvironment(Environment.ContentScript) ? encodeTextPayloadUI : (x) => x - } - const userID = data.authorUserID?.toText().replace('person:', '') || '' - const fields = [ - data.AESKeyEncrypted, - data.iv, - data.encryptedText, - data.signature, - data.authorPublicKey, - data.sharedPublic ? '1' : '0', - userID.includes('|') ? undefined : btoa(userID), - ] - return encoder(`๐ŸŽผ4/4|${fields.join('|')}:||`) -} diff --git a/packages/mask/src/utils/type-transform/asyncIteratorHelpers.ts b/packages/mask/src/utils/type-transform/asyncIteratorHelpers.ts deleted file mode 100644 index 86b302c742f3..000000000000 --- a/packages/mask/src/utils/type-transform/asyncIteratorHelpers.ts +++ /dev/null @@ -1,63 +0,0 @@ -export async function* asyncIteratorWithResult( - iter: AsyncIterator, - callback?: (promise: Promise>) => void, -) { - let yielded: IteratorResult - do { - const p = iter.next() - callback?.(p) - yielded = await p - yield yielded - } while (yielded.done === false) - return -} - -export function memorizeAsyncGenerator( - f: (...args: Args) => AsyncGenerator, - getKey: (...args: Args) => string, - expireAfter: number, -): typeof f { - type Iter = AsyncGenerator - type IterResult = Promise> - const iterCache = new Map() - const progressCache = new WeakMap() - return async function* memorizedAsyncGenerator(...args: Args) { - const key = getKey(...args) - const iter = iterCache.get(key) ?? f(...args) - iterCache.set(key, iter) - const process = progressCache.get(iter) - if (!process) { - setTimeout(() => iterCache.delete(key), expireAfter) - const arr: IterResult[] = [] - progressCache.set(iter, arr) - try { - for await (const _ of asyncIteratorWithResult(iter, (x) => arr.push(x))) { - if (_.done) return _.value - yield _.value - } - } catch { - iterCache.delete(key) - } - } else { - let index = 0 - while (index < process.length) { - const p = await process[index] - if (p.done) return p.value - yield p.value - index += 1 - } - } - // cache not working - for await (const _ of asyncIteratorWithResult(f(...args))) { - if (_.done) return _.value - yield _.value - } - throw new Error('Unreachable') - } -} - -export async function asyncIteratorToArray(it: AsyncIterable) { - const arr: T[] = [] - for await (const x of it) arr.push(x) - return arr -} diff --git a/packages/mask/src/utils/type-transform/index.ts b/packages/mask/src/utils/type-transform/index.ts index f19d98fa2759..8a50dc9c7bdc 100644 --- a/packages/mask/src/utils/type-transform/index.ts +++ b/packages/mask/src/utils/type-transform/index.ts @@ -1,6 +1,4 @@ -export * from './asyncIteratorHelpers' export * from './BackupFile' export * from './BackupFormat' export * from './CryptoKey-JsonWebKey' -export * from './Payload' export * from './SECP256k1-ETH' diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index 2bc2608e4235..c16a18682ce8 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -2,24 +2,15 @@ import { useObservableValues, useValueRef } from '@masknet/shared' import { ObservableMap, ObservableSet, - type Payload, type PostIdentifier, type ProfileIdentifier, type TypedMessageTuple, } from '@masknet/shared-base' import { ValueRef, LiveSelector, DOMProxy } from '@dimensiondev/holoflows-kit' -import type { Result } from 'ts-results' import { Context, createContext, createElement, memo, useContext } from 'react' import { Subscription, useSubscription } from 'use-subscription' import type { DecryptProgress } from '@masknet/encryption' export interface PostContextSNSActions { - /** Parse payload into Payload */ - payloadParser(raw: string): Result - /** - * It will run before parser. - * @returns a list of candidates of payload string. - */ - payloadDecoder?(postContent: string): string[] getURLFromPostIdentifier?(post: PostIdentifier): URL | null } export interface PostContextAuthor { diff --git a/packages/shared-base/src/MaskPayload/index.ts b/packages/shared-base/src/MaskPayload/index.ts deleted file mode 100644 index 9fc78a56cb13..000000000000 --- a/packages/shared-base/src/MaskPayload/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { ProfileIdentifier } from '../Identifier/type' - -export type Payload = PayloadAlpha40_Or_Alpha39 | PayloadAlpha38 -export type PayloadLatest = PayloadAlpha38 - -export interface PayloadAlpha40_Or_Alpha39 { - version: -40 | -39 - ownersAESKeyEncrypted: string - iv: string - encryptedText: string - signature?: string -} -export interface PayloadAlpha38 { - version: -38 - AESKeyEncrypted: string - iv: string - encryptedText: string - /** @deprecated but don't remove it cause it will break */ - signature: string - authorPublicKey?: string - authorUserID?: ProfileIdentifier - sharedPublic?: boolean -} - -/** - * The string part is in the front of the payload. - * The number part is used in the database. - */ -export enum PayloadVersions { - '2/4' = -40, - '3/4' = -39, - '4/4' = -38, -} From 77d6ff1644a82bc99df1d6ad59fd57d52d1d4267 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 19:33:15 +0800 Subject: [PATCH 26/85] refactor: remove unused code --- .../DecryptedPost/{DecryptedPost.tsx => DecryptedPosts.tsx} | 0 .../mask/src/components/InjectedComponents/PostInspector.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/mask/src/components/InjectedComponents/DecryptedPost/{DecryptedPost.tsx => DecryptedPosts.tsx} (100%) diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPosts.tsx similarity index 100% rename from packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx rename to packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptedPosts.tsx diff --git a/packages/mask/src/components/InjectedComponents/PostInspector.tsx b/packages/mask/src/components/InjectedComponents/PostInspector.tsx index 4c619b43a697..fcbe1bb0507f 100644 --- a/packages/mask/src/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/src/components/InjectedComponents/PostInspector.tsx @@ -1,4 +1,4 @@ -import { DecryptedPosts } from './DecryptedPost/DecryptedPost' +import { DecryptedPosts } from './DecryptedPost/DecryptedPosts' import { usePostInfoDetails } from '../DataSource/usePostInfo' import { createInjectHooksRenderer, useActivatedPluginsSNSAdaptor } from '@masknet/plugin-infra' import { PossiblePluginSuggestionPostInspector } from './DisabledPluginSuggestion' From 67b4d3085e12657848eb0f8a60b2ddda6fd122db Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 19:39:22 +0800 Subject: [PATCH 27/85] refactor: remove unused code --- packages/mask/src/crypto/crypto-alpha-38.ts | 10 --- packages/mask/src/crypto/crypto-alpha-39.ts | 6 +- packages/mask/src/crypto/crypto-alpha-40.ts | 75 +-------------------- 3 files changed, 5 insertions(+), 86 deletions(-) diff --git a/packages/mask/src/crypto/crypto-alpha-38.ts b/packages/mask/src/crypto/crypto-alpha-38.ts index 6ba04240faf9..fcd773b7d30a 100644 --- a/packages/mask/src/crypto/crypto-alpha-38.ts +++ b/packages/mask/src/crypto/crypto-alpha-38.ts @@ -23,13 +23,3 @@ export function typedMessageStringify(x: TypedMessage) { return JSON.stringify(obj) + '๐Ÿงฉ' + x.content } -export function typedMessageParse(x: string) { - const [maybeMetadata, ...end] = x.split('๐Ÿงฉ') - try { - const json: unknown = JSON.parse(maybeMetadata) - if (typeof json !== 'object' || json === null || Object.keys(json).length === 0) - throw new Error('Not a metadata') - return makeTypedMessageText(end.join('๐Ÿงฉ'), new Map(Object.entries(json))) - } catch {} - return makeTypedMessageText(x) -} diff --git a/packages/mask/src/crypto/crypto-alpha-39.ts b/packages/mask/src/crypto/crypto-alpha-39.ts index 58f8bfcddb62..d6d3df0430cc 100644 --- a/packages/mask/src/crypto/crypto-alpha-39.ts +++ b/packages/mask/src/crypto/crypto-alpha-39.ts @@ -79,8 +79,8 @@ export async function encrypt1ToN(info: { } } -export { encrypt1To1, decryptMessage1To1 } from './crypto-alpha-40' -export { decryptMessage1ToNByOther, decryptMessage1ToNByMyself, extractAESKeyInMessage } from './crypto-alpha-40' +export { encrypt1To1 } from './crypto-alpha-40' +export { extractAESKeyInMessage } from './crypto-alpha-40' export { encryptWithAES, decryptWithAES } from './crypto-alpha-40' export { encryptComment, decryptComment } from './crypto-alpha-40' -export { typedMessageStringify, typedMessageParse } from './crypto-alpha-40' +export { typedMessageStringify } from './crypto-alpha-40' diff --git a/packages/mask/src/crypto/crypto-alpha-40.ts b/packages/mask/src/crypto/crypto-alpha-40.ts index d8577b11d648..66b47d0395b9 100644 --- a/packages/mask/src/crypto/crypto-alpha-40.ts +++ b/packages/mask/src/crypto/crypto-alpha-40.ts @@ -4,9 +4,8 @@ */ import { encodeText, encodeArrayBuffer, decodeArrayBuffer, decodeText, concatArrayBuffer } from '@dimensiondev/kit' import { memoizePromise } from '../../utils-pure' -import { i18n } from '../../shared-ui/locales_legacy' import { CryptoWorker } from '../modules/workers' -import { EC_Private_JsonWebKey, EC_Public_JsonWebKey, AESJsonWebKey, makeTypedMessageText } from '@masknet/shared-base' +import type { EC_Private_JsonWebKey, EC_Public_JsonWebKey, AESJsonWebKey } from '@masknet/shared-base' import { derive_AES_GCM_256_Key_From_PBKDF2, derive_AES_GCM_256_Key_From_ECDH_256k1_Keys, @@ -156,59 +155,7 @@ export async function encrypt1ToN(info: { return { encryptedContent, iv, version: -40, ownersAESKeyEncrypted, othersAESKeyEncrypted, postAESKey: AESKey } } //#endregion -//#region decrypt text -/** - * Decrypt 1 to 1 - */ -export async function decryptMessage1To1(info: { - version: -40 | -39 | -38 - encryptedContent: string | ArrayBuffer - salt: string | ArrayBuffer - /** Your private key */ - privateKeyECDH: EC_Private_JsonWebKey - /** If you are the author, this should be the receiver's public key. - * Otherwise, this should be the author's public key */ - anotherPublicKeyECDH: EC_Public_JsonWebKey -}): Promise { - const { anotherPublicKeyECDH, version, salt, encryptedContent, privateKeyECDH } = info - const encrypted = typeof encryptedContent === 'string' ? decodeArrayBuffer(encryptedContent) : encryptedContent - - const { iv, key } = await deriveAESKey(privateKeyECDH, anotherPublicKeyECDH, salt) - return CryptoWorker.decrypt_aes_gcm(key, iv, encrypted) -} -/** - * Decrypt 1 to N message that send by other - */ -export async function decryptMessage1ToNByOther(info: { - version: -40 | -39 | -38 - encryptedContent: string | ArrayBuffer - privateKeyECDH: EC_Private_JsonWebKey - authorsPublicKeyECDH: EC_Public_JsonWebKey - AESKeyEncrypted: PublishedAESKey | PublishedAESKey[] - iv: ArrayBuffer | string -}): Promise<[ArrayBuffer, AESJsonWebKey]> { - const { encryptedContent, privateKeyECDH, authorsPublicKeyECDH, iv } = info - const AESKeyEncrypted = Array.isArray(info.AESKeyEncrypted) ? info.AESKeyEncrypted : [info.AESKeyEncrypted] - let resolvedAESKey: string | null = null - await Promise.all( - AESKeyEncrypted.map(async (key) => { - try { - const result = await decryptMessage1To1({ - version: -40, - salt: key.salt, - encryptedContent: key.encryptedKey, - anotherPublicKeyECDH: authorsPublicKeyECDH, - privateKeyECDH: privateKeyECDH, - }) - resolvedAESKey = decodeText(result) - } catch {} - }), - ) - if (resolvedAESKey === null) throw new Error(i18n.t('service_not_share_target')) - const aesKey: AESJsonWebKey = JSON.parse(resolvedAESKey) - return [await decryptWithAES({ aesKey, iv, encrypted: encryptedContent }), aesKey] -} export async function extractAESKeyInMessage( version: -40 | -39 | -38, encodedEncryptedKey: string | ArrayBuffer, @@ -223,22 +170,7 @@ export async function extractAESKeyInMessage( ) return decryptedAESKeyJWK } -/** - * Decrypt 1 to N message that send by myself - */ -export async function decryptMessage1ToNByMyself(info: { - version: -40 | -39 | -38 - encryptedContent: string | ArrayBuffer - /** This should be included in the message */ - encryptedAESKey: string | ArrayBuffer - myLocalKey: AESJsonWebKey - iv: string | ArrayBuffer -}): Promise<[ArrayBuffer, AESJsonWebKey]> { - const { encryptedContent, myLocalKey, iv, encryptedAESKey } = info - const decryptedAESKey = await extractAESKeyInMessage(-40, encryptedAESKey, iv, myLocalKey) - const post = await decryptWithAES({ aesKey: decryptedAESKey, encrypted: encryptedContent, iv }) - return [post, decryptedAESKey] -} + /** * Decrypt the content encrypted by AES */ @@ -323,6 +255,3 @@ export async function decryptComment( export function typedMessageStringify(x: any) { throw new Error('Not supported typed message in version older than v39.') } -export function typedMessageParse(x: string) { - return makeTypedMessageText(x) -} From 1a1cbea35a50994326053d34036f245e99f32d7d Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 19:43:42 +0800 Subject: [PATCH 28/85] chore: add correct sns --- .../mask/src/social-network-adaptor/facebook.com/shared.ts | 2 ++ .../mask/src/social-network-adaptor/instagram.com/shared.ts | 5 ++++- packages/mask/src/social-network-adaptor/minds.com/shared.ts | 2 ++ .../mask/src/social-network-adaptor/twitter.com/shared.ts | 2 ++ packages/mask/src/social-network/types.ts | 4 ---- .../mask/src/social-network/utils/create-post-context.ts | 5 +++-- packages/plugin-infra/src/PostContext.ts | 4 +++- 7 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts index 087174a847b0..e0c646923384 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts @@ -3,6 +3,7 @@ import { facebookBase } from './base' import { getPostUrlAtFacebook, isValidFacebookUsername } from './utils/parse-username' import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' +import { SocialNetworkEnum } from '@masknet/encryption' const getPostURL = (post: PostIdentifier): URL | null => { if (post.identifier instanceof ProfileIdentifier) @@ -24,6 +25,7 @@ export const facebookShared: SocialNetwork.Shared & SocialNetwork.Base = { }, createPostContext: createSNSAdaptorSpecializedPostContext({ getURLFromPostIdentifier: getPostURL, + socialNetwork: SocialNetworkEnum.Facebook, }), }, } diff --git a/packages/mask/src/social-network-adaptor/instagram.com/shared.ts b/packages/mask/src/social-network-adaptor/instagram.com/shared.ts index e7bf44d77470..73c0a1c46aa2 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/instagram.com/shared.ts @@ -1,3 +1,4 @@ +import { SocialNetworkEnum } from '@masknet/encryption' import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' import { instagramBase } from './base' @@ -7,6 +8,8 @@ export const instagramShared: SocialNetwork.Shared & SocialNetwork.Base = { utils: { getHomePage: () => 'https://www.instagram.com/', getProfilePage: () => 'https://www.instagram.com/', - createPostContext: createSNSAdaptorSpecializedPostContext({}), + createPostContext: createSNSAdaptorSpecializedPostContext({ + socialNetwork: SocialNetworkEnum.Instagram, + }), }, } diff --git a/packages/mask/src/social-network-adaptor/minds.com/shared.ts b/packages/mask/src/social-network-adaptor/minds.com/shared.ts index 01d78d4705ef..3fc586488165 100644 --- a/packages/mask/src/social-network-adaptor/minds.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/minds.com/shared.ts @@ -1,3 +1,4 @@ +import { SocialNetworkEnum } from '@masknet/encryption' import type { PostIdentifier } from '@masknet/shared-base' import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' @@ -19,6 +20,7 @@ export const mindsShared: SocialNetwork.Shared & SocialNetwork.Base = { }, createPostContext: createSNSAdaptorSpecializedPostContext({ getURLFromPostIdentifier: getPostURL, + socialNetwork: SocialNetworkEnum.Minds, }), }, } diff --git a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts index dfce4e69d632..d70a23087f8f 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts @@ -1,3 +1,4 @@ +import { SocialNetworkEnum } from '@masknet/encryption' import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' @@ -20,6 +21,7 @@ export const twitterShared: SocialNetwork.Shared & SocialNetwork.Base = { }, createPostContext: createSNSAdaptorSpecializedPostContext({ getURLFromPostIdentifier: getPostURL, + socialNetwork: SocialNetworkEnum.Twitter, }), }, } diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index ec631e2a68d1..0d85869696e5 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -19,10 +19,6 @@ import type { Subscription } from 'use-subscription' type ClassNameMap = { [P in ClassKey]: string } // Don't define values in namespaces export namespace SocialNetwork { - export interface PayloadEncoding { - decoder(text: string): string[] - } - export interface Utils { /** @returns the homepage url. e.g.: https://twitter.com/ */ getHomePage?(): string diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index dae82ae59b73..133a85db7017 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -108,6 +108,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct subscribe: (sub) => postIdentifier.subscribe(sub), }) const MaskPayloads = decryptionContext( + create.socialNetwork, { mentionedLinks, rawMessage: opt.rawMessage, @@ -197,6 +198,7 @@ export function createRefsForCreatePostContext() { } function decryptionContext( + currentSocialNetwork: SocialNetworkEnum, context: Pick, signal?: AbortSignal, ): PostContext['maskPayloads'] { @@ -274,8 +276,7 @@ function decryptionContext( async function decryption(key: string, signal: AbortSignal, payloads: SocialNetworkEncodedPayload[]) { const decryption = ServicesWithProgress.decryptionWithSocialNetworkDecoding(payloads, { - // TODO: - currentSocialNetwork: SocialNetworkEnum.Twitter, + currentSocialNetwork, authorHint: author.getCurrentValue(), currentProfile: CurrentIdentitySubscription.getCurrentValue()?.identifier, postURL: url.getCurrentValue()?.toString(), diff --git a/packages/plugin-infra/src/PostContext.ts b/packages/plugin-infra/src/PostContext.ts index c16a18682ce8..4cf9f127a603 100644 --- a/packages/plugin-infra/src/PostContext.ts +++ b/packages/plugin-infra/src/PostContext.ts @@ -9,9 +9,11 @@ import { import { ValueRef, LiveSelector, DOMProxy } from '@dimensiondev/holoflows-kit' import { Context, createContext, createElement, memo, useContext } from 'react' import { Subscription, useSubscription } from 'use-subscription' -import type { DecryptProgress } from '@masknet/encryption' +import type { DecryptProgress, SocialNetworkEnum } from '@masknet/encryption' export interface PostContextSNSActions { getURLFromPostIdentifier?(post: PostIdentifier): URL | null + /** This used for encode/decode */ + socialNetwork: SocialNetworkEnum } export interface PostContextAuthor { readonly nickname: Subscription From 0af20fccda3589b9b61bbda7d72398cc5ab3e9e3 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 5 Jan 2022 19:47:57 +0800 Subject: [PATCH 29/85] refactor: remove unused code --- packages/mask/shared-ui/locales/en-US.json | 11 ----------- packages/mask/shared-ui/locales/ja-JP.json | 11 ----------- packages/mask/shared-ui/locales/ko-KR.json | 11 ----------- packages/mask/shared-ui/locales/qya-AA.json | 11 ----------- packages/mask/shared-ui/locales/zh-CN.json | 11 ----------- packages/mask/shared-ui/locales/zh-TW.json | 11 ----------- packages/plugin-infra/package.json | 2 +- pnpm-lock.yaml | 2 +- 8 files changed, 2 insertions(+), 68 deletions(-) diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 930222ddff42..0ef8096565fd 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -29,7 +29,6 @@ "typed_message_can_not_be_rendered": "The TypedMessage has a circular structure so it can't be rendered on the screen.", "typed_message_text_alert": "Only TypedMessageText is supported currently.", "badge_renderer_provided_by_plugin": "Provided by plugin", - "add_to_key_store_success": "The public key has been added to the Keystore", "add_token": "Add Token", "add_nft_contract_search_hint": "Search NFT Contract Symbol Name or Address", "applications": "Applications", @@ -67,7 +66,6 @@ "days": "Every {{days}} days", "decrypted_postbox_add_recipients": "Append recipients", "decrypted_postbox_decrypting": "Mask decryptingโ€ฆ", - "decrypted_postbox_decoding": "Mask decodingโ€ฆ", "decrypted_postbox_decrypting_finding_person_key": "Mask is looking for the public key of the authorโ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask is looking for the key to decrypt this postโ€ฆ", "decrypted_postbox_decrypting_finding_post_key_long": "Mask is still trying to decrypt this post, but it seems like the post is not shared with you.", @@ -99,10 +97,6 @@ "no_search_result": "No result", "loading": "Loading...", "unlock": "Unlock", - "payload_bad": "This post seems to be corrupted. Mask cannot decrypt it.", - "payload_incomplete": "This post is incomplete. You need to view the full post to decrypt it.", - "payload_not_found": "Payload not found.", - "payload_throw_in_alpha41": "Support for Alpha41 is deprecated. Tell your friends to upgrade Mask!", "personas": "Personas", "browser_action_enter_dashboard": "Enter Dashboard", "pending": "Pending...", @@ -137,12 +131,7 @@ "select_specific_friends_dialog__title": "Select Specific Contacts", "service_decryption_failed": "Decryption failed.", "service_invalid_backup_file": "This does not seem like a backup of Mask.", - "service_not_share_target": "Mask does not find the post key. This post may not be intended to share with you.", - "service_others_key_not_found": "The public key of {{name}} cannot be found!", "service_publish_post_aes_key_failed": "Failed to publish the AES key!", - "service_self_key_decryption_failed": "Decryption failed. Maybe you selected a wrong identity or imported a key that was not used to encrypt this post.", - "service_decode_image_payload_failed": "Decode image failed.", - "service_unknown_payload": "Unknown post version. You may need to update Mask.", "service_username_invalid": "Invalid Username", "settings_enable_debug": "Debug Mode", "settings_enable_debug_desc": "See additional information for debugging.", diff --git a/packages/mask/shared-ui/locales/ja-JP.json b/packages/mask/shared-ui/locales/ja-JP.json index 87488e9583c5..419a765f5c0e 100644 --- a/packages/mask/shared-ui/locales/ja-JP.json +++ b/packages/mask/shared-ui/locales/ja-JP.json @@ -1,5 +1,4 @@ { - "add_to_key_store_success": "ๅ…ฌ้–‹้ตใฏใ‚ญใƒผใ‚นใƒˆใ‚ขใซ่ฟฝๅŠ ใ•ใ‚Œใพใ—ใŸใ€‚", "add_token": "ใƒˆใƒผใ‚ฏใƒณใ‚’่ฟฝๅŠ ", "additional_post_box__encrypted_post_pre": "#mask_io ใงๅพฉๅทใ—ใพใ—ใ‚‡ใ†๏ผ {{encrypted}}", "additional_post_box__steganography_post_pre": "ใ“ใฎๆŠ•็จฟใฏ #mask_io ใซใ‚ˆใฃใฆๆš—ๅทๅŒ–ใ•ใ‚Œใฆใ„ใพใ™ใ€‚ ๐Ÿ“ช๐Ÿ”‘ mask.io ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆๅพฉๅทใ—ใพใ—ใ‚‡ใ†๏ผ ([I:b]) {{random}}", @@ -23,7 +22,6 @@ "dashboard_no_collectible_found": "ใ‚ณใƒฌใ‚ฏใ‚ทใƒงใƒณใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“", "decrypted_postbox_add_recipients": "ๅ—ไฟก่€…ใ‚’่ฟฝๅŠ ", "decrypted_postbox_decrypting": "Mask ใŒๅพฉๅทใ—ใฆใ„ใพใ™ใ€‚", - "decrypted_postbox_decoding": "Mask ใŒ่งฃ่ชญใ—ใฆใ„ใพใ™ใ€‚", "decrypted_postbox_decrypting_finding_person_key": "Mask ใฏๅ…ฌ้–‹้ตใ‚’ๆŽขใ—ใฆใ„ใพใ™โ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask ใฏใ“ใฎๆŠ•็จฟใ‚’ๅพฉๅทใ™ใ‚‹ใŸใ‚ใซ ใƒใ‚นใƒˆใ‚ญใƒผใ‚’ๅ–ๅพ—ใ—ใฆใ„ใพใ™โ€ฆ", "decrypted_postbox_author_mismatch": "ๆœ€ๅˆใฎไฝœ่€…ใฏ {{name}}", @@ -43,10 +41,6 @@ "setup_guide_connect_failed": "ๅคฑๆ•—ใ—ใพใ—ใŸโ€ฆ ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚", "import": "ใ‚คใƒณใƒใƒผใƒˆ", "no_search_result": "่ฉฒๅฝ“ใชใ—", - "payload_bad": "ใ“ใฎๆŠ•็จฟใฏ็ ดๆใ—ใฆใ„ใ‚‹ใŸใ‚ใ€Mask ใงๅพฉๅทใงใใพใ›ใ‚“ใ€‚", - "payload_incomplete": "ใ“ใฎๆŠ•็จฟใฏไธๅฎŒๅ…จใงใ™ใ€‚ๅ…จๆ–‡ใ‚’่ฆ‹ใ‚‹ใ“ใจใซใ‚ˆใ‚ŠๅพฉๅทใŒๅฏ่ƒฝใซใชใ‚Šใพใ™ใ€‚", - "payload_not_found": "ใƒšใ‚คใƒญใƒผใƒ‰ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸใ€‚", - "payload_throw_in_alpha41": "Alpha41ๆ–นๅผใฏไฝฟ็”จใงใใพใ›ใ‚“ใ€‚Mask ใ‚’ใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใ—ใฆใใ ใ•ใ„ใ€‚", "personas": "ใƒšใƒซใ‚ฝใƒŠ", "browser_action_enter_dashboard": "ใƒ€ใƒƒใ‚ทใƒฅใƒœใƒผใƒ‰ใซๅ…ฅใ‚‹", "post_dialog__button": "ๅฎŒไบ†", @@ -69,12 +63,7 @@ "select_specific_friends_dialog__title": "็‰นๅฎšใฎ้€ฃ็ตกๅ…ˆใ‚’้ธใถ", "service_decryption_failed": "ๅพฉๅทใซๅคฑๆ•—", "service_invalid_backup_file": "ใ“ใ‚Œใฏ Mask ใฎใƒใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใงใฏใชใ„ใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“ใ€‚", - "service_not_share_target": " Mask ใฏๆŠ•็จฟ้ตใ‚’็™บ่ฆ‹ใงใใพใ›ใ‚“ใงใ—ใŸใ€‚ใ“ใฎๆŠ•็จฟใฏใ‚ใชใŸใซๅ…ฑๆœ‰ใ•ใ‚Œใฆใ„ใชใ„ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™ใ€‚", - "service_others_key_not_found": "{{name}} ใฎๅ…ฌ้–‹้ตใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“!", "service_publish_post_aes_key_failed": "AES ใ‚ญใƒผใ‚’ๅ…ฌ้–‹ใ™ใ‚‹ใฎใซๅคฑๆ•—ใ—ใพใ—ใŸใ€‚", - "service_self_key_decryption_failed": "ๅพฉๅทใซๅคฑๆ•—ใ—ใพใ—ใŸใ€‚้–“้•ใฃใŸIDใ‚’้ธๆŠžใ—ใŸใ‹ใ€ใ“ใฎๆŠ•็จฟใฎๆš—ๅทๅŒ–ใซไฝฟ็”จใ•ใ‚Œใฆใ„ใชใ„้ตใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใŸใฎใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“ใ€‚", - "service_decode_image_payload_failed": "็”ปๅƒใฎใƒ‡ใ‚ณใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸใ€‚", - "service_unknown_payload": "ๆŠ•็จฟใฎใƒใƒผใ‚ธใƒงใƒณใŒไธๆ˜Žใงใ™ใ€‚Mask ใฎใ‚ขใƒƒใƒ—ใƒ‡ใƒผใƒˆใŒๅฟ…่ฆใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“ใ€‚", "service_username_invalid": "็„กๅŠนใชใƒฆใƒผใ‚ถใƒผๅ", "settings_enable_debug": "ใƒ‡ใƒใƒƒใ‚ฐใƒขใƒผใƒ‰", "settings_enable_debug_desc": "ใƒ‡ใƒใƒƒใ‚ฐใฎใŸใ‚ใฎ่ฟฝๅŠ ๆƒ…ๅ ฑใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚", diff --git a/packages/mask/shared-ui/locales/ko-KR.json b/packages/mask/shared-ui/locales/ko-KR.json index 605c497aa87e..6b791af8ac88 100644 --- a/packages/mask/shared-ui/locales/ko-KR.json +++ b/packages/mask/shared-ui/locales/ko-KR.json @@ -29,7 +29,6 @@ "typed_message_can_not_be_rendered": "The TypedMessage has a circular structure so it can't be rendered on the screen.", "typed_message_text_alert": "ํ˜„์žฌ TypedMessageText๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.", "badge_renderer_provided_by_plugin": "ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ์ œ๊ณต", - "add_to_key_store_success": "ํ•ด๋‹น ๊ณต๊ฐœ ํ‚ค๊ฐ€ ์ด๋ฏธ ํ‚ค์Šคํ† ์–ด์—์„œ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค", "add_token": "ํ† ํฐ ์ถ”๊ฐ€", "add_nft_contract_search_hint": "NFT ์ปจํŠธ๋ ‰ํŠธ ์ด๋ฆ„์ด๋‚˜ ์ฃผ์†Œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ธฐ", "applications": "์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜", @@ -67,7 +66,6 @@ "days": "{{days}} ์ผ๋งˆ๋‹ค", "decrypted_postbox_add_recipients": "๋ฐ›๋Š”์‚ฌ๋žŒ ์ถ”๊ฐ€", "decrypted_postbox_decrypting": "Mask ํ•ด๋… ์ค‘", - "decrypted_postbox_decoding": "Mask ๋””์ฝ”๋”ฉ ์ค‘", "decrypted_postbox_decrypting_finding_person_key": "Mask๊ฐ€ ์ƒ๋Œ€๋ฐฉ์˜ ๊ณต๊ฐœ ํ‚ค๋ฅผ ์ฐพ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค...", "decrypted_postbox_decrypting_finding_post_key": "Mask๊ฐ€ ์ด ๊ฒŒ์‹œ๊ธ€์˜ ํ‚ค๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.", "decrypted_postbox_author_mismatch": "{{name}}๋‹˜์ด ์ด ๊ธ€์„ ์ฒ˜์Œ ๊ฒŒ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.", @@ -96,10 +94,6 @@ "no_search_result": "๊ฒฐ๊ณผ ์—†์Œ", "loading": "๋กœ๋”ฉ ์ค‘", "unlock": "์–ธ๋ฝ", - "payload_bad": "์ด ๊ฒŒ์‹œ๋ฌผ์€ ์†์ƒ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. Mask๊ฐ€ ํ•ด๋…ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "payload_incomplete": "๋ถˆ์™„์ „ํ•œ ๊ฒŒ์‹œ๋ฌผ์ž…๋‹ˆ๋‹ค. ํ•ด๋…ํ•˜๋ ค๋ฉด ์™„์ „ํ•œ ๊ฒŒ์‹œ๋ฌผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", - "payload_not_found": "ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "payload_throw_in_alpha41": "Alpha41 ์ด๋ฏธ ์ง€์›ํ•˜ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ Mask๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋ผ๊ณ  ํ•˜์„ธ์š”!", "personas": "ํŽ˜๋ฅด์†Œ๋‚˜", "browser_action_enter_dashboard": "๋Œ€์‹œ๋ณด๋“œ๋กœ ๋“ค์–ด๊ฐ€๊ธฐ", "pending": "๋Œ€๊ธฐ ์ค‘โ€ฆ", @@ -134,12 +128,7 @@ "select_specific_friends_dialog__title": "์ง€์ • ์—ฐ๋ฝ์ฒ˜ ์„ ํƒ", "service_decryption_failed": "ํ•ด๋… ์‹คํŒจ", "service_invalid_backup_file": "Mask์˜ ๋ฐฑ์—…์ด ์•„๋‹Œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.", - "service_not_share_target": "Mask๊ฐ€ ์ด ๊ฒŒ์‹œ๋ฌผ์˜ ํ‚ค๋ฅผ ์ฐพ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒŒ์‹œ๋ฌผ์€ ๋‹น์‹ ์—๊ฒŒ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.", - "service_others_key_not_found": "์ฐพ์„ ์ˆ˜ ์—†๋Š” {{name}}์˜ ๊ณต๊ฐœ ํ‚ค์ž…๋‹ˆ๋‹ค.", "service_publish_post_aes_key_failed": "AES ํ‚ค ๊ณต์œ  ์‹คํŒจ", - "service_self_key_decryption_failed": "ํ•ด๋… ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž˜๋ชป๋œ ์‹ ๋ถ„์ด๋‚˜ ํ‚ค๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.", - "service_decode_image_payload_failed": "์ด๋ฏธ์ง€ ๋””์ฝ”๋”ฉ ์‹คํŒจ.", - "service_unknown_payload": "์•Œ ์ˆ˜ ์—†๋Š” ๊ฒŒ์‹œ๋ฌผ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. Mask๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”.", "service_username_invalid": "๋ฌดํšจํ•œ ์•„์ด๋””์ž…๋‹ˆ๋‹ค", "settings_enable_debug": "๋””๋ฒ„๊ทธ ๋ชจ๋“œ", "settings_enable_debug_desc": "๋””๋ฒ„๊น…์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ •๋ณด ๋ณด๊ธฐ", diff --git a/packages/mask/shared-ui/locales/qya-AA.json b/packages/mask/shared-ui/locales/qya-AA.json index 138f4b8e176c..0e46cf8983e5 100644 --- a/packages/mask/shared-ui/locales/qya-AA.json +++ b/packages/mask/shared-ui/locales/qya-AA.json @@ -29,7 +29,6 @@ "typed_message_can_not_be_rendered": "crwdns10115:0crwdne10115:0", "typed_message_text_alert": "crwdns10117:0crwdne10117:0", "badge_renderer_provided_by_plugin": "crwdns10119:0crwdne10119:0", - "add_to_key_store_success": "crwdns3981:0crwdne3981:0", "add_token": "crwdns3983:0crwdne3983:0", "add_nft_contract_search_hint": "crwdns8123:0crwdne8123:0", "applications": "crwdns9513:0crwdne9513:0", @@ -67,7 +66,6 @@ "days": "crwdns4107:0{{days}}crwdne4107:0", "decrypted_postbox_add_recipients": "crwdns4109:0crwdne4109:0", "decrypted_postbox_decrypting": "crwdns4111:0crwdne4111:0", - "decrypted_postbox_decoding": "crwdns4113:0crwdne4113:0", "decrypted_postbox_decrypting_finding_person_key": "crwdns4115:0crwdne4115:0", "decrypted_postbox_decrypting_finding_post_key": "crwdns4117:0crwdne4117:0", "decrypted_postbox_author_mismatch": "crwdns4119:0{{name}}crwdne4119:0", @@ -98,10 +96,6 @@ "no_search_result": "crwdns4217:0crwdne4217:0", "loading": "crwdns8031:0crwdne8031:0", "unlock": "crwdns8033:0crwdne8033:0", - "payload_bad": "crwdns4227:0crwdne4227:0", - "payload_incomplete": "crwdns4229:0crwdne4229:0", - "payload_not_found": "crwdns4231:0crwdne4231:0", - "payload_throw_in_alpha41": "crwdns4233:0crwdne4233:0", "personas": "crwdns4237:0crwdne4237:0", "browser_action_enter_dashboard": "crwdns4247:0crwdne4247:0", "pending": "crwdns9391:0crwdne9391:0", @@ -136,12 +130,7 @@ "select_specific_friends_dialog__title": "crwdns4341:0crwdne4341:0", "service_decryption_failed": "crwdns4343:0crwdne4343:0", "service_invalid_backup_file": "crwdns4345:0crwdne4345:0", - "service_not_share_target": "crwdns4347:0crwdne4347:0", - "service_others_key_not_found": "crwdns4349:0{{name}}crwdne4349:0", "service_publish_post_aes_key_failed": "crwdns4351:0crwdne4351:0", - "service_self_key_decryption_failed": "crwdns4353:0crwdne4353:0", - "service_decode_image_payload_failed": "crwdns4355:0crwdne4355:0", - "service_unknown_payload": "crwdns4357:0crwdne4357:0", "service_username_invalid": "crwdns4359:0crwdne4359:0", "settings_enable_debug": "crwdns4427:0crwdne4427:0", "settings_enable_debug_desc": "crwdns4429:0crwdne4429:0", diff --git a/packages/mask/shared-ui/locales/zh-CN.json b/packages/mask/shared-ui/locales/zh-CN.json index cdacba72d114..ab752ced3286 100644 --- a/packages/mask/shared-ui/locales/zh-CN.json +++ b/packages/mask/shared-ui/locales/zh-CN.json @@ -29,7 +29,6 @@ "typed_message_can_not_be_rendered": "่พ“ๅ…ฅ็š„ๆถˆๆฏๅญ˜ๅœจๅพช็Žฏ็ป“ๆž„๏ผŒๅ› ๆญคๆ— ๆณ•ๅœจๅฑๅน•ไธŠๅ‘ˆ็Žฐใ€‚", "typed_message_text_alert": "ๅฝ“ๅ‰ๅชๆ”ฏๆŒ่พ“ๅ…ฅๆถˆๆฏๆ–‡ๆœฌใ€‚", "badge_renderer_provided_by_plugin": "ๆ’ไปถๆไพ›๏ผš", - "add_to_key_store_success": "ๅ…ฌ้’ฅๅทฒๆทปๅŠ ๅˆฐๅฏ†้’ฅๅบ“", "add_token": "ๆทปๅŠ ไปฃๅธ", "add_nft_contract_search_hint": "ๆœ็ดข NFT ๅˆ็บฆๅ็งฐๆˆ–ๅœฐๅ€", "applications": "ๅบ”็”จ็จ‹ๅบ", @@ -67,7 +66,6 @@ "days": "ๆฏ้š” {{days}} ๅคฉ", "decrypted_postbox_add_recipients": "่ฟฝๅŠ ๆ”ถไปถไบบ", "decrypted_postbox_decrypting": "Maskๆญฃๅœจ่งฃๅฏ†ไธญโ€ฆ", - "decrypted_postbox_decoding": "Maskๆญฃๅœจ่งฃ็ ไธญโ€ฆ", "decrypted_postbox_decrypting_finding_person_key": "Mask ๆญฃๅœจๅฏปๆ‰พไฝœ่€…็š„ๅ…ฌ้’ฅโ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask ๆญฃๅœจ่Žทๅ–่ดดๆ–‡ๅฏ†้’ฅไปฅ่งฃๅฏ†ๆญค่ดดๆ–‡โ€ฆ", "decrypted_postbox_author_mismatch": "ๆœ€ๅˆๅ‘ๅธƒไบŽ {{name}}", @@ -95,10 +93,6 @@ "no_search_result": "ๆฒกๆœ‰ไปปไฝ•็ป“ๆžœใ€‚", "loading": "ๅŠ ่ฝฝไธญ", "unlock": "่งฃ้”", - "payload_bad": "่ฟ™ไธช่ดดๆ–‡ไผผไนŽๅทฒๆŸๅใ€‚Maskๆ— ๆณ•่งฃๅฏ†ๅฎƒใ€‚", - "payload_incomplete": "ๆญค่ดดๆ–‡ไธๅฎŒๆ•ดใ€‚ๆ‚จ้œ€่ฆๆŸฅ็œ‹ๅฎŒๆ•ด่ดดๆ–‡ๆ‰่ƒฝ่งฃๅฏ†ใ€‚", - "payload_not_found": "ๆœชๆ‰พๅˆฐPayloadใ€‚", - "payload_throw_in_alpha41": "ไธๆ”ฏๆŒAlpha41 ใ€‚ๅ‘Š่ฏ‰ๆ‚จ็š„ๆœ‹ๅ‹ๅ‡็บงMask๏ผ", "personas": "่บซไปฝ", "browser_action_enter_dashboard": "่ฟ›ๅ…ฅๆŽงๅˆถ้ขๆฟ", "pending": "ๅพ…ๅฎšไธญ...", @@ -133,12 +127,7 @@ "select_specific_friends_dialog__title": "้€‰ๆ‹ฉ็‰นๅฎš่”็ณปไบบ", "service_decryption_failed": "่งฃๅฏ†ๅคฑ่ดฅ", "service_invalid_backup_file": "่ฟ™ไผผไนŽไธๆ˜ฏMask็š„ๅค‡ไปฝใ€‚", - "service_not_share_target": "Maskๆฒกๆœ‰ๆ‰พๅˆฐ่ฏฅ่ดดๆ–‡็š„ๅฏ†้’ฅใ€‚่ฟ™ไธช่ดดๆ–‡ๅฏ่ƒฝๆฒกๆœ‰ๅˆ†ไบซ็ป™ๆ‚จๆŸฅ็œ‹ใ€‚", - "service_others_key_not_found": "ๆ— ๆณ•ๆ‰พๅˆฐ {{name}} ็š„ๅ…ฌ้’ฅ ๏ผ", "service_publish_post_aes_key_failed": "AESๅฏ†้’ฅๅ‘ๅธƒๅคฑ่ดฅ๏ผ", - "service_self_key_decryption_failed": "่ดดๆ–‡่งฃๅฏ†ๅคฑ่ดฅใ€‚ๆˆ–่ฎธๆ‚จ้€‰ๆ‹ฉไบ†้”™่ฏฏ็š„่บซไปฝๆˆ–่€…ๅฏผๅ…ฅ็š„ๆ˜ฏๆœช็”จไบŽๅŠ ๅฏ†ๆญค่ดดๆ–‡็š„ๅฏ†้’ฅใ€‚", - "service_decode_image_payload_failed": "ๅ›พ็‰‡่งฃ็ ๅคฑ่ดฅใ€‚", - "service_unknown_payload": "ๆœช็Ÿฅ็š„่ดดๆ–‡็‰ˆๆœฌใ€‚ๆ‚จๅฏ่ƒฝ้œ€่ฆๆ›ดๆ–ฐMaskใ€‚", "service_username_invalid": "ๆ— ๆ•ˆ็š„็”จๆˆทๅ", "settings_enable_debug": "่ฐƒ่ฏ•ๆจกๅผ", "settings_enable_debug_desc": "ๆŸฅ็œ‹ๆ›ดๅคšไฟกๆฏ่ฟ›่กŒ่ฐƒ่ฏ•ใ€‚", diff --git a/packages/mask/shared-ui/locales/zh-TW.json b/packages/mask/shared-ui/locales/zh-TW.json index 7a5c2512295a..475095e6ce32 100644 --- a/packages/mask/shared-ui/locales/zh-TW.json +++ b/packages/mask/shared-ui/locales/zh-TW.json @@ -17,7 +17,6 @@ "contract": "ๅˆ็ด„", "initializing": "ๅˆๅง‹ๅŒ–ไธญ...", "redirect_alert": "ๅฆ‚ๆžœไฝ ็š„็€่ฆฝๅ™จๆฒ’ๆœ‰่ทณ่ฝ‰๏ผŒ ่ซ‹้ปžๆ“Šๆญค่™•.", - "add_to_key_store_success": "ๅ…ฌ้‘ฐๅทฒๆทปๅŠ ๅˆฐๅฏ†้‘ฐๅบซ", "add_token": "ๆ–ฐๅขžไปฃๅนฃ", "add_nft_contract_search_hint": "ๆชข็ดขNFTๅˆ็ด„ๅ็จฑๆˆ–ๅœฐๅ€", "applications": "ๆ‡‰็”จ", @@ -50,7 +49,6 @@ "dashboard_no_collectible_found": "ๆ‰พไธๅˆฐๆ”ถ่—ๅ“", "decrypted_postbox_add_recipients": "ๆ–ฐๅขžๆ”ถไปถไบบ", "decrypted_postbox_decrypting": "Mask ๆญฃๅœจ่งฃๅฏ†โ€ฆ", - "decrypted_postbox_decoding": "Mask ๆญฃๅœจ่งฃ็ขผโ€ฆ", "decrypted_postbox_decrypting_finding_person_key": "Mask ๆญฃๅœจๅฐ‹ๆ‰พ้€™ๅ€‹ไฝœ่€…็š„ๅ…ฌ้‘ฐโ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask ๆญฃๅœจๅ–ๅพ—่ฒผๆ–‡้‡‘้‘ฐไปฅ่งฃๅฏ†ๆญค่ฒผๆ–‡โ€ฆ", "decrypted_postbox_author_mismatch": "ๆœ€ๅˆ็”ฑ {{name}} ็™ผไฝˆ", @@ -72,10 +70,6 @@ "user_guide_tip_connected": "ๅทฒๆˆๅŠŸ้€ฃๆŽฅๆญคๅธณ่™Ÿ", "import": "ๅฐŽๅ…ฅ", "no_search_result": "ๆฒ’ๆœ‰็ตๆžœ", - "payload_bad": "้€™็ฏ‡่ฒผๆ–‡ไผผไนŽ่ขซ็ ดๅฃžไบ†ใ€‚Mask ็„กๆณ•่งฃๅฏ†ๅฎƒใ€‚", - "payload_incomplete": "้€™็ฏ‡่ฒผๆ–‡ไธๅฎŒๆ•ดใ€‚ไฝ ้œ€่ฆๆŸฅ็œ‹ๅ…จๆ–‡ๆ‰่ƒฝ่งฃๅฏ†ใ€‚", - "payload_not_found": "ๆ‰พไธๅˆฐ่ฒผๆ–‡ใ€‚", - "payload_throw_in_alpha41": "ไธๆŽจ่–ฆไฝฟ็”จ Alpha41ใ€‚ๅ‘Š่จดไฝ ็š„ๆœ‹ๅ‹ๅ‡็ดšMask๏ผ", "personas": "่ง’่‰ฒ", "browser_action_enter_dashboard": "้€ฒๅ…ฅๅ„€้Œถๆฟ", "post_dialog__button": "ๅฎŒๆˆ", @@ -101,12 +95,7 @@ "select_specific_friends_dialog__title": "้ธๆ“‡็‰นๅฎš่ฏ็ตกไบบ", "service_decryption_failed": "่งฃๅฏ†ๅคฑๆ•—ใ€‚", "service_invalid_backup_file": "้€™ไธๆ˜ฏไธ€ๅ€‹ Mask ็š„ๅ‚™ไปฝๆช”ใ€‚", - "service_not_share_target": "Mask ็„ก็™ผๆ‰พๅˆฐ่ฒผๆ–‡้‡‘้‘ฐใ€‚ ้€™็ฏ‡ๆ–‡็ซ ๆˆ–่จฑไธๆ˜ฏ่ฆๅˆ†ไบซ็ตฆไฝ ็š„ใ€‚", - "service_others_key_not_found": "็„กๆณ•ๆ‰พๅˆฐ {{name}} ็š„ๅ…ฌ้‘ฐ๏ผ", "service_publish_post_aes_key_failed": "็™ผไฝˆ AES ้‡‘้‘ฐๅคฑๆ•—๏ผ", - "service_self_key_decryption_failed": "่งฃๅฏ†ๅคฑๆ•—ใ€‚ๅฏ่ƒฝๆ‚จ้ธๆ“‡ไบ†้Œฏ่ชค็š„่บซๅˆ†ๆˆ–ๅฐŽๅ…ฅไบ†ไธ€ๅ€‹ไธ็”จๆ–ผๅŠ ๅฏ†ๆญค่ฒผๆ–‡็š„้‡‘้‘ฐใ€‚", - "service_decode_image_payload_failed": "ๅœ–็‰‡่งฃ็ขผๅคฑๆ•—", - "service_unknown_payload": "ๆœช็Ÿฅ็š„่ฒผๆ–‡็‰ˆๆœฌใ€‚ๆ‚จๆˆ–่จฑ้œ€่ฆๅ‡็ดš Maskใ€‚", "service_username_invalid": "้Œฏ่ชค็š„ไฝฟ็”จ่€…ๅ็จฑ", "settings_enable_debug": "้™ค้Œฏๆจกๅผ", "settings_enable_debug_desc": "่ซ‹ๅƒ่€ƒๆœ‰้—œ้™ค้Œฏ็š„้™„ๅŠ ่ณ‡่จŠ", diff --git a/packages/plugin-infra/package.json b/packages/plugin-infra/package.json index 385092d5ba63..28f6d5da4839 100644 --- a/packages/plugin-infra/package.json +++ b/packages/plugin-infra/package.json @@ -7,7 +7,7 @@ "scripts": {}, "dependencies": { "@dimensiondev/holoflows-kit": "^0.9.0-20210902104757-7c3d0d0", - "@masknet/encryption": "workspace:^0.0.0", + "@masknet/encryption": "workspace:^*", "@masknet/shared": "workspace:*", "@masknet/shared-base": "workspace:*", "@masknet/theme": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ecedb592d01..65b650feef98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -565,7 +565,7 @@ importers: packages/plugin-infra: specifiers: '@dimensiondev/holoflows-kit': ^0.9.0-20210902104757-7c3d0d0 - '@masknet/encryption': workspace:^0.0.0 + '@masknet/encryption': workspace:^* '@masknet/shared': workspace:* '@masknet/shared-base': workspace:* '@masknet/theme': workspace:* From 5aa9b683b63a8f140ce26a5048b4de797291b4b2 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 6 Jan 2022 09:55:42 +0800 Subject: [PATCH 30/85] fix: tsconfig --- packages/mask/utils-pure/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mask/utils-pure/tsconfig.json b/packages/mask/utils-pure/tsconfig.json index 55599407b5af..28492f65e59d 100644 --- a/packages/mask/utils-pure/tsconfig.json +++ b/packages/mask/utils-pure/tsconfig.json @@ -8,5 +8,5 @@ "lib": ["ES2021"] }, "include": ["./"], - "references": [] + "references": [{ "path": "../../shared-base" }] } From 3ea3500f64c8d39ec5c5e7ac4d5236587a1a0e4c Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 6 Jan 2022 09:56:14 +0800 Subject: [PATCH 31/85] fix: tsconfig --- packages/shared-base/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared-base/src/index.ts b/packages/shared-base/src/index.ts index 347dfe9de26b..90cb2d0ce8d2 100644 --- a/packages/shared-base/src/index.ts +++ b/packages/shared-base/src/index.ts @@ -1,7 +1,6 @@ export { serializer } from './serializer' export { IdentifierMap, ReadonlyIdentifierMap } from './Identifier/IdentifierMap' export * from './collections' -export * from './MaskPayload' export * from './Identifier/type' export * from './i18n' export * from './TypedMessage' From d568a53ef940cccd7d4961f81c28ed5526af71a7 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 6 Jan 2022 11:29:06 +0800 Subject: [PATCH 32/85] fix: co-op E2E with old implementation --- .../encryption/src/encryption/Decryption.ts | 26 +++++++------ .../src/encryption/DecryptionTypes.ts | 24 +++++++++++- .../background/database/persona/helper.ts | 39 ++++++++++++++++++- .../background/services/crypto/decryption.ts | 10 ++++- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/packages/encryption/src/encryption/Decryption.ts b/packages/encryption/src/encryption/Decryption.ts index 3817f6c21fb6..2dc7a8b76846 100644 --- a/packages/encryption/src/encryption/Decryption.ts +++ b/packages/encryption/src/encryption/Decryption.ts @@ -140,13 +140,6 @@ async function* v38To40StaticECDH( encrypted: Uint8Array, signal: AbortSignal | undefined, ): AsyncIterableIterator { - // Cannot do ECDH if no private key - const derivedKeys = await Result.wrapAsync(() => io.deriveAESKey(authorECPub)) - if (derivedKeys.err) return yield new DecryptError(ErrorReasons.PrivateKeyNotFound, derivedKeys.val) - if (derivedKeys.val.length === 0) return yield new DecryptError(ErrorReasons.PrivateKeyNotFound, undefined) - - // ECDH key derived. Now start looking for post AES key. - const postAESKeyIterator = { '-40': async function* (iv: Uint8Array) { const val = await io.queryPostKey_version40(iv) @@ -160,7 +153,10 @@ async function* v38To40StaticECDH( version, io, postAESKeyIterator, - { type: 'static', derive: derivedKeys.val }, + { + type: 'static-v38-or-older', + derive: (postKeyIV) => io.deriveAESKey_version38_or_older(authorECPub, postKeyIV), + }, iv, encrypted, ) @@ -171,7 +167,10 @@ async function* decryptByECDH( io: DecryptIO, possiblePostKeyIterator: AsyncIterableIterator, ecdhProvider: - | { type: 'static'; derive: AESCryptoKey[] } + | { + type: 'static-v38-or-older' + derive: (postKeyIV: Uint8Array) => Promise + } // it's optional argument because the ephemeralPublicKey maybe inlined in the post payload. | { type: 'ephemeral'; derive: (ephemeralPublicKey?: EC_Public_CryptoKey) => Promise }, iv: Uint8Array, @@ -182,10 +181,13 @@ async function* decryptByECDH( const { encryptedPostKey, postKeyIV, ephemeralPublicKey } = _ // TODO: how to deal with signature? // TODO: what to do if provider throws? - const derivedKeys = type === 'static' ? derive : await derive(ephemeralPublicKey) - for (const derivedKey of derivedKeys) { + const derivedKeys = + type === 'static-v38-or-older' + ? await derive(postKeyIV) + : await derive(ephemeralPublicKey).then((aesArr) => aesArr.map((aes) => [aes, iv] as const)) + for (const [derivedKey, derivedKeyNewIV] of derivedKeys) { const possiblePostKey = await andThenAsync( - decryptWithAES(AESAlgorithmEnum.A256GCM, derivedKey, postKeyIV, encryptedPostKey), + decryptWithAES(AESAlgorithmEnum.A256GCM, derivedKey, derivedKeyNewIV, encryptedPostKey), (postKeyRaw) => Result.wrapAsync(() => importAESKeyFromJWKFromTextEncoder(postKeyRaw)), ) if (possiblePostKey.err) { diff --git a/packages/encryption/src/encryption/DecryptionTypes.ts b/packages/encryption/src/encryption/DecryptionTypes.ts index e383be22c3c7..3755ecb23f24 100644 --- a/packages/encryption/src/encryption/DecryptionTypes.ts +++ b/packages/encryption/src/encryption/DecryptionTypes.ts @@ -67,7 +67,7 @@ export interface DecryptIO { */ queryPostKey_version37(iv: Uint8Array, signal?: AbortSignal): AsyncIterableIterator /** - * Derive a group of AES key for ECDH. + * Derive a group of AES key by ECDH(selfPriv, targetPub). * * Host should derive a new AES-GCM key for each private key they have access to. * @@ -78,6 +78,28 @@ export interface DecryptIO { * @param publicKey The public key used in ECDH */ deriveAESKey(publicKey: EC_Public_CryptoKey): Promise + /** + * Derive a group of AES key for ECDH. + * + * !! Note: This part is not simple ECDH. + * + * !! For the compatibility, you should refer to the original implementation: + * + * !! https://github.com/DimensionDev/Maskbook/blob/f3d83713d60dd0aad462e0648c4d38586c106edc/packages/mask/src/crypto/crypto-alpha-40.ts#L29-L58 + * + * Host should derive a new AES-GCM key for each private key they have access to. + * + * If the provided key cannot derive AES with any key (e.g. The given key is ED25519 but there is only P-256 private keys) + * please return an empty array. + * + * Error from this function will become a fatal error. + * @param publicKey The public key used in ECDH + * @param iv The IV used to generate new set of IVs + */ + deriveAESKey_version38_or_older( + publicKey: EC_Public_CryptoKey, + iv: Uint8Array, + ): Promise<[aes: AESCryptoKey, iv: Uint8Array][]> } export interface DecryptStaticECDH_PostKey { encryptedPostKey: Uint8Array diff --git a/packages/mask/background/database/persona/helper.ts b/packages/mask/background/database/persona/helper.ts index 33bf1b8b6b58..99d3b0d821f8 100644 --- a/packages/mask/background/database/persona/helper.ts +++ b/packages/mask/background/database/persona/helper.ts @@ -1,3 +1,4 @@ +import { concatArrayBuffer, decodeArrayBuffer } from '@dimensiondev/kit' import { AESCryptoKey, AESJsonWebKey, @@ -73,7 +74,7 @@ async function getLocalKeyOf( //#endregion //#region ECDH -export async function deriveAESByECDH(pub: EC_Public_CryptoKey) { +export async function deriveAESByECDH(pub: EC_Public_CryptoKey, extractable = true) { const curve = (pub.algorithm as EcKeyAlgorithm).namedCurve || '' const sameCurvePrivateKeys = new Map() @@ -102,7 +103,7 @@ export async function deriveAESByECDH(pub: EC_Public_CryptoKey) { { name: 'ECDH', public: pub }, k, { name: 'AES-GCM', length: 256 }, - false, + extractable, ['encrypt', 'decrypt'], ) deriveResult.set(id, result as AESCryptoKey) @@ -114,6 +115,40 @@ export async function deriveAESByECDH(pub: EC_Public_CryptoKey) { } return new IdentifierMap(deriveResult, ECKeyIdentifier) } + +const KEY = decodeArrayBuffer('KEY') +const IV = decodeArrayBuffer('IV') +export async function deriveAESByECDH_version38_or_older( + pub: EC_Public_CryptoKey, + iv: Uint8Array, + extractable = false, +) { + const deriveResult = (await deriveAESByECDH(pub, true)).__raw_map__ + type R = [key: AESCryptoKey, iv: Uint8Array] + const next_map = new Map() + + for (const [id, key] of deriveResult) { + const derivedKeyRaw = await crypto.subtle.exportKey('raw', key) + const _a = concatArrayBuffer(derivedKeyRaw, iv) + const nextAESKeyMaterial = await crypto.subtle.digest('SHA-256', concatArrayBuffer(_a, iv, KEY)) + const iv_pre = new Uint8Array(await crypto.subtle.digest('SHA-256', concatArrayBuffer(_a, iv, IV))) + const nextIV = new Uint8Array(16) + for (let i = 0; i <= 16; i += 1) { + // eslint-disable-next-line no-bitwise + nextIV[i] = iv_pre[i] ^ iv_pre[16 + i] + } + const nextAESKey = await crypto.subtle.importKey( + 'raw', + nextAESKeyMaterial, + { name: 'AES-GCM', length: 256 }, + extractable, + ['encrypt', 'decrypt'], + ) + next_map.set(id, [nextAESKey as AESCryptoKey, nextIV]) + } + + return new IdentifierMap(next_map, ECKeyIdentifier) +} //#endregion function abort() { diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index b0cadcc14bfe..9813e0c0c23f 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -22,7 +22,12 @@ import { } from '@masknet/shared-base' import EventIterator from 'event-iterator' import { queryPersonaByProfileDB } from '../../database/persona/db' -import { decryptByLocalKey, deriveAESByECDH, hasLocalKeyOf } from '../../database/persona/helper' +import { + decryptByLocalKey, + deriveAESByECDH, + deriveAESByECDH_version38_or_older, + hasLocalKeyOf, +} from '../../database/persona/helper' import { queryPostDB } from '../../database/post' import { savePostKeyToDB } from '../../database/post/helper' import { GUN_queryPostKey_version39Or38, GUN_queryPostKey_version40 } from '../../network/gun/encryption/queryPostKey' @@ -135,6 +140,9 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt async deriveAESKey(pub) { return Array.from((await deriveAESByECDH(pub)).values()) }, + async deriveAESKey_version38_or_older(pub, iv) { + return Array.from((await deriveAESByECDH_version38_or_older(pub, iv)).values()) + }, queryAuthorPublicKey(author, signal) { return queryPublicKey(author || authorHint, false, signal) }, From ed65c7dbe57d068a7a4aba17215a3824442f242d Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 6 Jan 2022 12:25:54 +0800 Subject: [PATCH 33/85] fix: type error --- packages/encryption/src/encryption/DecryptionTypes.ts | 1 + packages/encryption/src/index.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/encryption/src/encryption/DecryptionTypes.ts b/packages/encryption/src/encryption/DecryptionTypes.ts index 33d86006191f..882ad19b8451 100644 --- a/packages/encryption/src/encryption/DecryptionTypes.ts +++ b/packages/encryption/src/encryption/DecryptionTypes.ts @@ -149,6 +149,7 @@ export enum ErrorReasons { NotShareTarget = '[@masknet/encryption] No valid key is found. Likely this post is not shared with you', // Not used in this library. UnrecognizedAuthor = '[@masknet/encryption] No author is recognized which is required for the image steganography decoding.', + CurrentProfileDoesNotConnectedToPersona = '[@masknet/encryption] Cannot decrypt by E2E because no persona is linked with the current profile.', } export class DecryptError extends Error { static Reasons = ErrorReasons diff --git a/packages/encryption/src/index.ts b/packages/encryption/src/index.ts index b28af6f25739..8b56b2dbb8df 100644 --- a/packages/encryption/src/index.ts +++ b/packages/encryption/src/index.ts @@ -15,12 +15,15 @@ export { decrypt, DecryptError, DecryptProgressKind, + DecryptIntermediateProgressKind, ErrorReasons, type DecryptOptions, type DecryptIO, type DecryptEphemeralECDH_PostKey, type DecryptStaticECDH_PostKey, type DecryptProgress, + type DecryptIntermediateProgress, + type DecryptReportedInfo, type DecryptSuccess, } from './encryption' export { From 0d1b4df03eb32b310bec4d73e9c5789873fbcde7 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 6 Jan 2022 13:26:53 +0800 Subject: [PATCH 34/85] chore: some tweak --- .../background-script/CryptoServices/encryptTo.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts index 65b1b4297200..cf24b49f4abe 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts @@ -70,11 +70,13 @@ export async function encryptTo( let authorPublicKey: PayloadWellFormed.Payload['authorPublicKey'] = None try { - const jwk = (await queryPersonaByProfileDB(whoAmI))?.publicKey - if (jwk) { + const publicKey = (await queryPersonaByProfileDB(whoAmI))?.publicKey + if (publicKey) { authorPublicKey = Some({ algr: PublicKeyAlgorithmEnum.secp256k1, - key: (await importAsymmetryKeyFromJsonWebKeyOrSPKI(jwk, PublicKeyAlgorithmEnum.secp256k1)).unwrap(), + key: ( + await importAsymmetryKeyFromJsonWebKeyOrSPKI(publicKey, PublicKeyAlgorithmEnum.secp256k1) + ).unwrap(), }) } } catch { @@ -101,7 +103,7 @@ export async function encryptTo( } } - const payload: { -readonly [key in keyof PayloadWellFormed.Payload]: PayloadWellFormed.Payload[key] } = { + const payload: PayloadWellFormed.Payload = { version: -38, author: Some(whoAmI), encryption, From 3d2d0b7aac8686a03de8273300688fb8881465a0 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 6 Jan 2022 14:09:25 +0800 Subject: [PATCH 35/85] chore: change --- packages/mask/shared-ui/locales/en-US.json | 1 - packages/mask/shared-ui/locales/ja-JP.json | 1 - packages/mask/shared-ui/locales/ko-KR.json | 1 - packages/mask/shared-ui/locales/qya-AA.json | 1 - packages/mask/shared-ui/locales/zh-CN.json | 1 - packages/mask/shared-ui/locales/zh-TW.json | 1 - .../DecryptedPost/DecryptPostAwaiting.tsx | 20 ++++++++----------- 7 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 0ef8096565fd..7ecb10838564 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -66,7 +66,6 @@ "days": "Every {{days}} days", "decrypted_postbox_add_recipients": "Append recipients", "decrypted_postbox_decrypting": "Mask decryptingโ€ฆ", - "decrypted_postbox_decrypting_finding_person_key": "Mask is looking for the public key of the authorโ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask is looking for the key to decrypt this postโ€ฆ", "decrypted_postbox_decrypting_finding_post_key_long": "Mask is still trying to decrypt this post, but it seems like the post is not shared with you.", "decrypted_postbox_author_mismatch": "Originally posted by {{name}}", diff --git a/packages/mask/shared-ui/locales/ja-JP.json b/packages/mask/shared-ui/locales/ja-JP.json index 419a765f5c0e..d048d6afd78f 100644 --- a/packages/mask/shared-ui/locales/ja-JP.json +++ b/packages/mask/shared-ui/locales/ja-JP.json @@ -22,7 +22,6 @@ "dashboard_no_collectible_found": "ใ‚ณใƒฌใ‚ฏใ‚ทใƒงใƒณใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“", "decrypted_postbox_add_recipients": "ๅ—ไฟก่€…ใ‚’่ฟฝๅŠ ", "decrypted_postbox_decrypting": "Mask ใŒๅพฉๅทใ—ใฆใ„ใพใ™ใ€‚", - "decrypted_postbox_decrypting_finding_person_key": "Mask ใฏๅ…ฌ้–‹้ตใ‚’ๆŽขใ—ใฆใ„ใพใ™โ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask ใฏใ“ใฎๆŠ•็จฟใ‚’ๅพฉๅทใ™ใ‚‹ใŸใ‚ใซ ใƒใ‚นใƒˆใ‚ญใƒผใ‚’ๅ–ๅพ—ใ—ใฆใ„ใพใ™โ€ฆ", "decrypted_postbox_author_mismatch": "ๆœ€ๅˆใฎไฝœ่€…ใฏ {{name}}", "decrypted_postbox_title": "Mask ใงๆš—ๅทๅŒ–ใ•ใ‚Œใฆใ„ใพใ™:", diff --git a/packages/mask/shared-ui/locales/ko-KR.json b/packages/mask/shared-ui/locales/ko-KR.json index 6b791af8ac88..51441bc909fd 100644 --- a/packages/mask/shared-ui/locales/ko-KR.json +++ b/packages/mask/shared-ui/locales/ko-KR.json @@ -66,7 +66,6 @@ "days": "{{days}} ์ผ๋งˆ๋‹ค", "decrypted_postbox_add_recipients": "๋ฐ›๋Š”์‚ฌ๋žŒ ์ถ”๊ฐ€", "decrypted_postbox_decrypting": "Mask ํ•ด๋… ์ค‘", - "decrypted_postbox_decrypting_finding_person_key": "Mask๊ฐ€ ์ƒ๋Œ€๋ฐฉ์˜ ๊ณต๊ฐœ ํ‚ค๋ฅผ ์ฐพ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค...", "decrypted_postbox_decrypting_finding_post_key": "Mask๊ฐ€ ์ด ๊ฒŒ์‹œ๊ธ€์˜ ํ‚ค๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.", "decrypted_postbox_author_mismatch": "{{name}}๋‹˜์ด ์ด ๊ธ€์„ ์ฒ˜์Œ ๊ฒŒ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.", "decrypted_postbox_title": "Mask๋กœ ํ•ด๋…๋˜์—ˆ์Šต๋‹ˆ๋‹ค:", diff --git a/packages/mask/shared-ui/locales/qya-AA.json b/packages/mask/shared-ui/locales/qya-AA.json index 0e46cf8983e5..f403214c7adb 100644 --- a/packages/mask/shared-ui/locales/qya-AA.json +++ b/packages/mask/shared-ui/locales/qya-AA.json @@ -66,7 +66,6 @@ "days": "crwdns4107:0{{days}}crwdne4107:0", "decrypted_postbox_add_recipients": "crwdns4109:0crwdne4109:0", "decrypted_postbox_decrypting": "crwdns4111:0crwdne4111:0", - "decrypted_postbox_decrypting_finding_person_key": "crwdns4115:0crwdne4115:0", "decrypted_postbox_decrypting_finding_post_key": "crwdns4117:0crwdne4117:0", "decrypted_postbox_author_mismatch": "crwdns4119:0{{name}}crwdne4119:0", "decrypted_postbox_title": "crwdns4121:0crwdne4121:0", diff --git a/packages/mask/shared-ui/locales/zh-CN.json b/packages/mask/shared-ui/locales/zh-CN.json index ab752ced3286..fccb8af08f26 100644 --- a/packages/mask/shared-ui/locales/zh-CN.json +++ b/packages/mask/shared-ui/locales/zh-CN.json @@ -66,7 +66,6 @@ "days": "ๆฏ้š” {{days}} ๅคฉ", "decrypted_postbox_add_recipients": "่ฟฝๅŠ ๆ”ถไปถไบบ", "decrypted_postbox_decrypting": "Maskๆญฃๅœจ่งฃๅฏ†ไธญโ€ฆ", - "decrypted_postbox_decrypting_finding_person_key": "Mask ๆญฃๅœจๅฏปๆ‰พไฝœ่€…็š„ๅ…ฌ้’ฅโ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask ๆญฃๅœจ่Žทๅ–่ดดๆ–‡ๅฏ†้’ฅไปฅ่งฃๅฏ†ๆญค่ดดๆ–‡โ€ฆ", "decrypted_postbox_author_mismatch": "ๆœ€ๅˆๅ‘ๅธƒไบŽ {{name}}", "decrypted_postbox_title": "็”ฑMask่งฃๅฏ†๏ผš", diff --git a/packages/mask/shared-ui/locales/zh-TW.json b/packages/mask/shared-ui/locales/zh-TW.json index 475095e6ce32..7f0f63d16e6d 100644 --- a/packages/mask/shared-ui/locales/zh-TW.json +++ b/packages/mask/shared-ui/locales/zh-TW.json @@ -49,7 +49,6 @@ "dashboard_no_collectible_found": "ๆ‰พไธๅˆฐๆ”ถ่—ๅ“", "decrypted_postbox_add_recipients": "ๆ–ฐๅขžๆ”ถไปถไบบ", "decrypted_postbox_decrypting": "Mask ๆญฃๅœจ่งฃๅฏ†โ€ฆ", - "decrypted_postbox_decrypting_finding_person_key": "Mask ๆญฃๅœจๅฐ‹ๆ‰พ้€™ๅ€‹ไฝœ่€…็š„ๅ…ฌ้‘ฐโ€ฆ", "decrypted_postbox_decrypting_finding_post_key": "Mask ๆญฃๅœจๅ–ๅพ—่ฒผๆ–‡้‡‘้‘ฐไปฅ่งฃๅฏ†ๆญค่ฒผๆ–‡โ€ฆ", "decrypted_postbox_author_mismatch": "ๆœ€ๅˆ็”ฑ {{name}} ็™ผไฝˆ", "decrypted_postbox_title": "ๅทฒ็”ฑ Mask ่งฃๅฏ†๏ผš", diff --git a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx index 3bf24cdcbae1..26216a434348 100644 --- a/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx +++ b/packages/mask/src/components/InjectedComponents/DecryptedPost/DecryptPostAwaiting.tsx @@ -1,11 +1,10 @@ import { memo } from 'react' -import { ShadowRootTooltip, useI18N } from '../../../utils' +import { useI18N } from '../../../utils' import { AdditionalContent } from '../AdditionalPostContent' import type { ProfileIdentifier } from '@masknet/shared-base' import { wrapAuthorDifferentMessage } from './authorDifferentMessage' import { DecryptIntermediateProgressKind, DecryptIntermediateProgress } from '@masknet/encryption' import { useTimeout } from 'react-use' -import { Typography } from '@mui/material' export interface DecryptPostAwaitingProps { type?: DecryptIntermediateProgress /** The author in the payload */ @@ -24,16 +23,13 @@ export const DecryptPostAwaiting = memo(function DecryptPostAwaiting(props: Decr text = t('decrypted_postbox_decrypting_finding_post_key') if (isReady()) text = t('decrypted_postbox_decrypting_finding_post_key_long') } - // unused - t('decrypted_postbox_decrypting_finding_person_key') - const header = wrapAuthorDifferentMessage( - author, - postedBy, - - Can't decrypt? - , - ) const shouldShowProgress = type?.event !== DecryptIntermediateProgressKind.TryDecryptByE2E || !isReady() - return + return ( + + ) }) From 4de5c3610ef6e6260f050d1bead51f9dd712e09e Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 6 Jan 2022 15:36:43 +0800 Subject: [PATCH 36/85] chore: move gun to own package --- packages/gun-utils/package.json | 11 ++++ packages/gun-utils/src/index.ts | 1 + .../network/gun => gun-utils/src}/instance.ts | 10 ++-- .../gun/index.ts => gun-utils/src/server.ts} | 0 packages/gun-utils/src/utils.ts | 45 +++++++++++++++++ packages/gun-utils/tsconfig.json | 10 ++++ .../network/gun/encryption/queryPostKey.ts | 17 +++---- packages/mask/background/network/gun/utils.ts | 50 ------------------- .../background/services/crypto/decryption.ts | 5 +- packages/mask/background/tsconfig.json | 7 ++- packages/mask/package.json | 8 +-- packages/mask/shared-ui/locales/en-US.json | 2 - packages/mask/shared-ui/locales/ja-JP.json | 2 - packages/mask/shared-ui/locales/ko-KR.json | 2 - packages/mask/shared-ui/locales/qya-AA.json | 2 - packages/mask/shared-ui/locales/zh-CN.json | 2 - packages/mask/shared-ui/locales/zh-TW.json | 2 - .../InjectedComponents/PostReplacer.tsx | 5 -- .../TypedMessageRenderer.tsx | 1 - .../CryptoServices/appendShareTarget.ts | 2 - .../CryptoServices/encryptTo.ts | 3 +- .../mask/src/plugins/Avatar/Services/gun.ts | 9 ++-- packages/mask/src/settings/settings.ts | 8 --- .../utils/create-post-context.ts | 2 - pnpm-lock.yaml | 33 +++++------- pnpm-workspace.yaml | 1 + 26 files changed, 106 insertions(+), 134 deletions(-) create mode 100644 packages/gun-utils/package.json create mode 100644 packages/gun-utils/src/index.ts rename packages/{mask/background/network/gun => gun-utils/src}/instance.ts (80%) rename packages/{mask/background/network/gun/index.ts => gun-utils/src/server.ts} (100%) create mode 100644 packages/gun-utils/src/utils.ts create mode 100644 packages/gun-utils/tsconfig.json delete mode 100644 packages/mask/background/network/gun/utils.ts diff --git a/packages/gun-utils/package.json b/packages/gun-utils/package.json new file mode 100644 index 000000000000..acfcfb609881 --- /dev/null +++ b/packages/gun-utils/package.json @@ -0,0 +1,11 @@ +{ + "name": "@masknet/gun-utils", + "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "dependencies": { + "event-iterator": "^2.0.0", + "gun": "^0.2020.1234" + } +} diff --git a/packages/gun-utils/src/index.ts b/packages/gun-utils/src/index.ts new file mode 100644 index 000000000000..49e0187f9ea3 --- /dev/null +++ b/packages/gun-utils/src/index.ts @@ -0,0 +1 @@ +export { deleteGunData, getGunData, pushToGunDataArray, setGunData, subscribeGunMapData } from './utils' diff --git a/packages/mask/background/network/gun/instance.ts b/packages/gun-utils/src/instance.ts similarity index 80% rename from packages/mask/background/network/gun/instance.ts rename to packages/gun-utils/src/instance.ts index a341ccd114b2..26abf595dad5 100644 --- a/packages/mask/background/network/gun/instance.ts +++ b/packages/gun-utils/src/instance.ts @@ -4,10 +4,7 @@ import 'gun/lib/radix' import 'gun/lib/radisk' import 'gun/lib/store' import 'gun/lib/rindexed' -import { gunServers } from '.' -import { startEffects } from '../../../utils-pure' - -const { signal } = startEffects(import.meta.webpackHot) +import { gunServers } from './server' export type GunRoot = ReturnType let gun: GunRoot | undefined @@ -29,12 +26,11 @@ function createGun() { if (this.timer) clearTimeout(this.timer) this.timer = setTimeout(abort, 15 * 60 * 1000) }) - signal.addEventListener('abort', abort) - this.addEventListener('message', tick, { signal }) + this.addEventListener('message', tick, {}) } private declare abort: () => void private declare tick: () => void - declare timer: NodeJS.Timeout | undefined + declare timer: NodeJS.Timer | undefined override send(data: any) { this.tick() super.send(data) diff --git a/packages/mask/background/network/gun/index.ts b/packages/gun-utils/src/server.ts similarity index 100% rename from packages/mask/background/network/gun/index.ts rename to packages/gun-utils/src/server.ts diff --git a/packages/gun-utils/src/utils.ts b/packages/gun-utils/src/utils.ts new file mode 100644 index 000000000000..3d036b9626ea --- /dev/null +++ b/packages/gun-utils/src/utils.ts @@ -0,0 +1,45 @@ +import { EventIterator } from 'event-iterator' +import { getGunInstance, GunRoot } from './instance' + +function getGunNodeFromPath(path: string[]) { + const resultNode = path.reduce((gun, path) => gun.get(path as never), getGunInstance()) + return resultNode +} +/** + * A sugar for gun.get(path[0]).get(path[1])....get(path[n]).once() + */ +export function getGunData(...path: string[]) { + return new Promise<{ [x: string]: unknown } | undefined>((resolve) => { + getGunNodeFromPath(path).once(resolve) + }) +} + +export function setGunData(path: string[], data: any) { + getGunNodeFromPath(path).put(data) +} +export function deleteGunData(path: string[]) { + getGunNodeFromPath(path).put(null!) +} + +export function pushToGunDataArray(path: string[], value: object) { + getGunNodeFromPath(path).set(value) +} + +export async function* subscribeGunMapData(path: string[], isT: (x: unknown) => x is T, abortSignal: AbortSignal) { + yield* new EventIterator((queue) => { + // gun.off() will remove ALL listener on it + let listenerClosed = false + + abortSignal.addEventListener('abort', () => { + queue.stop() + listenerClosed = true + }) + + const resultNode = getGunNodeFromPath(path) + + resultNode.map().on((data) => { + if (listenerClosed) return + if (isT(data)) queue.push(data) + }) + }) +} diff --git a/packages/gun-utils/tsconfig.json b/packages/gun-utils/tsconfig.json new file mode 100644 index 000000000000..8bcad252c078 --- /dev/null +++ b/packages/gun-utils/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src/", + "outDir": "./dist/", + "tsBuildInfoFile": "./dist/.tsbuildinfo" + }, + "include": ["./src"], + "references": [] +} diff --git a/packages/mask/background/network/gun/encryption/queryPostKey.ts b/packages/mask/background/network/gun/encryption/queryPostKey.ts index ae37445bbba5..6f59c7f94fed 100644 --- a/packages/mask/background/network/gun/encryption/queryPostKey.ts +++ b/packages/mask/background/network/gun/encryption/queryPostKey.ts @@ -1,21 +1,18 @@ import { decodeArrayBuffer, encodeArrayBuffer } from '@dimensiondev/kit' import type { DecryptStaticECDH_PostKey } from '@masknet/encryption' import type { EC_Public_CryptoKey, EC_Public_JsonWebKey } from '@masknet/shared-base' -import type { default as gun } from 'gun' import { CryptoKeyToJsonWebKey } from '../../../../utils-pure' -import { getGunData, pushToGunDataArray, subscribeGunMapData } from '../utils' +import { getGunData, pushToGunDataArray, subscribeGunMapData } from '@masknet/gun-utils' import { EventIterator } from 'event-iterator' import { noop } from 'lodash-unified' -type Gun = ReturnType // !!! Change how this file access Gun will break the compatibility of v40 payload decryption. export async function GUN_queryPostKey_version40( - gun: Gun, iv: Uint8Array, whoAmI: string, ): Promise { // PATH ON GUN: maskbook > posts > iv > userID - const result = await getGunData(gun, 'maskbook', 'posts', encodeArrayBuffer(iv), whoAmI) + const result = await getGunData('maskbook', 'posts', encodeArrayBuffer(iv), whoAmI) if (!isValidData(result)) return null return { encryptedPostKey: new Uint8Array(decodeArrayBuffer(result.encryptedKey)), @@ -34,7 +31,6 @@ export async function GUN_queryPostKey_version40( namespace Version38Or39 { export async function* GUN_queryPostKey_version39Or38( - gun: Gun, version: -38 | -39, iv: Uint8Array, minePublicKey: EC_Public_CryptoKey, @@ -47,15 +43,15 @@ namespace Version38Or39 { /* cspell:disable-next-line */ // ? In this step we get something like ["jzarhbyjtexiE7aB1DvQ", "jzarhuse6xlTAtblKRx9"] console.log(`[@masknet/encryption] Reading key partition [${postHash}][${keyHash}]`) - const internalNodeNames = Object.keys((await getGunData(gun, postHash, keyHash)) || {}).filter((x) => x !== '_') + const internalNodeNames = Object.keys((await getGunData(postHash, keyHash)) || {}).filter((x) => x !== '_') // ? In this step we get all keys in this category (gun2[postHash][keyHash]) - const resultPromise = internalNodeNames.map((key) => getGunData(gun, key)) + const resultPromise = internalNodeNames.map((key) => getGunData(key)) const iter = new EventIterator((queue) => { for (const result of resultPromise) result.then(emit, noop) async function main() { - const iter = subscribeGunMapData(gun, [postHash], isValidData, abortSignal) + const iter = subscribeGunMapData([postHash], isValidData, abortSignal) for await (const data of iter) emit(data) } @@ -85,7 +81,6 @@ namespace Version38Or39 { * @param receiversKeys Keys needs to publish */ export async function publishPostAESKey_version39Or38( - gun: Gun, version: -39 | -38, postIV: Uint8Array, networkHint: string, @@ -96,7 +91,7 @@ namespace Version38Or39 { receiversKeys.forEach(async ({ aesKey, receiverKey }) => { const keyHash = await (version === -38 ? hashKey38 : hashKey39)(receiverKey) console.log(`gun[${postHash}][${keyHash}].push(`, aesKey, ')') - pushToGunDataArray(gun, [postHash, keyHash], aesKey) + pushToGunDataArray([postHash, keyHash], aesKey) }) } diff --git a/packages/mask/background/network/gun/utils.ts b/packages/mask/background/network/gun/utils.ts deleted file mode 100644 index 053b0735ed02..000000000000 --- a/packages/mask/background/network/gun/utils.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { EventIterator } from 'event-iterator' -import type { GunRoot } from './instance' - -function getGunNodeFromPath(gun: GunRoot, path: string[]) { - const resultNode = path.reduce((gun, path) => gun.get(path as never), gun) - return resultNode -} -/** - * A sugar for gun.get(path[0]).get(path[1])....get(path[n]).once() - */ -export function getGunData(gun: GunRoot, ...path: string[]) { - return new Promise<{ [x: string]: unknown } | undefined>((resolve) => { - getGunNodeFromPath(gun, path).once(resolve) - }) -} - -export function setGunData(gun: GunRoot, path: string[], data: any) { - getGunNodeFromPath(gun, path).put(data) -} -export function deleteGunData(gun: GunRoot, path: string[]) { - getGunNodeFromPath(gun, path).put(null!) -} - -export function pushToGunDataArray(gun: GunRoot, path: string[], value: object) { - getGunNodeFromPath(gun, path).set(value) -} - -export async function* subscribeGunMapData( - gun: GunRoot, - path: string[], - isT: (x: unknown) => x is T, - abortSignal: AbortSignal, -) { - yield* new EventIterator((queue) => { - // gun.off() will remove ALL listener on it - let listenerClosed = false - - abortSignal.addEventListener('abort', () => { - queue.stop() - listenerClosed = true - }) - - const resultNode = getGunNodeFromPath(gun, path) - - resultNode.map().on((data) => { - if (listenerClosed) return - if (isT(data)) queue.push(data) - }) - }) -} diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index 9813e0c0c23f..fc5b0990b4d9 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -31,7 +31,6 @@ import { import { queryPostDB } from '../../database/post' import { savePostKeyToDB } from '../../database/post/helper' import { GUN_queryPostKey_version39Or38, GUN_queryPostKey_version40 } from '../../network/gun/encryption/queryPostKey' -import { getGunInstance } from '../../network/gun/instance' export type DecryptionInfo = { type: DecryptProgressKind.Info @@ -154,7 +153,6 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt const author = await queryPublicKey(context.currentProfile, true) if (!author) throw new DecryptError(ErrorReasons.CurrentProfileDoesNotConnectedToPersona, undefined) yield* GUN_queryPostKey_version39Or38( - getGunInstance(), -38, iv, author, @@ -166,7 +164,6 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt const author = await queryPublicKey(context.currentProfile, true) if (!author) throw new DecryptError(ErrorReasons.CurrentProfileDoesNotConnectedToPersona, undefined) yield* GUN_queryPostKey_version39Or38( - getGunInstance(), -39, iv, author, @@ -176,7 +173,7 @@ export async function* decryption(payload: string | Uint8Array, context: Decrypt }, async queryPostKey_version40(iv) { if (!currentProfile) return null - return GUN_queryPostKey_version40(getGunInstance(), iv, currentProfile.userId) + return GUN_queryPostKey_version40(iv, currentProfile.userId) }, }, ) diff --git a/packages/mask/background/tsconfig.json b/packages/mask/background/tsconfig.json index 140c7b259376..b97bc2510f7c 100644 --- a/packages/mask/background/tsconfig.json +++ b/packages/mask/background/tsconfig.json @@ -10,5 +10,10 @@ "lib": ["ES2021", "WebWorker"] }, "include": ["./"], - "references": [{ "path": "../shared" }, { "path": "../utils-pure" }, { "path": "../../encryption" }] + "references": [ + { "path": "../shared" }, + { "path": "../utils-pure" }, + { "path": "../../encryption" }, + { "path": "../../gun-utils" } + ] } diff --git a/packages/mask/package.json b/packages/mask/package.json index abafe44a6898..14cec084f721 100644 --- a/packages/mask/package.json +++ b/packages/mask/package.json @@ -17,15 +17,16 @@ "@masknet/dashboard": "workspace:*", "@masknet/encryption": "workspace:^0.0.0", "@masknet/external-plugin-previewer": "workspace:*", + "@masknet/gun-utils": "workspace:^undefined", "@masknet/icons": "workspace:*", "@masknet/injected-script": "workspace:*", + "@masknet/plugin-dao": "workspace:*", + "@masknet/plugin-debugger": "workspace:*", "@masknet/plugin-example": "workspace:*", "@masknet/plugin-file-service": "workspace:*", "@masknet/plugin-flow": "workspace:*", - "@masknet/plugin-rss3": "workspace:*", - "@masknet/plugin-dao": "workspace:*", - "@masknet/plugin-debugger": "workspace:*", "@masknet/plugin-infra": "workspace:*", + "@masknet/plugin-rss3": "workspace:*", "@masknet/plugin-wallet": "workspace:*", "@masknet/public-api": "workspace:*", "@masknet/sdk": "workspace:*", @@ -77,7 +78,6 @@ "fuse.js": "^6.5.3", "graphql": "^15.5.1", "graphql-request": "^3.4.0", - "gun": "^0.2020.1234", "history": "^5.2.0", "https-browserify": "^1.0.0", "i18next-browser-languagedetector": "^6.1.2", diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 7ecb10838564..e6d9bf44595f 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -139,8 +139,6 @@ "settings_language": "Language", "settings_language_secondary": "Select the language you would like to use.", "settings_choose_eth_network": "Choose Ethereum Network", - "settings_post_replacement": "Substitute Post", - "settings_post_replacement_desc": "All posts on the web-pages will be rendered by Mask, including those not created by Mask.", "settings_launch_page": "Launch Page", "settings_launch_page_secondary": "Select the default page to open when launch the app", "speed_up": "Speed up", diff --git a/packages/mask/shared-ui/locales/ja-JP.json b/packages/mask/shared-ui/locales/ja-JP.json index d048d6afd78f..a15e98ed9f42 100644 --- a/packages/mask/shared-ui/locales/ja-JP.json +++ b/packages/mask/shared-ui/locales/ja-JP.json @@ -71,8 +71,6 @@ "settings_language": "่จ€่ชžใ‚’่จญๅฎš", "settings_language_secondary": "ใ”ๅˆฉ็”จใซใชใ‚ŠใŸใ„่จ€่ชžใ‚’ใŠ้ธใณใใ ใ•ใ„ใ€‚", "settings_choose_eth_network": "Ethereum ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„", - "settings_post_replacement": "ไปฃๆ›ฟใƒใ‚นใƒˆ", - "settings_post_replacement_desc": "Web ใƒšใƒผใ‚ธไธŠใฎใ™ในใฆใฎๆŠ•็จฟใฏใ€Mask ใงไฝœๆˆใ•ใ‚Œใฆใ„ใชใ„ๆŠ•็จฟใ‚’ๅซใ‚ใ€Mask ใซใ‚ˆใฃใฆใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ•ใ‚Œใพใ™ใ€‚", "settings_launch_page": "ๅˆๆœŸ็”ป้ข", "settings_launch_page_secondary": "ใ‚ขใƒ—ใƒชใ‚’้–‹ใ„ใŸใจใใซๆœ€ๅˆใซ้–‹ใ‹ใ‚Œใ‚‹็”ป้ขใ‚’้ธใ‚“ใงใใ ใ•ใ„ใ€‚", "skip": "ใ‚นใ‚ญใƒƒใƒ—", diff --git a/packages/mask/shared-ui/locales/ko-KR.json b/packages/mask/shared-ui/locales/ko-KR.json index 51441bc909fd..67ca3890c626 100644 --- a/packages/mask/shared-ui/locales/ko-KR.json +++ b/packages/mask/shared-ui/locales/ko-KR.json @@ -136,8 +136,6 @@ "settings_language": "์–ธ์–ด", "settings_language_secondary": "์ด์šฉํ•˜๊ณ  ์‹ถ์€ ์–ธ์–ด๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", "settings_choose_eth_network": "์ด๋”๋ฆฌ์›€ ๋„คํŠธ์›Œํฌ๋ฅผ ์„ ํƒํ•˜๊ธฐ", - "settings_post_replacement": "๋Œ€์ฒด ๊ฒŒ์‹œ๋ฌผ", - "settings_post_replacement_desc": "Mask๊ฐ€ ์ด ์›นํŽ˜์ด์ง€์— ๋ชจ๋“  ๊ฒŒ์‹œ๋ฌผ์„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. Mask๋กœ ๋งŒ๋“ค์ง€ ์•Š๋Š” ๊ฒƒ๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.", "settings_launch_page": "์‹œ์ž‘ ํŽ˜์ด์ง€", "settings_launch_page_secondary": "์•ฑ์„ ์—ด์„ ๋•Œ ๋””ํดํŠธ ํŽ˜์ด์ง€๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", "speed_up": "๊ฐ€์†ํ•˜๊ธฐ", diff --git a/packages/mask/shared-ui/locales/qya-AA.json b/packages/mask/shared-ui/locales/qya-AA.json index f403214c7adb..07169df90af4 100644 --- a/packages/mask/shared-ui/locales/qya-AA.json +++ b/packages/mask/shared-ui/locales/qya-AA.json @@ -138,8 +138,6 @@ "settings_language": "crwdns4441:0crwdne4441:0", "settings_language_secondary": "crwdns4443:0crwdne4443:0", "settings_choose_eth_network": "crwdns4445:0crwdne4445:0", - "settings_post_replacement": "crwdns4447:0crwdne4447:0", - "settings_post_replacement_desc": "crwdns4449:0crwdne4449:0", "settings_launch_page": "crwdns4457:0crwdne4457:0", "settings_launch_page_secondary": "crwdns4459:0crwdne4459:0", "speed_up": "crwdns9393:0crwdne9393:0", diff --git a/packages/mask/shared-ui/locales/zh-CN.json b/packages/mask/shared-ui/locales/zh-CN.json index fccb8af08f26..d482543b5ea1 100644 --- a/packages/mask/shared-ui/locales/zh-CN.json +++ b/packages/mask/shared-ui/locales/zh-CN.json @@ -135,8 +135,6 @@ "settings_language": "่ฏญ่จ€", "settings_language_secondary": "้€‰ๆ‹ฉๆ‚จ่ฆไฝฟ็”จ็š„่ฏญ่จ€.", "settings_choose_eth_network": "้€‰ๆ‹ฉไปฅๅคชๅŠ็ฝ‘็ปœ", - "settings_post_replacement": "ๆ›ฟๆข่ดดๆ–‡", - "settings_post_replacement_desc": "ๆ‰€ๆœ‰็ฝ‘้กตไธŠ็š„่ดดๆ–‡ๅฐ†็”ฑMaskๆไพ›๏ผŒๅŒ…ๆ‹ฌไธๆ˜ฏ็”ฑMaskๅˆ›ๅปบ็š„่ดดๆ–‡ใ€‚", "settings_launch_page": "ๅฏๅŠจ้กต้ข", "settings_launch_page_secondary": "้€‰ๆ‹ฉๅฏๅŠจๅบ”็”จ็จ‹ๅบๆ—ถ่ฆๆ‰“ๅผ€็š„้ป˜่ฎค้กต้ข", "speed_up": "ๅŠ ้€Ÿ", diff --git a/packages/mask/shared-ui/locales/zh-TW.json b/packages/mask/shared-ui/locales/zh-TW.json index 7f0f63d16e6d..e0891d9c3ff7 100644 --- a/packages/mask/shared-ui/locales/zh-TW.json +++ b/packages/mask/shared-ui/locales/zh-TW.json @@ -103,8 +103,6 @@ "settings_language": "่ชž่จ€", "settings_language_secondary": "้ธๆ“‡ไฝ ๆƒณไฝฟ็”จ็š„่ชž่จ€ใ€‚", "settings_choose_eth_network": "้ธๆ“‡ไน™ๅคชๅŠ็ถฒ่ทฏ", - "settings_post_replacement": "ๅ–ไปฃ่ฒผๆ–‡", - "settings_post_replacement_desc": "็ถฒ้ ไธŠ็š„ๆ‰€ๆœ‰่ฒผๆ–‡้ƒฝๅฐ‡้€š้Ž Mask ๅ‘ˆ็พ๏ผŒๅŒ…ๆ‹ฌ้‚ฃไบ›ไธๆ˜ฏ็”ฑ Mask ๅปบ็ซ‹็š„่ฒผๆ–‡ใ€‚", "settings_launch_page": "ๅ•Ÿๅ‹•้ ้ข", "settings_launch_page_secondary": "้ธๆ“‡ๅ•Ÿๅ‹•็จ‹ๅผๆ™‚่ฆๆ‰“้–‹็š„้ ่จญ้ ้ข", "speed_up": "ๅŠ ้€Ÿ", diff --git a/packages/mask/src/components/InjectedComponents/PostReplacer.tsx b/packages/mask/src/components/InjectedComponents/PostReplacer.tsx index 6269f7c54df0..4f1f6e7416fc 100644 --- a/packages/mask/src/components/InjectedComponents/PostReplacer.tsx +++ b/packages/mask/src/components/InjectedComponents/PostReplacer.tsx @@ -1,5 +1,4 @@ import { useActivatedPluginsSNSAdaptor } from '@masknet/plugin-infra' -import { useValueRef } from '@masknet/shared' import { isTypedMessagePromise, isTypedMessageTuple, @@ -9,7 +8,6 @@ import { import { makeStyles } from '@masknet/theme' import { useEffect, useMemo } from 'react' import { Result } from 'ts-results' -import { allPostReplacementSettings } from '../../settings/settings' import { usePostInfoDetails } from '../DataSource/usePostInfo' import { DefaultTypedMessageRenderer } from './TypedMessageRenderer' const useStyles = makeStyles()({ @@ -27,7 +25,6 @@ export function PostReplacer(props: PostReplacerProps) { const { classes } = useStyles() const postMessage = usePostInfoDetails.rawMessage() const hasMaskPayload = usePostInfoDetails.containsMaskPayload() - const allPostReplacement = useValueRef(allPostReplacementSettings) const plugins = useActivatedPluginsSNSAdaptor(false) const processedPostMessage = useMemo( @@ -44,8 +41,6 @@ export function PostReplacer(props: PostReplacerProps) { [plugins.map((x) => x.ID).join(), postMessage], ) const shouldReplacePost = - // replace all posts - allPostReplacement || // replace posts which enhanced by plugins processedPostMessage.items.some((x) => !isWellKnownTypedMessages(x)) || // replace posts which encrypted by Mask diff --git a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx index f659abf69525..790efbfb2b71 100644 --- a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx +++ b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx @@ -73,7 +73,6 @@ export const DefaultTypedMessageAnchorRenderer = memo(function DefaultTypedMessa {/* // TODO: */} - // TODO: shrink link size {content} diff --git a/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts b/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts index 5934093db48a..f18e5032fad2 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/appendShareTarget.ts @@ -12,7 +12,6 @@ import { updatePostDB, RecipientDetail, RecipientReason } from '../../../../back import { getNetworkWorkerUninitialized } from '../../../social-network/worker' import { queryPrivateKey, queryLocalKey } from '../../../database' import { publishPostAESKey_version39Or38 } from '../../../../background/network/gun/encryption/queryPostKey' -import { getGunInstance } from '../../../../background/network/gun/instance' import { decodeArrayBuffer } from '@dimensiondev/kit' export async function appendShareTarget( version: -40 | -39 | -38, @@ -44,7 +43,6 @@ export async function appendShareTarget( const gunHint = getNetworkWorkerUninitialized(whoAmI)?.gunNetworkHint gunHint && publishPostAESKey_version39Or38( - getGunInstance(), version, new Uint8Array(decodeArrayBuffer(iv)), gunHint, diff --git a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts index cf24b49f4abe..34fdba1fe596 100644 --- a/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts +++ b/packages/mask/src/extension/background-script/CryptoServices/encryptTo.ts @@ -9,7 +9,6 @@ import { queryPersonaByProfileDB } from '../../../../background/database/persona import { i18n } from '../../../../shared-ui/locales_legacy' import { isTypedMessageText, TypedMessage, TypedMessageText } from '@masknet/shared-base' import { publishPostAESKey_version39Or38 } from '../../../../background/network/gun/encryption/queryPostKey' -import { getGunInstance } from '../../../../background/network/gun/instance' import { PayloadWellFormed, PublicKeyAlgorithmEnum, @@ -145,7 +144,7 @@ export async function publishPostAESKey(iv: string) { if (!info) throw new Error(i18n.t('service_publish_post_aes_key_failed')) if (!info[1].length) return // Use the latest payload version here since we do not accept new post for older version. - return publishPostAESKey_version39Or38(getGunInstance(), -38, new Uint8Array(decodeArrayBuffer(iv)), ...info) + return publishPostAESKey_version39Or38(-38, new Uint8Array(decodeArrayBuffer(iv)), ...info) } const SUMMARY_MAX_LENGTH = 40 diff --git a/packages/mask/src/plugins/Avatar/Services/gun.ts b/packages/mask/src/plugins/Avatar/Services/gun.ts index 763cd4edeaa7..d668e30c7958 100644 --- a/packages/mask/src/plugins/Avatar/Services/gun.ts +++ b/packages/mask/src/plugins/Avatar/Services/gun.ts @@ -1,15 +1,14 @@ import { delay } from '@masknet/shared-base' -import { getGunInstance } from '../../../../background/network/gun/instance' -import { getGunData, setGunData } from '../../../../background/network/gun/utils' +import { getGunData, setGunData } from '@masknet/gun-utils' import { NFT_AVATAR_GUN_ROOT_NODE } from '../constants' // After reinstalling the system, it cannot be retrieved for the first time, so it needs to be taken twice export async function getUserAddress(userId: string) { - let result = await getGunData(getGunInstance(), NFT_AVATAR_GUN_ROOT_NODE, userId) + let result = await getGunData(NFT_AVATAR_GUN_ROOT_NODE, userId) if (!result) { await delay(500) - result = await getGunData(getGunInstance(), NFT_AVATAR_GUN_ROOT_NODE, userId) + result = await getGunData(NFT_AVATAR_GUN_ROOT_NODE, userId) } return result as any as string @@ -17,7 +16,7 @@ export async function getUserAddress(userId: string) { export async function setUserAddress(userId: string, address: string) { // save userId - setGunData(getGunInstance(), [NFT_AVATAR_GUN_ROOT_NODE, userId], address) + setGunData([NFT_AVATAR_GUN_ROOT_NODE, userId], address) } export async function getUserAddresses() { diff --git a/packages/mask/src/settings/settings.ts b/packages/mask/src/settings/settings.ts index b26a0119b832..61c3077731ad 100644 --- a/packages/mask/src/settings/settings.ts +++ b/packages/mask/src/settings/settings.ts @@ -14,14 +14,6 @@ export const debugModeSetting = createGlobalSettings('debugMode', false secondary: () => i18n.t('settings_enable_debug_desc'), }) -/** - * Whether if create substitute post for all posts - */ -export const allPostReplacementSettings = createGlobalSettings('post replacement all', false, { - primary: () => i18n.t('settings_post_replacement'), - secondary: () => i18n.t('settings_post_replacement_desc'), -}) - //#region appearance export const appearanceSettings = createGlobalSettings('appearance', Appearance.default, { primary: () => i18n.t('settings_appearance'), diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index 133a85db7017..3425f4662f8e 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -210,7 +210,6 @@ function decryptionContext( let abort = new AbortController() async function decryptByLinks() { const links = [...mentionedLinks.getCurrentValue()].sort() - console.log('start by link', ...links) if (links.length === 0) return const key = await hash(links.join(';')) await decryption( @@ -287,7 +286,6 @@ function decryptionContext( providerMap.delete(id) map.delete(id) }) - decryption.return() }) for await (const [decryptionID, progress] of decryption) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65b650feef98..fba93bc6eca1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -251,6 +251,14 @@ importers: '@masknet/theme': link:../theme ef.js: 0.14.2 + packages/gun-utils: + specifiers: + event-iterator: ^2.0.0 + gun: ^0.2020.1234 + dependencies: + event-iterator: 2.0.0 + gun: 0.2020.1235 + packages/icons: specifiers: '@emotion/server': ^11.4.0 @@ -286,6 +294,7 @@ importers: '@masknet/dashboard': workspace:* '@masknet/encryption': workspace:^0.0.0 '@masknet/external-plugin-previewer': workspace:* + '@masknet/gun-utils': workspace:^undefined '@masknet/icons': workspace:* '@masknet/injected-script': workspace:* '@masknet/plugin-dao': workspace:* @@ -353,7 +362,6 @@ importers: fuse.js: ^6.5.3 graphql: ^15.5.1 graphql-request: ^3.4.0 - gun: ^0.2020.1234 history: ^5.2.0 html-webpack-plugin: ^5.5.0 https-browserify: ^1.0.0 @@ -422,6 +430,7 @@ importers: '@masknet/dashboard': link:../dashboard '@masknet/encryption': link:../encryption '@masknet/external-plugin-previewer': link:../external-plugin-previewer + '@masknet/gun-utils': link:../gun-utils '@masknet/icons': link:../icons '@masknet/injected-script': link:../injected-script '@masknet/plugin-dao': link:../plugins/DAO @@ -482,7 +491,6 @@ importers: fuse.js: 6.5.3 graphql: 15.5.3 graphql-request: 3.5.0_graphql@15.5.3 - gun: 0.2020.1234 history: 5.2.0 https-browserify: 1.0.0 i18next-browser-languagedetector: 6.1.2 @@ -15177,15 +15185,14 @@ packages: glogg: 1.0.2 dev: true - /gun/0.2020.1234: - resolution: {integrity: sha512-RqlGDDze1U7gdRZCHx8VMBi0dzyJL4C6jQHwOuFjH9aldGcCQPN0HyVaKVn6jNHgS1AAbTBod4KCfMRXda+eZg==} + /gun/0.2020.1235: + resolution: {integrity: sha512-aba6s0X/MCCOByt0T3VrMzMMUEblfN2aLV2bAmIQWjkB7SKnm07zAZWigljwI69Rb5sOw7C1G116UyvCM2p9kA==} engines: {node: '>=0.8.4'} dependencies: - ws: 7.5.5 + ws: 7.5.6 optionalDependencies: '@peculiar/webcrypto': 1.2.3 emailjs: 2.2.0 - text-encoding: 0.7.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -24959,19 +24966,6 @@ packages: optional: true dev: false - /ws/7.5.5: - resolution: {integrity: sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - /ws/7.5.6: resolution: {integrity: sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==} engines: {node: '>=8.3.0'} @@ -24983,7 +24977,6 @@ packages: optional: true utf-8-validate: optional: true - dev: true /ws/8.3.0: resolution: {integrity: sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dab9fff242bf..2f54abd21e66 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,6 +9,7 @@ packages: - 'packages/dashboard' - 'packages/mask' - 'packages/injected-script' + - 'packages/gun-utils' - 'packages/mask-sdk' - 'packages/public-api' - 'packages/shared-base' From ac66f10290a5032a78b7645f0e993a573fc0ed23 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 11 Jan 2022 14:37:07 +0800 Subject: [PATCH 37/85] feat: fix post replacer --- packages/mask/.webpack/config.ts | 1 + .../InjectedComponents/PayloadReplacer.tsx | 61 ++++++++++++++++--- .../InjectedComponents/PostReplacer.tsx | 30 ++++----- .../TypedMessageRenderer.tsx | 25 ++++---- .../mask/src/plugins/Trader/base-deferred.ts | 12 ++-- packages/plugin-infra/src/types.ts | 2 +- packages/shared-base/src/TypedMessage/core.ts | 5 +- .../shared-base/src/TypedMessage/index.ts | 1 + .../src/TypedMessage/visitor/index.ts | 28 +++++++++ 9 files changed, 118 insertions(+), 47 deletions(-) create mode 100644 packages/shared-base/src/TypedMessage/visitor/index.ts diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts index fe5dc496fde0..58a46788b56d 100644 --- a/packages/mask/.webpack/config.ts +++ b/packages/mask/.webpack/config.ts @@ -66,6 +66,7 @@ export function createConfiguration(rawFlags: BuildFlags): Configuration { // we can speed up the compile because there is no need to wait tsc build them to the dist folder. '@masknet/dashboard$': require.resolve('../../dashboard/src/entry.tsx'), '@masknet/injected-script': join(__dirname, '../../injected-script/sdk'), + '@masknet/gun-utils': join(__dirname, '../../gun-utils/src/'), '@masknet/shared': join(__dirname, '../../shared/src/'), '@masknet/shared-base': join(__dirname, '../../shared-base/src/'), '@masknet/theme': join(__dirname, '../../theme/src/'), diff --git a/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx b/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx index a963f5e8b300..8f52020acf49 100644 --- a/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx +++ b/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx @@ -1,6 +1,18 @@ import { Typography, Link } from '@mui/material' import { makeStyles } from '@masknet/theme' import { useI18N } from '../../utils' +import { + isTypedMessageAnchor, + isTypedMessageText, + makeTypedMessageText, + makeTypedMessageTuple, + TypedMessage, + visitEachTypedMessageChild, +} from '@masknet/shared-base' +import { memo } from 'react' +import type { TypedMessageRendererProps } from './TypedMessageRenderer' +import { registerTypedMessageRenderer } from '../../protocols/typed-message' + const useStyle = makeStyles()((theme) => ({ text: { fontSize: 'inherit', @@ -9,18 +21,53 @@ const useStyle = makeStyles()((theme) => ({ }, })) -export interface PayloadReplacerProps { - payload: string +const TypedMessageExtension_MaskPayloadReplacerType = 'mask-x.payload-replacer' +interface TypedMessageExtension_MaskPayloadReplacer extends TypedMessage { + link?: string + type: typeof TypedMessageExtension_MaskPayloadReplacerType } - -export function PayloadReplacer({ payload }: PayloadReplacerProps) { +// match the link version payload +const shouldReplace = /^https?:\/\/mask(\.io|book\.com)\/\?PostData_v/i +// match the text version payload +const shouldReplace2 = /(๐ŸŽผ[\d\/\|\w=]+:\|\|)/gi +const PayloadReplacer = memo(({ link }: { link?: string }) => { const { t } = useI18N() const { classes } = useStyle() return ( - - {t('post_substitute_label')} - + {t('post_substitute_label')} ) +}) +export function PayloadReplacerTransformer(message: TypedMessage): TypedMessage { + if (isTypedMessageAnchor(message)) { + if (shouldReplace.test(message.content)) { + const alt: TypedMessageExtension_MaskPayloadReplacer = { + type: TypedMessageExtension_MaskPayloadReplacerType, + link: message.href, + } + return alt + } + } else if (isTypedMessageText(message)) { + if (!message.content.includes('๐ŸŽผ')) return message + const split = message.content.split(shouldReplace2) + if (split.length === 1) return message + const alt: TypedMessageExtension_MaskPayloadReplacer = { + type: TypedMessageExtension_MaskPayloadReplacerType, + } + return makeTypedMessageTuple(split.map((x) => (shouldReplace2.test(x) ? alt : makeTypedMessageText(x)))) + } + + return visitEachTypedMessageChild(message, PayloadReplacerTransformer) } + +const MaskPayloadReplacer = memo(function MaskPayloadReplacer( + props: TypedMessageRendererProps, +) { + return +}) +registerTypedMessageRenderer(TypedMessageExtension_MaskPayloadReplacerType, { + component: MaskPayloadReplacer, + id: TypedMessageExtension_MaskPayloadReplacerType, + priority: 0, +}) diff --git a/packages/mask/src/components/InjectedComponents/PostReplacer.tsx b/packages/mask/src/components/InjectedComponents/PostReplacer.tsx index 4f1f6e7416fc..99f52d5b67bd 100644 --- a/packages/mask/src/components/InjectedComponents/PostReplacer.tsx +++ b/packages/mask/src/components/InjectedComponents/PostReplacer.tsx @@ -1,14 +1,10 @@ import { useActivatedPluginsSNSAdaptor } from '@masknet/plugin-infra' -import { - isTypedMessagePromise, - isTypedMessageTuple, - isWellKnownTypedMessages, - makeTypedMessageTuple, -} from '@masknet/shared-base' +import { isTypedMessageTuple, isWellKnownTypedMessages, TypedMessage } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' import { useEffect, useMemo } from 'react' import { Result } from 'ts-results' import { usePostInfoDetails } from '../DataSource/usePostInfo' +import { PayloadReplacerTransformer } from './PayloadReplacer' import { DefaultTypedMessageRenderer } from './TypedMessageRenderer' const useStyles = makeStyles()({ root: { @@ -30,19 +26,19 @@ export function PostReplacer(props: PostReplacerProps) { const processedPostMessage = useMemo( () => plugins.reduce((x, plugin) => { - const result = Result.wrap(() => plugin.typedMessageTransformer?.(x) ?? x).unwrapOr(x) - if (isTypedMessageTuple(result)) return result - console.warn( - '[TypedMessage] typedMessageTransformer that return a non TypedMessageTuple is not supported yet. This transform is ignored', - result, - ) - return x - }, postMessage), + try { + return plugin.typedMessageTransformer?.(x) ?? x + } catch { + return x + } + }, PayloadReplacerTransformer(postMessage)), [plugins.map((x) => x.ID).join(), postMessage], ) const shouldReplacePost = // replace posts which enhanced by plugins - processedPostMessage.items.some((x) => !isWellKnownTypedMessages(x)) || + (isTypedMessageTuple(processedPostMessage) + ? processedPostMessage.items.some((x) => !isWellKnownTypedMessages(x)) + : true) || // replace posts which encrypted by Mask hasMaskPayload @@ -54,9 +50,7 @@ export function PostReplacer(props: PostReplacerProps) { return shouldReplacePost ? ( - !isTypedMessagePromise(x)))} - /> + ) : null } diff --git a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx index 790efbfb2b71..0919c67cdd19 100644 --- a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx +++ b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx @@ -17,7 +17,6 @@ import { import { Image } from '../shared/Image' import { useAsync } from 'react-use' import { getRendererOfTypedMessage, registerTypedMessageRenderer } from '../../protocols/typed-message' -import { PayloadReplacer } from './PayloadReplacer' import { useI18N } from '../../utils' export interface MetadataRendererProps { @@ -45,15 +44,13 @@ export const DefaultTypedMessageRenderer = memo(function DefaultTypedMessageRend return }) -export const DefaultTypedMessageTextRenderer = memo(function DefaultTypedMessageTextRenderer( +const DefaultTypedMessageTextRenderer = memo(function DefaultTypedMessageTextRenderer( props: TypedMessageRendererProps, ) { const { content } = props.message return renderWithMetadata( props, - {/* // TODO: */} - , ) @@ -64,15 +61,13 @@ registerTypedMessageRenderer('text', { priority: 0, }) -export const DefaultTypedMessageAnchorRenderer = memo(function DefaultTypedMessageAnchorRenderer( +const DefaultTypedMessageAnchorRenderer = memo(function DefaultTypedMessageAnchorRenderer( props: TypedMessageRendererProps, ) { const { content, href } = props.message return renderWithMetadata( props, - {/* // TODO: */} - {content} @@ -85,7 +80,7 @@ registerTypedMessageRenderer('x-anchor', { priority: 0, }) -export const DefaultTypedMessageImageRenderer = memo(function DefaultTypedMessageImageRenderer( +const DefaultTypedMessageImageRenderer = memo(function DefaultTypedMessageImageRenderer( props: TypedMessageRendererProps, ) { const { image, width, height } = props.message @@ -102,7 +97,7 @@ registerTypedMessageRenderer('image', { priority: 0, }) -export const DefaultTypedMessageTupleRenderer = memo(function DefaultTypedMessageTupleRenderer( +const DefaultTypedMessageTupleRenderer = memo(function DefaultTypedMessageTupleRenderer( props: TypedMessageRendererProps, ) { const { t } = useI18N() @@ -127,7 +122,7 @@ registerTypedMessageRenderer('tuple', { priority: 0, }) -export const DefaultTypedMessageEmptyRenderer = memo(function DefaultTypedMessageEmptyRenderer( +const DefaultTypedMessageEmptyRenderer = memo(function DefaultTypedMessageEmptyRenderer( props: TypedMessageRendererProps, ) { return renderWithMetadata(props, null) @@ -138,7 +133,7 @@ registerTypedMessageRenderer('empty', { priority: 0, }) -export const DefaultTypedMessageUnknownRenderer = memo(function DefaultTypedMessageUnknownRenderer( +const DefaultTypedMessageUnknownRenderer = memo(function DefaultTypedMessageUnknownRenderer( props: TypedMessageRendererProps, ) { const { t } = useI18N() @@ -156,16 +151,18 @@ registerTypedMessageRenderer('unknown', { priority: 0, }) -export const DefaultTypedMessageSuspendedRenderer = memo(function DefaultTypedMessageSuspendedRenderer( +const DefaultTypedMessageSuspendedRenderer = memo(function DefaultTypedMessageSuspendedRenderer( props: TypedMessageRendererProps, ) { - const { promise } = props.message + const { promise, explicit } = props.message const { loading, error, value } = useAsync(() => promise, [promise]) return renderWithMetadata( props, loading ? ( - + explicit ? ( + + ) : null ) : error ? ( ) : ( diff --git a/packages/mask/src/plugins/Trader/base-deferred.ts b/packages/mask/src/plugins/Trader/base-deferred.ts index c7e987101372..98f06dc53128 100644 --- a/packages/mask/src/plugins/Trader/base-deferred.ts +++ b/packages/mask/src/plugins/Trader/base-deferred.ts @@ -1,12 +1,12 @@ import type { Plugin } from '@masknet/plugin-infra' -import type { TypedMessage, TypedMessageTuple } from '@masknet/shared-base' +import { visitEachTypedMessageChild } from '@masknet/shared-base' import { isCashTagMessage, makeTypedMessageCashTrending } from './messages/TypedMessageCashTrending' export const baseDeferred: Plugin.Shared.Utilities = { - typedMessageTransformer(message) { - return { - ...message, - items: message.items.map((m: TypedMessage) => (isCashTagMessage(m) ? makeTypedMessageCashTrending(m) : m)), - } as TypedMessageTuple + typedMessageTransformer: function visitor(message) { + if (isCashTagMessage(message)) { + return makeTypedMessageCashTrending(message) + } + return visitEachTypedMessageChild(message, visitor) }, } diff --git a/packages/plugin-infra/src/types.ts b/packages/plugin-infra/src/types.ts index 21bee580d888..ea5723ca7654 100644 --- a/packages/plugin-infra/src/types.ts +++ b/packages/plugin-infra/src/types.ts @@ -139,7 +139,7 @@ export namespace Plugin.Shared { */ typedMessageTransformer?: TypedMessageTransformer } - export type TypedMessageTransformer = (message: TypedMessageTuple) => TypedMessageTuple + export type TypedMessageTransformer = (message: TypedMessage) => TypedMessage /** The publisher of the plugin */ export interface Publisher { /** The name of the publisher */ diff --git a/packages/shared-base/src/TypedMessage/core.ts b/packages/shared-base/src/TypedMessage/core.ts index e46700e3e935..6296ee2d7ff6 100644 --- a/packages/shared-base/src/TypedMessage/core.ts +++ b/packages/shared-base/src/TypedMessage/core.ts @@ -116,12 +116,15 @@ export interface TypedMessagePromise exte readonly type: 'promise' readonly promise: Promise readonly value?: T + /** Should this message rendered when it's in the pending state? */ + readonly explicit?: boolean } export const isTypedMessagePromise = createIsType('promise') export function makeTypedMessagePromise( promise: Promise, + explicit = false, ): TypedMessagePromise { - const x: TypedMessagePromise = { type: 'promise', serializable: false, promise } + const x: TypedMessagePromise = { type: 'promise', serializable: false, promise, explicit } promise.then((y) => ((x as any).value = y)) return x } diff --git a/packages/shared-base/src/TypedMessage/index.ts b/packages/shared-base/src/TypedMessage/index.ts index b6e7ef53fea2..3afc6d7c4d4a 100644 --- a/packages/shared-base/src/TypedMessage/index.ts +++ b/packages/shared-base/src/TypedMessage/index.ts @@ -11,3 +11,4 @@ export const isWellKnownTypedMessages = composeSome(isWellKnownCoreTypedMessages export { encodeTypedMessageToDocument } from './binary-encode/encode' export { decodeTypedMessageFromDocument } from './binary-encode/decode' export * from './deprecated-encode' +export * from './visitor' diff --git a/packages/shared-base/src/TypedMessage/visitor/index.ts b/packages/shared-base/src/TypedMessage/visitor/index.ts new file mode 100644 index 000000000000..dbbb19499104 --- /dev/null +++ b/packages/shared-base/src/TypedMessage/visitor/index.ts @@ -0,0 +1,28 @@ +import { isSerializableTypedMessage, isTypedMessagePromise, isTypedMessageTuple, makeTypedMessageTuple } from '..' +import type { TypedMessage } from '../base' + +export type Visitor = (message: TypedMessage) => TypedMessage +export function visitEachTypedMessageChild(node: TypedMessage, visitor: Visitor): TypedMessage { + if (isTypedMessageTuple(node)) { + return makeTypedMessageTuple(node.items.map(visitor), node.meta) + } else if (isTypedMessagePromise(node) && node.value) { + return visitor(node.value) + } + + if (isSerializableTypedMessage(node) && 'alt' in node) { + let alt = visitor(node.alt) + if (!isSerializableTypedMessage(alt)) { + console.warn( + '[@masknet/typed-message] You must return a serializable message in this position. Original:', + node, + 'Transformed child "alt":', + alt, + ) + // ignore the transform result + return node + } + return { ...node, alt } as TypedMessage + } + // return node with no child + return node +} From 2d37cd86d60f063a509dd389400bc384933f8500 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 11 Jan 2022 15:49:07 +0800 Subject: [PATCH 38/85] chore: change how detect text payload --- .../src/social-network-encode-decode/index.ts | 12 ++++-- .../background/services/crypto/decryption.ts | 39 +++++++++---------- .../InjectedComponents/PayloadReplacer.tsx | 2 +- .../InjectedComponents/PostReplacer.tsx | 10 ++++- .../TypedMessageRenderer.tsx | 14 ++++--- .../twitter.com/injection/PostInspector.tsx | 2 +- 6 files changed, 45 insertions(+), 34 deletions(-) diff --git a/packages/encryption/src/social-network-encode-decode/index.ts b/packages/encryption/src/social-network-encode-decode/index.ts index 975ed6eb58cd..c042842749d4 100644 --- a/packages/encryption/src/social-network-encode-decode/index.ts +++ b/packages/encryption/src/social-network-encode-decode/index.ts @@ -1,10 +1,14 @@ -import { Some } from 'ts-results' import { SocialNetworkEnum } from '../payload/types' import { TwitterDecoder, __TwitterEncoder } from './twitter' -export function socialNetworkDecoder(network: SocialNetworkEnum, content: string) { - if (network === SocialNetworkEnum.Twitter) return TwitterDecoder(content) - return Some(content) +export function socialNetworkDecoder(network: SocialNetworkEnum, content: string): string[] { + if (network === SocialNetworkEnum.Twitter) + return TwitterDecoder(content) + .map((x) => [x]) + .unwrapOr([]) + const all = content.match(/(๐ŸŽผ[\d\/\|\w=_\+]+:\|\|)/gi) + if (all) return all + return [] } export function socialNetworkEncoder(network: SocialNetworkEnum, content: string) { if (network === SocialNetworkEnum.Twitter) return __TwitterEncoder(content) diff --git a/packages/mask/background/services/crypto/decryption.ts b/packages/mask/background/services/crypto/decryption.ts index fc5b0990b4d9..93b31136bfb3 100644 --- a/packages/mask/background/services/crypto/decryption.ts +++ b/packages/mask/background/services/crypto/decryption.ts @@ -58,28 +58,27 @@ export async function* decryptionWithSocialNetworkDecoding( yield* new EventIterator<[id: string, progress: DecryptProgress | DecryptionInfo]>((flow) => { let globalID = 0 for (const e of encoded) { - const id = (globalID += 1) + '' - let decoded!: string | Uint8Array + let decoded: string[] | Promise = [] + if (e.type === 'text') { + decoded = socialNetworkDecoder(context.currentSocialNetwork, e.text) + } else if (e.type === 'image-url') { + if (!context.authorHint || context.authorHint.isUnknown) { + flow.push([globalID + '', new DecryptError(ErrorReasons.UnrecognizedAuthor, undefined)]) + return + } + const result = steganographyDecodeImageUrl(e.url, { + pass: context.authorHint.toText(), + downloadImage, + }).then((val) => socialNetworkDecoder(context.currentSocialNetwork, val)) + decoded = result + } async function main() { - if (e.type === 'text') { - // TODO: socialNetworkDecoder should emit multiple results if the text contains multiple payload. - decoded = socialNetworkDecoder(context.currentSocialNetwork, e.text).unwrapOr(e.text) - } else if (e.type === 'image-url') { - if (!context.authorHint || context.authorHint.isUnknown) { - flow.push([id, new DecryptError(ErrorReasons.UnrecognizedAuthor, undefined)]) - return - } - const result = socialNetworkDecoder( - context.currentSocialNetwork, - await steganographyDecodeImageUrl(e.url, { - pass: context.authorHint.toText(), - downloadImage, - }), - ) - if (result.none) return - decoded = result.val + let localID = 0 + for (const each of await decoded) { + localID += 1 + const ID = `${globalID}-${localID}` + for await (const x of decryption(each, context)) flow.push([ID, x]) } - for await (const x of decryption(decoded, context)) flow.push([id, x]) } main() } diff --git a/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx b/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx index 8f52020acf49..9c75faf99721 100644 --- a/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx +++ b/packages/mask/src/components/InjectedComponents/PayloadReplacer.tsx @@ -29,7 +29,7 @@ interface TypedMessageExtension_MaskPayloadReplacer extends TypedMessage { // match the link version payload const shouldReplace = /^https?:\/\/mask(\.io|book\.com)\/\?PostData_v/i // match the text version payload -const shouldReplace2 = /(๐ŸŽผ[\d\/\|\w=]+:\|\|)/gi +const shouldReplace2 = /(๐ŸŽผ[\d\/\|\w=_\+]+:\|\|)/gi const PayloadReplacer = memo(({ link }: { link?: string }) => { const { t } = useI18N() const { classes } = useStyle() diff --git a/packages/mask/src/components/InjectedComponents/PostReplacer.tsx b/packages/mask/src/components/InjectedComponents/PostReplacer.tsx index 99f52d5b67bd..21886dceefa8 100644 --- a/packages/mask/src/components/InjectedComponents/PostReplacer.tsx +++ b/packages/mask/src/components/InjectedComponents/PostReplacer.tsx @@ -1,8 +1,14 @@ import { useActivatedPluginsSNSAdaptor } from '@masknet/plugin-infra' -import { isTypedMessageTuple, isWellKnownTypedMessages, TypedMessage } from '@masknet/shared-base' +import { + isTypedMessageImage, + isTypedMessageTuple, + isWellKnownTypedMessages, + makeTypedMessageEmpty, + TypedMessage, + visitEachTypedMessageChild, +} from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' import { useEffect, useMemo } from 'react' -import { Result } from 'ts-results' import { usePostInfoDetails } from '../DataSource/usePostInfo' import { PayloadReplacerTransformer } from './PayloadReplacer' import { DefaultTypedMessageRenderer } from './TypedMessageRenderer' diff --git a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx index 0919c67cdd19..f231d8ea8197 100644 --- a/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx +++ b/packages/mask/src/components/InjectedComponents/TypedMessageRenderer.tsx @@ -84,12 +84,14 @@ const DefaultTypedMessageImageRenderer = memo(function DefaultTypedMessageImageR props: TypedMessageRendererProps, ) { const { image, width, height } = props.message - return renderWithMetadata( - props, - - - , - ) + return null + // TODO: + // return renderWithMetadata( + // props, + // + // + // , + // ) }) registerTypedMessageRenderer('image', { component: DefaultTypedMessageImageRenderer, diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx index 3a416187f469..8fcf42056b5d 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostInspector.tsx @@ -12,7 +12,7 @@ export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostI if (!content) return for (const a of content.querySelectorAll('a')) { - if (socialNetworkDecoder(SocialNetworkEnum.Twitter, a.title).some) hideDOM(a) + if (socialNetworkDecoder(SocialNetworkEnum.Twitter, a.title).length) hideDOM(a) if (/^https?:\/\/mask(\.io|book\.com)$/i.test(a.title)) hideDOM(a) } From 6d62b9165debce589fb1c2f30c1239eca8e37840 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 11 Jan 2022 17:36:42 +0800 Subject: [PATCH 39/85] chore: merge with main --- .lintstagedrc.mjs | 7 + cspell.json | 304 ++++++++---------- package.json | 8 +- packages/.eslintrc.json | 6 +- .../src/v3-EncryptedJSON/index.ts | 3 +- .../components/CreateWalletFrame/index.tsx | 3 +- .../src/components/FileUpload/index.tsx | 3 +- .../src/components/FooterLine/About.tsx | 3 +- .../src/components/FooterLine/index.tsx | 6 +- .../components/LoadingPlaceholder/index.tsx | 3 +- .../Restore/RestoreFromPrivateKey.tsx | 6 +- .../src/components/Restore/index.tsx | 3 +- .../components/CreateMnemonic/index.tsx | 3 +- .../components/CreateWalletForm/index.tsx | 4 +- .../components/VerifyMnemonicDialog/index.tsx | 3 +- .../src/pages/Labs/components/PluginItem.tsx | 3 +- .../src/pages/Labs/components/SettingItem.tsx | 2 +- .../components/AddPersonaCard/index.tsx | 3 +- .../components/ContactTableRow/index.tsx | 3 +- .../components/ContactsTable/index.tsx | 3 +- .../EmptyContactPlaceholder/index.tsx | 3 +- .../components/PersonaContent/index.tsx | 3 +- .../Personas/components/PersonaLine/index.tsx | 3 +- .../components/PersonaSetup/index.tsx | 3 +- .../components/PostHistory/PostHistoryRow.tsx | 3 +- .../components/BackupContentSelector.tsx | 3 +- .../Settings/components/BackupPreviewCard.tsx | 3 +- .../dialogs/BackupModeSelectDialog.tsx | 3 +- .../dialogs/CloudBackupMergeDialog.tsx | 3 +- .../components/dialogs/RestoreDialog.tsx | 3 +- .../components/dialogs/SettingEmailDialog.tsx | 3 +- .../dialogs/SettingPasswordDialog.tsx | 4 +- .../dialogs/SettingPhoneNumberDialog.tsx | 3 +- packages/dashboard/src/pages/SignIn/index.tsx | 3 +- .../src/pages/SignUp/steps/PersonaCreate.tsx | 3 +- .../CollectiblePlaceHolder/index.tsx | 3 +- .../Wallets/components/CreateWallet/index.tsx | 3 +- .../components/EmptyPlaceholder/index.tsx | 3 +- .../components/HistoryTableRow/index.tsx | 3 +- .../Wallets/components/ImportWallet/index.tsx | 3 +- .../components/ReceiveDialog/index.tsx | 3 +- .../components/Transfer/SelectNFTList.tsx | 3 +- .../components/Transfer/TransferERC20.tsx | 1 + .../components/Transfer/TransferERC721.tsx | 1 + .../encryption/tests/v37-encode-decode.ts | 3 +- .../encryption/tests/v38-encode-decode.ts | 3 +- packages/mask/package.json | 2 +- .../CompositionDialog/TypedMessageEditor.tsx | 4 +- .../AutoPasteFailedDialog.tsx | 3 +- .../shared/DebugMetadataInspector.tsx | 2 +- .../DashboardComponents/ActionButton.tsx | 15 +- .../Wallet/HideTokenConfirm.tsx | 3 +- .../DashboardDialogs/Wallet/TransferNFT.tsx | 1 + .../extension/popups/SignRequest/index.tsx | 6 +- packages/mask/src/extension/popups/UI.tsx | 3 +- .../popups/pages/Personas/Home/index.tsx | 3 +- .../Personas/components/PersonaList/index.tsx | 3 +- .../extension/popups/pages/Wallet/index.tsx | 3 +- .../plugins/Avatar/SNSAdaptor/NFTAvatar.tsx | 3 +- packages/mask/src/plugins/Avatar/messages.ts | 3 +- .../Collectible/SNSAdaptor/CheckoutDialog.tsx | 2 +- .../Collectible/SNSAdaptor/CheckoutOrder.tsx | 25 +- .../Collectible/SNSAdaptor/Collectible.tsx | 69 ++-- .../Collectible/SNSAdaptor/LinkingAvatar.tsx | 19 ++ .../SNSAdaptor/ListingByPriceCard.tsx | 3 +- .../Collectible/SNSAdaptor/TokenTab.tsx | 24 +- .../mask/src/plugins/Collectible/constants.ts | 3 + .../src/plugins/Collectible/pipes/index.ts | 81 ++++- .../CryptoartAI/SNSAdaptor/CheckoutDialog.tsx | 6 +- .../CryptoartAI/SNSAdaptor/Collectible.tsx | 6 +- .../SNSAdaptor/MakeOfferDialog.tsx | 6 +- .../CryptoartAI/hooks/usePlaceBidCallback.ts | 4 +- .../mask/src/plugins/EVM/hooks/useAsset.ts | 3 +- .../External/components/CompositionEntry.tsx | 3 +- .../plugins/FindTruman/SNSAdaptor/index.tsx | 3 +- .../plugins/GoodGhosting/hooks/useGameInfo.ts | 2 +- .../plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx | 3 +- .../mask/src/plugins/ITO/SNSAdaptor/ITO.tsx | 6 +- .../SNSAdaptor/components/DetailsTab.tsx | 2 - .../plugins/Polls/SNSAdaptor/PollsDialog.tsx | 3 +- .../PoolTogether/UI/PoolTogetherView.tsx | 3 +- .../PoolTogether/hooks/useAccountBalances.ts | 3 +- .../SNSAdaptor/RedPacketInHistoryList.tsx | 3 +- .../src/plugins/Snapshot/SNSAdaptor/index.tsx | 3 +- .../SNSAdaptor/trader/ConfirmDialog.tsx | 3 +- .../SNSAdaptor/trader/InputTokenPanel.tsx | 6 +- .../Trader/SNSAdaptor/trader/Trader.tsx | 2 +- .../plugins/Trader/apis/openocean/index.ts | 3 +- .../src/plugins/Trader/helpers/uniswap.ts | 14 +- .../Trader/trader/bancor/useTradeCallback.ts | 3 +- .../trader/openocean/useTradeGasLimit.ts | 8 +- .../Trader/trader/uniswap/useTradeGasLimit.ts | 3 +- .../Trader/trader/useAllTradeComputed.ts | 7 +- .../src/plugins/Trader/types/openocean.ts | 1 + .../SNSAdaptor/UnlockProtocolDialog.tsx | 3 +- .../SNSAdaptor/SelectNftContractDialog.tsx | 2 +- .../PluginProviderRender.tsx | 2 +- .../mask/src/plugins/dHEDGE/UI/PoolStats.tsx | 3 +- .../instagram.com/collecting/posts.ts | 3 +- .../twitter.com/collecting/identity.ts | 3 +- .../twitter.com/collecting/post.ts | 6 +- packages/mask/src/social-network/ui.ts | 3 +- .../src/utils/hooks/useSettingSwitcher.tsx | 3 +- packages/mask/src/utils/i18n-next-ui.ts | 3 +- packages/mask/src/utils/theme.ts | 3 +- .../BackupFormat/JSON/version-2.ts | 3 +- .../src/web3/UI/EthereumChainBoundary.tsx | 8 +- packages/plugin-infra/src/web3-types.ts | 3 +- packages/plugins/DAO/src/hooks/index.ts | 1 - packages/plugins/DAO/src/hooks/useDao.ts | 40 --- .../FileService/src/SNSAdaptor/MainDialog.tsx | 3 +- .../SNSAdaptor/components/UploadDropArea.tsx | 3 +- .../RSS3/src/SNSAdaptor/hooks/index.ts | 1 - .../RSS3/src/SNSAdaptor/hooks/useDao.ts | 41 --- packages/plugins/Wallet/src/messages.ts | 2 +- .../src/bin/precommit-hook-localekit.js | 12 - packages/scripts/src/index.ts | 1 + packages/scripts/src/spellcheck/index.ts | 41 +++ packages/scripts/src/utils/paths.ts | 2 +- packages/scripts/src/utils/prettier.ts | 17 +- .../UI/components/ErrorBoundary/CrashUI.tsx | 6 +- .../src/UI/components/ImageIcon/index.tsx | 3 +- .../UI/components/TokenAmountPanel/index.tsx | 3 +- .../src/UI/components/WalletIcon/index.tsx | 5 +- .../shared/src/wallet/FormattedBalance.tsx | 3 +- .../src/Components/SendingCodeField/index.tsx | 3 +- .../theme/src/Components/Snackbar/index.tsx | 3 +- packages/theme/src/hooks/useTabs.tsx | 3 +- packages/web3-providers/src/opensea/index.ts | 4 +- .../evm/hooks/useAssetsByTokenList.ts | 3 +- .../useERC20TokensDetailedFromTokenLists.ts | 3 +- packages/web3-shared/evm/utils/enum.ts | 5 +- pnpm-lock.yaml | 16 +- 133 files changed, 532 insertions(+), 583 deletions(-) create mode 100644 .lintstagedrc.mjs create mode 100644 packages/mask/src/plugins/Collectible/SNSAdaptor/LinkingAvatar.tsx delete mode 100644 packages/plugins/DAO/src/hooks/index.ts delete mode 100644 packages/plugins/DAO/src/hooks/useDao.ts delete mode 100644 packages/plugins/RSS3/src/SNSAdaptor/hooks/useDao.ts delete mode 100644 packages/scripts/src/bin/precommit-hook-localekit.js create mode 100644 packages/scripts/src/spellcheck/index.ts diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs new file mode 100644 index 000000000000..67490bc31131 --- /dev/null +++ b/.lintstagedrc.mjs @@ -0,0 +1,7 @@ +export default { + '*': 'prettier --write --ignore-unknown', + 'packages/**/*.{ts,tsx,js,jsx}': 'eslint -c packages/.eslintrc.json --fix', + 'packages/mask/**/*': () => 'gulp locale-kit --sync-keys --remove-unused-keys', + 'packages/web3-constants/**/*': 'pnpm start --filter web3-constants', + 'cspell.json': () => 'gulp reorder-spellcheck', +} diff --git a/cspell.json b/cspell.json index 72dfdff50818..020f73d8b1a1 100644 --- a/cspell.json +++ b/cspell.json @@ -1,64 +1,28 @@ { "dictionaries": ["typescript", "node", "html", "css", "fonts"], "ignorePaths": [ - "patches/**", - "*generated*", - "**/dist/**", + ".github", + ".prettierignore", + "*.snap", "**/*.json", + "**/dist/**", "**/node_modules/**", - "pnpm-lock.yaml", - ".prettierignore", - ".github", - "*.snap" + "*generated*", + "patches/**", + "pnpm-lock.yaml" ], "words": [ - "AMPL", - "Adai", - "Amountstate", - "Ampleforth", - "ARETH", - "Bolivarian", - "Bouvet", - "Burkina", - "CAIP", - "Caicos", - "Celo", - "Checksummed", - "Cunha", - "Cura", - "DHEDGE", - "Darussalam", - "ECID", - "Eustatius", - "Exchangetoken", - "Faso", - "Futuna", - "IDBP", - "IHDR", - "Kitts", - "Leste", - "Maarten", - "Marino", - "Maskbook", - "Mastdon", - "Mayen", - "Merkle", - "Mutex", - "Plurinational", - "Polkadot", - "Pooltogether", - "Rhonin", - "Rica", - "Sint", - "Tokelau", - "Transak", - "Vcent", - "Viet", "aave", + "Adai", "addrs", + "Amountstate", "ampl", + "AMPL", + "Ampleforth", "anchorme", + "apikey", "arbitrum", + "ARETH", "arweave", "bgcolor", "bignumber", @@ -67,9 +31,17 @@ "blockie", "blockies", "bluebar", + "Bolivarian", + "Bouvet", "builtins", + "Burkina", + "Caicos", + "CAIP", "canonify", "cashtag", + "Celo", + "Checksummed", + "Choudhary", "cipherparams", "ciphertext", "clearfix", @@ -78,9 +50,15 @@ "commentid", "consolas", "contenteditable", + "contrib", + "contribs", "cooldown", + "counterparty", "crwdne", + "Cunha", + "Cura", "d'Ivoire", + "Darussalam", "datas", "debank", "denorm", @@ -88,30 +66,40 @@ "deserialization", "destory", "dfyn", + "DHEDGE", "dimensiondev", "dklen", "dompurify", + "ECID", "ecsign", "elec", "encryptedmsg", "endregion", "ethersproject", + "Eustatius", "everytime", + "Exchangetoken", + "Faso", "fbid", "filelist", "fileservice", "formating", "fullfilled", "furucombo", + "Futuna", "gasnow", "geckoview", "gltf", "goodghosting", + "Gรถrli", "gundb", "hashmasks", + "hexlify", "holoflows", "iconbar", + "IDBP", "ifclaimed", + "IHDR", "imagebitmap", "immer", "importmap", @@ -120,27 +108,41 @@ "ipfs", "ipfsurl", "ittr", + "jailbroken", "jsbi", "jsonml", + "juicebox", "kdfparams", "keccak", + "Kitts", "kred", "labelledby", "languagedetector", "lemy", + "Leste", "linkedin", "linkswap", "locationchange", "lowercased", + "Maarten", + "macbinary", + "majeure", + "Marino", + "Maskbook", "maskbookjsonrpc", "maskface", "masknet", "masknetwork", "masknightly", "masktext", + "Mastdon", + "Mayen", + "Merkle", "messagepack", "metas", + "metaverse", "millify", + "misoperation", "monofont", "monospace", "mooniswap", @@ -150,18 +152,25 @@ "mskc", "multicall", "multihop", + "Mutex", "newsfeed", + "NFTRSS", + "nftscan", "nowallet", "nums", "opensea", "overridable", "overscan", + "Pageable", "pagelet", "pancakeswap", "pathnames", "perma", "pid", "pids", + "Plurinational", + "Polkadot", + "Pooltogether", "popper", "popperjs", "porportion", @@ -170,7 +179,6 @@ "promi", "proxied", "pushstate", - "unstyled", "pvtsutils", "qrcode", "quickswap", @@ -180,6 +188,9 @@ "redpacket", "replacestate", "repost", + "resizer", + "Rhonin", + "Rica", "rindexed", "rpid", "rpids", @@ -187,7 +198,10 @@ "sashimiswap", "scroller", "secp", + "Serializers", "shink", + "Sint", + "solana", "steelblue", "steganographic", "steganography", @@ -196,6 +210,7 @@ "superrare", "sushiswap", "swappable", + "Swither", "tabindex", "tablist", "testid", @@ -203,6 +218,9 @@ "thegraph", "timelock", "timelocked", + "Tokelau", + "tokenid", + "Transak", "twimg", "txid", "typechain", @@ -211,9 +229,12 @@ "unencrypted", "unreviewed", "unstake", + "unstyled", "untrusted", "urlcat", "usermenu", + "Vcent", + "Viet", "viewbox", "visualcompletion", "walletconnect", @@ -227,151 +248,112 @@ "xlarge", "xlink", "zerion", - "Zubin", - "Choudhary", - "macbinary", - "Choudhary", - "counterparty", - "jailbroken", - "misoperation", - "majeure", - "Serializers", - "Swither", - "nftscan", - "apikey", - "tokenid", - "NFTRSS", - "solana", - "Gรถrli", - "resizer", - "Pageable", - "metaverse", - "hexlify", - "contribs", - "contrib", - "juicebox" + "Zubin" ], "ignoreWords": [ - "cryptopunks", - "nftred", + "AICANFT", + "algr", + "armv", + "avator", + "blocto", "BSCT", "BTCB", + "CANFT", + "CELO", + "CEUR", + "chainid", + "consts", "COTM", + "crwdnd", + "crwdns", + "cryptoart", + "cryptoartai", + "cryptopunks", "Csvg", + "CUSD", + "Defi", + "devnet", "Dnoxg", + "dodoapi", + "drawed", + "DRAWED", "Energi", + "Enjin", + "ethjs", + "fbclid", + "findtruman", + "flac", + "FUSD", + "gamification", + "getdodoroute", + "getx", "Gorli", "Gwapj", - "HUSD", - "CUSD", - "CEUR", + "hookform", "Horiz", "Huobi", - "MSKA", - "MSKB", - "MSKC", - "MSKD", - "MSKE", - "Misaka", - "NFTX", - "NRGT", - "NYFI", - "QLURL", - "STETH", - "Strategie", - "UNITOKEN", - "USEI", - "Vussxxjdr", - "WNATIVE", - "algr", - "armv", - "crwdnd", - "crwdns", - "dodoapi", - "getdodoroute", - "openocean", - "OPENOCEAN", - "getx", - "hookform", + "HUSD", "idiv", "ifadmin", "ifrandom", "iife", "immerable", "inpage", + "irss", + "isfacebook", "jsxs", + "koda", + "Kodav", "lngs", "maskface", + "maskwallet", + "mathwallet", + "metadate", + "metaswap", + "METASWAP", + "Misaka", + "MSKA", + "MSKB", + "MSKC", + "MSKD", + "MSKE", + "NFT", + "nftred", + "NFTX", + "noamount", "northcutt", "notistack", "nrge", - "pnpm", - "setx", - "pkts", - "swaptoken", + "NRGT", "NYFI", - "Csvg", - "Horiz", - "hookform", - "METASWAP", - "metaswap", - "tinycolor", - "unstoppabledomains", - "vitalik", - "unlockprotocol", - "unlocklock", - "chainid", - "NFT", - "powahs", - "gamification", - "Defi", - "powah", - "Switcher", - "maskwallet", - "drawed", - "DRAWED", - "devnet", - "fbclid", - "noamount", - "txts", - "redpackets", - "blocto", "onflow", - "unauthenticate", - "mathwallet", - "walletlink", - "rpcs", - "FUSD", - "isfacebook", - "ethjs", - "isfacebook", - "findtruman", - "uaddr", - "consts", - "cryptoartai", - "cryptoart", - "avator", - "testweb", - "metadate", - "AICANFT", - "CANFT", - "Kodav", - "koda", - "Enjin", - "poap", - "CELO", - "flac", + "openocean", + "OPENOCEAN", + "pkts", "plusplus", + "pnpm", + "poap", "treeshake", - "xtest", + "txts", + "uaddr", + "unauthenticate", + "UNITOKEN", + "unlocklock", + "unlockprotocol", + "unstoppabledomains", + "USEI", + "vitalik", + "Vussxxjdr", + "walletlink", + "WNATIVE", "xdescribe", - "irss" + "xtest" ], "ignoreRegExpList": ["/@servie/"], "overrides": [ { "filename": "web3-contracts/types/GoodGhostingIncentives.d.ts", - "ignoreWords": ["ditribution", "Adming"] + "ignoreWords": ["Adming", "ditribution"] } ] } diff --git a/package.json b/package.json index a037d207c9e4..dee4a2935b59 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,6 @@ "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", "spellcheck": "cspell lint --no-must-find-files" }, - "lint-staged": { - "*": "prettier --write --ignore-unknown", - "packages/**/*.{ts,tsx,js,jsx}": "eslint -c packages/.eslintrc.json --fix", - "packages/mask/**/*": "node ./packages/scripts/src/bin/precommit-hook-localekit.js", - "packages/web3-constants/**/*": "pnpm start --filter web3-constants" - }, "dependencies": { "@dimensiondev/kit": "0.0.0-20211215044858-27d964f", "@emotion/cache": "^11.7.1", @@ -53,7 +47,7 @@ "devDependencies": { "@commitlint/cli": "^16.0.0", "@commitlint/config-conventional": "^16.0.0", - "@dimensiondev/eslint-plugin": "^0.0.1-20220104011158-0905fa1", + "@dimensiondev/eslint-plugin": "^0.0.1-20220110102506-9f382ee", "@dimensiondev/patch-package": "^6.5.0", "@jest/globals": "^27.4.4", "@magic-works/i18n-codegen": "^0.0.6", diff --git a/packages/.eslintrc.json b/packages/.eslintrc.json index 878b6f030b1d..5ed7c1f341fe 100644 --- a/packages/.eslintrc.json +++ b/packages/.eslintrc.json @@ -26,10 +26,10 @@ "yoda": "error", "radix": "error", "eqeqeq": ["error", "always"], - "import/no-deprecated": "warn", + "import/no-deprecated": "error", + "import/no-duplicates": "error", "no-cond-assign": "error", "no-constant-condition": "error", - "no-duplicate-imports": "off", "no-script-url": "error", "no-throw-literal": "error", "no-unmodified-loop-condition": "error", @@ -55,7 +55,9 @@ "unicorn/throw-new-error": "error", "unused-imports/no-unused-imports-ts": "error", "@dimensiondev/no-jsx-template-literal": "error", + "@dimensiondev/no-locale-case": "error", "@dimensiondev/no-number-constructor": "off", + "@dimensiondev/no-simple-string-interpolation": "off", "@dimensiondev/no-simple-template-literal": "error", "@dimensiondev/no-unsafe-location": "error", "@dimensiondev/prefer-early-return": "error", diff --git a/packages/backup-format/src/v3-EncryptedJSON/index.ts b/packages/backup-format/src/v3-EncryptedJSON/index.ts index 902fc3cc62e9..d60da9e33072 100644 --- a/packages/backup-format/src/v3-EncryptedJSON/index.ts +++ b/packages/backup-format/src/v3-EncryptedJSON/index.ts @@ -1,7 +1,6 @@ import { decode, encode } from '@msgpack/msgpack' -import { createContainer, parseEncryptedJSONContainer } from '../container' +import { createContainer, parseEncryptedJSONContainer, SupportedVersions } from '../container' import { BackupErrors } from '../BackupErrors' -import { SupportedVersions } from '../container' export async function encryptBackup(password: BufferSource, binaryBackup: Uint8Array) { const [pbkdf2IV, AESKey] = await createAESFromPassword(password) diff --git a/packages/dashboard/src/components/CreateWalletFrame/index.tsx b/packages/dashboard/src/components/CreateWalletFrame/index.tsx index 5aa79be11dec..7d686c863ae9 100644 --- a/packages/dashboard/src/components/CreateWalletFrame/index.tsx +++ b/packages/dashboard/src/components/CreateWalletFrame/index.tsx @@ -1,6 +1,5 @@ import { memo } from 'react' -import { makeStyles } from '@masknet/theme' -import { MaskColorVar } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' const useStyles = makeStyles()({ root: { diff --git a/packages/dashboard/src/components/FileUpload/index.tsx b/packages/dashboard/src/components/FileUpload/index.tsx index 072f361e2457..0c1117013e65 100644 --- a/packages/dashboard/src/components/FileUpload/index.tsx +++ b/packages/dashboard/src/components/FileUpload/index.tsx @@ -1,6 +1,5 @@ -import { MaskColorVar } from '@masknet/theme' +import { MaskColorVar, makeStyles } from '@masknet/theme' import { Card, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' import { ReactNode, useEffect, useState } from 'react' import { File as FileIcon } from '@masknet/icons' import { blobToText } from '@dimensiondev/kit' diff --git a/packages/dashboard/src/components/FooterLine/About.tsx b/packages/dashboard/src/components/FooterLine/About.tsx index e0b9f719294e..4a4bb92a05e6 100644 --- a/packages/dashboard/src/components/FooterLine/About.tsx +++ b/packages/dashboard/src/components/FooterLine/About.tsx @@ -1,6 +1,6 @@ import { DiscordIcon, MaskBlueIcon, MaskGreyIcon, MaskTextIcon, MaskTextNightlyIcon } from '@masknet/icons' import { Avatar, IconButton, Link, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, getMaskColor } from '@masknet/theme' import FacebookIcon from '@mui/icons-material/Facebook' import GitHubIcon from '@mui/icons-material/GitHub' import TelegramIcon from '@mui/icons-material/Telegram' @@ -8,7 +8,6 @@ import TwitterIcon from '@mui/icons-material/Twitter' import { useDashboardI18N } from '../../locales' import { styled } from '@mui/material/styles' import { Version } from './Version' -import { getMaskColor } from '@masknet/theme' import links from './links.json' import { ABOUT_DIALOG_BACKGROUND } from '../../assets' diff --git a/packages/dashboard/src/components/FooterLine/index.tsx b/packages/dashboard/src/components/FooterLine/index.tsx index b3b8916271ce..b893f706881d 100644 --- a/packages/dashboard/src/components/FooterLine/index.tsx +++ b/packages/dashboard/src/components/FooterLine/index.tsx @@ -1,13 +1,11 @@ -import { styled } from '@mui/material' +import { styled, Breadcrumbs, Dialog, IconButton, Link, Typography } from '@mui/material' import { useHref, useNavigate } from 'react-router-dom' -import { Breadcrumbs, Dialog, IconButton, Link, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, getMaskColor } from '@masknet/theme' import { useDashboardI18N } from '../../locales' import { memo, useState } from 'react' import { About } from './About' import { Close } from '@mui/icons-material' import { Version } from './Version' -import { getMaskColor } from '@masknet/theme' import links from './links.json' const useStyles = makeStyles()((theme) => ({ diff --git a/packages/dashboard/src/components/LoadingPlaceholder/index.tsx b/packages/dashboard/src/components/LoadingPlaceholder/index.tsx index bfddb50e3586..035ad4cac8be 100644 --- a/packages/dashboard/src/components/LoadingPlaceholder/index.tsx +++ b/packages/dashboard/src/components/LoadingPlaceholder/index.tsx @@ -1,7 +1,6 @@ import { memo } from 'react' import { Box, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { MaskColorVar } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { LoadingAnimation } from '@masknet/shared' const useStyles = makeStyles()((theme) => ({ diff --git a/packages/dashboard/src/components/Restore/RestoreFromPrivateKey.tsx b/packages/dashboard/src/components/Restore/RestoreFromPrivateKey.tsx index a2f67f5ba8f5..c8ad1cfb6b53 100644 --- a/packages/dashboard/src/components/Restore/RestoreFromPrivateKey.tsx +++ b/packages/dashboard/src/components/Restore/RestoreFromPrivateKey.tsx @@ -4,16 +4,14 @@ import { Box, Button } from '@mui/material' import { useDashboardI18N } from '../../locales' import { MaskAlert } from '../MaskAlert' import { ButtonContainer } from '../RegisterFrame/ButtonContainer' -import { SubmitHandler, useForm } from 'react-hook-form' +import { SubmitHandler, useForm, Controller } from 'react-hook-form' import { Services } from '../../API' import { PersonaContext } from '../../pages/Personas/hooks/usePersonaContext' import { zodResolver } from '@hookform/resolvers/zod' -import { Controller } from 'react-hook-form' import { z } from 'zod' import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' +import { DashboardRoutes, delay } from '@masknet/shared-base' import { SignUpRoutePath } from '../../pages/SignUp/routePath' -import { delay } from '@masknet/shared-base' type FormInputs = { privateKey: string } diff --git a/packages/dashboard/src/components/Restore/index.tsx b/packages/dashboard/src/components/Restore/index.tsx index e1a52afa8ab7..b43e44a4c05a 100644 --- a/packages/dashboard/src/components/Restore/index.tsx +++ b/packages/dashboard/src/components/Restore/index.tsx @@ -1,7 +1,6 @@ import { memo } from 'react' import { Tab } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { ButtonGroupTabList, useTabs } from '@masknet/theme' +import { makeStyles, ButtonGroupTabList, useTabs } from '@masknet/theme' import { useDashboardI18N } from '../../locales' import { RestoreFromLocal } from './RestoreFromLocal' import { RestoreFromMnemonic } from './RestoreFromMnemonic' diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateMnemonic/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/CreateMnemonic/index.tsx index ab1694ac8628..e0cbf00d6390 100644 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateMnemonic/index.tsx +++ b/packages/dashboard/src/pages/CreateMaskWallet/components/CreateMnemonic/index.tsx @@ -1,7 +1,6 @@ import { memo, useCallback, useEffect, useState } from 'react' import { Alert, Box, Button, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { MaskColorVar } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { InfoIcon, RefreshIcon } from '@masknet/icons' import { useDashboardI18N } from '../../../../locales' import { ChainId, ProviderType } from '@masknet/web3-shared-evm' diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateWalletForm/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/CreateWalletForm/index.tsx index 4e3e91f9ac64..224307ebbb42 100644 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/CreateWalletForm/index.tsx +++ b/packages/dashboard/src/pages/CreateMaskWallet/components/CreateWalletForm/index.tsx @@ -1,18 +1,16 @@ import { memo, useEffect, useMemo, useState } from 'react' import { Alert, Box, Button, formHelperTextClasses, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar, MaskTextField } from '@masknet/theme' import { z as zod } from 'zod' import { useForm, Controller } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { useNavigate, useSearchParams } from 'react-router-dom' import { DashboardRoutes } from '@masknet/shared-base' -import { MaskColorVar } from '@masknet/theme' import { useDashboardI18N } from '../../../../locales' import { useAsyncRetry } from 'react-use' import { WalletMessages } from '@masknet/plugin-wallet' import { PluginServices } from '../../../../API' import urlcat from 'urlcat' -import { MaskTextField } from '@masknet/theme' import PasswordField from '../../../../components/PasswordField' import { InfoIcon } from '@masknet/icons' diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/VerifyMnemonicDialog/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/VerifyMnemonicDialog/index.tsx index 9ae65c30e01c..cd47f3f2ad78 100644 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/VerifyMnemonicDialog/index.tsx +++ b/packages/dashboard/src/pages/CreateMaskWallet/components/VerifyMnemonicDialog/index.tsx @@ -1,8 +1,7 @@ import { memo, useState } from 'react' import { Box, Typography, styled, Button, Dialog, DialogTitle, DialogContent } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { LoadingButton } from '@mui/lab' -import { MaskColorVar } from '@masknet/theme' import { useSnackbarCallback } from '@masknet/shared' import { SuccessIcon, CopyIcon } from '@masknet/icons' import { DesktopMnemonicConfirm } from '../../../../components/Mnemonic' diff --git a/packages/dashboard/src/pages/Labs/components/PluginItem.tsx b/packages/dashboard/src/pages/Labs/components/PluginItem.tsx index 1585a54e15ca..411efb9ba2e4 100644 --- a/packages/dashboard/src/pages/Labs/components/PluginItem.tsx +++ b/packages/dashboard/src/pages/Labs/components/PluginItem.tsx @@ -1,7 +1,6 @@ import { SettingsIcon, TutorialIcon } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' +import { MaskColorVar, makeStyles } from '@masknet/theme' import { Avatar, Box, ListItem, ListItemAvatar, ListItemText, styled, listItemTextClasses } from '@mui/material' -import { makeStyles } from '@masknet/theme' import type { ReactNode } from 'react' import SettingSwitch from '../../Settings/components/SettingSwitch' import { Twitter, Facebook, Explore } from './Actions' diff --git a/packages/dashboard/src/pages/Labs/components/SettingItem.tsx b/packages/dashboard/src/pages/Labs/components/SettingItem.tsx index ce2236eed60c..b76e516214b1 100644 --- a/packages/dashboard/src/pages/Labs/components/SettingItem.tsx +++ b/packages/dashboard/src/pages/Labs/components/SettingItem.tsx @@ -1,6 +1,6 @@ import { MaskColorVar } from '@masknet/theme' -import { formControlLabelClasses } from '@mui/material' import { + formControlLabelClasses, FormControl, FormControlLabel, FormControlLabelProps, diff --git a/packages/dashboard/src/pages/Personas/components/AddPersonaCard/index.tsx b/packages/dashboard/src/pages/Personas/components/AddPersonaCard/index.tsx index 1281137bf048..5114be6598d4 100644 --- a/packages/dashboard/src/pages/Personas/components/AddPersonaCard/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/AddPersonaCard/index.tsx @@ -1,7 +1,6 @@ import { memo, useState } from 'react' import { Button, TextField } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { MaskColorVar } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { useDashboardI18N } from '../../../../locales' import { isPersonaNameLengthValid, PERSONA_NAME_MAX_LENGTH } from '../../../../utils/checkLengthExceed' diff --git a/packages/dashboard/src/pages/Personas/components/ContactTableRow/index.tsx b/packages/dashboard/src/pages/Personas/components/ContactTableRow/index.tsx index f35046ec569e..fbbbd0edf03d 100644 --- a/packages/dashboard/src/pages/Personas/components/ContactTableRow/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/ContactTableRow/index.tsx @@ -1,9 +1,8 @@ import type { RelationProfile } from '@masknet/shared-base' import { memo, useCallback } from 'react' import { Box, TableCell, TableRow, Typography, Avatar, useTheme } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { StarIcon, MaskBlueIcon } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' import { Services } from '../../../../API' import { useDashboardI18N } from '../../../../locales' import { generateContactAvatarColor } from '../../../../utils/generateContactAvatarColor' diff --git a/packages/dashboard/src/pages/Personas/components/ContactsTable/index.tsx b/packages/dashboard/src/pages/Personas/components/ContactsTable/index.tsx index 7aaba9c13302..dc5175850b14 100644 --- a/packages/dashboard/src/pages/Personas/components/ContactsTable/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/ContactsTable/index.tsx @@ -2,8 +2,7 @@ import { Dispatch, memo, SetStateAction, useEffect, useMemo, useState } from 're import { useContacts } from '../../hooks/useContacts' import type { RelationProfile } from '@masknet/shared-base' import { TableContainer, Box, TablePagination, Stack, Table, TableBody } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { MaskColorVar } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { EmptyContactPlaceholder } from '../EmptyContactPlaceholder' import { LoadingPlaceholder } from '../../../../components/LoadingPlaceholder' import { sortBy } from 'lodash-unified' diff --git a/packages/dashboard/src/pages/Personas/components/EmptyContactPlaceholder/index.tsx b/packages/dashboard/src/pages/Personas/components/EmptyContactPlaceholder/index.tsx index 2bba741ed469..3fa45eb1a47e 100644 --- a/packages/dashboard/src/pages/Personas/components/EmptyContactPlaceholder/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/EmptyContactPlaceholder/index.tsx @@ -1,8 +1,7 @@ import { memo } from 'react' import { Box, Typography, Link, Button, FilledInput } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { EmptyIcon } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' import { DashboardTrans } from '../../../../locales/i18n_generated' import { useCopyToClipboard } from 'react-use' import { useSnackbarCallback } from '@masknet/shared' diff --git a/packages/dashboard/src/pages/Personas/components/PersonaContent/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaContent/index.tsx index 996becfb35a0..3782b3a23ece 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaContent/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaContent/index.tsx @@ -1,7 +1,6 @@ import { memo, useState } from 'react' -import { ButtonGroupTabList } from '@masknet/theme' +import { ButtonGroupTabList, makeStyles } from '@masknet/theme' import { Box, Tab } from '@mui/material' -import { makeStyles } from '@masknet/theme' import { TabContext, TabPanel } from '@mui/lab' import { ContactsTable } from '../ContactsTable' import { PostHistory } from '../PostHistory' diff --git a/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx index fc860f8bf2be..6af75bd51342 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaLine/index.tsx @@ -1,8 +1,7 @@ import { memo, MouseEvent, useState } from 'react' import { Box, Button, Link, Stack, Typography } from '@mui/material' -import { getMaskColor, MaskColorVar } from '@masknet/theme' +import { getMaskColor, MaskColorVar, makeStyles } from '@masknet/theme' import { useDashboardI18N } from '../../../../locales' -import { makeStyles } from '@masknet/theme' import { DisconnectProfileDialog } from '../DisconnectProfileDialog' import type { ProfileIdentifier } from '@masknet/shared-base' import { SOCIAL_MEDIA_ICON_MAPPING } from '@masknet/shared' diff --git a/packages/dashboard/src/pages/Personas/components/PersonaSetup/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaSetup/index.tsx index 7c9251c804c1..66806d56f944 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaSetup/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaSetup/index.tsx @@ -1,8 +1,7 @@ import { memo } from 'react' import { Button, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { LinkIcon } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' import { useDashboardI18N } from '../../../../locales' const useStyles = makeStyles()((theme) => ({ diff --git a/packages/dashboard/src/pages/Personas/components/PostHistory/PostHistoryRow.tsx b/packages/dashboard/src/pages/Personas/components/PostHistory/PostHistoryRow.tsx index 8495c1366552..8391376c9f38 100644 --- a/packages/dashboard/src/pages/Personas/components/PostHistory/PostHistoryRow.tsx +++ b/packages/dashboard/src/pages/Personas/components/PostHistory/PostHistoryRow.tsx @@ -1,12 +1,11 @@ import { Box, Button, Link, Stack, Typography } from '@mui/material' import { memo, ReactNode, useCallback, useMemo } from 'react' import { FileMessageIcon, ITOIcon, MessageIcon, PollIcon, RedPacketIcon } from '@masknet/icons' -import { getMaskColor, MaskColorVar } from '@masknet/theme' +import { getMaskColor, MaskColorVar, makeStyles } from '@masknet/theme' import { Services } from '../../../../API' import type { PostRecord } from '@masknet/shared-base' import { useDashboardI18N } from '../../../../locales' import { PersonaContext } from '../../hooks/usePersonaContext' -import { makeStyles } from '@masknet/theme' import { PluginId } from '@masknet/plugin-infra' const MSG_DELIMITER = '2c1aca02' diff --git a/packages/dashboard/src/pages/Settings/components/BackupContentSelector.tsx b/packages/dashboard/src/pages/Settings/components/BackupContentSelector.tsx index aee9f6ceb489..f12d884dd25d 100644 --- a/packages/dashboard/src/pages/Settings/components/BackupContentSelector.tsx +++ b/packages/dashboard/src/pages/Settings/components/BackupContentSelector.tsx @@ -2,8 +2,7 @@ import { Box, Checkbox, Typography, experimentalStyled as styled } from '@mui/ma import { useDashboardI18N } from '../../../locales' import type { BackupPreview } from './BackupPreviewCard' import { MaskColorVar } from '@masknet/theme' -import { useState } from 'react' -import { useEffect } from 'react' +import { useState, useEffect } from 'react' const SelectItem = styled('div')(({ theme }) => ({ borderRadius: 8, diff --git a/packages/dashboard/src/pages/Settings/components/BackupPreviewCard.tsx b/packages/dashboard/src/pages/Settings/components/BackupPreviewCard.tsx index ff1a09331258..a3b48f7cc126 100644 --- a/packages/dashboard/src/pages/Settings/components/BackupPreviewCard.tsx +++ b/packages/dashboard/src/pages/Settings/components/BackupPreviewCard.tsx @@ -1,6 +1,5 @@ -import { MaskColorVar } from '@masknet/theme' +import { MaskColorVar, makeStyles } from '@masknet/theme' import { Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' import classNames from 'classnames' import { useDashboardI18N } from '../../../locales' import formatDateTime from 'date-fns/format' diff --git a/packages/dashboard/src/pages/Settings/components/dialogs/BackupModeSelectDialog.tsx b/packages/dashboard/src/pages/Settings/components/dialogs/BackupModeSelectDialog.tsx index 00d341dd7629..563253f8daaf 100644 --- a/packages/dashboard/src/pages/Settings/components/dialogs/BackupModeSelectDialog.tsx +++ b/packages/dashboard/src/pages/Settings/components/dialogs/BackupModeSelectDialog.tsx @@ -1,6 +1,5 @@ -import { MaskColorVar, MaskDialog } from '@masknet/theme' +import { MaskColorVar, MaskDialog, makeStyles } from '@masknet/theme' import { Box, DialogContent, Tooltip, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' import { LocalBackupIcon, CloudBackupIcon } from '@masknet/icons' import { useContext, useMemo } from 'react' import { UserContext } from '../../hooks/UserContext' diff --git a/packages/dashboard/src/pages/Settings/components/dialogs/CloudBackupMergeDialog.tsx b/packages/dashboard/src/pages/Settings/components/dialogs/CloudBackupMergeDialog.tsx index 6c5a7e14112a..bcb1a3740721 100644 --- a/packages/dashboard/src/pages/Settings/components/dialogs/CloudBackupMergeDialog.tsx +++ b/packages/dashboard/src/pages/Settings/components/dialogs/CloudBackupMergeDialog.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState } from 'react' -import { MaskColorVar, MaskDialog, useCustomSnackbar } from '@masknet/theme' +import { MaskColorVar, MaskDialog, useCustomSnackbar, makeStyles } from '@masknet/theme' import { Box, FormControlLabel, formControlLabelClasses, Radio, RadioGroup, styled, Typography } from '@mui/material' import { BackupInfoCard } from '../../../../components/Restore/BackupInfoCard' import type { BackupFileInfo } from '../../type' @@ -10,7 +10,6 @@ import { useAsyncFn } from 'react-use' import { decryptBackup } from '@masknet/backup-format' import { decode, encode } from '@msgpack/msgpack' import PasswordField from '../../../../components/PasswordField' -import { makeStyles } from '@masknet/theme' import { LoadingButton } from '../../../../components/LoadingButton' const StyledFormControlLabel = styled(FormControlLabel)({ diff --git a/packages/dashboard/src/pages/Settings/components/dialogs/RestoreDialog.tsx b/packages/dashboard/src/pages/Settings/components/dialogs/RestoreDialog.tsx index fa1fed57aeb5..65665c181e97 100644 --- a/packages/dashboard/src/pages/Settings/components/dialogs/RestoreDialog.tsx +++ b/packages/dashboard/src/pages/Settings/components/dialogs/RestoreDialog.tsx @@ -1,10 +1,9 @@ import { TabList, TabPanel, TabContext, tabPanelClasses } from '@mui/lab' import { Tab, styled, tabClasses, tabsClasses, InputBase, inputBaseClasses } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { useState } from 'react' import { useAsync } from 'react-use' import ConfirmDialog from '../../../../components/ConfirmDialog' -import { MaskColorVar } from '@masknet/theme' import FileUpload from '../../../../components/FileUpload' import { Services } from '../../../../API' import BackupPreviewCard from '../BackupPreviewCard' diff --git a/packages/dashboard/src/pages/Settings/components/dialogs/SettingEmailDialog.tsx b/packages/dashboard/src/pages/Settings/components/dialogs/SettingEmailDialog.tsx index f4c0506d67c0..ad9fe0afcd39 100644 --- a/packages/dashboard/src/pages/Settings/components/dialogs/SettingEmailDialog.tsx +++ b/packages/dashboard/src/pages/Settings/components/dialogs/SettingEmailDialog.tsx @@ -1,7 +1,7 @@ import ConfirmDialog from '../../../../components/ConfirmDialog' import { useContext, useEffect, useRef, useState } from 'react' import { Box, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, CountdownButton, MaskTextField, useCustomSnackbar } from '@masknet/theme' import { UserContext } from '../../hooks/UserContext' import { useDashboardI18N } from '../../../../locales' import { @@ -15,7 +15,6 @@ import { VerifyCodeRequest, } from '../../api' import { emailRegexp } from '../../regexp' -import { CountdownButton, MaskTextField, useCustomSnackbar } from '@masknet/theme' import { Locale, Scenario, AccountType } from '../../type' import { decryptBackup, encryptBackup } from '@masknet/backup-format' import { encode } from '@msgpack/msgpack' diff --git a/packages/dashboard/src/pages/Settings/components/dialogs/SettingPasswordDialog.tsx b/packages/dashboard/src/pages/Settings/components/dialogs/SettingPasswordDialog.tsx index 7e233e993ec5..c088941f6923 100644 --- a/packages/dashboard/src/pages/Settings/components/dialogs/SettingPasswordDialog.tsx +++ b/packages/dashboard/src/pages/Settings/components/dialogs/SettingPasswordDialog.tsx @@ -37,6 +37,8 @@ export default function SettingPasswordDialog({ open, onClose, onSet }: SettingP } } + if (!newPassword) return setValidState(false) + const matched = newPassword === repeatPassword setMatchState(matched) @@ -53,8 +55,6 @@ export default function SettingPasswordDialog({ open, onClose, onSet }: SettingP } const validCheck = () => { - if (!newPassword) return - const isValid = passwordRegexp.test(newPassword) setValidState(isValid) } diff --git a/packages/dashboard/src/pages/Settings/components/dialogs/SettingPhoneNumberDialog.tsx b/packages/dashboard/src/pages/Settings/components/dialogs/SettingPhoneNumberDialog.tsx index 258495c61d85..198bebd24379 100644 --- a/packages/dashboard/src/pages/Settings/components/dialogs/SettingPhoneNumberDialog.tsx +++ b/packages/dashboard/src/pages/Settings/components/dialogs/SettingPhoneNumberDialog.tsx @@ -1,11 +1,10 @@ import ConfirmDialog from '../../../../components/ConfirmDialog' import { useContext, useEffect, useRef, useState } from 'react' import { Box, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, CountdownButton, MaskTextField, useCustomSnackbar } from '@masknet/theme' import { UserContext } from '../../hooks/UserContext' import { useDashboardI18N } from '../../../../locales' import { phoneRegexp } from '../../regexp' -import { CountdownButton, MaskTextField, useCustomSnackbar } from '@masknet/theme' import { Scenario, Locale, AccountType } from '../../type' import { fetchBackupValue, diff --git a/packages/dashboard/src/pages/SignIn/index.tsx b/packages/dashboard/src/pages/SignIn/index.tsx index d85eea001f7a..2eefa8078c71 100644 --- a/packages/dashboard/src/pages/SignIn/index.tsx +++ b/packages/dashboard/src/pages/SignIn/index.tsx @@ -2,11 +2,10 @@ import { RowLayout } from '../../components/RegisterFrame/RowLayout' import { Header } from '../../components/RegisterFrame/ColumnContentHeader' import { useDashboardI18N } from '../../locales' import { Body, ColumnContentLayout } from '../../components/RegisterFrame/ColumnContentLayout' -import { useNavigate } from 'react-router-dom' +import { useNavigate, useLocation } from 'react-router-dom' import { DashboardRoutes } from '@masknet/shared-base' import { Restore } from '../../components/Restore' import { UserProvider } from '../Settings/hooks/UserContext' -import { useLocation } from 'react-router-dom' import { useMemo } from 'react' import type { Search } from 'history' diff --git a/packages/dashboard/src/pages/SignUp/steps/PersonaCreate.tsx b/packages/dashboard/src/pages/SignUp/steps/PersonaCreate.tsx index 75b9c15a4c55..2cf50b31f80f 100644 --- a/packages/dashboard/src/pages/SignUp/steps/PersonaCreate.tsx +++ b/packages/dashboard/src/pages/SignUp/steps/PersonaCreate.tsx @@ -8,12 +8,11 @@ import { PersonaLogoBox, SignUpAccountLogo, } from '../../../components/RegisterFrame/ColumnContentLayout' -import { DashboardRoutes } from '@masknet/shared-base' +import { DashboardRoutes, delay } from '@masknet/shared-base' import { Header } from '../../../components/RegisterFrame/ColumnContentHeader' import { Box, Typography } from '@mui/material' import { useDashboardI18N } from '../../../locales' import { SignUpRoutePath } from '../routePath' -import { delay } from '@masknet/shared-base' import { useCreatePersonaByPrivateKey, useCreatePersonaV2 } from '../../../hooks/useCreatePersonaV2' import { Services } from '../../../API' import { ButtonContainer } from '../../../components/RegisterFrame/ButtonContainer' diff --git a/packages/dashboard/src/pages/Wallets/components/CollectiblePlaceHolder/index.tsx b/packages/dashboard/src/pages/Wallets/components/CollectiblePlaceHolder/index.tsx index 087b572dda82..99430c67191f 100644 --- a/packages/dashboard/src/pages/Wallets/components/CollectiblePlaceHolder/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/CollectiblePlaceHolder/index.tsx @@ -1,7 +1,6 @@ import { memo } from 'react' import { MiniMaskIcon } from '@masknet/icons' -import { makeStyles } from '@masknet/theme' -import { MaskColorVar } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { useDashboardI18N } from '../../../../locales' import { WalletIcon } from '@masknet/shared' import { Box } from '@mui/material' diff --git a/packages/dashboard/src/pages/Wallets/components/CreateWallet/index.tsx b/packages/dashboard/src/pages/Wallets/components/CreateWallet/index.tsx index 0796e6c2c69d..20058b0de2a1 100644 --- a/packages/dashboard/src/pages/Wallets/components/CreateWallet/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/CreateWallet/index.tsx @@ -1,6 +1,5 @@ import { Button, styled, FilledInput, Tab, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { ButtonGroupTabList, MaskColorVar, useTabs } from '@masknet/theme' +import { makeStyles, ButtonGroupTabList, MaskColorVar, useTabs } from '@masknet/theme' import { memo } from 'react' import { RefreshIcon } from '@masknet/icons' import { MnemonicReveal } from '../../../../components/Mnemonic' diff --git a/packages/dashboard/src/pages/Wallets/components/EmptyPlaceholder/index.tsx b/packages/dashboard/src/pages/Wallets/components/EmptyPlaceholder/index.tsx index 8f13e4bd5a96..11136b1d3543 100644 --- a/packages/dashboard/src/pages/Wallets/components/EmptyPlaceholder/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/EmptyPlaceholder/index.tsx @@ -1,8 +1,7 @@ import { memo } from 'react' import { Box, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { EmptyIcon } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' const useStyles = makeStyles()((theme) => ({ container: { diff --git a/packages/dashboard/src/pages/Wallets/components/HistoryTableRow/index.tsx b/packages/dashboard/src/pages/Wallets/components/HistoryTableRow/index.tsx index 16d693d446e9..ab6493d531f1 100644 --- a/packages/dashboard/src/pages/Wallets/components/HistoryTableRow/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/HistoryTableRow/index.tsx @@ -2,7 +2,7 @@ import { memo, useMemo } from 'react' import formatDateTime from 'date-fns/format' import type { Transaction } from '@masknet/web3-shared-evm' import { Box, TableCell, TableRow, Typography, Link, Stack } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, MaskColorVar } from '@masknet/theme' import { ChainId, DebankTransactionDirection, @@ -11,7 +11,6 @@ import { } from '@masknet/web3-shared-evm' import { TransactionIcon } from '../TransactionIcon' import { LinkOutIcon } from '@masknet/icons' -import { MaskColorVar } from '@masknet/theme' import classNames from 'classnames' import { useReverseAddress, useWeb3State } from '@masknet/plugin-infra' diff --git a/packages/dashboard/src/pages/Wallets/components/ImportWallet/index.tsx b/packages/dashboard/src/pages/Wallets/components/ImportWallet/index.tsx index d234d34ba3bb..85cc1be1e553 100644 --- a/packages/dashboard/src/pages/Wallets/components/ImportWallet/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/ImportWallet/index.tsx @@ -1,7 +1,6 @@ import { memo } from 'react' import { Button, styled, FilledInput, Tab } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { ButtonGroupTabList, useTabs } from '@masknet/theme' +import { makeStyles, ButtonGroupTabList, useTabs } from '@masknet/theme' import { DesktopMnemonicConfirm } from '../../../../components/Mnemonic' import { MaskAlert } from '../../../../components/MaskAlert' import { useDashboardI18N } from '../../../../locales' diff --git a/packages/dashboard/src/pages/Wallets/components/ReceiveDialog/index.tsx b/packages/dashboard/src/pages/Wallets/components/ReceiveDialog/index.tsx index cd8edde47a3d..5c7b3779d18d 100644 --- a/packages/dashboard/src/pages/Wallets/components/ReceiveDialog/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/ReceiveDialog/index.tsx @@ -1,9 +1,8 @@ import { memo } from 'react' import { useDashboardI18N } from '../../../../locales' -import { MaskColorVar, MaskDialog } from '@masknet/theme' +import { MaskColorVar, MaskDialog, makeStyles } from '@masknet/theme' import { QRCode, useSnackbarCallback } from '@masknet/shared' import { DialogContent, Typography, DialogActions, Button } from '@mui/material' -import { makeStyles } from '@masknet/theme' import { WalletQRCodeContainer } from '../../../../components/WalletQRCodeContainer' import { useCopyToClipboard } from 'react-use' import { useCurrentSelectedWalletNetwork } from '../../api' diff --git a/packages/dashboard/src/pages/Wallets/components/Transfer/SelectNFTList.tsx b/packages/dashboard/src/pages/Wallets/components/Transfer/SelectNFTList.tsx index 47b154150fd6..b5ec4fd32f85 100644 --- a/packages/dashboard/src/pages/Wallets/components/Transfer/SelectNFTList.tsx +++ b/packages/dashboard/src/pages/Wallets/components/Transfer/SelectNFTList.tsx @@ -1,9 +1,8 @@ import type { ERC721TokenDetailed } from '@masknet/web3-shared-evm' import { memo, useMemo } from 'react' -import { Box, ImageList, Typography } from '@mui/material' +import { Box, ImageList, Typography, ImageListItem, Stack } from '@mui/material' import { NFTCard } from './NFTCard' import { LoadingAnimation } from '@masknet/shared' -import { ImageListItem, Stack } from '@mui/material' import { MaskColorVar } from '@masknet/theme' import { useDashboardI18N } from '../../../../locales' diff --git a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx index 8dc3d2ccc51d..c0e7afaf202f 100644 --- a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx +++ b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx @@ -246,6 +246,7 @@ export const TransferERC20 = memo(({ token }) => { if (!!ensContent) setPopoverOpen(true) setMinPopoverWidth(event.currentTarget.clientWidth) }, + spellCheck: false, }} onChange={(e) => setAddress(e.currentTarget.value)} label={t.wallets_transfer_to_address()} diff --git a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx index c269f742ef1b..2eddd864d454 100644 --- a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx +++ b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx @@ -313,6 +313,7 @@ export const TransferERC721 = memo(() => { if (!!ensContent) setPopoverOpen(true) setMinPopoverWidth(event.currentTarget.clientWidth) }, + spellCheck: false, }} label={t.wallets_transfer_to_address()} /> diff --git a/packages/encryption/tests/v37-encode-decode.ts b/packages/encryption/tests/v37-encode-decode.ts index fb6a64586718..091823ceae3d 100644 --- a/packages/encryption/tests/v37-encode-decode.ts +++ b/packages/encryption/tests/v37-encode-decode.ts @@ -1,10 +1,9 @@ -import './setup' +import { ECDH_K256_PublicKey } from './setup' import { test, expect } from '@jest/globals' import { None, Some } from 'ts-results' import { encodePayload, AESAlgorithmEnum, parsePayload, PayloadWellFormed, PublicKeyAlgorithmEnum } from '../src' import { importAESFromJWK, importAsymmetryKeyFromJsonWebKeyOrSPKI } from '../src/utils' import { ProfileIdentifier } from '@masknet/shared-base' -import { ECDH_K256_PublicKey } from './setup' test('Encode v37 payload', async () => { const payload: PayloadWellFormed.Payload = { diff --git a/packages/encryption/tests/v38-encode-decode.ts b/packages/encryption/tests/v38-encode-decode.ts index 945b21eef682..b7f1c281e6d9 100644 --- a/packages/encryption/tests/v38-encode-decode.ts +++ b/packages/encryption/tests/v38-encode-decode.ts @@ -1,10 +1,9 @@ -import './setup' +import { ECDH_K256_PublicKey } from './setup' import { expect, test } from '@jest/globals' import { AESAlgorithmEnum, encodePayload, parsePayload, PayloadWellFormed, PublicKeyAlgorithmEnum } from '../src' import { None, Some } from 'ts-results' import { ProfileIdentifier } from '@masknet/shared-base' import { importAESFromJWK, importAsymmetryKeyFromJsonWebKeyOrSPKI } from '../src/utils' -import { ECDH_K256_PublicKey } from './setup' test('Parse v38 encoded by old infra', async () => { const out = (await parsePayload(oldInfraOutput)).unwrap() diff --git a/packages/mask/package.json b/packages/mask/package.json index 14cec084f721..bbd54c6f6c2b 100644 --- a/packages/mask/package.json +++ b/packages/mask/package.json @@ -17,7 +17,7 @@ "@masknet/dashboard": "workspace:*", "@masknet/encryption": "workspace:^0.0.0", "@masknet/external-plugin-previewer": "workspace:*", - "@masknet/gun-utils": "workspace:^undefined", + "@masknet/gun-utils": "workspace:*", "@masknet/icons": "workspace:*", "@masknet/injected-script": "workspace:*", "@masknet/plugin-dao": "workspace:*", diff --git a/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx b/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx index ed145e6085fd..801716b65111 100644 --- a/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx +++ b/packages/mask/src/components/CompositionDialog/TypedMessageEditor.tsx @@ -7,9 +7,7 @@ import { } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' import { InputBase, Alert, Button } from '@mui/material' -import { useCallback, useImperativeHandle, useState } from 'react' -import { useRef } from 'react' -import { forwardRef, memo } from 'react' +import { useCallback, useImperativeHandle, useState, useRef, forwardRef, memo } from 'react' import { useI18N } from '../../utils' import { BadgeRenderer } from './BadgeRenderer' diff --git a/packages/mask/src/components/InjectedComponents/AutoPasteFailedDialog.tsx b/packages/mask/src/components/InjectedComponents/AutoPasteFailedDialog.tsx index 1f5bafc168fb..556d81792cfd 100644 --- a/packages/mask/src/components/InjectedComponents/AutoPasteFailedDialog.tsx +++ b/packages/mask/src/components/InjectedComponents/AutoPasteFailedDialog.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { useCopyToClipboard } from 'react-use' import { useI18N, useMatchXS, useQueryNavigatorPermission } from '../../utils' import formatDateTime from 'date-fns/format' -import { makeStyles, useStylesExtends } from '@masknet/theme' +import { makeStyles, useStylesExtends, useCustomSnackbar } from '@masknet/theme' import { DialogActions, DialogContent, @@ -18,7 +18,6 @@ import { } from '@mui/material' import type { AutoPasteFailedEvent } from '@masknet/shared-base' import { Image } from '../shared/Image' -import { useCustomSnackbar } from '@masknet/theme' import { DraggableDiv } from '../shared/DraggableDiv' import Download from '@mui/icons-material/CloudDownload' import CloseIcon from '@mui/icons-material/Close' diff --git a/packages/mask/src/components/shared/DebugMetadataInspector.tsx b/packages/mask/src/components/shared/DebugMetadataInspector.tsx index 56f81153eaf7..14981f00ae7d 100644 --- a/packages/mask/src/components/shared/DebugMetadataInspector.tsx +++ b/packages/mask/src/components/shared/DebugMetadataInspector.tsx @@ -10,10 +10,10 @@ import { DialogContent, TextField, Typography, + Autocomplete, } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { InjectedDialog } from './InjectedDialog' -import { Autocomplete } from '@mui/material' import { isDataMatchJSONSchema, getKnownMetadataKeys, getMetadataSchema } from '@masknet/shared-base' import { ShadowRootPopper } from '../../utils/shadow-root/ShadowRootComponents' import { useState } from 'react' diff --git a/packages/mask/src/extension/options-page/DashboardComponents/ActionButton.tsx b/packages/mask/src/extension/options-page/DashboardComponents/ActionButton.tsx index d3f81ac1d162..bec6832b90c0 100644 --- a/packages/mask/src/extension/options-page/DashboardComponents/ActionButton.tsx +++ b/packages/mask/src/extension/options-page/DashboardComponents/ActionButton.tsx @@ -6,7 +6,7 @@ import CheckIcon from '@mui/icons-material/Check' import ErrorIcon from '@mui/icons-material/Error' import { red, green } from '@mui/material/colors' import classNames from 'classnames' -import { useDebounce, useAsyncFn } from 'react-use' +import { useDebounce, useAsyncFn, useUpdateEffect } from 'react-use' import { useErrorStyles } from '../../../utils/theme' const circle = @@ -87,7 +87,7 @@ export interface ActionButtonPromiseProps extends ButtonProps { completeOnClick?: 'use executor' | (() => void) waiting: React.ReactChild waitingOnClick?: () => ActionButtonPromiseState - failed: React.ReactChild + failed?: React.ReactChild failedOnClick?: 'use executor' | (() => void) completeIcon?: React.ReactNode failIcon?: React.ReactNode @@ -122,7 +122,10 @@ export function ActionButtonPromise(props: ActionButtonPromiseProps) { setState('complete') onComplete?.() }, - () => setState('fail'), + (error) => { + if (error.message.includes('Switch Chain Error')) setState('init') + else setState('fail') + }, ) } const cancel = () => { @@ -131,6 +134,11 @@ export function ActionButtonPromise(props: ActionButtonPromiseProps) { } const completeClick = completeOnClick === 'use executor' ? run : completeOnClick const failClick = failedOnClick === 'use executor' ? run : failedOnClick + + useUpdateEffect(() => { + setState((prev) => (prev === 'init' ? prev : 'init')) + }, [executor]) + if (state === 'wait') return