Move to ESM, Vite, Vitest and Turborepo (#286)
* fix example hot module replacement * add turbo * migrate to vite * use turbo for running scripts * migrate testing to vitest * set yarn in settings.json * set noEmit in base tsconfig * update yarn.lock * move protos to src * remove relative paths from status-js * remove unused files * update declaration dir * use vite-node as a debugging runtime * fix test * unify tests * fix test case typo
This commit is contained in:
parent
36f448cb96
commit
680ce2f79b
|
@ -72,3 +72,6 @@ node_modules/
|
||||||
|
|
||||||
# Serverless directories
|
# Serverless directories
|
||||||
.serverless/
|
.serverless/
|
||||||
|
|
||||||
|
# Turborepo
|
||||||
|
.turbo
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"zxh404.vscode-proto3",
|
"zxh404.vscode-proto3",
|
||||||
"ms-vscode.hexeditor",
|
"ms-vscode.hexeditor",
|
||||||
"Orta.vscode-jest"
|
"zixuanchen.vitest-explorer",
|
||||||
|
"mikestead.dotenv"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,27 @@
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Client",
|
"name": "Debug Client",
|
||||||
"program": "${workspaceFolder}/packages/status-js/src/index.ts",
|
"autoAttachChildProcesses": true,
|
||||||
"preLaunchTask": "npm: build:status-js",
|
"skipFiles": ["<node_internals>/**"],
|
||||||
"outFiles": ["${workspaceFolder}/packages/status-js/dist/index.js"],
|
"cwd": "${workspaceFolder}/packages/status-js",
|
||||||
"sourceMaps": true,
|
"program": "${workspaceRoot}/node_modules/vite-node/dist/cli.mjs",
|
||||||
|
"args": ["src/index.ts"],
|
||||||
"smartStep": true,
|
"smartStep": true,
|
||||||
"internalConsoleOptions": "openOnSessionStart",
|
"console": "integratedTerminal",
|
||||||
"skipFiles": [
|
"sourceMaps": true
|
||||||
"${workspaceFolder}/node_modules/**/*",
|
},
|
||||||
"<node_internals>/**/*.js"
|
{
|
||||||
],
|
"type": "node",
|
||||||
"sourceMapPathOverrides": {
|
"request": "launch",
|
||||||
"packages/status-js/*": "${workspaceFolder}/packages/status-js/*"
|
"name": "Debug Test File",
|
||||||
}
|
"autoAttachChildProcesses": true,
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
|
||||||
|
"args": ["run", "${relativeFile}"],
|
||||||
|
"smartStep": true,
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"sourceMaps": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"jestRunIt.debugTestLabel": "Debug",
|
"eslint.packageManager": "yarn",
|
||||||
"jestRunIt.runTestLabel": "Run"
|
"npm.packageManager": "yarn"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
<link rel="icon" type="image/png" href="public/favicon.png" />
|
<link rel="icon" type="image/png" href="./public/favicon.png" />
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
|
@ -19,6 +19,6 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="./index.tsx"></script>
|
<script type="module" src="./src/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "community",
|
"name": "community",
|
||||||
|
"type": "module",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"source": "./index.html",
|
|
||||||
"browserslist": "> 0.5%, last 2 versions, not dead, not ios_saf < 13",
|
"browserslist": "> 0.5%, last 2 versions, not dead, not ios_saf < 13",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "parcel --https",
|
"dev": "parcel index.html --https --no-cache --open",
|
||||||
"prebuild": "rm -rf dist",
|
"prebuild": "rm -rf dist",
|
||||||
"build": "parcel build"
|
"build": "parcel build index.html"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@status-im/react": "^0.0.0",
|
"@status-im/react": "^0.0.0",
|
||||||
|
@ -17,10 +17,7 @@
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
"parcel": "^2.0.0",
|
"parcel": "^2.0.0",
|
||||||
"assert": "^2.0.0",
|
"typescript": "^4.0.0",
|
||||||
"crypto-browserify": "^3.12.0",
|
"process": "^0.11.10"
|
||||||
"process": "^0.11.10",
|
|
||||||
"stream-browserify": "^3.0.0",
|
|
||||||
"typescript": "^4.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Community } from '@status-im/react'
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
return <Community publicKey="<YOUR_COMMUNITY_KEY>" theme="light" />
|
||||||
|
}
|
|
@ -1,16 +1,7 @@
|
||||||
import React, { StrictMode } from 'react'
|
import React, { StrictMode } from 'react'
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
|
|
||||||
import { Community } from '@status-im/react'
|
import { App } from './app'
|
||||||
|
|
||||||
const App = () => {
|
|
||||||
return (
|
|
||||||
<Community
|
|
||||||
publicKey="<YOUR_COMMUNITY_KEY>"
|
|
||||||
theme="light"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<StrictMode>
|
<StrictMode>
|
|
@ -2,4 +2,5 @@ body,
|
||||||
html,
|
html,
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overscroll-behavior-y: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
|
||||||
module.exports = {
|
|
||||||
// preset: 'ts-jest',
|
|
||||||
testEnvironment: 'node',
|
|
||||||
extensionsToTreatAsEsm: ['.ts'],
|
|
||||||
preset: 'ts-jest/presets/default-esm',
|
|
||||||
globals: {
|
|
||||||
'ts-jest': {
|
|
||||||
useESM: true,
|
|
||||||
tsconfig: 'tsconfig.base.json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
moduleNameMapper: {
|
|
||||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
||||||
},
|
|
||||||
}
|
|
49
package.json
49
package.json
|
@ -1,8 +1,5 @@
|
||||||
{
|
{
|
||||||
"alias": {
|
"type": "module",
|
||||||
"protons-runtime": "./node_modules/protons-runtime/dist/src/index.js",
|
|
||||||
"uint8arraylist": "./node_modules/uint8arraylist/dist/src/index.js"
|
|
||||||
},
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
@ -11,30 +8,19 @@
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"fix": "run-s 'fix:*' && wsrun -e -c -s fix",
|
"test": "turbo run test --filter=@status-im/* -- --run",
|
||||||
"build": "wsrun -e -c -s build",
|
"dev": "turbo run dev --parallel --filter=@status-im/*",
|
||||||
"build:status-js": "yarn workspace @status-im/js build",
|
"build": "turbo run build --filter=@status-im/*",
|
||||||
"build:status-react": "yarn workspace @status-im/react build",
|
"lint": "turbo run lint --filter=@status-im/*",
|
||||||
"lint": "eslint 'packages/**/*.{ts,tsx}'",
|
"check": "turbo run check --filter=@status-im/*",
|
||||||
"lint:fix": "eslint 'packages/**/*.{ts,tsx}' --fix",
|
|
||||||
"lint:examples": "eslint 'examples/**/*.{ts,tsx}'",
|
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"typecheck": "wsrun -e -c -s typecheck",
|
"format:check": "prettier --check .",
|
||||||
"test": "jest"
|
"clean": "turbo run clean && rm -rf node_modules .parcel-cache"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.2",
|
"@tsconfig/strictest": "^1.0.1",
|
||||||
"@babel/preset-env": "^7.18.2",
|
|
||||||
"@babel/preset-typescript": "^7.17.12",
|
|
||||||
"@parcel/config-default": "^2.6.0",
|
|
||||||
"@parcel/packager-ts": "^2.6.0",
|
|
||||||
"@parcel/transformer-js": "^2.6.0",
|
|
||||||
"@parcel/transformer-react-refresh-wrap": "^2.6.0",
|
|
||||||
"@parcel/transformer-typescript-types": "^2.6.0",
|
|
||||||
"@types/jest": "^27.5.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||||
"@typescript-eslint/parser": "^5.12.0",
|
"@typescript-eslint/parser": "^5.12.0",
|
||||||
"babel-jest": "^28.1.0",
|
|
||||||
"eslint": "^8.9.0",
|
"eslint": "^8.9.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-import-resolver-node": "^0.3.6",
|
"eslint-import-resolver-node": "^0.3.6",
|
||||||
|
@ -48,14 +34,13 @@
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"jest": "^28.1.0",
|
|
||||||
"lint-staged": "^12.3.4",
|
"lint-staged": "^12.3.4",
|
||||||
"npm-run-all": "^4.1.5",
|
"prettier": "^2.7.1",
|
||||||
"parcel": "^2.6.0",
|
"turbo": "^1.3.1",
|
||||||
"prettier": "^2.5.1",
|
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"wsrun": "^5.2.4",
|
"vite": "^2.9.12",
|
||||||
"ts-jest": "^28.0.4"
|
"vite-node": "^0.16.0",
|
||||||
|
"vitest": "^0.16.0"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx,js,jsx}": [
|
"*.{ts,tsx,js,jsx}": [
|
||||||
|
@ -66,5 +51,9 @@
|
||||||
"prettier --write"
|
"prettier --write"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.17"
|
"packageManager": "yarn@1.22.17",
|
||||||
|
"alias": {
|
||||||
|
"protons-runtime": "./node_modules/protons-runtime/dist/src/index.js",
|
||||||
|
"uint8arraylist": "./node_modules/uint8arraylist/dist/src/index.js"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
"name": "@status-im/js",
|
"name": "@status-im/js",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"import": "./dist/index.es.js",
|
||||||
|
"default": "./dist/index.es.js"
|
||||||
|
},
|
||||||
|
"module": "dist/index.es.js",
|
||||||
|
"types": "dist/types/index.d.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/status-im/status-web.git",
|
"url": "https://github.com/status-im/status-web.git",
|
||||||
"directory": "packages/status-js",
|
"directory": "packages/status-js",
|
||||||
|
@ -10,32 +18,16 @@
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/status-im/status-web/issues"
|
"url": "https://github.com/status-im/status-web/issues"
|
||||||
},
|
},
|
||||||
"source": "src/index.ts",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"module": "dist/index.esm.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"targets": {
|
|
||||||
"main": {
|
|
||||||
"includeNodeModules": [
|
|
||||||
"protons-runtime",
|
|
||||||
"uint8arraylist"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "rm -rf dist",
|
"dev": "vite build --watch",
|
||||||
"dev": "parcel",
|
"build": "vite build && yarn typegen",
|
||||||
"build": "parcel build",
|
"test": "vitest",
|
||||||
"build:vite": "vite build",
|
|
||||||
"build:types": "tsc --emitDeclarationOnly",
|
|
||||||
"build:protos": "protons protos/*.proto",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
|
"typecheck": "tsc",
|
||||||
|
"typegen": "tsc --noEmit false --emitDeclarationOnly --paths null || true",
|
||||||
"format": "prettier --write src",
|
"format": "prettier --write src",
|
||||||
"clean": "rm -rf node_modules && rm -rf dist",
|
"protos": "protons protos/*.proto",
|
||||||
"proto": "run-s 'proto:*'",
|
"clean": "rm -rf dist node_modules .turbo"
|
||||||
"proto:lint": "buf lint",
|
|
||||||
"proto:build": "buf generate"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ethereum-cryptography": "^1.0.3",
|
"ethereum-cryptography": "^1.0.3",
|
||||||
|
@ -45,7 +37,6 @@
|
||||||
"protons-runtime": "^1.0.4"
|
"protons-runtime": "^1.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"protons": "^3.0.4"
|
"protons": "^3.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { keccak256 } from 'ethereum-cryptography/keccak'
|
import { keccak256 } from 'ethereum-cryptography/keccak'
|
||||||
import * as secp from 'ethereum-cryptography/secp256k1'
|
import * as secp from 'ethereum-cryptography/secp256k1'
|
||||||
import { utf8ToBytes } from 'ethereum-cryptography/utils'
|
import { utf8ToBytes } from 'ethereum-cryptography/utils'
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { Account } from './account'
|
import { Account } from './account'
|
||||||
|
|
||||||
describe('Account', () => {
|
test('should verify the signature', async () => {
|
||||||
it('should verify the signature', async () => {
|
|
||||||
const account = new Account()
|
const account = new Account()
|
||||||
|
|
||||||
const message = utf8ToBytes('123')
|
const message = utf8ToBytes('123')
|
||||||
|
@ -17,9 +17,9 @@ describe('Account', () => {
|
||||||
expect(
|
expect(
|
||||||
secp.verify(signatureWithoutRecoveryId, messageHash, account.publicKey)
|
secp.verify(signatureWithoutRecoveryId, messageHash, account.publicKey)
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not verify signature with different message', async () => {
|
test('should not verify signature with different message', async () => {
|
||||||
const account = new Account()
|
const account = new Account()
|
||||||
|
|
||||||
const message = utf8ToBytes('123')
|
const message = utf8ToBytes('123')
|
||||||
|
@ -28,5 +28,4 @@ describe('Account', () => {
|
||||||
const signature = await account.sign(utf8ToBytes('abc'))
|
const signature = await account.sign(utf8ToBytes('abc'))
|
||||||
|
|
||||||
expect(secp.verify(signature, messageHash, account.publicKey)).toBeFalsy()
|
expect(secp.verify(signature, messageHash, account.publicKey)).toBeFalsy()
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
import { PageDirection } from 'js-waku'
|
import { PageDirection } from 'js-waku'
|
||||||
|
|
||||||
|
import { containsOnlyEmoji } from '../helpers/contains-only-emoji'
|
||||||
import {
|
import {
|
||||||
AudioMessage,
|
AudioMessage,
|
||||||
ChatMessage as ChatMessageProto,
|
ChatMessage as ChatMessageProto,
|
||||||
DeleteMessage,
|
DeleteMessage,
|
||||||
EditMessage,
|
EditMessage,
|
||||||
ImageType,
|
ImageType,
|
||||||
} from '~/protos/chat-message'
|
} from '../protos/chat-message'
|
||||||
import { EmojiReaction } from '~/protos/emoji-reaction'
|
import { EmojiReaction } from '../protos/emoji-reaction'
|
||||||
|
|
||||||
import { containsOnlyEmoji } from '../helpers/contains-only-emoji'
|
|
||||||
import { generateKeyFromPassword } from '../utils/generate-key-from-password'
|
import { generateKeyFromPassword } from '../utils/generate-key-from-password'
|
||||||
import { idToContentTopic } from '../utils/id-to-content-topic'
|
import { idToContentTopic } from '../utils/id-to-content-topic'
|
||||||
import { getReactions } from './community/get-reactions'
|
import { getReactions } from './community/get-reactions'
|
||||||
|
|
||||||
import type { MessageType } from '../../protos/enums'
|
import type { CommunityChat } from '../proto/communities/v1/communities'
|
||||||
|
import type { ImageMessage } from '../protos/chat-message'
|
||||||
|
import type { MessageType } from '../protos/enums'
|
||||||
import type { Client } from './client'
|
import type { Client } from './client'
|
||||||
import type { Community } from './community/community'
|
import type { Community } from './community/community'
|
||||||
import type { Reactions } from './community/get-reactions'
|
import type { Reactions } from './community/get-reactions'
|
||||||
import type { ImageMessage } from '~/protos/chat-message'
|
|
||||||
import type { CommunityChat } from '~/src/proto/communities/v1/communities'
|
|
||||||
import type { WakuMessage } from 'js-waku'
|
import type { WakuMessage } from 'js-waku'
|
||||||
|
|
||||||
export type ChatMessage = ChatMessageProto & {
|
export type ChatMessage = ChatMessageProto & {
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
import { hexToBytes } from 'ethereum-cryptography/utils'
|
import { hexToBytes } from 'ethereum-cryptography/utils'
|
||||||
import { Waku, WakuMessage } from 'js-waku'
|
import { Waku, WakuMessage } from 'js-waku'
|
||||||
|
|
||||||
import { ApplicationMetadataMessage } from '~/protos/application-metadata-message'
|
import { ApplicationMetadataMessage } from '../protos/application-metadata-message'
|
||||||
|
|
||||||
import { Account } from './account'
|
import { Account } from './account'
|
||||||
import { Community } from './community/community'
|
import { Community } from './community/community'
|
||||||
import { handleWakuMessage } from './community/handle-waku-message'
|
import { handleWakuMessage } from './community/handle-waku-message'
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import { hexToBytes } from 'ethereum-cryptography/utils'
|
import { hexToBytes } from 'ethereum-cryptography/utils'
|
||||||
import { waku_message } from 'js-waku'
|
import { waku_message } from 'js-waku'
|
||||||
|
|
||||||
import { CommunityRequestToJoin } from '~/protos/communities'
|
import { getDifferenceByKeys } from '../../helpers/get-difference-by-keys'
|
||||||
import { MessageType } from '~/protos/enums'
|
import { getObjectsDifference } from '../../helpers/get-objects-difference'
|
||||||
import { getDifferenceByKeys } from '~/src/helpers/get-difference-by-keys'
|
import { CommunityRequestToJoin } from '../../protos/communities'
|
||||||
import { getObjectsDifference } from '~/src/helpers/get-objects-difference'
|
import { MessageType } from '../../protos/enums'
|
||||||
import { compressPublicKey } from '~/src/utils/compress-public-key'
|
import { compressPublicKey } from '../../utils/compress-public-key'
|
||||||
import { generateKeyFromPassword } from '~/src/utils/generate-key-from-password'
|
import { generateKeyFromPassword } from '../../utils/generate-key-from-password'
|
||||||
import { idToContentTopic } from '~/src/utils/id-to-content-topic'
|
import { idToContentTopic } from '../../utils/id-to-content-topic'
|
||||||
|
|
||||||
import { Chat } from '../chat'
|
import { Chat } from '../chat'
|
||||||
import { Member } from '../member'
|
import { Member } from '../member'
|
||||||
|
|
||||||
import type { Client } from '../client'
|
|
||||||
import type {
|
import type {
|
||||||
CommunityChat,
|
CommunityChat,
|
||||||
CommunityDescription,
|
CommunityDescription,
|
||||||
} from '~/src/proto/communities/v1/communities'
|
} from '../../proto/communities/v1/communities'
|
||||||
|
import type { Client } from '../client'
|
||||||
|
|
||||||
export class Community {
|
export class Community {
|
||||||
private client: Client
|
private client: Client
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { EmojiReaction } from '../../../protos/emoji-reaction'
|
import type { EmojiReaction } from '../../protos/emoji-reaction'
|
||||||
|
|
||||||
type Reaction = Exclude<`${EmojiReaction.Type}`, 'UNKNOWN_EMOJI_REACTION_TYPE'>
|
type Reaction = Exclude<`${EmojiReaction.Type}`, 'UNKNOWN_EMOJI_REACTION_TYPE'>
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { bytesToHex } from 'ethereum-cryptography/utils'
|
import { bytesToHex } from 'ethereum-cryptography/utils'
|
||||||
|
|
||||||
import { ApplicationMetadataMessage } from '../../../protos/application-metadata-message'
|
import { CommunityDescription } from '../../proto/communities/v1/communities'
|
||||||
|
import { ApplicationMetadataMessage } from '../../protos/application-metadata-message'
|
||||||
import {
|
import {
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
DeleteMessage,
|
DeleteMessage,
|
||||||
EditMessage,
|
EditMessage,
|
||||||
MessageType,
|
MessageType,
|
||||||
} from '../../../protos/chat-message'
|
} from '../../protos/chat-message'
|
||||||
import { EmojiReaction } from '../../../protos/emoji-reaction'
|
import { EmojiReaction } from '../../protos/emoji-reaction'
|
||||||
import { PinMessage } from '../../../protos/pin-message'
|
import { PinMessage } from '../../protos/pin-message'
|
||||||
import { ProtocolMessage } from '../../../protos/protocol-message'
|
import { ProtocolMessage } from '../../protos/protocol-message'
|
||||||
import { CommunityDescription } from '../../proto/communities/v1/communities'
|
|
||||||
import { payloadToId } from '../../utils/payload-to-id'
|
import { payloadToId } from '../../utils/payload-to-id'
|
||||||
import { recoverPublicKey } from '../../utils/recover-public-key'
|
import { recoverPublicKey } from '../../utils/recover-public-key'
|
||||||
import { getChatUuid } from './get-chat-uuid'
|
import { getChatUuid } from './get-chat-uuid'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import type { ChatMessage as ChatMessageProto } from '../../protos/chat-message'
|
||||||
import type { ChatMessage } from '../chat'
|
import type { ChatMessage } from '../chat'
|
||||||
import type { ChatMessage as ChatMessageProto } from '~/protos/chat-message'
|
|
||||||
|
|
||||||
export function mapChatMessage(
|
export function mapChatMessage(
|
||||||
decodedMessage: ChatMessageProto,
|
decodedMessage: ChatMessageProto,
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { containsOnlyEmoji } from './contains-only-emoji'
|
import { containsOnlyEmoji } from './contains-only-emoji'
|
||||||
|
|
||||||
describe('containsOnlyEmoji', () => {
|
test('should be truthy', () => {
|
||||||
it('should be truthy', () => {
|
|
||||||
expect(containsOnlyEmoji('💩')).toBeTruthy()
|
expect(containsOnlyEmoji('💩')).toBeTruthy()
|
||||||
expect(containsOnlyEmoji('💩💩💩💩💩💩')).toBeTruthy()
|
expect(containsOnlyEmoji('💩💩💩💩💩💩')).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be falsy', () => {
|
test('should be falsy', () => {
|
||||||
expect(containsOnlyEmoji('')).toBeFalsy()
|
expect(containsOnlyEmoji('')).toBeFalsy()
|
||||||
expect(containsOnlyEmoji(' ')).toBeFalsy()
|
expect(containsOnlyEmoji(' ')).toBeFalsy()
|
||||||
expect(containsOnlyEmoji(' 💩')).toBeFalsy()
|
expect(containsOnlyEmoji(' 💩')).toBeFalsy()
|
||||||
expect(containsOnlyEmoji('💩 ')).toBeFalsy()
|
expect(containsOnlyEmoji('💩 ')).toBeFalsy()
|
||||||
expect(containsOnlyEmoji('text 💩')).toBeFalsy()
|
expect(containsOnlyEmoji('text 💩')).toBeFalsy()
|
||||||
expect(containsOnlyEmoji('💩 text')).toBeFalsy()
|
expect(containsOnlyEmoji('💩 text')).toBeFalsy()
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { getObjectsDifference } from './get-objects-difference'
|
import { getObjectsDifference } from './get-objects-difference'
|
||||||
|
|
||||||
describe('getObjectsDifference', () => {
|
test('should return correct difference', () => {
|
||||||
it('returns correct difference', () => {
|
|
||||||
const oldObject = { a: 1, b: 2, c: 3 }
|
const oldObject = { a: 1, b: 2, c: 3 }
|
||||||
const newObject = { c: 3, d: 4, e: 5 }
|
const newObject = { c: 3, d: 4, e: 5 }
|
||||||
|
|
||||||
|
@ -12,14 +13,13 @@ describe('getObjectsDifference', () => {
|
||||||
},
|
},
|
||||||
removed: ['a', 'b'],
|
removed: ['a', 'b'],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns empty arrays for the same object', () => {
|
test('should return empty arrays for the same object', () => {
|
||||||
const object = { a: 1, b: 2, c: 3 }
|
const object = { a: 1, b: 2, c: 3 }
|
||||||
|
|
||||||
expect(getObjectsDifference(object, object)).toEqual({
|
expect(getObjectsDifference(object, object)).toEqual({
|
||||||
added: {},
|
added: {},
|
||||||
removed: [],
|
removed: [],
|
||||||
})
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
import { getPublicKey, utils } from 'ethereum-cryptography/secp256k1'
|
import * as secp from 'ethereum-cryptography/secp256k1'
|
||||||
import { bytesToHex } from 'ethereum-cryptography/utils'
|
import { bytesToHex } from 'ethereum-cryptography/utils'
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { compressPublicKey } from './compress-public-key'
|
import { compressPublicKey } from './compress-public-key'
|
||||||
|
|
||||||
describe('compressPublicKey', () => {
|
test('should return compressed public key', () => {
|
||||||
it('should return compressed public key', () => {
|
const privateKey = secp.utils.randomPrivateKey()
|
||||||
const privateKey = utils.randomPrivateKey()
|
|
||||||
|
|
||||||
const publicKey = bytesToHex(getPublicKey(privateKey))
|
const publicKey = bytesToHex(secp.getPublicKey(privateKey))
|
||||||
const compressedPublicKey = bytesToHex(getPublicKey(privateKey, true))
|
const compressedPublicKey = bytesToHex(secp.getPublicKey(privateKey, true))
|
||||||
|
|
||||||
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should accept public key with a base prefix', () => {
|
test('should accept public key with a base prefix', () => {
|
||||||
const privateKey = utils.randomPrivateKey()
|
const privateKey = secp.utils.randomPrivateKey()
|
||||||
|
|
||||||
const publicKey = '0x' + bytesToHex(getPublicKey(privateKey))
|
const publicKey = '0x' + bytesToHex(secp.getPublicKey(privateKey))
|
||||||
const compressedPublicKey = bytesToHex(getPublicKey(privateKey, true))
|
const compressedPublicKey = bytesToHex(secp.getPublicKey(privateKey, true))
|
||||||
|
|
||||||
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error if public key is not a valid hex', () => {
|
test('should throw error if public key is not a valid hex', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
compressPublicKey('not a valid public key')
|
compressPublicKey('not a valid public key')
|
||||||
}).toThrowErrorMatchingInlineSnapshot(`"Invalid public key"`)
|
}).toThrowErrorMatchingInlineSnapshot(`"Invalid public key"`)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { bytesToHex } from 'ethereum-cryptography/utils'
|
import { bytesToHex } from 'ethereum-cryptography/utils'
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { generateKeyFromPassword } from './generate-key-from-password'
|
import { generateKeyFromPassword } from './generate-key-from-password'
|
||||||
|
|
||||||
describe('createSymKeyFromPassword', () => {
|
test('should create symmetric key from password', async () => {
|
||||||
it('should create symmetric key from password', async () => {
|
|
||||||
const password = 'arbitrary data here'
|
const password = 'arbitrary data here'
|
||||||
const symKey = await generateKeyFromPassword(password)
|
const symKey = await generateKeyFromPassword(password)
|
||||||
|
|
||||||
expect(bytesToHex(symKey)).toEqual(
|
expect(bytesToHex(symKey)).toEqual(
|
||||||
'c49ad65ebf2a7b7253bf400e3d27719362a91b2c9b9f54d50a69117021666c33'
|
'c49ad65ebf2a7b7253bf400e3d27719362a91b2c9b9f54d50a69117021666c33'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should generate symmetric key from chat ID', async () => {
|
test('should generate symmetric key from chat ID', async () => {
|
||||||
const chatId =
|
const chatId =
|
||||||
'0x02dcec6041fb999d65f1d33363e08c93d3c1f6f0fbbb26add383e2cf46c2b921f41dc14fd8-9a8b-4df5-a358-2c3067be5439'
|
'0x02dcec6041fb999d65f1d33363e08c93d3c1f6f0fbbb26add383e2cf46c2b921f41dc14fd8-9a8b-4df5-a358-2c3067be5439'
|
||||||
const symKey = await generateKeyFromPassword(chatId)
|
const symKey = await generateKeyFromPassword(chatId)
|
||||||
|
@ -20,5 +20,4 @@ describe('createSymKeyFromPassword', () => {
|
||||||
expect(bytesToHex(symKey)).toEqual(
|
expect(bytesToHex(symKey)).toEqual(
|
||||||
'76ff5bf0a74a8e724367c7fc003f066d477641f468768a8da2817addf5c2ce76'
|
'76ff5bf0a74a8e724367c7fc003f066d477641f468768a8da2817addf5c2ce76'
|
||||||
)
|
)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { generateUsername } from './generate-username'
|
import { generateUsername } from './generate-username'
|
||||||
|
|
||||||
describe('generateUsername', () => {
|
test('should generate the username', () => {
|
||||||
it('should generate the username', async () => {
|
|
||||||
const publicKey1 =
|
const publicKey1 =
|
||||||
'0x04eedbaafd6adf4a9233a13e7b1c3c14461fffeba2e9054b8d456ce5f6ebeafadcbf3dce3716253fbc391277fa5a086b60b283daf61fb5b1f26895f456c2f31ae3'
|
'0x04eedbaafd6adf4a9233a13e7b1c3c14461fffeba2e9054b8d456ce5f6ebeafadcbf3dce3716253fbc391277fa5a086b60b283daf61fb5b1f26895f456c2f31ae3'
|
||||||
expect(generateUsername(publicKey1)).toBe('Darkorange Blue Bubblefish')
|
expect(generateUsername(publicKey1)).toBe('Darkorange Blue Bubblefish')
|
||||||
|
@ -13,5 +14,4 @@ describe('generateUsername', () => {
|
||||||
const publicKey3 =
|
const publicKey3 =
|
||||||
'0x0403aeff2fdd0044b136e06afa6d69bb563bb7b3fd518bb30c0d5115a2e020840a2247966c2cc9953ed02cc391e8883b3319f63a31e5f5369d0fb72b62b23dfcbd'
|
'0x0403aeff2fdd0044b136e06afa6d69bb563bb7b3fd518bb30c0d5115a2e020840a2247966c2cc9953ed02cc391e8883b3319f63a31e5f5369d0fb72b62b23dfcbd'
|
||||||
expect(generateUsername(publicKey3)).toBe('Back Careful Cuckoo')
|
expect(generateUsername(publicKey3)).toBe('Back Careful Cuckoo')
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { idToContentTopic } from './id-to-content-topic'
|
import { idToContentTopic } from './id-to-content-topic'
|
||||||
|
|
||||||
describe('idToContentTopic', () => {
|
test('should return content topic', () => {
|
||||||
it('should return content topic', () => {
|
|
||||||
expect(idToContentTopic).toBeDefined()
|
expect(idToContentTopic).toBeDefined()
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hexToColorHash,
|
hexToColorHash,
|
||||||
publicKeyToColorHash,
|
publicKeyToColorHash,
|
||||||
} from './public-key-to-color-hash'
|
} from './public-key-to-color-hash'
|
||||||
|
|
||||||
test('returns color hash from public key', () => {
|
test('should return color hash from public key', () => {
|
||||||
expect(
|
expect(
|
||||||
publicKeyToColorHash(
|
publicKeyToColorHash(
|
||||||
'0x04e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
'0x04e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
||||||
|
@ -23,29 +25,29 @@ test('returns color hash from public key', () => {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('returns undefined for invalid public keys', () => {
|
test('should throw for invalid public keys', () => {
|
||||||
expect(publicKeyToColorHash('abc')).toBeUndefined()
|
expect(() => publicKeyToColorHash('abc')).toThrow()
|
||||||
expect(publicKeyToColorHash('0x01')).toBeUndefined()
|
expect(() => publicKeyToColorHash('0x01')).toThrow()
|
||||||
expect(
|
expect(() =>
|
||||||
publicKeyToColorHash(
|
publicKeyToColorHash(
|
||||||
'0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
'0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
||||||
)
|
)
|
||||||
).toBeUndefined()
|
).toThrow()
|
||||||
expect(
|
expect(() =>
|
||||||
publicKeyToColorHash(
|
publicKeyToColorHash(
|
||||||
'0x04425da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
'0x04425da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
||||||
)
|
)
|
||||||
).toBeUndefined()
|
).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('returns color hash from hex', () => {
|
test('should return color hash from hex', () => {
|
||||||
expect(hexToColorHash('0', 4, 4)).toEqual([[1, 0]])
|
expect(hexToColorHash('0', 4, 4)).toEqual([[1, 0]])
|
||||||
expect(hexToColorHash('1', 4, 4)).toEqual([[1, 1]])
|
expect(hexToColorHash('1', 4, 4)).toEqual([[1, 1]])
|
||||||
expect(hexToColorHash('4', 4, 4)).toEqual([[2, 0]])
|
expect(hexToColorHash('4', 4, 4)).toEqual([[2, 0]])
|
||||||
expect(hexToColorHash('F', 4, 4)).toEqual([[4, 3]])
|
expect(hexToColorHash('F', 4, 4)).toEqual([[4, 3]])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('returns color hash from hex with redecued collision resistance', () => {
|
test('should return color hash from hex with reduced collision resistance', () => {
|
||||||
expect(hexToColorHash('FF', 4, 4)).toEqual([
|
expect(hexToColorHash('FF', 4, 4)).toEqual([
|
||||||
[4, 3],
|
[4, 3],
|
||||||
[4, 0],
|
[4, 0],
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils'
|
import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils'
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import { Account } from '../client/account'
|
import { Account } from '../client/account'
|
||||||
import { recoverPublicKey } from './recover-public-key'
|
import { recoverPublicKey } from './recover-public-key'
|
||||||
|
|
||||||
import type { ApplicationMetadataMessage } from '../../protos/application-metadata-message'
|
import type { ApplicationMetadataMessage } from '../protos/application-metadata-message'
|
||||||
|
|
||||||
describe('recoverPublicKey', () => {
|
test('should recover public key', async () => {
|
||||||
it('should recover public key', async () => {
|
|
||||||
const payload = utf8ToBytes('hello')
|
const payload = utf8ToBytes('hello')
|
||||||
|
|
||||||
const account = new Account()
|
const account = new Account()
|
||||||
|
@ -15,39 +15,38 @@ describe('recoverPublicKey', () => {
|
||||||
expect(bytesToHex(recoverPublicKey(signature, payload))).toEqual(
|
expect(bytesToHex(recoverPublicKey(signature, payload))).toEqual(
|
||||||
account.publicKey
|
account.publicKey
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should recover public key from fixture', async () => {
|
test('should recover public key from fixture', async () => {
|
||||||
const metadataFixture: ApplicationMetadataMessage = {
|
const metadataFixture: ApplicationMetadataMessage = {
|
||||||
type: 'TYPE_EMOJI_REACTION' as ApplicationMetadataMessage.Type,
|
type: 'TYPE_EMOJI_REACTION' as ApplicationMetadataMessage.Type,
|
||||||
signature: new Uint8Array([
|
signature: new Uint8Array([
|
||||||
250, 132, 234, 119, 159, 124, 98, 93, 197, 108, 99, 52, 186, 234, 142,
|
250, 132, 234, 119, 159, 124, 98, 93, 197, 108, 99, 52, 186, 234, 142,
|
||||||
101, 147, 180, 50, 190, 102, 61, 219, 189, 95, 124, 29, 74, 43, 46, 106,
|
101, 147, 180, 50, 190, 102, 61, 219, 189, 95, 124, 29, 74, 43, 46, 106,
|
||||||
108, 102, 234, 77, 209, 130, 140, 87, 96, 210, 34, 11, 115, 56, 98, 223,
|
108, 102, 234, 77, 209, 130, 140, 87, 96, 210, 34, 11, 115, 56, 98, 223,
|
||||||
154, 30, 239, 23, 197, 243, 196, 248, 63, 162, 20, 108, 84, 250, 150,
|
154, 30, 239, 23, 197, 243, 196, 248, 63, 162, 20, 108, 84, 250, 150, 230,
|
||||||
230, 129, 0,
|
129, 0,
|
||||||
]),
|
]),
|
||||||
payload: new Uint8Array([
|
payload: new Uint8Array([
|
||||||
8, 138, 245, 146, 158, 148, 48, 18, 104, 48, 120, 48, 50, 57, 102, 49,
|
8, 138, 245, 146, 158, 148, 48, 18, 104, 48, 120, 48, 50, 57, 102, 49, 57,
|
||||||
57, 54, 98, 98, 102, 101, 102, 52, 102, 97, 54, 97, 53, 101, 98, 56, 49,
|
54, 98, 98, 102, 101, 102, 52, 102, 97, 54, 97, 53, 101, 98, 56, 49, 100,
|
||||||
100, 100, 56, 48, 50, 49, 51, 51, 97, 54, 51, 52, 57, 56, 51, 50, 53,
|
100, 56, 48, 50, 49, 51, 51, 97, 54, 51, 52, 57, 56, 51, 50, 53, 52, 52,
|
||||||
52, 52, 53, 99, 97, 49, 97, 102, 49, 100, 49, 53, 52, 98, 49, 98, 98,
|
53, 99, 97, 49, 97, 102, 49, 100, 49, 53, 52, 98, 49, 98, 98, 52, 53, 52,
|
||||||
52, 53, 52, 50, 57, 53, 53, 49, 51, 51, 51, 48, 56, 48, 52, 101, 97, 55,
|
50, 57, 53, 53, 49, 51, 51, 51, 48, 56, 48, 52, 101, 97, 55, 45, 98, 100,
|
||||||
45, 98, 100, 54, 54, 45, 52, 100, 53, 100, 45, 57, 49, 101, 98, 45, 98,
|
54, 54, 45, 52, 100, 53, 100, 45, 57, 49, 101, 98, 45, 98, 50, 100, 99,
|
||||||
50, 100, 99, 102, 101, 50, 53, 49, 53, 98, 51, 26, 66, 48, 120, 53, 97,
|
102, 101, 50, 53, 49, 53, 98, 51, 26, 66, 48, 120, 53, 97, 57, 49, 99, 52,
|
||||||
57, 49, 99, 52, 54, 48, 97, 97, 100, 101, 99, 51, 99, 55, 54, 100, 48,
|
54, 48, 97, 97, 100, 101, 99, 51, 99, 55, 54, 100, 48, 56, 48, 98, 54, 99,
|
||||||
56, 48, 98, 54, 99, 55, 50, 97, 50, 48, 101, 49, 53, 97, 51, 51, 55,
|
55, 50, 97, 50, 48, 101, 49, 53, 97, 51, 51, 55, 102, 55, 99, 48, 98, 55,
|
||||||
102, 55, 99, 48, 98, 55, 55, 97, 55, 99, 48, 97, 53, 101, 98, 97, 53,
|
55, 97, 55, 99, 48, 97, 53, 101, 98, 97, 53, 102, 97, 57, 100, 52, 100,
|
||||||
102, 97, 57, 100, 52, 100, 57, 49, 98, 97, 56, 32, 5, 40, 2,
|
57, 49, 98, 97, 56, 32, 5, 40, 2,
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKeySnapshot = new Uint8Array([
|
const publicKeySnapshot = new Uint8Array([
|
||||||
4, 172, 65, 157, 172, 154, 139, 187, 88, 130, 90, 60, 222, 96, 238, 240,
|
4, 172, 65, 157, 172, 154, 139, 187, 88, 130, 90, 60, 222, 96, 238, 240,
|
||||||
238, 113, 184, 207, 108, 99, 223, 97, 30, 238, 252, 142, 122, 172, 124,
|
238, 113, 184, 207, 108, 99, 223, 97, 30, 238, 252, 142, 122, 172, 124, 121,
|
||||||
121, 181, 89, 84, 182, 121, 210, 76, 245, 236, 130, 218, 126, 217, 33,
|
181, 89, 84, 182, 121, 210, 76, 245, 236, 130, 218, 126, 217, 33, 202, 242,
|
||||||
202, 242, 64, 98, 138, 155, 251, 52, 80, 197, 17, 26, 156, 255, 229, 78,
|
64, 98, 138, 155, 251, 52, 80, 197, 17, 26, 156, 255, 229, 78, 99, 24, 17,
|
||||||
99, 24, 17,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const result = recoverPublicKey(
|
const result = recoverPublicKey(
|
||||||
|
@ -56,9 +55,9 @@ describe('recoverPublicKey', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result).toEqual(publicKeySnapshot)
|
expect(result).toEqual(publicKeySnapshot)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not recover public key with different payload', async () => {
|
test('should not recover public key with different payload', async () => {
|
||||||
const payload = utf8ToBytes('1')
|
const payload = utf8ToBytes('1')
|
||||||
|
|
||||||
const account = new Account()
|
const account = new Account()
|
||||||
|
@ -66,9 +65,9 @@ describe('recoverPublicKey', () => {
|
||||||
|
|
||||||
const payload2 = utf8ToBytes('2')
|
const payload2 = utf8ToBytes('2')
|
||||||
expect(recoverPublicKey(signature, payload2)).not.toEqual(account.publicKey)
|
expect(recoverPublicKey(signature, payload2)).not.toEqual(account.publicKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error when signature length is not 65 bytes', async () => {
|
test('should throw error when signature length is not 65 bytes', async () => {
|
||||||
const payload = utf8ToBytes('hello')
|
const payload = utf8ToBytes('hello')
|
||||||
const signature = new Uint8Array([
|
const signature = new Uint8Array([
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||||
|
@ -77,5 +76,4 @@ describe('recoverPublicKey', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
recoverPublicKey(signature, payload)
|
recoverPublicKey(signature, payload)
|
||||||
).toThrowErrorMatchingInlineSnapshot(`"Signature must be 65 bytes long"`)
|
).toThrowErrorMatchingInlineSnapshot(`"Signature must be 65 bytes long"`)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ChatMessage } from '~/protos/chat-message'
|
import type { ChatMessage } from '../protos/chat-message'
|
||||||
|
|
||||||
// TODO?: maybe this should normalize the message?
|
// TODO?: maybe this should normalize the message?
|
||||||
export function validateMessage(message: ChatMessage): boolean {
|
export function validateMessage(message: ChatMessage): boolean {
|
||||||
|
|
|
@ -2,11 +2,7 @@
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist",
|
"outDir": "./dist",
|
||||||
"declarationDir": "dist/types",
|
"declarationDir": "./dist/types"
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./*"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/// <reference types="vitest" />
|
||||||
|
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
import { dependencies } from './package.json'
|
||||||
|
|
||||||
|
const external = [
|
||||||
|
...Object.keys(dependencies || {}),
|
||||||
|
// ...Object.keys(peerDependencies || {}),
|
||||||
|
].map(name => new RegExp(`^${name}(/.*)?`))
|
||||||
|
|
||||||
|
export default defineConfig(({ command }) => {
|
||||||
|
return {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': resolve('.'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: './src/index.ts',
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
target: 'es2020',
|
||||||
|
emptyOutDir: command === 'build',
|
||||||
|
rollupOptions: {
|
||||||
|
external,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
test: {
|
||||||
|
// environment: 'happy-dom',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
|
@ -2,7 +2,14 @@
|
||||||
"name": "@status-im/react",
|
"name": "@status-im/react",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"homepage": "https://github.com/status-im/status-web",
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"import": "./dist/index.es.js",
|
||||||
|
"default": "./dist/index.es.js"
|
||||||
|
},
|
||||||
|
"module": "dist/index.es.js",
|
||||||
|
"types": "dist/types/index.d.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/status-im/status-web.git",
|
"url": "https://github.com/status-im/status-web.git",
|
||||||
"directory": "packages/status-react",
|
"directory": "packages/status-react",
|
||||||
|
@ -11,18 +18,15 @@
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/status-im/status-web/issues"
|
"url": "https://github.com/status-im/status-web/issues"
|
||||||
},
|
},
|
||||||
"source": "src/index.tsx",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"module": "dist/index.esm.js",
|
|
||||||
"types": "dist/types.d.ts",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "rm -rf dist",
|
"dev": "vite build --watch",
|
||||||
"build": "parcel build",
|
"build": "vite build && yarn typegen",
|
||||||
"build:types": "tsc --emitDeclarationOnly",
|
"#test": "vitest",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc",
|
||||||
|
"typegen": "tsc --noEmit false --emitDeclarationOnly --paths null || true",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"format": "prettier --write src",
|
"format": "prettier --write src",
|
||||||
"clean": "rm -rf node_modules && rm -rf dist"
|
"clean": "rm -rf dist node_modules .turbo"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hcaptcha/react-hcaptcha": "^1.0.0",
|
"@hcaptcha/react-hcaptcha": "^1.0.0",
|
||||||
|
@ -45,9 +49,7 @@
|
||||||
"emoji-mart": "^3.0.1",
|
"emoji-mart": "^3.0.1",
|
||||||
"html-entities": "^2.3.2",
|
"html-entities": "^2.3.2",
|
||||||
"qrcode.react": "^3.0.1",
|
"qrcode.react": "^3.0.1",
|
||||||
"react": "^17.0.2",
|
|
||||||
"react-content-loader": "^6.2.0",
|
"react-content-loader": "^6.2.0",
|
||||||
"react-dom": "^17.0.2",
|
|
||||||
"react-is": "^17.0.2",
|
"react-is": "^17.0.2",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"styled-components": "^5.3.1",
|
"styled-components": "^5.3.1",
|
||||||
|
@ -58,7 +60,9 @@
|
||||||
"@types/hcaptcha__react-hcaptcha": "^0.1.5",
|
"@types/hcaptcha__react-hcaptcha": "^0.1.5",
|
||||||
"@types/node": "^16.9.6",
|
"@types/node": "^16.9.6",
|
||||||
"@types/react": "^17.0.16",
|
"@types/react": "^17.0.16",
|
||||||
"@types/styled-components": "^5.1.12"
|
"@types/styled-components": "^5.1.12",
|
||||||
|
"@vitejs/plugin-react": "^1.3.2",
|
||||||
|
"happy-dom": "^5.3.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17.0.0",
|
"react": "^16.8.0 || ^17.0.0",
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
import { useEffect, useMemo, useReducer } from 'react'
|
|
||||||
|
|
||||||
import { useUserPublicKey } from '../contexts/identityProvider'
|
|
||||||
import { useMessengerContext } from '../contexts/messengerProvider'
|
|
||||||
|
|
||||||
import type { Activities, Activity, ActivityStatus } from '../models/Activity'
|
|
||||||
import type { ChatMessage } from '../models/ChatMessage'
|
|
||||||
|
|
||||||
export type ActivityAction =
|
|
||||||
| { type: 'addActivity'; payload: Activity }
|
|
||||||
| { type: 'removeActivity'; payload: 'string' }
|
|
||||||
| { type: 'setAsRead'; payload: string }
|
|
||||||
| { type: 'setAllAsRead' }
|
|
||||||
| { type: 'setStatus'; payload: { id: string; status: ActivityStatus } }
|
|
||||||
|
|
||||||
function activityReducer(
|
|
||||||
state: Activities,
|
|
||||||
action: ActivityAction
|
|
||||||
): Activities {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'setStatus': {
|
|
||||||
const activity = state[action.payload.id]
|
|
||||||
if (activity && 'status' in activity) {
|
|
||||||
activity.status = action.payload.status
|
|
||||||
activity.isRead = true
|
|
||||||
return { ...state, [activity.id]: activity }
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
case 'setAsRead': {
|
|
||||||
const activity = state[action.payload]
|
|
||||||
if (activity) {
|
|
||||||
activity.isRead = true
|
|
||||||
return { ...state, [activity.id]: activity }
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
case 'setAllAsRead': {
|
|
||||||
return Object.entries(state).reduce((prev, curr) => {
|
|
||||||
const activity = curr[1]
|
|
||||||
activity.isRead = true
|
|
||||||
return { ...prev, [curr[0]]: activity }
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
case 'addActivity': {
|
|
||||||
return { ...state, [action.payload.id]: action.payload }
|
|
||||||
}
|
|
||||||
case 'removeActivity': {
|
|
||||||
if (state[action.payload]) {
|
|
||||||
const newState = { ...state }
|
|
||||||
delete newState[action.payload]
|
|
||||||
return newState
|
|
||||||
} else {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error('Wrong activity reducer type')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useActivities() {
|
|
||||||
const [activitiesObj, dispatch] = useReducer(activityReducer, {})
|
|
||||||
const activities = useMemo(
|
|
||||||
() => Object.values(activitiesObj),
|
|
||||||
[activitiesObj]
|
|
||||||
)
|
|
||||||
const userPK = useUserPublicKey()
|
|
||||||
const { subscriptionsDispatch, channels } = useMessengerContext()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (userPK) {
|
|
||||||
const subscribeFunction = (message: ChatMessage, id: string) => {
|
|
||||||
if (message.quote && message.quote.sender === userPK) {
|
|
||||||
const newActivity: Activity = {
|
|
||||||
id: message.date.getTime().toString() + message.content,
|
|
||||||
type: 'reply',
|
|
||||||
date: message.date,
|
|
||||||
user: message.sender,
|
|
||||||
message: message,
|
|
||||||
channel: channels[id],
|
|
||||||
quote: message.quote,
|
|
||||||
}
|
|
||||||
dispatch({ type: 'addActivity', payload: newActivity })
|
|
||||||
}
|
|
||||||
|
|
||||||
const split = message.content.split(' ')
|
|
||||||
const userMentioned = split.some(
|
|
||||||
fragment => fragment.startsWith('@') && fragment.slice(1) == userPK
|
|
||||||
)
|
|
||||||
if (userMentioned) {
|
|
||||||
const newActivity: Activity = {
|
|
||||||
id: message.date.getTime().toString() + message.content,
|
|
||||||
type: 'mention',
|
|
||||||
date: message.date,
|
|
||||||
user: message.sender,
|
|
||||||
message: message,
|
|
||||||
channel: channels[id],
|
|
||||||
}
|
|
||||||
dispatch({ type: 'addActivity', payload: newActivity })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscriptionsDispatch({
|
|
||||||
type: 'addSubscription',
|
|
||||||
payload: { name: 'activityCenter', subFunction: subscribeFunction },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return () =>
|
|
||||||
subscriptionsDispatch({
|
|
||||||
type: 'removeSubscription',
|
|
||||||
payload: { name: 'activityCenter' },
|
|
||||||
})
|
|
||||||
}, [subscriptionsDispatch, userPK, channels])
|
|
||||||
|
|
||||||
return { activities, activityDispatch: dispatch }
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import { useMessengerContext } from '../contexts/messengerProvider'
|
|
||||||
|
|
||||||
import type { ChatMessage } from '../models/ChatMessage'
|
|
||||||
|
|
||||||
export function useChatScrollHandle(
|
|
||||||
messages: ChatMessage[],
|
|
||||||
ref: React.RefObject<HTMLHeadingElement>
|
|
||||||
) {
|
|
||||||
const { loadPrevDay, loadingMessages, activeChannel } = useMessengerContext()
|
|
||||||
const [scrollOnBot, setScrollOnBot] = useState(true)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref && ref.current && scrollOnBot) {
|
|
||||||
ref.current.scrollTop = ref.current.scrollHeight
|
|
||||||
}
|
|
||||||
}, [messages.length, scrollOnBot, ref])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (activeChannel) {
|
|
||||||
if (
|
|
||||||
(ref?.current?.clientHeight ?? 0) >= (ref?.current?.scrollHeight ?? 0)
|
|
||||||
) {
|
|
||||||
setScrollOnBot(true)
|
|
||||||
loadPrevDay(activeChannel.id, activeChannel.type !== 'channel')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [messages.length, activeChannel, loadPrevDay, setScrollOnBot, ref])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentRef = ref.current
|
|
||||||
const setScroll = () => {
|
|
||||||
if (ref?.current && activeChannel) {
|
|
||||||
if (ref.current.scrollTop <= 0) {
|
|
||||||
loadPrevDay(activeChannel.id, activeChannel.type !== 'channel')
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
ref.current.scrollTop + ref.current.clientHeight ==
|
|
||||||
ref.current.scrollHeight
|
|
||||||
) {
|
|
||||||
if (scrollOnBot === false) {
|
|
||||||
setScrollOnBot(true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (scrollOnBot === true) {
|
|
||||||
setScrollOnBot(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentRef?.addEventListener('scroll', setScroll)
|
|
||||||
return () => currentRef?.removeEventListener('scroll', setScroll)
|
|
||||||
}, [ref, scrollOnBot, activeChannel, loadPrevDay])
|
|
||||||
return loadingMessages
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
import { useMessengerContext } from '../contexts/messengerProvider'
|
|
||||||
|
|
||||||
export enum NameErrors {
|
|
||||||
NoError = 0,
|
|
||||||
NameExists = 1,
|
|
||||||
BadCharacters = 2,
|
|
||||||
EndingWithEth = 3,
|
|
||||||
TooLong = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useNameError(name: string) {
|
|
||||||
const { contacts } = useMessengerContext()
|
|
||||||
|
|
||||||
const error = useMemo(() => {
|
|
||||||
const RegName = new RegExp('^[a-z0-9_-]+$')
|
|
||||||
if (name === '') {
|
|
||||||
return NameErrors.NoError
|
|
||||||
}
|
|
||||||
const nameExists = Object.values(contacts).find(
|
|
||||||
contact => contact.trueName === name
|
|
||||||
)
|
|
||||||
if (nameExists) {
|
|
||||||
return NameErrors.NameExists
|
|
||||||
}
|
|
||||||
if (!name.match(RegName)) {
|
|
||||||
return NameErrors.BadCharacters
|
|
||||||
}
|
|
||||||
if (name.slice(-4) === '_eth' || name.slice(-4) === '-eth') {
|
|
||||||
return NameErrors.EndingWithEth
|
|
||||||
}
|
|
||||||
if (name.length >= 24) {
|
|
||||||
return NameErrors.TooLong
|
|
||||||
}
|
|
||||||
return NameErrors.NoError
|
|
||||||
}, [name, contacts])
|
|
||||||
|
|
||||||
return error
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
export function useRefBreak(dimension: number, sizeThreshold: number) {
|
|
||||||
const [widthBreak, setWidthBreak] = useState(dimension < sizeThreshold)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkDimensions = () => {
|
|
||||||
if (dimension && dimension < sizeThreshold && dimension > 0) {
|
|
||||||
if (widthBreak === false) {
|
|
||||||
setWidthBreak(true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (widthBreak === true) {
|
|
||||||
setWidthBreak(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkDimensions()
|
|
||||||
window.addEventListener('resize', checkDimensions)
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', checkDimensions)
|
|
||||||
}
|
|
||||||
}, [dimension, widthBreak, sizeThreshold])
|
|
||||||
|
|
||||||
return widthBreak
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
export function binarySetInsert<T>(
|
|
||||||
arr: T[],
|
|
||||||
val: T,
|
|
||||||
compFunc: (a: T, b: T) => boolean,
|
|
||||||
eqFunc: (a: T, b: T) => boolean
|
|
||||||
) {
|
|
||||||
let low = 0
|
|
||||||
let high = arr.length
|
|
||||||
while (low < high) {
|
|
||||||
const mid = (low + high) >> 1
|
|
||||||
if (compFunc(arr[mid], val)) {
|
|
||||||
low = mid + 1
|
|
||||||
} else {
|
|
||||||
high = mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (arr.length === low || !eqFunc(arr[low], val)) {
|
|
||||||
arr.splice(low, 0, val)
|
|
||||||
}
|
|
||||||
return arr
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
const copyToClipboard = async (pngBlob: Blob) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.write([
|
|
||||||
new ClipboardItem({
|
|
||||||
[pngBlob.type]: pngBlob,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Images are already converted to png by useMessenger when received
|
|
||||||
export const copyImg = async (image: string) => {
|
|
||||||
const img = await fetch(image)
|
|
||||||
const imgBlob = await img.blob()
|
|
||||||
return copyToClipboard(imgBlob)
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export function equalDate(a: Date, b: Date) {
|
|
||||||
return (
|
|
||||||
a.getDate() === b.getDate() &&
|
|
||||||
a.getMonth() === b.getMonth() &&
|
|
||||||
a.getFullYear() === b.getFullYear()
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
import { bufToHex, hexToBuf, Identity } from '@status-im/js'
|
|
||||||
|
|
||||||
export async function saveIdentity(identity: Identity, password: string) {
|
|
||||||
const salt = window.crypto.getRandomValues(new Uint8Array(16))
|
|
||||||
const wrapKey = await getWrapKey(password, salt)
|
|
||||||
|
|
||||||
const iv = window.crypto.getRandomValues(new Uint8Array(12))
|
|
||||||
const cipher = await window.crypto.subtle.encrypt(
|
|
||||||
{
|
|
||||||
name: 'AES-GCM',
|
|
||||||
iv: iv,
|
|
||||||
},
|
|
||||||
wrapKey,
|
|
||||||
identity.privateKey
|
|
||||||
)
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
salt: bufToHex(salt),
|
|
||||||
iv: bufToHex(iv),
|
|
||||||
cipher: bufToHex(cipher),
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem('cipherIdentity', JSON.stringify(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadEncryptedIdentity(): string | null {
|
|
||||||
return localStorage.getItem('cipherIdentity')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getWrapKey(password: string, salt: Uint8Array) {
|
|
||||||
const enc = new TextEncoder()
|
|
||||||
const keyMaterial = await window.crypto.subtle.importKey(
|
|
||||||
'raw',
|
|
||||||
enc.encode(password),
|
|
||||||
{ name: 'PBKDF2' },
|
|
||||||
false,
|
|
||||||
['deriveBits', 'deriveKey']
|
|
||||||
)
|
|
||||||
return await window.crypto.subtle.deriveKey(
|
|
||||||
{
|
|
||||||
name: 'PBKDF2',
|
|
||||||
salt,
|
|
||||||
iterations: 100000,
|
|
||||||
hash: 'SHA-256',
|
|
||||||
},
|
|
||||||
keyMaterial,
|
|
||||||
{ name: 'AES-GCM', length: 256 },
|
|
||||||
true,
|
|
||||||
['encrypt', 'decrypt']
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function decryptIdentity(
|
|
||||||
encryptedIdentity: string,
|
|
||||||
password: string
|
|
||||||
): Promise<Identity | undefined> {
|
|
||||||
const data = JSON.parse(encryptedIdentity)
|
|
||||||
|
|
||||||
const salt = hexToBuf(data.salt)
|
|
||||||
const iv = hexToBuf(data.iv)
|
|
||||||
const cipherKeyPair = hexToBuf(data.cipher)
|
|
||||||
|
|
||||||
const key = await getWrapKey(password, salt)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const decrypted = await window.crypto.subtle.decrypt(
|
|
||||||
{
|
|
||||||
name: 'AES-GCM',
|
|
||||||
iv: iv,
|
|
||||||
},
|
|
||||||
key,
|
|
||||||
cipherKeyPair
|
|
||||||
)
|
|
||||||
|
|
||||||
return new Identity(new Uint8Array(decrypted))
|
|
||||||
} catch (e) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
export { binarySetInsert } from './binarySetInsert'
|
|
||||||
export { copyImg } from './copyImg'
|
|
||||||
export { equalDate } from './equalDate'
|
|
||||||
export {
|
|
||||||
decryptIdentity,
|
|
||||||
loadEncryptedIdentity,
|
|
||||||
saveIdentity,
|
|
||||||
} from './identityStorage'
|
|
||||||
export { reduceString } from './reduceString'
|
|
||||||
export { uintToImgUrl } from './uintToImgUrl'
|
|
|
@ -1,9 +0,0 @@
|
||||||
export const paste = (elementId: string) => {
|
|
||||||
navigator.clipboard
|
|
||||||
.readText()
|
|
||||||
.then(
|
|
||||||
clipText =>
|
|
||||||
((<HTMLInputElement>document.getElementById(elementId)).value =
|
|
||||||
clipText)
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
export const reduceString = (
|
|
||||||
string: string,
|
|
||||||
limitBefore: number,
|
|
||||||
limitAfter: number
|
|
||||||
) => {
|
|
||||||
return `${string.substring(0, limitBefore)}...${string.substring(
|
|
||||||
string.length - limitAfter
|
|
||||||
)}`
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
export function uintToImgUrl(img: Uint8Array) {
|
|
||||||
const blob = new Blob([img], { type: 'image/png' })
|
|
||||||
return URL.createObjectURL(blob)
|
|
||||||
}
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/// <reference types="vitest" />
|
||||||
|
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
import { dependencies, peerDependencies } from './package.json'
|
||||||
|
|
||||||
|
const external = [
|
||||||
|
...Object.keys(dependencies || {}),
|
||||||
|
...Object.keys(peerDependencies || {}),
|
||||||
|
].map(name => new RegExp(`^${name}(/.*)?`))
|
||||||
|
|
||||||
|
export default defineConfig(({ command }) => {
|
||||||
|
return {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': resolve('.'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: './src/index.tsx',
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: command === 'build',
|
||||||
|
// sourcemap: true,
|
||||||
|
target: 'es2020',
|
||||||
|
|
||||||
|
rollupOptions: {
|
||||||
|
external,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
react({
|
||||||
|
// jsxRuntime: 'classic',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
test: {
|
||||||
|
environment: 'happy-dom',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
|
@ -1,9 +1,11 @@
|
||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
// "extends": "@tsconfig/strictest/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"target": "ES2020",
|
|
||||||
"module": "ES2020",
|
"module": "ES2020",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"target": "ES2020",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
@ -13,7 +15,7 @@
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
// "noEmit": true,
|
"noEmit": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://turborepo.org/schema.json",
|
||||||
|
"baseBranch": "origin/main",
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"outputs": [],
|
||||||
|
"dependsOn": []
|
||||||
|
},
|
||||||
|
"clean": {
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globalDependencies": ["tsconfig.base.json"]
|
||||||
|
}
|
Loading…
Reference in New Issue