@onyx-p/imlib-web 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "name": "@onyx-p/imlib-web",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "main": "index.umd.js",
5
5
  "module": "index.esm.js",
6
6
  "types": "types/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./types/index.d.ts",
10
+ "import": "./index.esm.js",
11
+ "require": "./index.umd.js"
12
+ },
13
+ "./main": {
14
+ "types": "./types/database/main-entry.d.ts",
15
+ "require": "./database/main.cjs"
16
+ },
17
+ "./preload": {
18
+ "types": "./types/database/preload-entry.d.ts",
19
+ "require": "./database/preload.cjs"
20
+ }
21
+ },
7
22
  "files": [
8
23
  "index.umd.js",
9
24
  "index.esm.js",
@@ -11,9 +26,20 @@
11
26
  "types/types.d.ts",
12
27
  "types/model",
13
28
  "types/constants",
29
+ "types/database",
30
+ "database",
14
31
  "README.md"
15
32
  ],
33
+ "dependencies": {
34
+ "@signalapp/sqlcipher": "4.0.5",
35
+ "crypto-js": "^4.2.0",
36
+ "long": "^5.2.3",
37
+ "protobufjs": "^7.4.0"
38
+ },
39
+ "peerDependencies": {
40
+ "electron": ">=30.0.0"
41
+ },
16
42
  "keywords": ["im"],
17
43
  "author": "zimu",
18
44
  "license": "MIT"
19
- }
45
+ }
@@ -0,0 +1,44 @@
1
+ export declare const DATABASE_GLOBAL: "acimDatabase";
2
+ export declare const DATABASE_CHANNEL: "acim-database:request";
3
+ export declare const READ_METHOD_NAMES: readonly ["getMessages", "getLatestMessage", "getLatestMessages", "getDialogLoadedState", "getMessageById", "getMessageByUId", "getMessageIdRange", "searchMessages", "searchTextMessages", "getLegacyMigrationState"];
4
+ export declare const WRITE_METHOD_NAMES: readonly ["addMessages", "clearConversationCache", "clearAllCache", "removeMessagesByUId", "updateMessageReceiptStatus", "clearBurnAfterReadingExpiredMessages", "upsertMessage", "convertToRecallMessages", "importLegacyBatch", "completeLegacyMigration"];
5
+ export type DatabaseReadMethod = (typeof READ_METHOD_NAMES)[number];
6
+ export type DatabaseWriteMethod = (typeof WRITE_METHOD_NAMES)[number];
7
+ export type DatabaseMethod = DatabaseReadMethod | DatabaseWriteMethod;
8
+ export declare const READ_METHODS: ReadonlySet<string>;
9
+ export declare const WRITE_METHODS: ReadonlySet<string>;
10
+ export type DatabaseOpenOptions = Readonly<{
11
+ appKey: string;
12
+ userId: string;
13
+ }>;
14
+ export type LegacyMigrationState = 'pending' | 'in_progress' | 'complete';
15
+ export type DatabaseRequest = Readonly<{
16
+ type: 'open';
17
+ options: DatabaseOpenOptions;
18
+ }> | Readonly<{
19
+ type: 'close';
20
+ }> | Readonly<{
21
+ type: 'read';
22
+ method: DatabaseReadMethod;
23
+ args: ReadonlyArray<unknown>;
24
+ }> | Readonly<{
25
+ type: 'write';
26
+ method: DatabaseWriteMethod;
27
+ args: ReadonlyArray<unknown>;
28
+ }>;
29
+ export type SerializedDatabaseError = Readonly<{
30
+ name: string;
31
+ message: string;
32
+ stack: string | undefined;
33
+ }>;
34
+ export type DatabaseResponse = Readonly<{
35
+ ok: true;
36
+ value: unknown;
37
+ }> | Readonly<{
38
+ ok: false;
39
+ error: SerializedDatabaseError;
40
+ }>;
41
+ export interface DatabaseBridge {
42
+ request(request: DatabaseRequest): Promise<DatabaseResponse>;
43
+ }
44
+ export declare function serializeDatabaseError(error: unknown): SerializedDatabaseError;
@@ -0,0 +1,2 @@
1
+ export { DatabaseKeyStore, type SafeStorageAdapter } from './keyStore';
2
+ export { MainDatabaseService, ThreadWorkerClient, createThreadWorkerFactory, type DatabaseWorkerClient, type WorkerClientFactory } from './service';
@@ -0,0 +1,18 @@
1
+ import { type DatabaseOpenOptions, type DatabaseReadMethod, type DatabaseResponse, type DatabaseWriteMethod } from '../contracts';
2
+ export interface DatabaseIpcMain {
3
+ handle(channel: string, handler: (event: unknown, request: unknown) => Promise<DatabaseResponse>): void;
4
+ removeHandler(channel: string): void;
5
+ }
6
+ export interface DatabaseServiceLike {
7
+ open(options: DatabaseOpenOptions): Promise<void>;
8
+ close(): Promise<void>;
9
+ read(method: DatabaseReadMethod, args: ReadonlyArray<unknown>): Promise<unknown>;
10
+ write(method: DatabaseWriteMethod, args: ReadonlyArray<unknown>): Promise<unknown>;
11
+ }
12
+ type RegisterDatabaseIpcOptions = Readonly<{
13
+ ipcMain: DatabaseIpcMain;
14
+ service: DatabaseServiceLike;
15
+ isTrustedEvent?: (event: unknown) => boolean;
16
+ }>;
17
+ export declare function registerDatabaseIpc({ ipcMain, service, isTrustedEvent }: RegisterDatabaseIpcOptions): () => void;
18
+ export {};
@@ -0,0 +1,14 @@
1
+ export interface SafeStorageAdapter {
2
+ encryptString(value: string): Uint8Array;
3
+ decryptString(value: Uint8Array): string;
4
+ }
5
+ export declare class DatabaseKeyStore {
6
+ private readonly directory;
7
+ private readonly safeStorage;
8
+ private readonly filePath;
9
+ constructor(directory: string, safeStorage: SafeStorageAdapter);
10
+ getOrCreate(accountId: string): string;
11
+ private readKeys;
12
+ private writeKeys;
13
+ private validateKey;
14
+ }
@@ -0,0 +1,58 @@
1
+ import type { LegacyMigrationState } from '../contracts';
2
+ import type { SqlDatabase } from './sqlTypes';
3
+ export type StoredMessage = {
4
+ messageId: number;
5
+ messageUId?: string;
6
+ sentTime: string;
7
+ content?: any;
8
+ quotedReply?: any;
9
+ receivedStatus?: number;
10
+ burnAfterReadingFlag?: boolean;
11
+ burnAfterReadingTime?: number;
12
+ messageType?: number;
13
+ isPersited?: boolean;
14
+ [key: string]: any;
15
+ };
16
+ export type DialogState = Readonly<{
17
+ dialogId: string;
18
+ isEnd: boolean;
19
+ updateTime?: number;
20
+ }>;
21
+ export type LegacyImportBatch = Readonly<{
22
+ messages: ReadonlyArray<StoredMessage & {
23
+ dialogId?: string;
24
+ }>;
25
+ dialogStates: ReadonlyArray<DialogState>;
26
+ }>;
27
+ export declare class MessageStore {
28
+ private readonly database;
29
+ constructor(database: SqlDatabase);
30
+ addMessages(messages: ReadonlyArray<StoredMessage>, dialogId: string, isEnd?: boolean): void;
31
+ upsertMessage(message: StoredMessage, dialogId: string): void;
32
+ getMessages(dialogId: string, timestamp?: string, count?: number, isForward?: boolean): {
33
+ messages: StoredMessage[];
34
+ hasMore: boolean;
35
+ };
36
+ getLatestMessage(dialogId: string): StoredMessage | null;
37
+ getLatestMessages(dialogIds: ReadonlyArray<string>): Record<string, StoredMessage | null>;
38
+ getMessageById(messageId: number): StoredMessage | null;
39
+ getMessageByUId(messageUId: string): StoredMessage | null;
40
+ getMessageIdRange(): {
41
+ newest: number;
42
+ oldest: number;
43
+ } | null;
44
+ getDialogLoadedState(dialogId: string): boolean;
45
+ clearConversationCache(dialogId: string): void;
46
+ clearAllCache(): void;
47
+ removeMessagesByUId(messageUIds: ReadonlyArray<string>): void;
48
+ updateMessageReceiptStatus(messageUIds: ReadonlyArray<string>, receivedStatus: number): void;
49
+ clearBurnAfterReadingExpiredMessages(dialogId: string, now?: number): string[];
50
+ convertToRecallMessages(messageUIds: ReadonlyArray<string>, recallMessageType: number): void;
51
+ searchTextMessages(dialogId: string, keyword: string): StoredMessage[];
52
+ searchMessages(keyword: string, dialogId?: string, limit?: number): StoredMessage[];
53
+ getLegacyMigrationState(): LegacyMigrationState;
54
+ importLegacyBatch(batch: LegacyImportBatch): void;
55
+ completeLegacyMigration(): void;
56
+ private setDialogState;
57
+ private setMetadata;
58
+ }
@@ -0,0 +1,9 @@
1
+ import type { SqlDatabase } from './sqlTypes';
2
+ export type SchemaMigration = Readonly<{
3
+ version: number;
4
+ up(database: SqlDatabase): void;
5
+ }>;
6
+ export declare const SCHEMA_MIGRATIONS: ReadonlyArray<SchemaMigration>;
7
+ export declare const LATEST_SCHEMA_VERSION: number;
8
+ export declare function validateMigrations(migrations: ReadonlyArray<SchemaMigration>): void;
9
+ export declare function runMigrations(database: SqlDatabase, migrations?: ReadonlyArray<SchemaMigration>): void;
@@ -0,0 +1,36 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import type { DatabaseOpenOptions, DatabaseReadMethod, DatabaseWriteMethod } from '../contracts';
3
+ import type { DatabaseKeyStore } from './keyStore';
4
+ import type { DatabaseWorkerRequest } from './workerProtocol';
5
+ export interface DatabaseWorkerClient {
6
+ request(request: DatabaseWorkerRequest): Promise<unknown>;
7
+ close(): Promise<void>;
8
+ }
9
+ export type WorkerClientFactory = (index: number) => DatabaseWorkerClient;
10
+ export type MainDatabaseServiceOptions = Readonly<{
11
+ userDataPath: string;
12
+ keyStore: DatabaseKeyStore;
13
+ workerFactory: WorkerClientFactory;
14
+ workerCount?: number;
15
+ }>;
16
+ export declare class MainDatabaseService {
17
+ private readonly options;
18
+ private readonly workerCount;
19
+ private readonly pool;
20
+ private accountId;
21
+ constructor(options: MainDatabaseServiceOptions);
22
+ open(options: DatabaseOpenOptions): Promise<void>;
23
+ read(method: DatabaseReadMethod, args: ReadonlyArray<unknown>): Promise<unknown>;
24
+ write(method: DatabaseWriteMethod, args: ReadonlyArray<unknown>): Promise<unknown>;
25
+ close(): Promise<void>;
26
+ private assertOpen;
27
+ }
28
+ export declare class ThreadWorkerClient implements DatabaseWorkerClient {
29
+ private readonly worker;
30
+ private sequence;
31
+ private readonly pending;
32
+ constructor(worker: Worker);
33
+ request(request: DatabaseWorkerRequest): Promise<unknown>;
34
+ close(): Promise<void>;
35
+ }
36
+ export declare function createThreadWorkerFactory(workerPath: string): WorkerClientFactory;
@@ -0,0 +1,2 @@
1
+ import type { Database } from '@signalapp/sqlcipher';
2
+ export type SqlDatabase = Database;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { type DatabaseMethod } from '../contracts';
2
+ export type DatabaseWorkerRequest = Readonly<{
3
+ type: 'init';
4
+ dbPath: string;
5
+ key: string;
6
+ isPrimary: boolean;
7
+ }> | Readonly<{
8
+ type: 'call';
9
+ access: 'read' | 'write';
10
+ method: DatabaseMethod | string;
11
+ args: ReadonlyArray<unknown>;
12
+ }> | Readonly<{
13
+ type: 'close';
14
+ }>;
15
+ export type WrappedWorkerRequest = Readonly<{
16
+ sequence: number;
17
+ request: DatabaseWorkerRequest;
18
+ }>;
19
+ export type WrappedWorkerResponse = Readonly<{
20
+ sequence: number;
21
+ ok: boolean;
22
+ value?: unknown;
23
+ error?: Readonly<{
24
+ name: string;
25
+ message: string;
26
+ stack?: string;
27
+ }>;
28
+ }>;
29
+ export declare class WorkerDispatcher {
30
+ private database;
31
+ private store;
32
+ private isPrimary;
33
+ handle(request: DatabaseWorkerRequest): unknown;
34
+ }
@@ -0,0 +1,12 @@
1
+ import { MainDatabaseService } from './main/service';
2
+ export type InitializeDatabaseMainOptions = Readonly<{
3
+ userDataPath?: string;
4
+ workerPath?: string;
5
+ isTrustedEvent?: (event: unknown) => boolean;
6
+ }>;
7
+ export type DatabaseMainController = Readonly<{
8
+ service: MainDatabaseService;
9
+ dispose(): Promise<void>;
10
+ }>;
11
+ export declare function initializeDatabaseMain(options?: InitializeDatabaseMainOptions): DatabaseMainController;
12
+ export * from './main/index';
@@ -0,0 +1 @@
1
+ export { exposeDatabaseBridge } from './preload';
@@ -0,0 +1,8 @@
1
+ import { type DatabaseRequest, type DatabaseResponse } from './contracts';
2
+ export interface ContextBridgeLike {
3
+ exposeInMainWorld(name: string, value: unknown): void;
4
+ }
5
+ export interface IpcRendererLike {
6
+ invoke(channel: string, request: DatabaseRequest): Promise<DatabaseResponse>;
7
+ }
8
+ export declare function exposeDatabaseBridge(contextBridge: ContextBridgeLike, ipcRenderer: IpcRendererLike): void;
@@ -0,0 +1,3 @@
1
+ import type { DatabaseBridge, DatabaseRequest } from '../contracts';
2
+ export declare function invokeDatabase(bridge: DatabaseBridge, request: DatabaseRequest): Promise<unknown>;
3
+ export declare function getDatabaseBridge(): DatabaseBridge;
@@ -0,0 +1,28 @@
1
+ import CryptoJS from 'crypto-js';
2
+ import type { DatabaseBridge } from '../contracts';
3
+ type LegacySecret = {
4
+ key: string;
5
+ iv: string;
6
+ };
7
+ export type LegacyMigrationProgress = Readonly<{
8
+ processedMessages: number;
9
+ processedDialogStates: number;
10
+ }>;
11
+ export type LegacyMigrationResult = Readonly<{
12
+ migrated: boolean;
13
+ messages: number;
14
+ dialogStates: number;
15
+ }>;
16
+ type MigrationOptions = Readonly<{
17
+ appKey: string;
18
+ userId: string;
19
+ bridge: DatabaseBridge;
20
+ indexedDBFactory?: IDBFactory;
21
+ keyRange?: typeof IDBKeyRange;
22
+ batchSize?: number;
23
+ onProgress?: (progress: LegacyMigrationProgress) => void;
24
+ }>;
25
+ export declare function deriveLegacyEncryptionKey(appKey: string, userId: string): LegacySecret;
26
+ export declare function decryptLegacyMessage<T extends Record<string, any>>(source: T, secret: LegacySecret, crypto?: typeof CryptoJS): T;
27
+ export declare function migrateLegacyIndexedDb({ appKey, userId, bridge, indexedDBFactory, keyRange, batchSize, onProgress }: MigrationOptions): Promise<LegacyMigrationResult>;
28
+ export {};
@@ -0,0 +1,43 @@
1
+ import type IReceivedMessage from '../../model/iReceivedMessage';
2
+ import type { IConversationOption, IReceiptReceivedEvent } from '../../types';
3
+ import type { DatabaseBridge } from '../contracts';
4
+ import { type LegacyMigrationProgress, type LegacyMigrationResult } from './legacyIndexedDbMigration';
5
+ export declare class SqlMessageRepository {
6
+ private readonly appKey;
7
+ private readonly userId;
8
+ private readonly bridge;
9
+ private initialization;
10
+ constructor(appKey: string, userId: string, bridge?: DatabaseBridge);
11
+ initDB(): Promise<void>;
12
+ migrateLegacyDatabase(onProgress?: (progress: LegacyMigrationProgress) => void): Promise<LegacyMigrationResult>;
13
+ close(): Promise<void>;
14
+ addMessages(messages: IReceivedMessage[], conversation: IConversationOption, isEnd?: boolean): Promise<void>;
15
+ getMessages(conversation: IConversationOption, timestamp?: string, count?: number, isForward?: boolean): Promise<{
16
+ messages: IReceivedMessage[];
17
+ hasMore: boolean;
18
+ }>;
19
+ getPreviousMessages(conversation: IConversationOption, timestamp?: string, count?: number): Promise<{
20
+ messages: IReceivedMessage[];
21
+ hasMore: boolean;
22
+ }>;
23
+ getLatestMessage(conversation: IConversationOption): Promise<IReceivedMessage | null>;
24
+ getLatestMessages(conversations: ReadonlyArray<IConversationOption>): Promise<Record<string, IReceivedMessage | null>>;
25
+ getMessageById(messageId: number): Promise<IReceivedMessage | null>;
26
+ getMessageByUId(messageUId: string): Promise<IReceivedMessage | null>;
27
+ getMessageIdRange(): Promise<{
28
+ newest: number;
29
+ oldest: number;
30
+ } | null>;
31
+ getDialogLoadedState(dialogId: string): Promise<boolean>;
32
+ clearConversationCache(conversation: IConversationOption): Promise<void>;
33
+ clearAllCache(): Promise<void>;
34
+ removeMessagesByUId(messageUIds: string[]): Promise<void>;
35
+ updateMessageReceiptStatus(event: IReceiptReceivedEvent, type: 0 | 1): Promise<void>;
36
+ clearBurnAfterReadingExpiredMessages(conversation: IConversationOption): Promise<string[]>;
37
+ upsertMessage(message: IReceivedMessage): Promise<void>;
38
+ convertToRecallMessages(messageUIds: string[]): Promise<void>;
39
+ searchTextMessages(conversation: IConversationOption, keyword: string): Promise<IReceivedMessage[]>;
40
+ searchMessages(keyword: string, limit?: number): Promise<IReceivedMessage[]>;
41
+ private read;
42
+ private write;
43
+ }
package/types/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import IReceivedConversation from './model/iReceivedConversation';
7
7
  import { CommonReqResult, PBCodec } from './net/connection/webSocketServer';
8
8
  import { BaseResp } from './net/pbs/rpc.base';
9
9
  import { IChatRecordMsgDetail } from './model/messages/otherMediaMessages';
10
+ import type { LegacyMigrationProgress, LegacyMigrationResult } from './database/renderer/legacyIndexedDbMigration';
10
11
  export { TextMessage, ImageMessage, HQVoiceMessage, GIFMessage, FileMessage, VideoMessage, RecallCommandMessage, LocationMessage, ChatRecordMessage, ContactMessage, InvitationMessage, RedEnvelopeMessage, TransferMessage, LinkMessage, PrivateOpenBurnAfterReadingMessage, PrivateCloseBurnAfterReadingMessage, GroupOpenBurnAfterReadingMessage, GroupCloseBurnAfterReadingMessage } from './model/messages';
11
12
  export type { ITextMessageBody, IImageMessageBody, IGIFMessageBody, IFileMessageBody, IHQVoiceMessageBody, IRecallCommandMessageBody, IVideoMessageBody, ILocationMessageBody, IChatRecordMessageBody, IContactMessageBody, IInvitationMessageBody, IRedEnvelopeMessageBody, ITransferMessageBody, IBurnAfterReadingMessageBody, ILinkMessageBody } from './model/messages';
12
13
  export { MessageTypes, NotiMessageTypes } from './constants/messageTypes';
@@ -18,6 +19,11 @@ export * from './model/statusTypes';
18
19
  * @param initOption
19
20
  */
20
21
  export declare const init: (initOption: IInitOption) => void;
22
+ /**
23
+ * Explicitly retries the one-time IndexedDB to SQLCipher migration.
24
+ * Migration also runs automatically when the current user's database opens.
25
+ */
26
+ export declare const migrateLegacyDatabase: (onProgress?: (progress: LegacyMigrationProgress) => void) => Promise<LegacyMigrationResult>;
21
27
  /**
22
28
  * 建立 IM 连接
23
29
  * @param token
@@ -28,8 +34,8 @@ export declare const connect: () => IPromiseResult<void>;
28
34
  * @description 调用后将不再接收消息,不可发送消息,不可获取历史消息,不可获取会话列表
29
35
  */
30
36
  export declare const disconnect: () => Promise<void>;
31
- export declare const setUserLogged: (info: ProfileInfo) => void;
32
- export declare const logOut: () => void;
37
+ export declare const setUserLogged: (info: ProfileInfo) => Promise<void>;
38
+ export declare const logOut: () => Promise<void>;
33
39
  /**
34
40
  * 获取 IM 连接状态
35
41
  */
@@ -286,3 +292,8 @@ export declare const getMessageById: (messageId: number) => IPromiseResult<IRece
286
292
  */
287
293
  export declare const getMessageByUId: (messageUId: string) => IPromiseResult<IReceivedMessage | null | undefined>;
288
294
  export declare const searchTextMessages: (conversation: IConversationOption, keyword: string) => IPromiseResult<IReceivedMessage[]>;
295
+ /**
296
+ * 全局搜索本地消息,按发送时间倒序返回。
297
+ * 默认最多返回 500 条,与 Signal Desktop 的全局搜索默认值一致。
298
+ */
299
+ export declare const searchMessages: (keyword: string, limit?: number) => IPromiseResult<IReceivedMessage[]>;