Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/slimy-cows-lose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@masknet/typed-message-react': minor
---

Require React.use now
473 changes: 214 additions & 259 deletions eslint.config.js

Large diffs are not rendered by default.

27 changes: 13 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "module",
"packageManager": "pnpm@11.0.8",
"engines": {
"node": ">=23.6.0",
"node": ">=24",
"yarn": ">=999.0.0",
"npm": ">=999.0.0"
},
Expand Down Expand Up @@ -59,40 +59,39 @@
"@changesets/cli": "^2.31.0",
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@eslint-react/eslint-plugin": "^1.52.3",
"@eslint/compat": "^1.3.1",
"@eslint-react/eslint-plugin": "^5.14.1",
"@eslint/js": "^10.0.1",
"@lingui/cli": "^6.5.0",
"@lingui/format-po": "^6.5.0",
"@lingui/swc-plugin": "^6.5.1",
"@masknet/cli": "workspace:^",
"@masknet/config": "workspace:^",
"@masknet/eslint-plugin": "^0.4.0",
"@masknet/eslint-plugin": "^0.4.1",
"@masknet/typescript-plugin": "workspace:^",
"@nice-labs/git-rev": "^3.5.1",
"@swc/core": "1.15.43",
"@tanstack/eslint-plugin-query": "^5.83.1",
"@tanstack/eslint-plugin-query": "^5.101.2",
"@types/lodash-es": "^4.17.12",
"@typescript/native-preview": "7.0.0-dev.20260128.1",
"@vitest/ui": "^4.1.10",
"cspell": "^8.17.5",
"eslint": "9.32.0",
"eslint-formatter-junit": "^8.40.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint": "10.7.0",
"eslint-formatter-junit": "^9.0.1",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-lingui": "^0.10.1",
"eslint-plugin-lingui": "^0.14.0",
"eslint-plugin-react-compiler": "19.1.0-rc.2",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-tss-unused-classes": "^1.0.3",
"eslint-plugin-unicorn": "^60.0.0",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-tss-unused-classes": "^1.0.4",
"eslint-plugin-unicorn": "^71.1.0",
"eslint-plugin-unused-imports": "^4.4.1",
"gulp": "^5.0.0",
"husky": "^9.1.7",
"knip": "^5.45.0",
"lint-staged": "^15.4.3",
"prettier": "^3.6.2",
"svgo": "^3.3.2",
"typescript": "5.9.2",
"typescript-eslint": "^8.39.0",
"typescript-eslint": "^8.63.0",
"vite": "^6.2.0",
"vitest": "^4.1.10"
}
Expand Down
24 changes: 16 additions & 8 deletions packages/backup-format/src/utils/backupPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,29 @@ export function getBackupSummary(json: NormalizedBackup.Data): BackupSummary {

try {
files = Number((json.plugins['com.maskbook.fileservice'] as any)?.length || 0)
} catch {}
} catch {
// ignore
}

const ownerPersonas = [...json.personas.values()].filter((persona) => !persona.privateKey.isNone())
const ownerProfiles = flatten(ownerPersonas.map((persona) => [...persona.linkedProfiles.keys()])).map((item) =>
item.toText(),
const ownerPersonas = json.personas
.values()
.filter((persona) => !persona.privateKey.isNone())
.toArray()
const ownerProfiles = new Set(
flatten(ownerPersonas.map((persona) => persona.linkedProfiles.keys().toArray())).map((item) => item.toText()),
)

const personas = compact(
ownerPersonas
.sort((p) => (p.nickname.unwrapOr(false) ? -1 : 0))
.toSorted((p) => (p.nickname.unwrapOr(false) ? -1 : 0))
.map((p) => p.nickname.unwrapOr(p.identifier.rawPublicKey).trim()),
)
const contacts = [...json.profiles.values()].filter((profile) => {
return !ownerProfiles.includes(profile.identifier.toText()) && profile.linkedPersona.isSome()
})
const contacts = json.profiles
.values()
.filter((profile) => {
return !ownerProfiles.has(profile.identifier.toText()) && profile.linkedPersona.isSome()
})
.toArray()
return {
// Names or publicKeys */
personas,
Expand Down
4 changes: 2 additions & 2 deletions packages/backup-format/src/utils/hex2buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ function concat(...buf: Array<Uint8Array | number[]>) {
const res = new Uint8Array(sum(buf.map((item) => item.length)))
let offset = 0
buf.forEach((item) => {
for (let i = 0; i < item.length; i += 1) {
res[offset + i] = item[i]
for (const [i, element] of item.entries()) {
res[offset + i] = element
}
offset += item.length
})
Expand Down
3 changes: 1 addition & 2 deletions packages/backup-format/src/version-0/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export function isBackupVersion0(obj: unknown): obj is BackupJSONFileVersion0 {
if (!isObjectLike(obj)) return false
try {
const data: BackupJSONFileVersion0 = obj as any
if (!data.local || !data.key?.key?.privateKey || !data.key.key.publicKey) return false
return true
return !(!data.local || !data.key?.key?.privateKey || !data.key.key.publicKey)
} catch {
return false
}
Expand Down
20 changes: 11 additions & 9 deletions packages/backup-format/src/version-2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export function isBackupVersion2(item: unknown): item is BackupJSONFileVersion2
try {
const x = item as BackupJSONFileVersion2
return x._meta_.version === 2
} catch {}
} catch {
// ignore
}
return false
}

Expand Down Expand Up @@ -89,10 +91,10 @@ export async function normalizeBackupVersion2(item: BackupJSONFileVersion2): Pro

for (const post of posts) {
const identifier = PostIVIdentifier.from(post.identifier)
if (identifier.isNone()) continue

const postBy = ProfileIdentifier.from(post.postBy)
const encryptBy = ECKeyIdentifier.from(post.encryptBy)

if (identifier.isNone()) continue
const interestedMeta = new Map<string, any>()
const normalizedPost: NormalizedBackup.PostBackup = {
identifier: identifier.value,
Expand Down Expand Up @@ -149,7 +151,7 @@ export async function normalizeBackupVersion2(item: BackupJSONFileVersion2): Pro
const key = ec.keyFromPrivate(wallet.privateKey.d)
const hexPub = key.getPublic('hex').slice(2)
const hexX = hexPub.slice(0, hexPub.length / 2)
const hexY = hexPub.slice(hexPub.length / 2, hexPub.length)
const hexY = hexPub.slice(hexPub.length / 2)
wallet.privateKey.x = Convert.ToBase64Url(hex2buffer(hexX))
wallet.privateKey.y = Convert.ToBase64Url(hex2buffer(hexY))
}
Expand Down Expand Up @@ -204,10 +206,10 @@ export function generateBackupVersion2(item: NormalizedBackup.Data): BackupJSONF
createdAt: Number(data.createdAt.unwrapOr(now)),
updatedAt: Number(data.updatedAt.unwrapOr(now)),
nickname: data.nickname.unwrapOr(undefined),
linkedProfiles: [...data.linkedProfiles.keys()].map((id) => [
id.toText(),
{ connectionConfirmState: 'confirmed' } as LinkedProfileDetails,
]),
linkedProfiles: data.linkedProfiles
.keys()
.map((id) => [id.toText(), { connectionConfirmState: 'confirmed' }] as [string, LinkedProfileDetails])
.toArray(),
publicKey: data.publicKey,
privateKey: data.privateKey.unwrapOr(undefined),
mnemonic: data.mnemonic
Expand Down Expand Up @@ -304,7 +306,7 @@ function MetaFromJson(meta: string | undefined): Map<string, unknown> {
return new Map(Object.entries(raw))
}
function MetaToJson(meta: ReadonlyMap<string, unknown>) {
return encodeArrayBuffer(encode(Object.fromEntries(meta.entries())))
return encodeArrayBuffer(encode(Object.fromEntries(meta)))
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/backup-format/src/version-3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async function createAESFromPassword(password: Uint8Array<ArrayBuffer>) {
const pbkdf = await crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
const iv = crypto.getRandomValues(new Uint8Array(16))
const aes = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: iv, iterations: 10000, hash: 'SHA-256' },
{ name: 'PBKDF2', salt: iv, iterations: 10_000, hash: 'SHA-256' },
pbkdf,
{ name: 'AES-GCM', length: 256 },
true,
Expand All @@ -43,7 +43,7 @@ async function createAESFromPassword(password: Uint8Array<ArrayBuffer>) {
async function getAESFromPassword(password: Uint8Array<ArrayBuffer>, iv: Uint8Array<ArrayBuffer>) {
const pbkdf = await crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
const aes = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: iv, iterations: 10000, hash: 'SHA-256' },
{ name: 'PBKDF2', salt: iv, iterations: 10_000, hash: 'SHA-256' },
pbkdf,
{ name: 'AES-GCM', length: 256 },
true,
Expand Down
12 changes: 6 additions & 6 deletions packages/base/src/Identifier/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export abstract class Identifier {
return ProfileIdentifier.of(network, userID)
} else if (input.startsWith('post:')) {
const [postID, ...rest] = input.slice('post:'.length).split('/')
const inner = Identifier.from(rest.join('/'))
const inner = this.from(rest.join('/'))
if (inner.isNone()) return None
if (inner.value instanceof ProfileIdentifier) return Some(new PostIdentifier(inner.value, postID))
return None
Expand Down Expand Up @@ -71,7 +71,7 @@ export class ECKeyIdentifier extends Identifier {
static override from(input: string | null | undefined): Option<ECKeyIdentifier> {
if (!input) return None
input = String(input)
if (input.startsWith('ec_key:')) return Identifier.from(input) as Option<ECKeyIdentifier>
if (input.startsWith('ec_key:')) return super.from(input) as Option<ECKeyIdentifier>
return None
}
static fromHexPublicKeyK256(hex: string | null | undefined): Option<ECKeyIdentifier> {
Expand All @@ -92,7 +92,7 @@ export class ECKeyIdentifier extends Identifier {
if ((key.algorithm as EcKeyAlgorithm).namedCurve !== 'K-256') return Err('curve is not K-256')
const jwk = await Result.wrapAsync(() => crypto.subtle.exportKey('jwk', key))
if (jwk.isErr()) return jwk
return ECKeyIdentifier.fromJsonWebKey(jwk.value as EC_JsonWebKey)
return this.fromJsonWebKey(jwk.value as EC_JsonWebKey)
}
async toJsonWebKey(usage: 'sign_and_verify' | 'derive'): Promise<EC_Public_JsonWebKey> {
const key = await decompressK256Key(this.rawPublicKey)
Expand Down Expand Up @@ -155,7 +155,7 @@ export class PostIVIdentifier extends Identifier {
static override from(input: string | null | undefined): Option<PostIVIdentifier> {
if (!input) return None
input = String(input)
if (input.startsWith('post_iv:')) return Identifier.from(input) as Option<PostIVIdentifier>
if (input.startsWith('post_iv:')) return super.from(input) as Option<PostIVIdentifier>
return None
}
declare readonly network: string
Expand Down Expand Up @@ -210,7 +210,7 @@ export class PostIdentifier extends Identifier {
static override from(input: string | null | undefined): Option<PostIdentifier> {
if (!input) return None
input = String(input)
if (input.startsWith('post:')) return Identifier.from(input) as Option<PostIdentifier>
if (input.startsWith('post:')) return super.from(input) as Option<PostIdentifier>
return None
}
declare readonly identifier: ProfileIdentifier
Expand Down Expand Up @@ -259,7 +259,7 @@ export class ProfileIdentifier extends Identifier {
static override from(input: string | null | undefined): Option<ProfileIdentifier> {
input = String(input)
if (input === 'person:localhost/$unknown') return None
if (input.startsWith('person:')) return Identifier.from(input) as Option<ProfileIdentifier>
if (input.startsWith('person:')) return super.from(input) as Option<ProfileIdentifier>
return None
}
static of(network: string | undefined | null, userID: string | undefined | null): Option<ProfileIdentifier> {
Expand Down
11 changes: 7 additions & 4 deletions packages/base/src/Identifier/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ export function convertRawMapToIdentifierMap<T>(it: I<T>, ...of: unknown[]): Map
continue
}

if (hasProfileIdentifier && id.value instanceof A) result.set(id.value, value)
else if (hasECKeyIdentifier && id.value instanceof B) result.set(id.value, value)
else if (hasPostIdentifier && id.value instanceof C) result.set(id.value, value)
else if (hasPostIVIdentifier && id.value instanceof D) result.set(id.value, value)
if (
(hasProfileIdentifier && id.value instanceof A) ||
(hasECKeyIdentifier && id.value instanceof B) ||
(hasPostIdentifier && id.value instanceof C) ||
(hasPostIVIdentifier && id.value instanceof D)
)
result.set(id.value, value)
else droppedValues.set(key, value)
}

Expand Down
1 change: 1 addition & 0 deletions packages/base/src/WebCrypto/CryptoKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type EC_CryptoKey = EC_Private_CryptoKey | EC_Public_CryptoKey
export interface EC_Public_CryptoKey extends CryptoKey, Nominal<'EC public'> {}
export interface EC_Private_CryptoKey extends CryptoKey, Nominal<'EC private'> {}
export interface AESCryptoKey extends CryptoKey, Nominal<'AES'> {}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
declare class Nominal<T> {
/** Ghost property, don't use it! */
private __brand: T
Expand Down
7 changes: 3 additions & 4 deletions packages/base/src/WebCrypto/JsonWebKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ export interface AESJsonWebKey extends JsonWebKey, Nominal<'AES'> {}
export function isAESJsonWebKey(x: unknown): x is AESJsonWebKey {
if (typeof x !== 'object' || x === null) return false
const { alg, k, key_ops, kty } = x as JsonWebKey
if (!alg || !k || !Array.isArray(key_ops) || kty !== 'oct') return false
return true
return !(!alg || !k) && Array.isArray(key_ops) && kty === 'oct'
}
export function isEC_JsonWebKey(o: unknown): o is EC_JsonWebKey {
if (typeof o !== 'object' || o === null) return false
const { crv, key_ops, kty, x, y } = o as JsonWebKey
if (!crv || !Array.isArray(key_ops) || !kty || !x || !y) return false
return true
return !(!crv || !Array.isArray(key_ops) || !kty || !x || !y)
}
export function isEC_Public_JsonWebKey(o: unknown): o is EC_Public_JsonWebKey {
if (!isEC_JsonWebKey(o)) return false
Expand All @@ -30,6 +28,7 @@ export function isEC_Private_JsonWebKey(o: unknown): o is EC_Private_JsonWebKey
if (!isEC_JsonWebKey(o)) return false
return !!o.d
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
declare class Nominal<T> {
/** Ghost property, don't use it! */
private __brand: T
Expand Down
4 changes: 2 additions & 2 deletions packages/base/src/ts-results/CheckedError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export class CheckedError<T> extends Error {
): (...args: P) => Result<T, CheckedError<E>> | Promise<Result<T, CheckedError<E>>> {
return (...args: P) => {
const r = f(...args)
if ('then' in r) return r.then((r) => r.mapErr(CheckedError.mapErr(o)))
return r.mapErr(CheckedError.mapErr(o))
if ('then' in r) return r.then((r) => r.mapErr(this.mapErr(o)))
return r.mapErr(this.mapErr(o))
}
}
toErr() {
Expand Down
4 changes: 3 additions & 1 deletion packages/encryption/src/encryption/Decryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,9 @@ async function* parseTypedMessage(
return yield makeDecryptError(DecryptErrorReasons.PayloadDecryptedButTypedMessageBroken, { cause: _.error })
try {
report?.(_.value)
} catch {}
} catch {
// ignore
}
return yield progress(DecryptProgressKind.Success, { content: _.value })
}

Expand Down
2 changes: 1 addition & 1 deletion packages/encryption/src/encryption/DecryptionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,6 @@ export enum DecryptErrorReasons {
NoPayloadFound = '[@masknet/encryption] No payload found in this material.',
}
/** @internal */
export function makeDecryptError(message: DecryptErrorReasons, options?: ErrorOptions | undefined): DecryptError {
export function makeDecryptError(message: DecryptErrorReasons, options?: ErrorOptions): DecryptError {
return { type: DecryptProgressKind.Error, error: new Error(message, options) }
}
6 changes: 3 additions & 3 deletions packages/encryption/src/encryption/Encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ export async function encodePostKey(
.then((x) => {
// An implementation MUST NOT depend on the order of keys in a JsonWebKey.
// preferred order (used to snapshot our tests):
const ord = ['key_ops', 'ext', 'kty', 'k', 'alg']
const ord = new Set(['key_ops', 'ext', 'kty', 'k', 'alg'])
const replica_object: Record<string, unknown> = {}
const rest: Record<string, unknown> = {}
ord.forEach((k) => {
if (!(k in x)) return
replica_object[k] = (x as Record<string, unknown>)[k]
})
Object.keys(x).forEach((k) => {
if (ord.includes(k)) return
if (ord.has(k)) return
rest[k] = (x as Record<string, unknown>)[k]
})
return JSON.stringify({ ...replica_object, ...rest })
Expand All @@ -116,7 +116,7 @@ async function e2e_v37(
const { ephemeralKeys, getEphemeralKey } = createEphemeralKeysMap(io)
const ecdhResult = v37_addReceiver(true, { ...context, getEphemeralKey }, target, io)

const ownersAESKeyEncrypted = Promise.resolve().then(async () => {
const ownersAESKeyEncrypted = Promise.try(async () => {
const [, ephemeralPrivateKey] = await getEphemeralKey(authorPublic.value.algr)

// we get rid of localKey in v38
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ function getDimensionAsPNG(buf: ArrayBuffer) {

/**
* Get dimension of a JPEG image
*
* @see http://vip.sugovica.hu/Sardi/kepnezo/JPEG%20File%20Layout%20and%20Format.htm
*/
function getDimensionAsJPEG(buf: ArrayBuffer) {
const dataView = new DataView(buf)
Expand Down
2 changes: 1 addition & 1 deletion packages/encryption/src/image-steganography/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface DecodeImageOptions extends SteganographyIO {
export async function steganographyDecodeImage(image: Blob | string, options: DecodeImageOptions) {
const buffer = typeof image === 'string' ? await options.downloadImage(image) : await image.arrayBuffer()

const dimension = (await getDimensionByDOM(image).catch(() => undefined)) ?? getDimensionAsBuffer(buffer)
const dimension = (await getDimensionByDOM(image).catch(() => {})) ?? getDimensionAsBuffer(buffer)
if (!dimension) return null

const preset = findPreset(dimension)
Expand Down
Loading
Loading