mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
chore: setup @opchan/core
This commit is contained in:
parent
5d8f1e5154
commit
0102c107c3
94
.eslintrc.js
Normal file
94
.eslintrc.js
Normal file
@ -0,0 +1,94 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
es2020: true,
|
||||
},
|
||||
globals: {
|
||||
indexedDB: 'readonly',
|
||||
IDBDatabase: 'readonly',
|
||||
localStorage: 'readonly',
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './packages/*/tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
// TypeScript specific rules
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||
'@typescript-eslint/no-unsafe-call': 'warn',
|
||||
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
'@typescript-eslint/restrict-template-expressions': 'warn',
|
||||
'@typescript-eslint/restrict-plus-operands': 'warn',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
|
||||
// General rules
|
||||
'no-console': 'warn',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'object-shorthand': 'error',
|
||||
'prefer-template': 'error',
|
||||
'no-unused-vars': 'off', // Turn off base rule
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
// React package specific configuration
|
||||
files: ['packages/react/**/*.{ts,tsx}'],
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './packages/react/tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'react/react-in-jsx-scope': 'off', // Not needed with React 17+
|
||||
'react/prop-types': 'off', // Using TypeScript for prop validation
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
'no-unused-vars': 'off', // Turn off base rule
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||
'@typescript-eslint/no-unsafe-call': 'warn',
|
||||
},
|
||||
},
|
||||
],
|
||||
ignorePatterns: [
|
||||
'dist/',
|
||||
'node_modules/',
|
||||
'*.js',
|
||||
'*.d.ts',
|
||||
],
|
||||
};
|
||||
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
|
||||
4151
app/package-lock.json
generated
4151
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -50,7 +50,6 @@
|
||||
"@reown/appkit-adapter-wagmi": "^1.7.17",
|
||||
"@reown/appkit-wallet-button": "^1.7.17",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@waku/sdk": "^0.0.35-67a7287.0",
|
||||
"bitcoinjs-message": "^2.2.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
11268
package-lock.json
generated
11268
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -11,11 +11,24 @@
|
||||
"dev": "npm run dev --workspaces",
|
||||
"test": "npm run test --workspaces",
|
||||
"lint": "npm run lint --workspaces",
|
||||
"lint:fix": "npm run lint:fix --workspaces",
|
||||
"check": "npm run check --workspaces",
|
||||
"fix": "npm run fix --workspaces",
|
||||
"clean": "npm run clean --workspaces"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/ed25519": "^3.0.0",
|
||||
"@waku/sdk": "^0.0.35-67a7287.0",
|
||||
"bitcoinjs-message": "^2.2.0",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
39
packages/core/.eslintrc.cjs
Normal file
39
packages/core/.eslintrc.cjs
Normal file
@ -0,0 +1,39 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
es2020: true,
|
||||
},
|
||||
globals: {
|
||||
indexedDB: 'readonly',
|
||||
IDBDatabase: 'readonly',
|
||||
localStorage: 'readonly',
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
// General rules
|
||||
'no-console': 'off',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'object-shorthand': 'error',
|
||||
'prefer-template': 'error',
|
||||
'no-unused-vars': 'off', // Turn off base rule
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
ignorePatterns: [
|
||||
'dist/',
|
||||
'node_modules/',
|
||||
'*.js',
|
||||
'*.d.ts',
|
||||
],
|
||||
};
|
||||
@ -2,8 +2,16 @@
|
||||
"name": "@opchan/core",
|
||||
"version": "1.0.0",
|
||||
"description": "Core package for opchan",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
@ -11,13 +19,34 @@
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "echo 'No linting configured'"
|
||||
"lint": "eslint src --ext .ts,.tsx",
|
||||
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
||||
"check": "tsc --noEmit && eslint src --ext .ts,.tsx && prettier --check src",
|
||||
"fix": "prettier --write src && eslint src --ext .ts,.tsx --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.0.0",
|
||||
"prettier": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@waku/sdk": "^0.0.35-67a7287.0",
|
||||
"typescript": ">=5.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"browser": {
|
||||
"fs": false,
|
||||
"path": false,
|
||||
"os": false
|
||||
},
|
||||
"dependencies": {
|
||||
"@reown/appkit": "^1.8.3",
|
||||
"@reown/appkit-adapter-bitcoin": "^1.8.3",
|
||||
"@reown/appkit-adapter-wagmi": "^1.8.3",
|
||||
"wagmi": "^2.16.9"
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,13 +11,13 @@ import {
|
||||
PostMessage,
|
||||
CommentMessage,
|
||||
VoteMessage,
|
||||
} from '@/types/waku';
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { MessageValidator } from '@/lib/utils/MessageValidator';
|
||||
import { EVerificationStatus, User } from '@/types/identity';
|
||||
import { DelegationInfo } from '@/lib/delegation/types';
|
||||
import { openLocalDB, STORE, StoreName } from '@/lib/database/schema';
|
||||
import { Bookmark, BookmarkCache } from '@/types/forum';
|
||||
} from '../types/waku';
|
||||
import { OpchanMessage } from '../types/forum';
|
||||
import { MessageValidator } from '../utils/MessageValidator';
|
||||
import { EVerificationStatus, User } from '../types/identity';
|
||||
import { DelegationInfo } from '../delegation/types';
|
||||
import { openLocalDB, STORE, StoreName } from '../database/schema';
|
||||
import { Bookmark, BookmarkCache } from '../types/forum';
|
||||
|
||||
export interface LocalDatabaseCache {
|
||||
cells: CellCache;
|
||||
@ -266,26 +266,26 @@ export class LocalDatabase {
|
||||
this.getAllFromStore<Bookmark>(STORE.BOOKMARKS),
|
||||
]);
|
||||
|
||||
this.cache.cells = Object.fromEntries(cells.map(c => [c.id, c]));
|
||||
this.cache.posts = Object.fromEntries(posts.map(p => [p.id, p]));
|
||||
this.cache.comments = Object.fromEntries(comments.map(cm => [cm.id, cm]));
|
||||
this.cache.cells = Object.fromEntries(cells.map((c) => [c.id, c]));
|
||||
this.cache.posts = Object.fromEntries(posts.map((p) => [p.id, p]));
|
||||
this.cache.comments = Object.fromEntries(comments.map((cm) => [cm.id, cm]));
|
||||
this.cache.votes = Object.fromEntries(
|
||||
votes.map(v => {
|
||||
votes.map((v) => {
|
||||
const { key, ...rest } = v;
|
||||
const vote: VoteMessage = rest as VoteMessage;
|
||||
return [key, vote];
|
||||
})
|
||||
);
|
||||
this.cache.moderations = Object.fromEntries(
|
||||
moderations.map(m => [m.targetId, m])
|
||||
moderations.map((m) => [m.targetId, m])
|
||||
);
|
||||
this.cache.userIdentities = Object.fromEntries(
|
||||
identities.map(u => {
|
||||
identities.map((u) => {
|
||||
const { address, ...record } = u;
|
||||
return [address, record];
|
||||
})
|
||||
);
|
||||
this.cache.bookmarks = Object.fromEntries(bookmarks.map(b => [b.id, b]));
|
||||
this.cache.bookmarks = Object.fromEntries(bookmarks.map((b) => [b.id, b]));
|
||||
}
|
||||
|
||||
private async hydratePendingFromMeta(): Promise<void> {
|
||||
@ -295,10 +295,10 @@ export class LocalDatabase {
|
||||
);
|
||||
meta
|
||||
.filter(
|
||||
entry =>
|
||||
(entry) =>
|
||||
typeof entry.key === 'string' && entry.key.startsWith('pending:')
|
||||
)
|
||||
.forEach(entry => {
|
||||
.forEach((entry) => {
|
||||
const id = (entry.key as string).substring('pending:'.length);
|
||||
this.pendingIds.add(id);
|
||||
});
|
||||
@ -359,7 +359,7 @@ export class LocalDatabase {
|
||||
const tx = this.db.transaction(STORE.META, 'readwrite');
|
||||
const store = tx.objectStore(STORE.META);
|
||||
store.put({ key: `pending:${id}`, value: true });
|
||||
this.pendingListeners.forEach(l => l());
|
||||
this.pendingListeners.forEach((l) => l());
|
||||
}
|
||||
|
||||
public clearPending(id: string): void {
|
||||
@ -368,7 +368,7 @@ export class LocalDatabase {
|
||||
const tx = this.db.transaction(STORE.META, 'readwrite');
|
||||
const store = tx.objectStore(STORE.META);
|
||||
store.delete(`pending:${id}`);
|
||||
this.pendingListeners.forEach(l => l());
|
||||
this.pendingListeners.forEach((l) => l());
|
||||
}
|
||||
|
||||
public isPending(id: string): boolean {
|
||||
@ -590,7 +590,7 @@ export class LocalDatabase {
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
const bookmarks = request.result as Bookmark[];
|
||||
const filtered = bookmarks.filter(b => b.type === type);
|
||||
const filtered = bookmarks.filter((b) => b.type === type);
|
||||
resolve(filtered);
|
||||
};
|
||||
});
|
||||
@ -1,10 +1,6 @@
|
||||
import * as ed from '@noble/ed25519';
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
import { bytesToHex, hexToBytes } from '@/lib/utils';
|
||||
import { WalletManager } from '@/lib/wallet';
|
||||
|
||||
// Set up ed25519 with sha512
|
||||
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
||||
import { bytesToHex, hexToBytes } from '../utils';
|
||||
import { WalletManager } from '../wallet';
|
||||
|
||||
/**
|
||||
* Delegation-specific cryptographic utilities
|
||||
@ -60,7 +56,7 @@ export class DelegationCrypto {
|
||||
* @returns Object with public and private keys in hex format
|
||||
*/
|
||||
static generateKeypair(): { publicKey: string; privateKey: string } {
|
||||
const privateKey = ed.utils.randomPrivateKey();
|
||||
const privateKey = ed.utils.randomSecretKey();
|
||||
const privateKeyHex = bytesToHex(privateKey);
|
||||
|
||||
const publicKey = ed.getPublicKey(privateKey);
|
||||
@ -1,5 +1,5 @@
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { UnsignedMessage } from '@/types/waku';
|
||||
import { OpchanMessage } from '../types/forum';
|
||||
import { UnsignedMessage } from '../types/waku';
|
||||
import {
|
||||
DelegationDuration,
|
||||
DelegationInfo,
|
||||
@ -1,4 +1,4 @@
|
||||
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||
import { localDatabase } from '../database/LocalDatabase';
|
||||
import { DelegationInfo } from './types';
|
||||
|
||||
export class DelegationStorage {
|
||||
@ -7,7 +7,7 @@ export class DelegationStorage {
|
||||
*/
|
||||
static async store(delegation: DelegationInfo): Promise<void> {
|
||||
// Reduce verbose logging in production; keep minimal signal
|
||||
if (import.meta.env?.MODE !== 'production') {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('DelegationStorage.store');
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ export class DelegationStorage {
|
||||
static async retrieve(): Promise<DelegationInfo | null> {
|
||||
try {
|
||||
const delegation = await localDatabase.loadDelegation();
|
||||
if (import.meta.env?.MODE !== 'production') {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('DelegationStorage.retrieve');
|
||||
}
|
||||
return delegation;
|
||||
@ -9,14 +9,14 @@ import {
|
||||
CellMessage,
|
||||
CommentMessage,
|
||||
PostMessage,
|
||||
EModerationAction,
|
||||
} from '@/types/waku';
|
||||
import { Cell, Comment, Post } from '@/types/forum';
|
||||
import { EVerificationStatus, User } from '@/types/identity';
|
||||
EModerationAction
|
||||
} from '../types/waku';
|
||||
import { Cell, Comment, Post } from '../types/forum';
|
||||
import { EVerificationStatus, User } from '../types/identity';
|
||||
import { transformCell, transformComment, transformPost } from './transformers';
|
||||
import { DelegationManager } from '@/lib/delegation';
|
||||
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||
import messageManager from '@/lib/waku';
|
||||
import { DelegationManager } from '../delegation';
|
||||
import { localDatabase } from '../database/LocalDatabase';
|
||||
import messageManager from '../waku';
|
||||
|
||||
type ActionResult<T> = {
|
||||
success: boolean;
|
||||
@ -122,7 +122,7 @@ export class ForumActions {
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.catch((err) => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
const transformedPost = await transformPost(signed as PostMessage);
|
||||
@ -193,7 +193,7 @@ export class ForumActions {
|
||||
// Fire-and-forget network send; LocalDatabase will clear pending on ack
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.catch((err) => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
const transformed = await transformComment(signed as CommentMessage);
|
||||
@ -263,7 +263,7 @@ export class ForumActions {
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.catch((err) => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
const transformedCell = await transformCell(signed as CellMessage);
|
||||
@ -335,7 +335,7 @@ export class ForumActions {
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.catch((err) => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
return { success: true, data: true };
|
||||
@ -407,7 +407,7 @@ export class ForumActions {
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.catch((err) => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
return { success: true, data: true };
|
||||
@ -481,7 +481,7 @@ export class ForumActions {
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.catch((err) => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
return { success: true, data: true };
|
||||
@ -555,7 +555,7 @@ export class ForumActions {
|
||||
|
||||
messageManager
|
||||
.sendMessage(signed)
|
||||
.catch(err => console.error('Background send failed:', err))
|
||||
.catch((err) => console.error('Background send failed:', err))
|
||||
.finally(() => localDatabase.setSyncing(false));
|
||||
|
||||
return { success: true, data: true };
|
||||
@ -4,9 +4,9 @@ import {
|
||||
Cell,
|
||||
RelevanceScoreDetails,
|
||||
UserVerificationStatus,
|
||||
} from '@/types/forum';
|
||||
import { EVerificationStatus, User } from '@/types/identity';
|
||||
import { VoteMessage } from '@/types/waku';
|
||||
} from '../types/forum';
|
||||
import { EVerificationStatus, User } from '../types/identity';
|
||||
import { VoteMessage } from '../types/waku';
|
||||
|
||||
export class RelevanceCalculator {
|
||||
private static readonly BASE_SCORES = {
|
||||
@ -40,7 +40,7 @@ export class RelevanceCalculator {
|
||||
let score = this.applyBaseScore('POST');
|
||||
const baseScore = score;
|
||||
|
||||
const upvotes = votes.filter(vote => vote.value === 1);
|
||||
const upvotes = votes.filter((vote) => vote.value === 1);
|
||||
const engagementScore = this.applyEngagementScore(upvotes, comments);
|
||||
score += engagementScore;
|
||||
|
||||
@ -107,7 +107,7 @@ export class RelevanceCalculator {
|
||||
let score = this.applyBaseScore('COMMENT');
|
||||
const baseScore = score;
|
||||
|
||||
const upvotes = votes.filter(vote => vote.value === 1);
|
||||
const upvotes = votes.filter((vote) => vote.value === 1);
|
||||
const engagementScore = this.applyEngagementScore(upvotes, []);
|
||||
score += engagementScore;
|
||||
|
||||
@ -170,7 +170,7 @@ export class RelevanceCalculator {
|
||||
const baseScore = score;
|
||||
|
||||
// Calculate cell-specific engagement
|
||||
const cellPosts = posts.filter(post => post.cellId === cell.id);
|
||||
const cellPosts = posts.filter((post) => post.cellId === cell.id);
|
||||
const totalUpvotes = cellPosts.reduce((sum, post) => {
|
||||
return sum + (post.upvotes?.length || 0);
|
||||
}, 0);
|
||||
@ -235,7 +235,7 @@ export class RelevanceCalculator {
|
||||
buildUserVerificationStatus(users: User[]): UserVerificationStatus {
|
||||
const status: UserVerificationStatus = {};
|
||||
|
||||
users.forEach(user => {
|
||||
users.forEach((user) => {
|
||||
status[user.address] = {
|
||||
isVerified: this.isUserVerified(user),
|
||||
hasENS: !!user.ensDetails,
|
||||
@ -303,7 +303,7 @@ export class RelevanceCalculator {
|
||||
upvotes: VoteMessage[],
|
||||
userVerificationStatus: UserVerificationStatus
|
||||
): { bonus: number; verifiedUpvotes: number } {
|
||||
const verifiedUpvotes = upvotes.filter(vote => {
|
||||
const verifiedUpvotes = upvotes.filter((vote) => {
|
||||
const voterStatus = userVerificationStatus[vote.author];
|
||||
return voterStatus?.isVerified;
|
||||
});
|
||||
@ -322,7 +322,7 @@ export class RelevanceCalculator {
|
||||
): { bonus: number; verifiedCommenters: number } {
|
||||
const verifiedCommenters = new Set<string>();
|
||||
|
||||
comments.forEach(comment => {
|
||||
comments.forEach((comment) => {
|
||||
const commenterStatus = userVerificationStatus[comment.authorAddress];
|
||||
if (commenterStatus?.isVerified) {
|
||||
verifiedCommenters.add(comment.authorAddress);
|
||||
@ -1,14 +1,14 @@
|
||||
import { Cell, Post, Comment } from '@/types/forum';
|
||||
import { Cell, Post, Comment } from '../types/forum';
|
||||
import {
|
||||
CellMessage,
|
||||
CommentMessage,
|
||||
PostMessage,
|
||||
VoteMessage,
|
||||
EModerationAction,
|
||||
} from '@/types/waku';
|
||||
import messageManager from '@/lib/waku';
|
||||
} from '../types/waku';
|
||||
import messageManager from '../waku';
|
||||
import { RelevanceCalculator } from './RelevanceCalculator';
|
||||
import { UserVerificationStatus } from '@/types/forum';
|
||||
import { UserVerificationStatus } from '../types/forum';
|
||||
// Validation is enforced at ingestion time by LocalDatabase. Transformers assume
|
||||
// cache contains only valid, verified messages.
|
||||
|
||||
@ -43,9 +43,9 @@ export const transformCell = async (
|
||||
);
|
||||
|
||||
// Calculate active member count
|
||||
const cellPosts = posts.filter(post => post.cellId === cellMessage.id);
|
||||
const cellPosts = posts.filter((post) => post.cellId === cellMessage.id);
|
||||
const activeMembers = new Set<string>();
|
||||
cellPosts.forEach(post => {
|
||||
cellPosts.forEach((post) => {
|
||||
activeMembers.add(post.authorAddress);
|
||||
});
|
||||
|
||||
@ -68,7 +68,7 @@ export const transformPost = async (
|
||||
// Message validity already enforced upstream
|
||||
|
||||
const votes = Object.values(messageManager.messageCache.votes).filter(
|
||||
vote => vote.targetId === postMessage.id
|
||||
(vote) => vote.targetId === postMessage.id
|
||||
);
|
||||
// Votes in cache are already validated; just map
|
||||
const filteredVotes = votes;
|
||||
@ -87,7 +87,7 @@ export const transformPost = async (
|
||||
const userModMsg = Object.values(
|
||||
messageManager.messageCache.moderations
|
||||
).find(
|
||||
m =>
|
||||
(m) =>
|
||||
m.targetType === 'user' &&
|
||||
m.cellId === postMessage.cellId &&
|
||||
m.targetId === postMessage.author
|
||||
@ -135,14 +135,14 @@ export const transformPost = async (
|
||||
|
||||
// Get comments for this post
|
||||
const comments = await Promise.all(
|
||||
Object.values(messageManager.messageCache.comments).map(comment =>
|
||||
Object.values(messageManager.messageCache.comments).map((comment) =>
|
||||
transformComment(comment, undefined, userVerificationStatus)
|
||||
)
|
||||
).then(comments =>
|
||||
).then((comments) =>
|
||||
comments.filter((comment): comment is Comment => comment !== null)
|
||||
);
|
||||
const postComments = comments.filter(
|
||||
comment => comment.postId === postMessage.id
|
||||
(comment) => comment.postId === postMessage.id
|
||||
);
|
||||
|
||||
const relevanceResult = relevanceCalculator.calculatePostScore(
|
||||
@ -155,13 +155,13 @@ export const transformPost = async (
|
||||
const relevanceScore = relevanceResult.score;
|
||||
|
||||
// Calculate verified upvotes and commenters
|
||||
const verifiedUpvotes = upvotes.filter(vote => {
|
||||
const verifiedUpvotes = upvotes.filter((vote) => {
|
||||
const voterStatus = userVerificationStatus[vote.author];
|
||||
return voterStatus?.isVerified;
|
||||
}).length;
|
||||
|
||||
const verifiedCommenters = new Set<string>();
|
||||
postComments.forEach(comment => {
|
||||
postComments.forEach((comment) => {
|
||||
const commenterStatus = userVerificationStatus[comment.authorAddress];
|
||||
if (commenterStatus?.isVerified) {
|
||||
verifiedCommenters.add(comment.authorAddress);
|
||||
@ -187,7 +187,7 @@ export const transformComment = async (
|
||||
): Promise<Comment | null> => {
|
||||
// Message validity already enforced upstream
|
||||
const votes = Object.values(messageManager.messageCache.votes).filter(
|
||||
vote => vote.targetId === commentMessage.id
|
||||
(vote) => vote.targetId === commentMessage.id
|
||||
);
|
||||
// Votes in cache are already validated
|
||||
const filteredVotes = votes;
|
||||
@ -205,12 +205,12 @@ export const transformComment = async (
|
||||
modMsg.action === EModerationAction.MODERATE;
|
||||
// Find the post to get the correct cell ID
|
||||
const parentPost = Object.values(messageManager.messageCache.posts).find(
|
||||
post => post.id === commentMessage.postId
|
||||
(post) => post.id === commentMessage.postId
|
||||
);
|
||||
const userModMsg = Object.values(
|
||||
messageManager.messageCache.moderations
|
||||
).find(
|
||||
m =>
|
||||
(m) =>
|
||||
m.targetType === 'user' &&
|
||||
m.cellId === parentPost?.cellId &&
|
||||
m.targetId === commentMessage.author
|
||||
@ -286,25 +286,25 @@ export const getDataFromCache = async (
|
||||
// First transform posts and comments to get relevance scores
|
||||
// All validation is now handled internally by the transform functions
|
||||
const posts = await Promise.all(
|
||||
Object.values(messageManager.messageCache.posts).map(post =>
|
||||
Object.values(messageManager.messageCache.posts).map((post) =>
|
||||
transformPost(post, undefined, userVerificationStatus)
|
||||
)
|
||||
).then(posts => posts.filter((post): post is Post => post !== null));
|
||||
).then((posts) => posts.filter((post): post is Post => post !== null));
|
||||
|
||||
const comments = await Promise.all(
|
||||
Object.values(messageManager.messageCache.comments).map(c =>
|
||||
Object.values(messageManager.messageCache.comments).map((c) =>
|
||||
transformComment(c, undefined, userVerificationStatus)
|
||||
)
|
||||
).then(comments =>
|
||||
).then((comments) =>
|
||||
comments.filter((comment): comment is Comment => comment !== null)
|
||||
);
|
||||
|
||||
// Then transform cells with posts for relevance calculation
|
||||
const cells = await Promise.all(
|
||||
Object.values(messageManager.messageCache.cells).map(cell =>
|
||||
Object.values(messageManager.messageCache.cells).map((cell) =>
|
||||
transformCell(cell, undefined, userVerificationStatus, posts)
|
||||
)
|
||||
).then(cells => cells.filter((cell): cell is Cell => cell !== null));
|
||||
).then((cells) => cells.filter((cell): cell is Cell => cell !== null));
|
||||
|
||||
return { cells, posts, comments };
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export {};
|
||||
@ -1,5 +1,5 @@
|
||||
import { Bookmark, BookmarkType, Post, Comment } from '@/types/forum';
|
||||
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||
import { Bookmark, BookmarkType, Post, Comment } from '../types/forum';
|
||||
import { localDatabase } from '../database/LocalDatabase';
|
||||
|
||||
/**
|
||||
* Service for managing bookmarks
|
||||
@ -24,6 +24,7 @@ export class BookmarkService {
|
||||
title: post.title,
|
||||
author: post.author,
|
||||
cellId: cellId || post.cellId,
|
||||
postId: post.id,
|
||||
};
|
||||
|
||||
await localDatabase.addBookmark(bookmark);
|
||||
@ -41,7 +42,7 @@ export class BookmarkService {
|
||||
// Create a preview of the comment content for display
|
||||
const preview =
|
||||
comment.content.length > 100
|
||||
? comment.content.substring(0, 100) + '...'
|
||||
? `${comment.content.substring(0, 100)}...`
|
||||
: comment.content;
|
||||
|
||||
const bookmark: Bookmark = {
|
||||
@ -166,7 +167,7 @@ export class BookmarkService {
|
||||
public static async clearUserBookmarks(userId: string): Promise<void> {
|
||||
const bookmarks = await this.getUserBookmarks(userId);
|
||||
await Promise.all(
|
||||
bookmarks.map(bookmark => localDatabase.removeBookmark(bookmark.id))
|
||||
bookmarks.map((bookmark) => localDatabase.removeBookmark(bookmark.id))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { UnsignedMessage } from '@/types/waku';
|
||||
import { DelegationManager } from '@/lib/delegation';
|
||||
import messageManager from '@/lib/waku';
|
||||
import { OpchanMessage } from '../types/forum';
|
||||
import { UnsignedMessage } from '../types/waku';
|
||||
import { DelegationManager } from '../delegation';
|
||||
import messageManager from '../waku';
|
||||
|
||||
export interface MessageResult {
|
||||
success: boolean;
|
||||
@ -9,7 +9,7 @@ export class OrdinalAPI {
|
||||
* @returns A promise that resolves with the API response.
|
||||
*/
|
||||
async getOperatorDetails(address: string): Promise<OrdinalApiResponse> {
|
||||
if (import.meta.env.VITE_OPCHAN_MOCK_ORDINAL_CHECK === 'true') {
|
||||
if (process.env.VITE_OPCHAN_MOCK_ORDINAL_CHECK === 'true') {
|
||||
console.log(
|
||||
`[DEV] Bypassing ordinal verification for address: ${address}`
|
||||
);
|
||||
@ -1,13 +1,13 @@
|
||||
import { EVerificationStatus, EDisplayPreference } from '@/types/identity';
|
||||
import { EVerificationStatus, EDisplayPreference } from '../types/identity';
|
||||
import {
|
||||
UnsignedUserProfileUpdateMessage,
|
||||
UserProfileUpdateMessage,
|
||||
MessageType,
|
||||
UserIdentityCache,
|
||||
} from '@/types/waku';
|
||||
} from '../types/waku';
|
||||
import { MessageService } from './MessageService';
|
||||
import messageManager from '@/lib/waku';
|
||||
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||
import messageManager from '../waku';
|
||||
import { localDatabase } from '../database/LocalDatabase';
|
||||
|
||||
export interface UserIdentity {
|
||||
address: string;
|
||||
@ -38,7 +38,7 @@ export class UserIdentityService {
|
||||
// Check internal cache first
|
||||
if (this.userIdentityCache[address]) {
|
||||
const cached = this.userIdentityCache[address];
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug('UserIdentityService: cache hit (internal)');
|
||||
}
|
||||
// Enrich with ENS name if missing and ETH address
|
||||
@ -99,7 +99,7 @@ export class UserIdentityService {
|
||||
messageManager.messageCache.userIdentities[address];
|
||||
|
||||
if (cacheServiceData) {
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug('UserIdentityService: cache hit (message cache)');
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ export class UserIdentityService {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug('UserIdentityService: cache miss, resolving');
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ export class UserIdentityService {
|
||||
displayPreference: EDisplayPreference
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug('UserIdentityService: updating profile', { address });
|
||||
}
|
||||
|
||||
@ -196,14 +196,14 @@ export class UserIdentityService {
|
||||
unsignedMessage.callSign = callSign.trim();
|
||||
}
|
||||
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug('UserIdentityService: created unsigned message');
|
||||
}
|
||||
|
||||
const signedMessage =
|
||||
await this.messageService.signAndBroadcastMessage(unsignedMessage);
|
||||
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug(
|
||||
'UserIdentityService: message broadcast result',
|
||||
!!signedMessage
|
||||
@ -309,7 +309,7 @@ export class UserIdentityService {
|
||||
try {
|
||||
// Import the ENS resolver from wagmi
|
||||
const { getEnsName } = await import('@wagmi/core');
|
||||
const { config } = await import('@/lib/wallet/config');
|
||||
const { config } = await import('../wallet/config');
|
||||
|
||||
const ensName = await getEnsName(config, {
|
||||
address: address as `0x${string}`,
|
||||
@ -413,7 +413,7 @@ export class UserIdentityService {
|
||||
* Notify all listeners that user identity data has changed
|
||||
*/
|
||||
private notifyRefreshListeners(address: string): void {
|
||||
this.refreshListeners.forEach(listener => listener(address));
|
||||
this.refreshListeners.forEach((listener) => listener(address));
|
||||
}
|
||||
|
||||
/**
|
||||
61
packages/core/src/types/browser.ts
Normal file
61
packages/core/src/types/browser.ts
Normal file
@ -0,0 +1,61 @@
|
||||
// Browser-compatible types for the core package
|
||||
|
||||
export interface Cell {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
createdAt: number;
|
||||
author: string;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
cellId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
author: string;
|
||||
createdAt: number;
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
id: string;
|
||||
postId: string;
|
||||
content: string;
|
||||
author: string;
|
||||
createdAt: number;
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
address: string;
|
||||
ensName?: string;
|
||||
callSign?: string;
|
||||
displayPreference: 'ens' | 'callsign' | 'address';
|
||||
verificationStatus: 'unverified' | 'verified' | 'pending';
|
||||
}
|
||||
|
||||
export interface Bookmark {
|
||||
id: string;
|
||||
userId: string;
|
||||
type: 'post' | 'comment' | 'cell';
|
||||
targetId: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
// IndexedDB store names
|
||||
export const STORE_NAMES = {
|
||||
CELLS: 'cells',
|
||||
POSTS: 'posts',
|
||||
COMMENTS: 'comments',
|
||||
VOTES: 'votes',
|
||||
MODERATIONS: 'moderations',
|
||||
USER_IDENTITIES: 'userIdentities',
|
||||
USER_AUTH: 'userAuth',
|
||||
DELEGATION: 'delegation',
|
||||
UI_STATE: 'uiState',
|
||||
META: 'meta',
|
||||
BOOKMARKS: 'bookmarks',
|
||||
} as const;
|
||||
|
||||
export type StoreName = (typeof STORE_NAMES)[keyof typeof STORE_NAMES];
|
||||
@ -5,9 +5,9 @@ import {
|
||||
VoteMessage,
|
||||
ModerateMessage,
|
||||
UserProfileUpdateMessage,
|
||||
} from '@/types/waku';
|
||||
} from './waku';
|
||||
import { EVerificationStatus } from './identity';
|
||||
import { DelegationProof } from '@/lib/delegation/types';
|
||||
import { DelegationProof } from '../delegation/types';
|
||||
|
||||
/**
|
||||
* Union type of all message types
|
||||
@ -1,5 +1,5 @@
|
||||
import { EDisplayPreference, EVerificationStatus } from './identity';
|
||||
import { DelegationProof } from '@/lib/delegation/types';
|
||||
import { DelegationProof } from '../delegation/types';
|
||||
|
||||
/**
|
||||
* Message types for Waku communication
|
||||
@ -1,13 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function bytesToHex(bytes: Uint8Array): string {
|
||||
return Array.from(bytes)
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { OpchanMessage, PartialMessage } from '@/types/forum';
|
||||
import { DelegationManager } from '@/lib/delegation';
|
||||
import { OpchanMessage, PartialMessage } from '../types/forum';
|
||||
import { DelegationManager } from '../delegation';
|
||||
|
||||
interface ValidationReport {
|
||||
validMessages: OpchanMessage[];
|
||||
@ -235,7 +235,7 @@ export class MessageValidator {
|
||||
messages: OpchanMessage[],
|
||||
messageType: string
|
||||
): T[] {
|
||||
return messages.filter(msg => msg.type === messageType) as T[];
|
||||
return messages.filter((msg) => msg.type === messageType) as T[];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1,4 +1,4 @@
|
||||
import { Post, Comment, Cell } from '@/types/forum';
|
||||
import { Post, Comment, Cell } from '../types/forum';
|
||||
|
||||
export type SortOption = 'relevance' | 'time';
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { IDecodedMessage, IDecoder, IEncoder, LightNode } from '@waku/sdk';
|
||||
import { MessageType, UserProfileUpdateMessage } from '../../types/waku';
|
||||
import { MessageType, UserProfileUpdateMessage } from '../types/waku';
|
||||
import {
|
||||
CellMessage,
|
||||
PostMessage,
|
||||
CommentMessage,
|
||||
VoteMessage,
|
||||
ModerateMessage,
|
||||
} from '../../types/waku';
|
||||
} from '../types/waku';
|
||||
import { CONTENT_TOPIC } from './constants';
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { OpchanMessage } from '../types/forum';
|
||||
|
||||
export class CodecManager {
|
||||
private encoder: IEncoder;
|
||||
@ -48,7 +48,7 @@ export class CodecManager {
|
||||
case MessageType.USER_PROFILE_UPDATE:
|
||||
return message as UserProfileUpdateMessage;
|
||||
default:
|
||||
throw new Error(`Unknown message type: `, message);
|
||||
throw new Error(`Unknown message type: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ import {
|
||||
ReliableChannelEvent,
|
||||
} from '@waku/sdk';
|
||||
import { CodecManager } from '../CodecManager';
|
||||
import { generateStringId } from '@/lib/utils';
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { generateStringId } from '../../utils';
|
||||
import { OpchanMessage } from '../../types/forum';
|
||||
|
||||
export interface MessageStatusCallback {
|
||||
onSent?: (messageId: string) => void;
|
||||
@ -50,30 +50,33 @@ export class ReliableMessaging {
|
||||
private setupChannelListeners(
|
||||
channel: ReliableChannel<IDecodedMessage>
|
||||
): void {
|
||||
channel.addEventListener(ReliableChannelEvent.InMessageReceived, event => {
|
||||
try {
|
||||
const wakuMessage = event.detail;
|
||||
if (wakuMessage.payload) {
|
||||
const opchanMessage = this.codecManager.decodeMessage(
|
||||
wakuMessage.payload
|
||||
);
|
||||
this.incomingMessageCallbacks.forEach(callback =>
|
||||
callback(opchanMessage)
|
||||
);
|
||||
channel.addEventListener(
|
||||
ReliableChannelEvent.InMessageReceived,
|
||||
(event) => {
|
||||
try {
|
||||
const wakuMessage = event.detail;
|
||||
if (wakuMessage.payload) {
|
||||
const opchanMessage = this.codecManager.decodeMessage(
|
||||
wakuMessage.payload
|
||||
);
|
||||
this.incomingMessageCallbacks.forEach((callback) =>
|
||||
callback(opchanMessage)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to process incoming message:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to process incoming message:', error);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
channel.addEventListener(ReliableChannelEvent.OutMessageSent, event => {
|
||||
channel.addEventListener(ReliableChannelEvent.OutMessageSent, (event) => {
|
||||
const messageId = event.detail;
|
||||
this.messageCallbacks.get(messageId)?.onSent?.(messageId);
|
||||
});
|
||||
|
||||
channel.addEventListener(
|
||||
ReliableChannelEvent.OutMessageAcknowledged,
|
||||
event => {
|
||||
(event) => {
|
||||
const messageId = event.detail;
|
||||
this.messageCallbacks.get(messageId)?.onAcknowledged?.(messageId);
|
||||
}
|
||||
@ -81,7 +84,7 @@ export class ReliableMessaging {
|
||||
|
||||
channel.addEventListener(
|
||||
ReliableChannelEvent.OutMessageIrrecoverableError,
|
||||
event => {
|
||||
(event) => {
|
||||
const messageId = event.detail.messageId;
|
||||
const error = event.detail.error;
|
||||
const callback = this.messageCallbacks.get(messageId);
|
||||
@ -28,11 +28,11 @@ export class WakuNodeManager {
|
||||
private setupHealthMonitoring(): void {
|
||||
if (!this.node) return;
|
||||
|
||||
this.node.events.addEventListener(WakuEvent.Health, event => {
|
||||
this.node.events.addEventListener(WakuEvent.Health, (event) => {
|
||||
const health = event.detail;
|
||||
this._currentHealth = health;
|
||||
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug(`Waku health status: ${health}`);
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ export class WakuNodeManager {
|
||||
}
|
||||
|
||||
private notifyHealthChange(): void {
|
||||
this.healthListeners.forEach(listener =>
|
||||
this.healthListeners.forEach((listener) =>
|
||||
listener(this._isReady, this._currentHealth)
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { HealthStatus } from '@waku/sdk';
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { OpchanMessage } from '../types/forum';
|
||||
import { WakuNodeManager, HealthChangeCallback } from './core/WakuNodeManager';
|
||||
import {
|
||||
MessageService,
|
||||
@ -34,7 +34,7 @@ class MessageManager {
|
||||
);
|
||||
|
||||
// Set up health-based reliable messaging initialization
|
||||
this.nodeManager.onHealthChange(isReady => {
|
||||
this.nodeManager.onHealthChange((isReady) => {
|
||||
if (isReady && !this.reliableMessaging) {
|
||||
this.initializeReliableMessaging();
|
||||
} else if (!isReady && this.reliableMessaging) {
|
||||
@ -1,4 +1,4 @@
|
||||
import messageManager from '@/lib/waku';
|
||||
import messageManager from './index';
|
||||
import { HealthStatus } from '@waku/sdk';
|
||||
|
||||
export type ToastFunction = (props: {
|
||||
@ -1,10 +1,10 @@
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { OpchanMessage } from '../../types/forum';
|
||||
import {
|
||||
ReliableMessaging,
|
||||
MessageStatusCallback,
|
||||
} from '../core/ReliableMessaging';
|
||||
import { WakuNodeManager } from '../core/WakuNodeManager';
|
||||
import { localDatabase } from '@/lib/database/LocalDatabase';
|
||||
import { localDatabase } from '../../database/LocalDatabase';
|
||||
|
||||
export type MessageReceivedCallback = (message: OpchanMessage) => void;
|
||||
export type { MessageStatusCallback };
|
||||
@ -21,13 +21,13 @@ export class MessageService {
|
||||
|
||||
private setupMessageHandling(): void {
|
||||
if (this.reliableMessaging) {
|
||||
this.reliableMessaging.onMessage(async message => {
|
||||
this.reliableMessaging.onMessage(async (message) => {
|
||||
localDatabase.setSyncing(true);
|
||||
const isNew = await localDatabase.updateCache(message);
|
||||
// Defensive: clear pending on inbound message to avoid stuck state
|
||||
localDatabase.clearPending(message.id);
|
||||
localDatabase.setSyncing(false);
|
||||
if (isNew) this.messageReceivedCallbacks.forEach(cb => cb(message));
|
||||
if (isNew) this.messageReceivedCallbacks.forEach((cb) => cb(message));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -52,11 +52,11 @@ export class MessageService {
|
||||
// Send via reliable messaging with status tracking
|
||||
localDatabase.setSyncing(true);
|
||||
await this.reliableMessaging.sendMessage(message, {
|
||||
onSent: id => {
|
||||
onSent: (id) => {
|
||||
console.log(`Message ${id} sent`);
|
||||
statusCallback?.onSent?.(id);
|
||||
},
|
||||
onAcknowledged: id => {
|
||||
onAcknowledged: (id) => {
|
||||
console.log(`Message ${id} acknowledged`);
|
||||
statusCallback?.onAcknowledged?.(id);
|
||||
localDatabase.clearPending(message.id);
|
||||
@ -31,7 +31,7 @@ const bitcoinAdapter = new BitcoinAdapter({
|
||||
|
||||
const metadata = {
|
||||
name: 'OpChan',
|
||||
description: 'Decentralized forum powered by Bitcoin Ordinals',
|
||||
description: 'A Forum Library over Waku',
|
||||
url:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? 'https://opchan.app'
|
||||
@ -190,7 +190,7 @@ export class WalletManager {
|
||||
walletType: 'bitcoin' | 'ethereum'
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Keep this lightweight in dev; avoid logging full message/signature repeatedly
|
||||
console.debug('WalletManager.verifySignature', { walletType });
|
||||
}
|
||||
@ -201,12 +201,12 @@ export class WalletManager {
|
||||
signature: signature as `0x${string}`,
|
||||
});
|
||||
} else if (walletType === 'bitcoin') {
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug('WalletManager.verifySignature (bitcoin)');
|
||||
}
|
||||
const bitcoinMessage = await loadBitcoinMessage();
|
||||
const result = bitcoinMessage.verify(message, walletAddress, signature);
|
||||
if (import.meta.env?.DEV) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug(
|
||||
'WalletManager.verifySignature (bitcoin) result',
|
||||
result
|
||||
@ -1,26 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable", "WebWorker"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
"isolatedModules": true,
|
||||
"noEmit": false,
|
||||
"jsx": "preserve",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
||||
47
packages/react/.eslintrc.cjs
Normal file
47
packages/react/.eslintrc.cjs
Normal file
@ -0,0 +1,47 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
es2020: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'react/react-in-jsx-scope': 'off', // Not needed with React 17+
|
||||
'react/prop-types': 'off', // Using TypeScript for prop validation
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
'no-unused-vars': 'off', // Turn off base rule
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
|
||||
// General rules
|
||||
'no-console': 'warn',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'object-shorthand': 'error',
|
||||
'prefer-template': 'error',
|
||||
},
|
||||
ignorePatterns: [
|
||||
'dist/',
|
||||
'node_modules/',
|
||||
'*.js',
|
||||
'*.d.ts',
|
||||
],
|
||||
};
|
||||
@ -11,7 +11,10 @@
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "echo 'No linting configured'"
|
||||
"lint": "eslint src --ext .ts,.tsx",
|
||||
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
||||
"check": "tsc --noEmit && eslint src --ext .ts,.tsx && prettier --check src",
|
||||
"fix": "prettier --write src && eslint src --ext .ts,.tsx --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opchan/core": "*",
|
||||
@ -20,10 +23,16 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-plugin-react": "^7.0.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"prettier": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0"
|
||||
"react": "^18.0.0",
|
||||
"typescript": ">=5.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user