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/
|
||||
|
||||
# Turborepo
|
||||
.turbo
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"dbaeumer.vscode-eslint",
|
||||
"zxh404.vscode-proto3",
|
||||
"ms-vscode.hexeditor",
|
||||
"Orta.vscode-jest"
|
||||
"zixuanchen.vitest-explorer",
|
||||
"mikestead.dotenv"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,20 +4,27 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Client",
|
||||
"program": "${workspaceFolder}/packages/status-js/src/index.ts",
|
||||
"preLaunchTask": "npm: build:status-js",
|
||||
"outFiles": ["${workspaceFolder}/packages/status-js/dist/index.js"],
|
||||
"sourceMaps": true,
|
||||
"name": "Debug Client",
|
||||
"autoAttachChildProcesses": true,
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"cwd": "${workspaceFolder}/packages/status-js",
|
||||
"program": "${workspaceRoot}/node_modules/vite-node/dist/cli.mjs",
|
||||
"args": ["src/index.ts"],
|
||||
"smartStep": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"skipFiles": [
|
||||
"${workspaceFolder}/node_modules/**/*",
|
||||
"<node_internals>/**/*.js"
|
||||
],
|
||||
"sourceMapPathOverrides": {
|
||||
"packages/status-js/*": "${workspaceFolder}/packages/status-js/*"
|
||||
}
|
||||
"console": "integratedTerminal",
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"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",
|
||||
"jestRunIt.debugTestLabel": "Debug",
|
||||
"jestRunIt.runTestLabel": "Run"
|
||||
"eslint.packageManager": "yarn",
|
||||
"npm.packageManager": "yarn"
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
|
@ -19,6 +19,6 @@
|
|||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
<script type="module" src="./src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "community",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"source": "./index.html",
|
||||
"browserslist": "> 0.5%, last 2 versions, not dead, not ios_saf < 13",
|
||||
"scripts": {
|
||||
"dev": "parcel --https",
|
||||
"dev": "parcel index.html --https --no-cache --open",
|
||||
"prebuild": "rm -rf dist",
|
||||
"build": "parcel build"
|
||||
"build": "parcel build index.html"
|
||||
},
|
||||
"dependencies": {
|
||||
"@status-im/react": "^0.0.0",
|
||||
|
@ -17,10 +17,7 @@
|
|||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"parcel": "^2.0.0",
|
||||
"assert": "^2.0.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"process": "^0.11.10",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"typescript": "^4.0.0"
|
||||
"typescript": "^4.0.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 { render } from 'react-dom'
|
||||
|
||||
import { Community } from '@status-im/react'
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Community
|
||||
publicKey="<YOUR_COMMUNITY_KEY>"
|
||||
theme="light"
|
||||
/>
|
||||
)
|
||||
}
|
||||
import { App } from './app'
|
||||
|
||||
render(
|
||||
<StrictMode>
|
|
@ -2,4 +2,5 @@ body,
|
|||
html,
|
||||
#root {
|
||||
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": {
|
||||
"protons-runtime": "./node_modules/protons-runtime/dist/src/index.js",
|
||||
"uint8arraylist": "./node_modules/uint8arraylist/dist/src/index.js"
|
||||
},
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
@ -11,30 +8,19 @@
|
|||
"keywords": [],
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"fix": "run-s 'fix:*' && wsrun -e -c -s fix",
|
||||
"build": "wsrun -e -c -s build",
|
||||
"build:status-js": "yarn workspace @status-im/js build",
|
||||
"build:status-react": "yarn workspace @status-im/react build",
|
||||
"lint": "eslint 'packages/**/*.{ts,tsx}'",
|
||||
"lint:fix": "eslint 'packages/**/*.{ts,tsx}' --fix",
|
||||
"lint:examples": "eslint 'examples/**/*.{ts,tsx}'",
|
||||
"test": "turbo run test --filter=@status-im/* -- --run",
|
||||
"dev": "turbo run dev --parallel --filter=@status-im/*",
|
||||
"build": "turbo run build --filter=@status-im/*",
|
||||
"lint": "turbo run lint --filter=@status-im/*",
|
||||
"check": "turbo run check --filter=@status-im/*",
|
||||
"format": "prettier --write .",
|
||||
"typecheck": "wsrun -e -c -s typecheck",
|
||||
"test": "jest"
|
||||
"format:check": "prettier --check .",
|
||||
"clean": "turbo run clean && rm -rf node_modules .parcel-cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.2",
|
||||
"@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",
|
||||
"@tsconfig/strictest": "^1.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||
"@typescript-eslint/parser": "^5.12.0",
|
||||
"babel-jest": "^28.1.0",
|
||||
"eslint": "^8.9.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-node": "^0.3.6",
|
||||
|
@ -48,14 +34,13 @@
|
|||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"husky": "^7.0.4",
|
||||
"jest": "^28.1.0",
|
||||
"lint-staged": "^12.3.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"parcel": "^2.6.0",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier": "^2.7.1",
|
||||
"turbo": "^1.3.1",
|
||||
"typescript": "^4.5.5",
|
||||
"wsrun": "^5.2.4",
|
||||
"ts-jest": "^28.0.4"
|
||||
"vite": "^2.9.12",
|
||||
"vite-node": "^0.16.0",
|
||||
"vitest": "^0.16.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,jsx}": [
|
||||
|
@ -66,5 +51,9 @@
|
|||
"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",
|
||||
"version": "0.0.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": {
|
||||
"url": "https://github.com/status-im/status-web.git",
|
||||
"directory": "packages/status-js",
|
||||
|
@ -10,32 +18,16 @@
|
|||
"bugs": {
|
||||
"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": {
|
||||
"prebuild": "rm -rf dist",
|
||||
"dev": "parcel",
|
||||
"build": "parcel build",
|
||||
"build:vite": "vite build",
|
||||
"build:types": "tsc --emitDeclarationOnly",
|
||||
"build:protos": "protons protos/*.proto",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "vite build --watch",
|
||||
"build": "vite build && yarn typegen",
|
||||
"test": "vitest",
|
||||
"lint": "eslint src",
|
||||
"typecheck": "tsc",
|
||||
"typegen": "tsc --noEmit false --emitDeclarationOnly --paths null || true",
|
||||
"format": "prettier --write src",
|
||||
"clean": "rm -rf node_modules && rm -rf dist",
|
||||
"proto": "run-s 'proto:*'",
|
||||
"proto:lint": "buf lint",
|
||||
"proto:build": "buf generate"
|
||||
"protos": "protons protos/*.proto",
|
||||
"clean": "rm -rf dist node_modules .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethereum-cryptography": "^1.0.3",
|
||||
|
@ -45,7 +37,6 @@
|
|||
"protons-runtime": "^1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"npm-run-all": "^4.1.5",
|
||||
"protons": "^3.0.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
import { keccak256 } from 'ethereum-cryptography/keccak'
|
||||
import * as secp from 'ethereum-cryptography/secp256k1'
|
||||
import { utf8ToBytes } from 'ethereum-cryptography/utils'
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
import { Account } from './account'
|
||||
|
||||
describe('Account', () => {
|
||||
it('should verify the signature', async () => {
|
||||
const account = new Account()
|
||||
test('should verify the signature', async () => {
|
||||
const account = new Account()
|
||||
|
||||
const message = utf8ToBytes('123')
|
||||
const messageHash = keccak256(message)
|
||||
const message = utf8ToBytes('123')
|
||||
const messageHash = keccak256(message)
|
||||
|
||||
const signature = await account.sign(message)
|
||||
const signatureWithoutRecoveryId = signature.slice(0, -1)
|
||||
const signature = await account.sign(message)
|
||||
const signatureWithoutRecoveryId = signature.slice(0, -1)
|
||||
|
||||
expect(
|
||||
secp.verify(signatureWithoutRecoveryId, messageHash, account.publicKey)
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should not verify signature with different message', async () => {
|
||||
const account = new Account()
|
||||
|
||||
const message = utf8ToBytes('123')
|
||||
const messageHash = keccak256(message)
|
||||
|
||||
const signature = await account.sign(utf8ToBytes('abc'))
|
||||
|
||||
expect(secp.verify(signature, messageHash, account.publicKey)).toBeFalsy()
|
||||
})
|
||||
expect(
|
||||
secp.verify(signatureWithoutRecoveryId, messageHash, account.publicKey)
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
test('should not verify signature with different message', async () => {
|
||||
const account = new Account()
|
||||
|
||||
const message = utf8ToBytes('123')
|
||||
const messageHash = keccak256(message)
|
||||
|
||||
const signature = await account.sign(utf8ToBytes('abc'))
|
||||
|
||||
expect(secp.verify(signature, messageHash, account.publicKey)).toBeFalsy()
|
||||
})
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
import { PageDirection } from 'js-waku'
|
||||
|
||||
import { containsOnlyEmoji } from '../helpers/contains-only-emoji'
|
||||
import {
|
||||
AudioMessage,
|
||||
ChatMessage as ChatMessageProto,
|
||||
DeleteMessage,
|
||||
EditMessage,
|
||||
ImageType,
|
||||
} from '~/protos/chat-message'
|
||||
import { EmojiReaction } from '~/protos/emoji-reaction'
|
||||
|
||||
import { containsOnlyEmoji } from '../helpers/contains-only-emoji'
|
||||
} from '../protos/chat-message'
|
||||
import { EmojiReaction } from '../protos/emoji-reaction'
|
||||
import { generateKeyFromPassword } from '../utils/generate-key-from-password'
|
||||
import { idToContentTopic } from '../utils/id-to-content-topic'
|
||||
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 { Community } from './community/community'
|
||||
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'
|
||||
|
||||
export type ChatMessage = ChatMessageProto & {
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
import { hexToBytes } from 'ethereum-cryptography/utils'
|
||||
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 { Community } from './community/community'
|
||||
import { handleWakuMessage } from './community/handle-waku-message'
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
import { hexToBytes } from 'ethereum-cryptography/utils'
|
||||
import { waku_message } from 'js-waku'
|
||||
|
||||
import { CommunityRequestToJoin } from '~/protos/communities'
|
||||
import { MessageType } from '~/protos/enums'
|
||||
import { getDifferenceByKeys } from '~/src/helpers/get-difference-by-keys'
|
||||
import { getObjectsDifference } from '~/src/helpers/get-objects-difference'
|
||||
import { compressPublicKey } from '~/src/utils/compress-public-key'
|
||||
import { generateKeyFromPassword } from '~/src/utils/generate-key-from-password'
|
||||
import { idToContentTopic } from '~/src/utils/id-to-content-topic'
|
||||
|
||||
import { getDifferenceByKeys } from '../../helpers/get-difference-by-keys'
|
||||
import { getObjectsDifference } from '../../helpers/get-objects-difference'
|
||||
import { CommunityRequestToJoin } from '../../protos/communities'
|
||||
import { MessageType } from '../../protos/enums'
|
||||
import { compressPublicKey } from '../../utils/compress-public-key'
|
||||
import { generateKeyFromPassword } from '../../utils/generate-key-from-password'
|
||||
import { idToContentTopic } from '../../utils/id-to-content-topic'
|
||||
import { Chat } from '../chat'
|
||||
import { Member } from '../member'
|
||||
|
||||
import type { Client } from '../client'
|
||||
import type {
|
||||
CommunityChat,
|
||||
CommunityDescription,
|
||||
} from '~/src/proto/communities/v1/communities'
|
||||
} from '../../proto/communities/v1/communities'
|
||||
import type { Client } from '../client'
|
||||
|
||||
export class Community {
|
||||
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'>
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
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 {
|
||||
ChatMessage,
|
||||
DeleteMessage,
|
||||
EditMessage,
|
||||
MessageType,
|
||||
} from '../../../protos/chat-message'
|
||||
import { EmojiReaction } from '../../../protos/emoji-reaction'
|
||||
import { PinMessage } from '../../../protos/pin-message'
|
||||
import { ProtocolMessage } from '../../../protos/protocol-message'
|
||||
import { CommunityDescription } from '../../proto/communities/v1/communities'
|
||||
} from '../../protos/chat-message'
|
||||
import { EmojiReaction } from '../../protos/emoji-reaction'
|
||||
import { PinMessage } from '../../protos/pin-message'
|
||||
import { ProtocolMessage } from '../../protos/protocol-message'
|
||||
import { payloadToId } from '../../utils/payload-to-id'
|
||||
import { recoverPublicKey } from '../../utils/recover-public-key'
|
||||
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 as ChatMessageProto } from '~/protos/chat-message'
|
||||
|
||||
export function mapChatMessage(
|
||||
decodedMessage: ChatMessageProto,
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { expect, test } from 'vitest'
|
||||
|
||||
import { containsOnlyEmoji } from './contains-only-emoji'
|
||||
|
||||
describe('containsOnlyEmoji', () => {
|
||||
it('should be truthy', () => {
|
||||
expect(containsOnlyEmoji('💩')).toBeTruthy()
|
||||
expect(containsOnlyEmoji('💩💩💩💩💩💩')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should be falsy', () => {
|
||||
expect(containsOnlyEmoji('')).toBeFalsy()
|
||||
expect(containsOnlyEmoji(' ')).toBeFalsy()
|
||||
expect(containsOnlyEmoji(' 💩')).toBeFalsy()
|
||||
expect(containsOnlyEmoji('💩 ')).toBeFalsy()
|
||||
expect(containsOnlyEmoji('text 💩')).toBeFalsy()
|
||||
expect(containsOnlyEmoji('💩 text')).toBeFalsy()
|
||||
})
|
||||
test('should be truthy', () => {
|
||||
expect(containsOnlyEmoji('💩')).toBeTruthy()
|
||||
expect(containsOnlyEmoji('💩💩💩💩💩💩')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('should be falsy', () => {
|
||||
expect(containsOnlyEmoji('')).toBeFalsy()
|
||||
expect(containsOnlyEmoji(' ')).toBeFalsy()
|
||||
expect(containsOnlyEmoji(' 💩')).toBeFalsy()
|
||||
expect(containsOnlyEmoji('💩 ')).toBeFalsy()
|
||||
expect(containsOnlyEmoji('text 💩')).toBeFalsy()
|
||||
expect(containsOnlyEmoji('💩 text')).toBeFalsy()
|
||||
})
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { expect, test } from 'vitest'
|
||||
|
||||
import { getObjectsDifference } from './get-objects-difference'
|
||||
|
||||
describe('getObjectsDifference', () => {
|
||||
it('returns correct difference', () => {
|
||||
const oldObject = { a: 1, b: 2, c: 3 }
|
||||
const newObject = { c: 3, d: 4, e: 5 }
|
||||
test('should return correct difference', () => {
|
||||
const oldObject = { a: 1, b: 2, c: 3 }
|
||||
const newObject = { c: 3, d: 4, e: 5 }
|
||||
|
||||
expect(getObjectsDifference(oldObject, newObject)).toEqual({
|
||||
added: {
|
||||
d: 4,
|
||||
e: 5,
|
||||
},
|
||||
removed: ['a', 'b'],
|
||||
})
|
||||
})
|
||||
|
||||
it('returns empty arrays for the same object', () => {
|
||||
const object = { a: 1, b: 2, c: 3 }
|
||||
|
||||
expect(getObjectsDifference(object, object)).toEqual({
|
||||
added: {},
|
||||
removed: [],
|
||||
})
|
||||
expect(getObjectsDifference(oldObject, newObject)).toEqual({
|
||||
added: {
|
||||
d: 4,
|
||||
e: 5,
|
||||
},
|
||||
removed: ['a', 'b'],
|
||||
})
|
||||
})
|
||||
|
||||
test('should return empty arrays for the same object', () => {
|
||||
const object = { a: 1, b: 2, c: 3 }
|
||||
|
||||
expect(getObjectsDifference(object, object)).toEqual({
|
||||
added: {},
|
||||
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 { expect, test } from 'vitest'
|
||||
|
||||
import { compressPublicKey } from './compress-public-key'
|
||||
|
||||
describe('compressPublicKey', () => {
|
||||
it('should return compressed public key', () => {
|
||||
const privateKey = utils.randomPrivateKey()
|
||||
test('should return compressed public key', () => {
|
||||
const privateKey = secp.utils.randomPrivateKey()
|
||||
|
||||
const publicKey = bytesToHex(getPublicKey(privateKey))
|
||||
const compressedPublicKey = bytesToHex(getPublicKey(privateKey, true))
|
||||
const publicKey = bytesToHex(secp.getPublicKey(privateKey))
|
||||
const compressedPublicKey = bytesToHex(secp.getPublicKey(privateKey, true))
|
||||
|
||||
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||
})
|
||||
|
||||
it('should accept public key with a base prefix', () => {
|
||||
const privateKey = utils.randomPrivateKey()
|
||||
|
||||
const publicKey = '0x' + bytesToHex(getPublicKey(privateKey))
|
||||
const compressedPublicKey = bytesToHex(getPublicKey(privateKey, true))
|
||||
|
||||
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||
})
|
||||
|
||||
it('should throw error if public key is not a valid hex', () => {
|
||||
expect(() => {
|
||||
compressPublicKey('not a valid public key')
|
||||
}).toThrowErrorMatchingInlineSnapshot(`"Invalid public key"`)
|
||||
})
|
||||
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||
})
|
||||
|
||||
test('should accept public key with a base prefix', () => {
|
||||
const privateKey = secp.utils.randomPrivateKey()
|
||||
|
||||
const publicKey = '0x' + bytesToHex(secp.getPublicKey(privateKey))
|
||||
const compressedPublicKey = bytesToHex(secp.getPublicKey(privateKey, true))
|
||||
|
||||
expect(compressPublicKey(publicKey)).toEqual(compressedPublicKey)
|
||||
})
|
||||
|
||||
test('should throw error if public key is not a valid hex', () => {
|
||||
expect(() => {
|
||||
compressPublicKey('not a valid public key')
|
||||
}).toThrowErrorMatchingInlineSnapshot(`"Invalid public key"`)
|
||||
})
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
import { bytesToHex } from 'ethereum-cryptography/utils'
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
import { generateKeyFromPassword } from './generate-key-from-password'
|
||||
|
||||
describe('createSymKeyFromPassword', () => {
|
||||
it('should create symmetric key from password', async () => {
|
||||
const password = 'arbitrary data here'
|
||||
const symKey = await generateKeyFromPassword(password)
|
||||
test('should create symmetric key from password', async () => {
|
||||
const password = 'arbitrary data here'
|
||||
const symKey = await generateKeyFromPassword(password)
|
||||
|
||||
expect(bytesToHex(symKey)).toEqual(
|
||||
'c49ad65ebf2a7b7253bf400e3d27719362a91b2c9b9f54d50a69117021666c33'
|
||||
)
|
||||
})
|
||||
|
||||
it('should generate symmetric key from chat ID', async () => {
|
||||
const chatId =
|
||||
'0x02dcec6041fb999d65f1d33363e08c93d3c1f6f0fbbb26add383e2cf46c2b921f41dc14fd8-9a8b-4df5-a358-2c3067be5439'
|
||||
const symKey = await generateKeyFromPassword(chatId)
|
||||
|
||||
expect(bytesToHex(symKey)).toEqual(
|
||||
'76ff5bf0a74a8e724367c7fc003f066d477641f468768a8da2817addf5c2ce76'
|
||||
)
|
||||
})
|
||||
expect(bytesToHex(symKey)).toEqual(
|
||||
'c49ad65ebf2a7b7253bf400e3d27719362a91b2c9b9f54d50a69117021666c33'
|
||||
)
|
||||
})
|
||||
|
||||
test('should generate symmetric key from chat ID', async () => {
|
||||
const chatId =
|
||||
'0x02dcec6041fb999d65f1d33363e08c93d3c1f6f0fbbb26add383e2cf46c2b921f41dc14fd8-9a8b-4df5-a358-2c3067be5439'
|
||||
const symKey = await generateKeyFromPassword(chatId)
|
||||
|
||||
expect(bytesToHex(symKey)).toEqual(
|
||||
'76ff5bf0a74a8e724367c7fc003f066d477641f468768a8da2817addf5c2ce76'
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { expect, test } from 'vitest'
|
||||
|
||||
import { generateUsername } from './generate-username'
|
||||
|
||||
describe('generateUsername', () => {
|
||||
it('should generate the username', async () => {
|
||||
const publicKey1 =
|
||||
'0x04eedbaafd6adf4a9233a13e7b1c3c14461fffeba2e9054b8d456ce5f6ebeafadcbf3dce3716253fbc391277fa5a086b60b283daf61fb5b1f26895f456c2f31ae3'
|
||||
expect(generateUsername(publicKey1)).toBe('Darkorange Blue Bubblefish')
|
||||
test('should generate the username', () => {
|
||||
const publicKey1 =
|
||||
'0x04eedbaafd6adf4a9233a13e7b1c3c14461fffeba2e9054b8d456ce5f6ebeafadcbf3dce3716253fbc391277fa5a086b60b283daf61fb5b1f26895f456c2f31ae3'
|
||||
expect(generateUsername(publicKey1)).toBe('Darkorange Blue Bubblefish')
|
||||
|
||||
const publicKey2 =
|
||||
'0x04ac419dac9a8bbb58825a3cde60eef0ee71b8cf6c63df611eeefc8e7aac7c79b55954b679d24cf5ec82da7ed921caf240628a9bfb3450c5111a9cffe54e631811'
|
||||
expect(generateUsername(publicKey2)).toBe('Bumpy Absolute Crustacean')
|
||||
const publicKey2 =
|
||||
'0x04ac419dac9a8bbb58825a3cde60eef0ee71b8cf6c63df611eeefc8e7aac7c79b55954b679d24cf5ec82da7ed921caf240628a9bfb3450c5111a9cffe54e631811'
|
||||
expect(generateUsername(publicKey2)).toBe('Bumpy Absolute Crustacean')
|
||||
|
||||
const publicKey3 =
|
||||
'0x0403aeff2fdd0044b136e06afa6d69bb563bb7b3fd518bb30c0d5115a2e020840a2247966c2cc9953ed02cc391e8883b3319f63a31e5f5369d0fb72b62b23dfcbd'
|
||||
expect(generateUsername(publicKey3)).toBe('Back Careful Cuckoo')
|
||||
})
|
||||
const publicKey3 =
|
||||
'0x0403aeff2fdd0044b136e06afa6d69bb563bb7b3fd518bb30c0d5115a2e020840a2247966c2cc9953ed02cc391e8883b3319f63a31e5f5369d0fb72b62b23dfcbd'
|
||||
expect(generateUsername(publicKey3)).toBe('Back Careful Cuckoo')
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { expect, test } from 'vitest'
|
||||
|
||||
import { idToContentTopic } from './id-to-content-topic'
|
||||
|
||||
describe('idToContentTopic', () => {
|
||||
it('should return content topic', () => {
|
||||
expect(idToContentTopic).toBeDefined()
|
||||
})
|
||||
test('should return content topic', () => {
|
||||
expect(idToContentTopic).toBeDefined()
|
||||
})
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { expect, test } from 'vitest'
|
||||
|
||||
import {
|
||||
hexToColorHash,
|
||||
publicKeyToColorHash,
|
||||
} from './public-key-to-color-hash'
|
||||
|
||||
test('returns color hash from public key', () => {
|
||||
test('should return color hash from public key', () => {
|
||||
expect(
|
||||
publicKeyToColorHash(
|
||||
'0x04e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
||||
|
@ -23,29 +25,29 @@ test('returns color hash from public key', () => {
|
|||
])
|
||||
})
|
||||
|
||||
test('returns undefined for invalid public keys', () => {
|
||||
expect(publicKeyToColorHash('abc')).toBeUndefined()
|
||||
expect(publicKeyToColorHash('0x01')).toBeUndefined()
|
||||
expect(
|
||||
test('should throw for invalid public keys', () => {
|
||||
expect(() => publicKeyToColorHash('abc')).toThrow()
|
||||
expect(() => publicKeyToColorHash('0x01')).toThrow()
|
||||
expect(() =>
|
||||
publicKeyToColorHash(
|
||||
'0x01e25da6994ea2dc4ac70727e07eca153ae92bf7609db7befb7ebdceaad348f4fc55bbe90abf9501176301db5aa103fc0eb3bc3750272a26c424a10887db2a7ea8'
|
||||
)
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
).toThrow()
|
||||
expect(() =>
|
||||
publicKeyToColorHash(
|
||||
'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('1', 4, 4)).toEqual([[1, 1]])
|
||||
expect(hexToColorHash('4', 4, 4)).toEqual([[2, 0]])
|
||||
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([
|
||||
[4, 3],
|
||||
[4, 0],
|
||||
|
|
|
@ -1,81 +1,79 @@
|
|||
import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils'
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
import { Account } from '../client/account'
|
||||
import { recoverPublicKey } from './recover-public-key'
|
||||
|
||||
import type { ApplicationMetadataMessage } from '../../protos/application-metadata-message'
|
||||
import type { ApplicationMetadataMessage } from '../protos/application-metadata-message'
|
||||
|
||||
describe('recoverPublicKey', () => {
|
||||
it('should recover public key', async () => {
|
||||
const payload = utf8ToBytes('hello')
|
||||
test('should recover public key', async () => {
|
||||
const payload = utf8ToBytes('hello')
|
||||
|
||||
const account = new Account()
|
||||
const signature = await account.sign(payload)
|
||||
const account = new Account()
|
||||
const signature = await account.sign(payload)
|
||||
|
||||
expect(bytesToHex(recoverPublicKey(signature, payload))).toEqual(
|
||||
account.publicKey
|
||||
)
|
||||
})
|
||||
|
||||
it('should recover public key from fixture', async () => {
|
||||
const metadataFixture: ApplicationMetadataMessage = {
|
||||
type: 'TYPE_EMOJI_REACTION' as ApplicationMetadataMessage.Type,
|
||||
signature: new Uint8Array([
|
||||
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,
|
||||
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,
|
||||
230, 129, 0,
|
||||
]),
|
||||
payload: new Uint8Array([
|
||||
8, 138, 245, 146, 158, 148, 48, 18, 104, 48, 120, 48, 50, 57, 102, 49,
|
||||
57, 54, 98, 98, 102, 101, 102, 52, 102, 97, 54, 97, 53, 101, 98, 56, 49,
|
||||
100, 100, 56, 48, 50, 49, 51, 51, 97, 54, 51, 52, 57, 56, 51, 50, 53,
|
||||
52, 52, 53, 99, 97, 49, 97, 102, 49, 100, 49, 53, 52, 98, 49, 98, 98,
|
||||
52, 53, 52, 50, 57, 53, 53, 49, 51, 51, 51, 48, 56, 48, 52, 101, 97, 55,
|
||||
45, 98, 100, 54, 54, 45, 52, 100, 53, 100, 45, 57, 49, 101, 98, 45, 98,
|
||||
50, 100, 99, 102, 101, 50, 53, 49, 53, 98, 51, 26, 66, 48, 120, 53, 97,
|
||||
57, 49, 99, 52, 54, 48, 97, 97, 100, 101, 99, 51, 99, 55, 54, 100, 48,
|
||||
56, 48, 98, 54, 99, 55, 50, 97, 50, 48, 101, 49, 53, 97, 51, 51, 55,
|
||||
102, 55, 99, 48, 98, 55, 55, 97, 55, 99, 48, 97, 53, 101, 98, 97, 53,
|
||||
102, 97, 57, 100, 52, 100, 57, 49, 98, 97, 56, 32, 5, 40, 2,
|
||||
]),
|
||||
}
|
||||
|
||||
const publicKeySnapshot = new Uint8Array([
|
||||
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,
|
||||
121, 181, 89, 84, 182, 121, 210, 76, 245, 236, 130, 218, 126, 217, 33,
|
||||
202, 242, 64, 98, 138, 155, 251, 52, 80, 197, 17, 26, 156, 255, 229, 78,
|
||||
99, 24, 17,
|
||||
])
|
||||
|
||||
const result = recoverPublicKey(
|
||||
metadataFixture.signature,
|
||||
metadataFixture.payload
|
||||
)
|
||||
|
||||
expect(result).toEqual(publicKeySnapshot)
|
||||
})
|
||||
|
||||
it('should not recover public key with different payload', async () => {
|
||||
const payload = utf8ToBytes('1')
|
||||
|
||||
const account = new Account()
|
||||
const signature = await account.sign(payload)
|
||||
|
||||
const payload2 = utf8ToBytes('2')
|
||||
expect(recoverPublicKey(signature, payload2)).not.toEqual(account.publicKey)
|
||||
})
|
||||
|
||||
it('should throw error when signature length is not 65 bytes', async () => {
|
||||
const payload = utf8ToBytes('hello')
|
||||
const signature = new Uint8Array([
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
])
|
||||
|
||||
expect(() =>
|
||||
recoverPublicKey(signature, payload)
|
||||
).toThrowErrorMatchingInlineSnapshot(`"Signature must be 65 bytes long"`)
|
||||
})
|
||||
expect(bytesToHex(recoverPublicKey(signature, payload))).toEqual(
|
||||
account.publicKey
|
||||
)
|
||||
})
|
||||
|
||||
test('should recover public key from fixture', async () => {
|
||||
const metadataFixture: ApplicationMetadataMessage = {
|
||||
type: 'TYPE_EMOJI_REACTION' as ApplicationMetadataMessage.Type,
|
||||
signature: new Uint8Array([
|
||||
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,
|
||||
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, 230,
|
||||
129, 0,
|
||||
]),
|
||||
payload: new Uint8Array([
|
||||
8, 138, 245, 146, 158, 148, 48, 18, 104, 48, 120, 48, 50, 57, 102, 49, 57,
|
||||
54, 98, 98, 102, 101, 102, 52, 102, 97, 54, 97, 53, 101, 98, 56, 49, 100,
|
||||
100, 56, 48, 50, 49, 51, 51, 97, 54, 51, 52, 57, 56, 51, 50, 53, 52, 52,
|
||||
53, 99, 97, 49, 97, 102, 49, 100, 49, 53, 52, 98, 49, 98, 98, 52, 53, 52,
|
||||
50, 57, 53, 53, 49, 51, 51, 51, 48, 56, 48, 52, 101, 97, 55, 45, 98, 100,
|
||||
54, 54, 45, 52, 100, 53, 100, 45, 57, 49, 101, 98, 45, 98, 50, 100, 99,
|
||||
102, 101, 50, 53, 49, 53, 98, 51, 26, 66, 48, 120, 53, 97, 57, 49, 99, 52,
|
||||
54, 48, 97, 97, 100, 101, 99, 51, 99, 55, 54, 100, 48, 56, 48, 98, 54, 99,
|
||||
55, 50, 97, 50, 48, 101, 49, 53, 97, 51, 51, 55, 102, 55, 99, 48, 98, 55,
|
||||
55, 97, 55, 99, 48, 97, 53, 101, 98, 97, 53, 102, 97, 57, 100, 52, 100,
|
||||
57, 49, 98, 97, 56, 32, 5, 40, 2,
|
||||
]),
|
||||
}
|
||||
|
||||
const publicKeySnapshot = new Uint8Array([
|
||||
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, 121,
|
||||
181, 89, 84, 182, 121, 210, 76, 245, 236, 130, 218, 126, 217, 33, 202, 242,
|
||||
64, 98, 138, 155, 251, 52, 80, 197, 17, 26, 156, 255, 229, 78, 99, 24, 17,
|
||||
])
|
||||
|
||||
const result = recoverPublicKey(
|
||||
metadataFixture.signature,
|
||||
metadataFixture.payload
|
||||
)
|
||||
|
||||
expect(result).toEqual(publicKeySnapshot)
|
||||
})
|
||||
|
||||
test('should not recover public key with different payload', async () => {
|
||||
const payload = utf8ToBytes('1')
|
||||
|
||||
const account = new Account()
|
||||
const signature = await account.sign(payload)
|
||||
|
||||
const payload2 = utf8ToBytes('2')
|
||||
expect(recoverPublicKey(signature, payload2)).not.toEqual(account.publicKey)
|
||||
})
|
||||
|
||||
test('should throw error when signature length is not 65 bytes', async () => {
|
||||
const payload = utf8ToBytes('hello')
|
||||
const signature = new Uint8Array([
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
])
|
||||
|
||||
expect(() =>
|
||||
recoverPublicKey(signature, payload)
|
||||
).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?
|
||||
export function validateMessage(message: ChatMessage): boolean {
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"declarationDir": "dist/types",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./*"]
|
||||
}
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "./dist/types"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
"version": "0.0.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": {
|
||||
"url": "https://github.com/status-im/status-web.git",
|
||||
"directory": "packages/status-react",
|
||||
|
@ -11,18 +18,15 @@
|
|||
"bugs": {
|
||||
"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": {
|
||||
"prebuild": "rm -rf dist",
|
||||
"build": "parcel build",
|
||||
"build:types": "tsc --emitDeclarationOnly",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "vite build --watch",
|
||||
"build": "vite build && yarn typegen",
|
||||
"#test": "vitest",
|
||||
"typecheck": "tsc",
|
||||
"typegen": "tsc --noEmit false --emitDeclarationOnly --paths null || true",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src",
|
||||
"clean": "rm -rf node_modules && rm -rf dist"
|
||||
"clean": "rm -rf dist node_modules .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcaptcha/react-hcaptcha": "^1.0.0",
|
||||
|
@ -45,9 +49,7 @@
|
|||
"emoji-mart": "^3.0.1",
|
||||
"html-entities": "^2.3.2",
|
||||
"qrcode.react": "^3.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-content-loader": "^6.2.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"styled-components": "^5.3.1",
|
||||
|
@ -58,7 +60,9 @@
|
|||
"@types/hcaptcha__react-hcaptcha": "^0.1.5",
|
||||
"@types/node": "^16.9.6",
|
||||
"@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": {
|
||||
"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": {
|
||||
"incremental": true,
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2020",
|
||||
"jsx": "preserve",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
@ -13,7 +15,7 @@
|
|||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
// "noEmit": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": 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