chore: update dependencies
This commit is contained in:
parent
2d9f108ed2
commit
0e1eed664d
14 changed files with 1371 additions and 640 deletions
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@duskflower:registry=https://git.duskflower.dev/api/packages/duskflower/npm/
|
|
@ -108,13 +108,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ignore": [
|
"ignore": ["dist/**/*.ts", "dist/**", "**/*.mjs", "eslint.config.js", "**/*.js"]
|
||||||
"dist/**/*.ts",
|
|
||||||
"dist/**",
|
|
||||||
"**/*.mjs",
|
|
||||||
"eslint.config.js",
|
|
||||||
"**/*.js"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
|
|
25
package.json
25
package.json
|
@ -15,30 +15,31 @@
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@commitlint/cli": "^19.6.1",
|
"@commitlint/cli": "^19.6.1",
|
||||||
"@commitlint/config-conventional": "^19.6.0",
|
"@commitlint/config-conventional": "^19.6.0",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.7",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"better-sqlite3": "^11.7.0",
|
"better-sqlite3": "^11.8.1",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"kysely-codegen": "^0.17.0",
|
"kysely-codegen": "^0.17.0",
|
||||||
"lint-staged": "^15.2.11",
|
"lint-staged": "^15.4.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.5.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^6.0.6",
|
"vite": "^6.0.9",
|
||||||
"vite-plugin-solid": "^2.11.0",
|
"vite-plugin-solid": "^2.11.0",
|
||||||
"vite-plugin-wasm": "^3.4.1"
|
"vite-plugin-wasm": "^3.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@duskflower/signal-decrypt-backup-wasm": "^0.2.0",
|
"@duskflower/signal-decrypt-backup-wasm": "^0.2.1",
|
||||||
"@kobalte/core": "^0.13.7",
|
"@kobalte/core": "^0.13.7",
|
||||||
"@kobalte/tailwindcss": "^0.9.0",
|
"@kobalte/tailwindcss": "^0.9.0",
|
||||||
"@solid-primitives/refs": "^1.0.8",
|
"@solid-primitives/refs": "^1.0.8",
|
||||||
"@solid-primitives/storage": "^4.2.1",
|
"@solid-primitives/storage": "^4.2.1",
|
||||||
|
"@solid-primitives/upload": "^0.0.117",
|
||||||
"@solid-primitives/workers": "^0.3.0",
|
"@solid-primitives/workers": "^0.3.0",
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@solidjs/meta": "^0.29.4",
|
||||||
"@solidjs/router": "^0.15.2",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@sqlite.org/sqlite-wasm": "3.47.2-build1",
|
"@sqlite.org/sqlite-wasm": "3.48.0-build2",
|
||||||
"@tanstack/solid-table": "^8.20.5",
|
"@tanstack/solid-table": "^8.20.5",
|
||||||
"chart.js": "^4.4.7",
|
"chart.js": "^4.4.7",
|
||||||
"chartjs-chart-wordcloud": "^4.4.4",
|
"chartjs-chart-wordcloud": "^4.4.4",
|
||||||
|
@ -49,9 +50,9 @@
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"kysely": "^0.27.5",
|
"kysely": "^0.27.5",
|
||||||
"kysely-wasm": "^0.7.0",
|
"kysely-wasm": "^0.7.0",
|
||||||
"lucide-solid": "^0.469.0",
|
"lucide-solid": "^0.473.0",
|
||||||
"seroval": "^1.1.1",
|
"seroval": "^1.2.0",
|
||||||
"solid-js": "^1.9.3",
|
"solid-js": "^1.9.4",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
|
|
871
pnpm-lock.yaml
generated
871
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,12 +1,8 @@
|
||||||
import { sql, type NotNull } from "kysely";
|
import { sql, type NotNull } from "kysely";
|
||||||
import { db, kyselyDb, SELF_ID, setDbHash } from "./db";
|
import { db, kyselyDb, SELF_ID } from "./db";
|
||||||
import { cached } from "./lib/db-cache";
|
import { cached } from "../lib/db-cache";
|
||||||
import { hashString } from "./lib/hash";
|
|
||||||
|
|
||||||
export const loadDb = (
|
export const loadDb = (statements: string[], progressCallback?: (percentage: number) => void) => {
|
||||||
statements: string[],
|
|
||||||
progressCallback?: (percentage: number) => void,
|
|
||||||
) => {
|
|
||||||
const length = statements.length;
|
const length = statements.length;
|
||||||
let percentage = 0;
|
let percentage = 0;
|
||||||
|
|
||||||
|
@ -15,20 +11,19 @@ export const loadDb = (
|
||||||
const newPercentage = Math.round((i / length) * 100);
|
const newPercentage = Math.round((i / length) * 100);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.exec(statement);
|
if (progressCallback && newPercentage !== percentage) {
|
||||||
|
progressCallback(newPercentage);
|
||||||
|
|
||||||
if (newPercentage !== percentage) {
|
|
||||||
progressCallback?.(newPercentage);
|
|
||||||
percentage = newPercentage;
|
percentage = newPercentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.exec(statement);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`statement failed: ${statement}`, {
|
throw new Error(`statement failed: ${statement}`, {
|
||||||
cause: e,
|
cause: e,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setDbHash(hashString(statements.join()));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const allThreadsOverviewQueryRaw = () =>
|
const allThreadsOverviewQueryRaw = () =>
|
||||||
|
@ -38,15 +33,9 @@ const allThreadsOverviewQueryRaw = () =>
|
||||||
(eb) =>
|
(eb) =>
|
||||||
eb
|
eb
|
||||||
.selectFrom("message")
|
.selectFrom("message")
|
||||||
.select((eb) => [
|
.select((eb) => ["message.thread_id", eb.fn.countAll().as("message_count")])
|
||||||
"message.thread_id",
|
|
||||||
eb.fn.countAll().as("message_count"),
|
|
||||||
])
|
|
||||||
.where((eb) => {
|
.where((eb) => {
|
||||||
return eb.and([
|
return eb.and([eb("message.body", "is not", null), eb("message.body", "is not", "")]);
|
||||||
eb("message.body", "is not", null),
|
|
||||||
eb("message.body", "is not", ""),
|
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
.groupBy("message.thread_id")
|
.groupBy("message.thread_id")
|
||||||
.as("message"),
|
.as("message"),
|
||||||
|
@ -100,9 +89,7 @@ const dmPartnerRecipientQueryRaw = (dmId: number) =>
|
||||||
"recipient.nickname_joined_name",
|
"recipient.nickname_joined_name",
|
||||||
])
|
])
|
||||||
.innerJoin("thread", "recipient._id", "thread.recipient_id")
|
.innerJoin("thread", "recipient._id", "thread.recipient_id")
|
||||||
.where((eb) =>
|
.where((eb) => eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)]))
|
||||||
eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)]),
|
|
||||||
)
|
|
||||||
.$narrowType<{
|
.$narrowType<{
|
||||||
_id: number;
|
_id: number;
|
||||||
}>()
|
}>()
|
||||||
|
@ -113,25 +100,12 @@ export const dmPartnerRecipientQuery = cached(dmPartnerRecipientQueryRaw);
|
||||||
const threadSentMessagesOverviewQueryRaw = (threadId: number) =>
|
const threadSentMessagesOverviewQueryRaw = (threadId: number) =>
|
||||||
kyselyDb
|
kyselyDb
|
||||||
.selectFrom("message")
|
.selectFrom("message")
|
||||||
.select([
|
.select(["from_recipient_id", sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as("message_datetime")])
|
||||||
"from_recipient_id",
|
|
||||||
sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as(
|
|
||||||
"message_datetime",
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.orderBy(["message_datetime"])
|
.orderBy(["message_datetime"])
|
||||||
.where((eb) =>
|
.where((eb) => eb.and([eb("body", "is not", null), eb("body", "!=", ""), eb("thread_id", "=", threadId)]))
|
||||||
eb.and([
|
|
||||||
eb("body", "is not", null),
|
|
||||||
eb("body", "!=", ""),
|
|
||||||
eb("thread_id", "=", threadId),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
export const threadSentMessagesOverviewQuery = cached(
|
export const threadSentMessagesOverviewQuery = cached(threadSentMessagesOverviewQueryRaw);
|
||||||
threadSentMessagesOverviewQueryRaw,
|
|
||||||
);
|
|
||||||
|
|
||||||
const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
||||||
kyselyDb
|
kyselyDb
|
||||||
|
@ -139,20 +113,16 @@ const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
||||||
return eb
|
return eb
|
||||||
.selectFrom("message")
|
.selectFrom("message")
|
||||||
.select([
|
.select([
|
||||||
sql`LOWER(substr(body, 1, instr(body || " ", " ") - 1))`.as("word"),
|
sql`LOWER(substr(body, 1, instr(body || ' ', ' ') - 1))`.as("word"),
|
||||||
sql`(substr(body, instr(body || " ", " ") + 1))`.as("rest"),
|
sql`substr(body, instr(body || ' ', ' ') + 1)`.as("rest"),
|
||||||
])
|
])
|
||||||
.where((eb) =>
|
.where((eb) => eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)]))
|
||||||
eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)]),
|
|
||||||
)
|
|
||||||
.unionAll((ebInner) => {
|
.unionAll((ebInner) => {
|
||||||
return ebInner
|
return ebInner
|
||||||
.selectFrom("words")
|
.selectFrom("words")
|
||||||
.select([
|
.select([
|
||||||
sql`LOWER(substr(rest, 1, instr(rest || " ", " ") - 1))`.as(
|
sql`LOWER(substr(rest, 1, instr(rest || ' ', ' ') - 1))`.as("word"),
|
||||||
"word",
|
sql`substr(rest, instr(rest || ' ', ' ') + 1)`.as("rest"),
|
||||||
),
|
|
||||||
sql`(substr(rest, instr(rest || " ", " ") + 1))`.as("rest"),
|
|
||||||
])
|
])
|
||||||
.where("rest", "<>", "");
|
.where("rest", "<>", "");
|
||||||
});
|
});
|
708
src/db/db-schema.d.ts
vendored
Normal file
708
src/db/db-schema.d.ts
vendored
Normal file
|
@ -0,0 +1,708 @@
|
||||||
|
/**
|
||||||
|
* This file was generated by kysely-codegen.
|
||||||
|
* Please do not edit it manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ColumnType } from "kysely";
|
||||||
|
|
||||||
|
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||||
|
? ColumnType<S, I | undefined, U>
|
||||||
|
: ColumnType<T, T | undefined, T>;
|
||||||
|
|
||||||
|
export interface Attachment {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
archive_cdn: Generated<number | null>;
|
||||||
|
archive_media_id: Generated<string | null>;
|
||||||
|
archive_media_name: Generated<string | null>;
|
||||||
|
archive_thumbnail_media_id: Generated<string | null>;
|
||||||
|
archive_transfer_file: Generated<string | null>;
|
||||||
|
archive_transfer_state: Generated<number | null>;
|
||||||
|
attachment_uuid: Generated<string | null>;
|
||||||
|
blur_hash: Generated<string | null>;
|
||||||
|
borderless: Generated<number | null>;
|
||||||
|
caption: Generated<string | null>;
|
||||||
|
cdn_number: Generated<number | null>;
|
||||||
|
content_type: string | null;
|
||||||
|
data_file: string | null;
|
||||||
|
data_hash_end: Generated<string | null>;
|
||||||
|
data_hash_start: Generated<string | null>;
|
||||||
|
data_random: Buffer | null;
|
||||||
|
data_size: number | null;
|
||||||
|
display_order: Generated<number | null>;
|
||||||
|
fast_preflight_id: string | null;
|
||||||
|
file_name: string | null;
|
||||||
|
height: Generated<number | null>;
|
||||||
|
message_id: number | null;
|
||||||
|
offload_restored_at: Generated<number | null>;
|
||||||
|
quote: Generated<number | null>;
|
||||||
|
remote_digest: Buffer | null;
|
||||||
|
remote_incremental_digest: Buffer | null;
|
||||||
|
remote_incremental_digest_chunk_size: Generated<number | null>;
|
||||||
|
remote_iv: Generated<Buffer | null>;
|
||||||
|
remote_key: string | null;
|
||||||
|
remote_location: string | null;
|
||||||
|
sticker_emoji: Generated<string | null>;
|
||||||
|
sticker_id: Generated<number | null>;
|
||||||
|
sticker_pack_id: Generated<string | null>;
|
||||||
|
sticker_pack_key: Generated<string | null>;
|
||||||
|
thumbnail_file: Generated<string | null>;
|
||||||
|
thumbnail_random: Generated<Buffer | null>;
|
||||||
|
thumbnail_restore_state: Generated<number | null>;
|
||||||
|
transfer_file: Generated<string | null>;
|
||||||
|
transfer_state: number | null;
|
||||||
|
transform_properties: Generated<string | null>;
|
||||||
|
upload_timestamp: Generated<number | null>;
|
||||||
|
video_gif: Generated<number | null>;
|
||||||
|
voice_note: Generated<number | null>;
|
||||||
|
width: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvatarPicker {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
avatar: Buffer;
|
||||||
|
group_id: Generated<string | null>;
|
||||||
|
last_used: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Call {
|
||||||
|
_id: number | null;
|
||||||
|
call_id: number;
|
||||||
|
deletion_timestamp: Generated<number | null>;
|
||||||
|
direction: number;
|
||||||
|
event: number;
|
||||||
|
group_call_active: Generated<number | null>;
|
||||||
|
local_joined: Generated<number | null>;
|
||||||
|
message_id: Generated<number | null>;
|
||||||
|
peer: number;
|
||||||
|
read: Generated<number | null>;
|
||||||
|
ringer: Generated<number | null>;
|
||||||
|
timestamp: number;
|
||||||
|
type: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CallLink {
|
||||||
|
_id: number | null;
|
||||||
|
admin_key: Buffer | null;
|
||||||
|
deletion_timestamp: Generated<number>;
|
||||||
|
expiration: number;
|
||||||
|
name: string;
|
||||||
|
recipient_id: number | null;
|
||||||
|
restrictions: number;
|
||||||
|
revoked: number;
|
||||||
|
room_id: string;
|
||||||
|
root_key: Buffer | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Cds {
|
||||||
|
_id: number | null;
|
||||||
|
e164: string;
|
||||||
|
last_seen_at: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatColors {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
chat_colors: Buffer | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatFolder {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
folder_type: Generated<number | null>;
|
||||||
|
is_muted: Generated<number | null>;
|
||||||
|
name: Generated<string | null>;
|
||||||
|
position: Generated<number | null>;
|
||||||
|
show_groups: Generated<number | null>;
|
||||||
|
show_individual: Generated<number | null>;
|
||||||
|
show_muted: Generated<number | null>;
|
||||||
|
show_unread: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatFolderMembership {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
chat_folder_id: number;
|
||||||
|
membership_type: Generated<number | null>;
|
||||||
|
thread_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DistributionList {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
allows_replies: Generated<number | null>;
|
||||||
|
deletion_timestamp: Generated<number | null>;
|
||||||
|
distribution_id: string;
|
||||||
|
is_unknown: Generated<number | null>;
|
||||||
|
name: string;
|
||||||
|
privacy_mode: Generated<number | null>;
|
||||||
|
recipient_id: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DistributionListMember {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
list_id: number;
|
||||||
|
privacy_mode: Generated<number | null>;
|
||||||
|
recipient_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DonationReceipt {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
amount: string;
|
||||||
|
currency: string;
|
||||||
|
receipt_date: number;
|
||||||
|
receipt_type: string;
|
||||||
|
subscription_level: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Drafts {
|
||||||
|
_id: number | null;
|
||||||
|
thread_id: number | null;
|
||||||
|
type: string | null;
|
||||||
|
value: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmojiSearch {
|
||||||
|
_id: number | null;
|
||||||
|
emoji: string;
|
||||||
|
label: string;
|
||||||
|
rank: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupMembership {
|
||||||
|
_id: number | null;
|
||||||
|
endorsement: Generated<Buffer | null>;
|
||||||
|
group_id: string;
|
||||||
|
recipient_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupReceipts {
|
||||||
|
_id: number | null;
|
||||||
|
address: number | null;
|
||||||
|
mms_id: number | null;
|
||||||
|
status: number | null;
|
||||||
|
timestamp: number | null;
|
||||||
|
unidentified: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Groups {
|
||||||
|
_id: number | null;
|
||||||
|
active: Generated<number | null>;
|
||||||
|
avatar_content_type: Generated<string | null>;
|
||||||
|
avatar_digest: Generated<Buffer | null>;
|
||||||
|
avatar_id: Generated<number | null>;
|
||||||
|
avatar_key: Generated<Buffer | null>;
|
||||||
|
decrypted_group: Generated<Buffer | null>;
|
||||||
|
distribution_id: Generated<string | null>;
|
||||||
|
expected_v2_id: Generated<string | null>;
|
||||||
|
group_id: string;
|
||||||
|
group_send_endorsements_expiration: Generated<number | null>;
|
||||||
|
last_force_update_timestamp: Generated<number | null>;
|
||||||
|
master_key: Generated<Buffer | null>;
|
||||||
|
mms: Generated<number | null>;
|
||||||
|
recipient_id: number;
|
||||||
|
revision: Generated<Buffer | null>;
|
||||||
|
show_as_story_state: Generated<number | null>;
|
||||||
|
timestamp: Generated<number | null>;
|
||||||
|
title: Generated<string | null>;
|
||||||
|
unmigrated_v1_members: Generated<string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Identities {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
address: number | null;
|
||||||
|
first_use: Generated<number | null>;
|
||||||
|
identity_key: string | null;
|
||||||
|
nonblocking_approval: Generated<number | null>;
|
||||||
|
timestamp: Generated<number | null>;
|
||||||
|
verified: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InAppPayment {
|
||||||
|
_id: number | null;
|
||||||
|
data: Buffer;
|
||||||
|
end_of_period: Generated<number | null>;
|
||||||
|
inserted_at: number;
|
||||||
|
notified: Generated<number | null>;
|
||||||
|
state: number;
|
||||||
|
subscriber_id: string | null;
|
||||||
|
type: number;
|
||||||
|
updated_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InAppPaymentSubscriber {
|
||||||
|
_id: number | null;
|
||||||
|
currency_code: string;
|
||||||
|
payment_method_type: Generated<number | null>;
|
||||||
|
requires_cancel: Generated<number | null>;
|
||||||
|
subscriber_id: string;
|
||||||
|
type: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KyberPrekey {
|
||||||
|
_id: number | null;
|
||||||
|
account_id: string;
|
||||||
|
key_id: number;
|
||||||
|
last_resort: number;
|
||||||
|
serialized: Buffer;
|
||||||
|
stale_timestamp: Generated<number>;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Mention {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
message_id: number | null;
|
||||||
|
range_length: number | null;
|
||||||
|
range_start: number | null;
|
||||||
|
recipient_id: number | null;
|
||||||
|
thread_id: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
body: string | null;
|
||||||
|
ct_l: string | null;
|
||||||
|
date_received: number;
|
||||||
|
date_sent: number;
|
||||||
|
date_server: Generated<number | null>;
|
||||||
|
exp: number | null;
|
||||||
|
expire_started: Generated<number | null>;
|
||||||
|
expire_timer_version: Generated<number>;
|
||||||
|
expires_in: Generated<number | null>;
|
||||||
|
export_state: Generated<Buffer | null>;
|
||||||
|
exported: Generated<number | null>;
|
||||||
|
from_device_id: number | null;
|
||||||
|
from_recipient_id: number;
|
||||||
|
has_delivery_receipt: Generated<number | null>;
|
||||||
|
has_read_receipt: Generated<number | null>;
|
||||||
|
latest_revision_id: Generated<number | null>;
|
||||||
|
link_previews: Generated<string | null>;
|
||||||
|
m_size: number | null;
|
||||||
|
m_type: number | null;
|
||||||
|
mentions_self: Generated<number | null>;
|
||||||
|
message_extras: Generated<Buffer | null>;
|
||||||
|
message_ranges: Generated<Buffer | null>;
|
||||||
|
mismatched_identities: Generated<string | null>;
|
||||||
|
network_failures: Generated<string | null>;
|
||||||
|
notified: Generated<number | null>;
|
||||||
|
notified_timestamp: Generated<number | null>;
|
||||||
|
original_message_id: Generated<number | null>;
|
||||||
|
parent_story_id: Generated<number | null>;
|
||||||
|
quote_author: Generated<number | null>;
|
||||||
|
quote_body: Generated<string | null>;
|
||||||
|
quote_id: Generated<number | null>;
|
||||||
|
quote_mentions: Generated<Buffer | null>;
|
||||||
|
quote_missing: Generated<number | null>;
|
||||||
|
quote_type: Generated<number | null>;
|
||||||
|
reactions_last_seen: Generated<number | null>;
|
||||||
|
reactions_unread: Generated<number | null>;
|
||||||
|
read: Generated<number | null>;
|
||||||
|
receipt_timestamp: Generated<number | null>;
|
||||||
|
remote_deleted: Generated<number | null>;
|
||||||
|
revision_number: Generated<number | null>;
|
||||||
|
scheduled_date: Generated<number | null>;
|
||||||
|
server_guid: Generated<string | null>;
|
||||||
|
shared_contacts: Generated<string | null>;
|
||||||
|
st: number | null;
|
||||||
|
story_type: Generated<number | null>;
|
||||||
|
subscription_id: Generated<number | null>;
|
||||||
|
thread_id: number;
|
||||||
|
to_recipient_id: number;
|
||||||
|
tr_id: string | null;
|
||||||
|
type: number;
|
||||||
|
unidentified: Generated<number | null>;
|
||||||
|
view_once: Generated<number | null>;
|
||||||
|
viewed: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageFts {
|
||||||
|
body: string | null;
|
||||||
|
thread_id: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageFtsConfig {
|
||||||
|
k: string;
|
||||||
|
v: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageFtsData {
|
||||||
|
block: Buffer | null;
|
||||||
|
id: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageFtsDocsize {
|
||||||
|
id: number | null;
|
||||||
|
sz: Buffer | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageFtsIdx {
|
||||||
|
pgno: string | null;
|
||||||
|
segid: string;
|
||||||
|
term: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MslMessage {
|
||||||
|
_id: number | null;
|
||||||
|
message_id: number;
|
||||||
|
payload_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MslPayload {
|
||||||
|
_id: number | null;
|
||||||
|
content: Buffer;
|
||||||
|
content_hint: number;
|
||||||
|
date_sent: number;
|
||||||
|
urgent: Generated<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MslRecipient {
|
||||||
|
_id: number | null;
|
||||||
|
device: number;
|
||||||
|
payload_id: number;
|
||||||
|
recipient_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NameCollision {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
dismissed: Generated<number | null>;
|
||||||
|
hash: Generated<string | null>;
|
||||||
|
thread_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NameCollisionMembership {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
collision_id: number;
|
||||||
|
profile_change_details: Generated<Buffer | null>;
|
||||||
|
recipient_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationProfile {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
allow_all_calls: Generated<number>;
|
||||||
|
allow_all_mentions: Generated<number>;
|
||||||
|
color: string;
|
||||||
|
created_at: number;
|
||||||
|
emoji: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationProfileAllowedMembers {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
notification_profile_id: number;
|
||||||
|
recipient_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationProfileSchedule {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
days_enabled: string;
|
||||||
|
enabled: Generated<number>;
|
||||||
|
end: number;
|
||||||
|
notification_profile_id: number;
|
||||||
|
start: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OneTimePrekeys {
|
||||||
|
_id: number | null;
|
||||||
|
account_id: string;
|
||||||
|
key_id: number;
|
||||||
|
private_key: string;
|
||||||
|
public_key: string;
|
||||||
|
stale_timestamp: Generated<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Payments {
|
||||||
|
_id: number | null;
|
||||||
|
amount: Buffer;
|
||||||
|
block_index: Generated<number | null>;
|
||||||
|
block_timestamp: Generated<number | null>;
|
||||||
|
direction: number | null;
|
||||||
|
failure_reason: number | null;
|
||||||
|
fee: Buffer;
|
||||||
|
note: Generated<string | null>;
|
||||||
|
payment_metadata: Generated<Buffer | null>;
|
||||||
|
receipt: Generated<Buffer | null>;
|
||||||
|
receipt_public_key: Generated<string | null>;
|
||||||
|
recipient: Generated<number | null>;
|
||||||
|
recipient_address: Generated<string | null>;
|
||||||
|
seen: number | null;
|
||||||
|
state: number | null;
|
||||||
|
timestamp: number | null;
|
||||||
|
transaction_record: Generated<Buffer | null>;
|
||||||
|
uuid: Generated<string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PendingPniSignatureMessage {
|
||||||
|
_id: number | null;
|
||||||
|
device_id: number;
|
||||||
|
recipient_id: number;
|
||||||
|
sent_timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PendingRetryReceipts {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
author: string;
|
||||||
|
device: number;
|
||||||
|
received_timestamp: string;
|
||||||
|
sent_timestamp: number;
|
||||||
|
thread_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Reaction {
|
||||||
|
_id: number | null;
|
||||||
|
author_id: number;
|
||||||
|
date_received: number;
|
||||||
|
date_sent: number;
|
||||||
|
emoji: string;
|
||||||
|
message_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Recipient {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
about: Generated<string | null>;
|
||||||
|
about_emoji: Generated<string | null>;
|
||||||
|
aci: Generated<string | null>;
|
||||||
|
avatar_color: Generated<string | null>;
|
||||||
|
badges: Generated<Buffer | null>;
|
||||||
|
blocked: Generated<number | null>;
|
||||||
|
call_link_room_id: Generated<string | null>;
|
||||||
|
call_ringtone: Generated<string | null>;
|
||||||
|
call_vibrate: Generated<number | null>;
|
||||||
|
capabilities: Generated<number | null>;
|
||||||
|
chat_colors: Generated<Buffer | null>;
|
||||||
|
custom_chat_colors_id: Generated<number | null>;
|
||||||
|
distribution_list_id: Generated<number | null>;
|
||||||
|
e164: Generated<string | null>;
|
||||||
|
email: Generated<string | null>;
|
||||||
|
extras: Generated<Buffer | null>;
|
||||||
|
group_id: Generated<string | null>;
|
||||||
|
groups_in_common: Generated<number | null>;
|
||||||
|
hidden: Generated<number | null>;
|
||||||
|
last_profile_fetch: Generated<number | null>;
|
||||||
|
last_session_reset: Generated<Buffer | null>;
|
||||||
|
mention_setting: Generated<number | null>;
|
||||||
|
message_expiration_time: Generated<number | null>;
|
||||||
|
message_expiration_time_version: Generated<number>;
|
||||||
|
message_ringtone: Generated<string | null>;
|
||||||
|
message_vibrate: Generated<number | null>;
|
||||||
|
mute_until: Generated<number | null>;
|
||||||
|
needs_pni_signature: Generated<number | null>;
|
||||||
|
nickname_family_name: Generated<string | null>;
|
||||||
|
nickname_given_name: Generated<string | null>;
|
||||||
|
nickname_joined_name: Generated<string | null>;
|
||||||
|
note: Generated<string | null>;
|
||||||
|
notification_channel: Generated<string | null>;
|
||||||
|
phone_number_discoverable: Generated<number | null>;
|
||||||
|
phone_number_sharing: Generated<number | null>;
|
||||||
|
pni: Generated<string | null>;
|
||||||
|
pni_signature_verified: Generated<number | null>;
|
||||||
|
profile_avatar: Generated<string | null>;
|
||||||
|
profile_family_name: Generated<string | null>;
|
||||||
|
profile_given_name: Generated<string | null>;
|
||||||
|
profile_joined_name: Generated<string | null>;
|
||||||
|
profile_key: Generated<string | null>;
|
||||||
|
profile_key_credential: Generated<string | null>;
|
||||||
|
profile_sharing: Generated<number | null>;
|
||||||
|
registered: Generated<number | null>;
|
||||||
|
reporting_token: Generated<Buffer | null>;
|
||||||
|
sealed_sender_mode: Generated<number | null>;
|
||||||
|
storage_service_id: Generated<string | null>;
|
||||||
|
storage_service_proto: Generated<string | null>;
|
||||||
|
system_contact_uri: Generated<string | null>;
|
||||||
|
system_family_name: Generated<string | null>;
|
||||||
|
system_given_name: Generated<string | null>;
|
||||||
|
system_info_pending: Generated<number | null>;
|
||||||
|
system_joined_name: Generated<string | null>;
|
||||||
|
system_nickname: Generated<string | null>;
|
||||||
|
system_phone_label: Generated<string | null>;
|
||||||
|
system_phone_type: Generated<number | null>;
|
||||||
|
system_photo_uri: Generated<string | null>;
|
||||||
|
type: Generated<number | null>;
|
||||||
|
unregistered_timestamp: Generated<number | null>;
|
||||||
|
username: Generated<string | null>;
|
||||||
|
wallpaper: Generated<Buffer | null>;
|
||||||
|
wallpaper_uri: Generated<string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemappedRecipients {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
new_id: number | null;
|
||||||
|
old_id: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemappedThreads {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
new_id: number | null;
|
||||||
|
old_id: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoteMegaphone {
|
||||||
|
_id: number | null;
|
||||||
|
body: string;
|
||||||
|
conditional_id: string | null;
|
||||||
|
countries: string | null;
|
||||||
|
dont_show_after: number;
|
||||||
|
dont_show_before: number;
|
||||||
|
finished_at: Generated<number | null>;
|
||||||
|
image_uri: Generated<string | null>;
|
||||||
|
image_url: string | null;
|
||||||
|
minimum_version: number;
|
||||||
|
primary_action_data: Generated<string | null>;
|
||||||
|
primary_action_id: string | null;
|
||||||
|
primary_action_text: string | null;
|
||||||
|
priority: number;
|
||||||
|
secondary_action_data: Generated<string | null>;
|
||||||
|
secondary_action_id: string | null;
|
||||||
|
secondary_action_text: string | null;
|
||||||
|
seen_count: Generated<number | null>;
|
||||||
|
show_for_days: number;
|
||||||
|
shown_at: Generated<number | null>;
|
||||||
|
snoozed_at: Generated<number | null>;
|
||||||
|
title: string;
|
||||||
|
uuid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SenderKeys {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
address: string;
|
||||||
|
created_at: number;
|
||||||
|
device: number;
|
||||||
|
distribution_id: string;
|
||||||
|
record: Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SenderKeyShared {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
address: string;
|
||||||
|
device: number;
|
||||||
|
distribution_id: string;
|
||||||
|
timestamp: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Sessions {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
account_id: string;
|
||||||
|
address: string;
|
||||||
|
device: number;
|
||||||
|
record: Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignedPrekeys {
|
||||||
|
_id: number | null;
|
||||||
|
account_id: string;
|
||||||
|
key_id: number;
|
||||||
|
private_key: string;
|
||||||
|
public_key: string;
|
||||||
|
signature: string;
|
||||||
|
timestamp: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Sticker {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
content_type: Generated<string | null>;
|
||||||
|
cover: number | null;
|
||||||
|
emoji: string;
|
||||||
|
file_length: number | null;
|
||||||
|
file_path: string;
|
||||||
|
file_random: Buffer | null;
|
||||||
|
installed: number | null;
|
||||||
|
last_used: number | null;
|
||||||
|
pack_author: string;
|
||||||
|
pack_id: string;
|
||||||
|
pack_key: string;
|
||||||
|
pack_order: number | null;
|
||||||
|
pack_title: string;
|
||||||
|
sticker_id: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageKey {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
key: string | null;
|
||||||
|
type: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorySends {
|
||||||
|
_id: number | null;
|
||||||
|
allows_replies: number;
|
||||||
|
distribution_id: string;
|
||||||
|
message_id: number;
|
||||||
|
recipient_id: number;
|
||||||
|
sent_timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Thread {
|
||||||
|
_id: Generated<number | null>;
|
||||||
|
active: Generated<number | null>;
|
||||||
|
archived: Generated<number | null>;
|
||||||
|
date: Generated<number | null>;
|
||||||
|
error: Generated<number | null>;
|
||||||
|
expires_in: Generated<number | null>;
|
||||||
|
has_delivery_receipt: Generated<number | null>;
|
||||||
|
has_read_receipt: Generated<number | null>;
|
||||||
|
has_sent: Generated<number | null>;
|
||||||
|
last_scrolled: Generated<number | null>;
|
||||||
|
last_seen: Generated<number | null>;
|
||||||
|
meaningful_messages: Generated<number | null>;
|
||||||
|
pinned: Generated<number | null>;
|
||||||
|
read: Generated<number | null>;
|
||||||
|
recipient_id: number;
|
||||||
|
snippet: string | null;
|
||||||
|
snippet_content_type: Generated<string | null>;
|
||||||
|
snippet_extras: Generated<string | null>;
|
||||||
|
snippet_message_extras: Generated<Buffer | null>;
|
||||||
|
snippet_type: Generated<number | null>;
|
||||||
|
snippet_uri: Generated<string | null>;
|
||||||
|
status: Generated<number | null>;
|
||||||
|
type: Generated<number | null>;
|
||||||
|
unread_count: Generated<number | null>;
|
||||||
|
unread_self_mention_count: Generated<number | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DB {
|
||||||
|
attachment: Attachment;
|
||||||
|
avatar_picker: AvatarPicker;
|
||||||
|
call: Call;
|
||||||
|
call_link: CallLink;
|
||||||
|
cds: Cds;
|
||||||
|
chat_colors: ChatColors;
|
||||||
|
chat_folder: ChatFolder;
|
||||||
|
chat_folder_membership: ChatFolderMembership;
|
||||||
|
distribution_list: DistributionList;
|
||||||
|
distribution_list_member: DistributionListMember;
|
||||||
|
donation_receipt: DonationReceipt;
|
||||||
|
drafts: Drafts;
|
||||||
|
emoji_search: EmojiSearch;
|
||||||
|
group_membership: GroupMembership;
|
||||||
|
group_receipts: GroupReceipts;
|
||||||
|
groups: Groups;
|
||||||
|
identities: Identities;
|
||||||
|
in_app_payment: InAppPayment;
|
||||||
|
in_app_payment_subscriber: InAppPaymentSubscriber;
|
||||||
|
kyber_prekey: KyberPrekey;
|
||||||
|
mention: Mention;
|
||||||
|
message: Message;
|
||||||
|
message_fts: MessageFts;
|
||||||
|
message_fts_config: MessageFtsConfig;
|
||||||
|
message_fts_data: MessageFtsData;
|
||||||
|
message_fts_docsize: MessageFtsDocsize;
|
||||||
|
message_fts_idx: MessageFtsIdx;
|
||||||
|
msl_message: MslMessage;
|
||||||
|
msl_payload: MslPayload;
|
||||||
|
msl_recipient: MslRecipient;
|
||||||
|
name_collision: NameCollision;
|
||||||
|
name_collision_membership: NameCollisionMembership;
|
||||||
|
notification_profile: NotificationProfile;
|
||||||
|
notification_profile_allowed_members: NotificationProfileAllowedMembers;
|
||||||
|
notification_profile_schedule: NotificationProfileSchedule;
|
||||||
|
one_time_prekeys: OneTimePrekeys;
|
||||||
|
payments: Payments;
|
||||||
|
pending_pni_signature_message: PendingPniSignatureMessage;
|
||||||
|
pending_retry_receipts: PendingRetryReceipts;
|
||||||
|
reaction: Reaction;
|
||||||
|
recipient: Recipient;
|
||||||
|
remapped_recipients: RemappedRecipients;
|
||||||
|
remapped_threads: RemappedThreads;
|
||||||
|
remote_megaphone: RemoteMegaphone;
|
||||||
|
sender_key_shared: SenderKeyShared;
|
||||||
|
sender_keys: SenderKeys;
|
||||||
|
sessions: Sessions;
|
||||||
|
signed_prekeys: SignedPrekeys;
|
||||||
|
sticker: Sticker;
|
||||||
|
storage_key: StorageKey;
|
||||||
|
story_sends: StorySends;
|
||||||
|
thread: Thread;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { makePersisted } from "@solid-primitives/storage";
|
import { makePersisted } from "@solid-primitives/storage";
|
||||||
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
||||||
import { Kysely } from "kysely";
|
import { Kysely } from "kysely";
|
||||||
import type { DB } from "kysely-codegen";
|
import type { DB } from "./db-schema";
|
||||||
import { OfficialWasmDialect } from "kysely-wasm";
|
import { OfficialWasmDialect } from "kysely-wasm";
|
||||||
import { createSignal } from "solid-js";
|
import { createSignal } from "solid-js";
|
||||||
|
|
2
src/db/index.ts
Normal file
2
src/db/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./db";
|
||||||
|
export * from "./db-queries";
|
|
@ -1,9 +1,13 @@
|
||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { MetaProvider } from "@solidjs/meta";
|
import { MetaProvider } from "@solidjs/meta";
|
||||||
import { Router } from "@solidjs/router";
|
import { Router, useNavigate } from "@solidjs/router";
|
||||||
import { render } from "solid-js/web";
|
import { render } from "solid-js/web";
|
||||||
|
import { createEffect, enableScheduling } from "solid-js";
|
||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import { db } from "./db/db";
|
||||||
|
|
||||||
|
enableScheduling();
|
||||||
|
|
||||||
const root = document.getElementById("root");
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
|
@ -19,18 +23,18 @@ if (root) {
|
||||||
<div class="mx-auto max-w-screen-2xl">
|
<div class="mx-auto max-w-screen-2xl">
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Router
|
<Router
|
||||||
// root={(props) => {
|
root={(props) => {
|
||||||
// const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// const { pathname } = props.location;
|
const { pathname } = props.location;
|
||||||
|
|
||||||
// createEffect(() => {
|
createEffect(() => {
|
||||||
// if (!db() && pathname !== "/") {
|
if (!db && pathname !== "/") {
|
||||||
// navigate("/");
|
navigate("/");
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
// return props.children;
|
return props.children;
|
||||||
// }}
|
}}
|
||||||
>
|
>
|
||||||
<App />
|
<App />
|
||||||
</Router>
|
</Router>
|
||||||
|
|
|
@ -1,47 +1,44 @@
|
||||||
import { deserialize, serialize } from "seroval";
|
import { deserialize, serialize } from "seroval";
|
||||||
import { createEffect, createMemo, createRoot, on } from "solid-js";
|
import {} from "solid-js";
|
||||||
import { dbHash } from "~/db";
|
|
||||||
import { hashString } from "./hash";
|
import { hashString } from "./hash";
|
||||||
|
|
||||||
export const DATABASE_HASH_PREFIX = "database";
|
export const DATABASE_HASH_PREFIX = "database";
|
||||||
|
|
||||||
// clear the cache on new session so that selecting a different database does not result in wrong cache entries
|
// clear the cache on new session so that selecting a different database does not result in wrong cache entries
|
||||||
const clearDbCache = () => {
|
// const clearDbCache = () => {
|
||||||
for (let i = 0, len = localStorage.length; i < len; i++) {
|
// for (let i = 0, len = localStorage.length; i < len; i++) {
|
||||||
const key = localStorage.key(i);
|
// const key = localStorage.key(i);
|
||||||
|
|
||||||
if (key?.startsWith(DATABASE_HASH_PREFIX)) {
|
// if (key?.startsWith(DATABASE_HASH_PREFIX)) {
|
||||||
localStorage.removeItem(key);
|
// localStorage.removeItem(key);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
let prevDbHash = dbHash();
|
// let prevDbHash = dbHash();
|
||||||
|
|
||||||
createRoot(() => {
|
// createRoot(() => {
|
||||||
createEffect(() => {
|
// createEffect(() => {
|
||||||
on(
|
// on(
|
||||||
dbHash,
|
// dbHash,
|
||||||
(currentDbHash) => {
|
// (currentDbHash) => {
|
||||||
if (currentDbHash && currentDbHash !== prevDbHash) {
|
// if (currentDbHash && currentDbHash !== prevDbHash) {
|
||||||
prevDbHash = currentDbHash;
|
// prevDbHash = currentDbHash;
|
||||||
clearDbCache();
|
// clearDbCache();
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
defer: true,
|
// defer: true,
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
class LocalStorageCacheAdapter {
|
class LocalStorageCacheAdapter {
|
||||||
keys = new Set<string>(
|
keys = new Set<string>(Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)));
|
||||||
Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)),
|
|
||||||
);
|
|
||||||
prefix = "database";
|
prefix = "database";
|
||||||
// TODO: real way of detecting if the db is loaded, on loading the db and opfs (if persisted db?)
|
// TODO: real way of detecting if the db is loaded, on loading the db and opfs (if persisted db?)
|
||||||
#dbLoaded = createMemo(() => !!dbHash());
|
// #dbLoaded = createMemo(() => !!dbHash());
|
||||||
|
|
||||||
#createKey(cacheName: string, key: string): string {
|
#createKey(cacheName: string, key: string): string {
|
||||||
return `${this.prefix}-${cacheName}-${key}`;
|
return `${this.prefix}-${cacheName}-${key}`;
|
||||||
|
@ -54,10 +51,7 @@ class LocalStorageCacheAdapter {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(fullKey, serialize({ isPromise, value }));
|
localStorage.setItem(fullKey, serialize({ isPromise, value }));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (
|
if (error instanceof DOMException && error.name === "QUOTA_EXCEEDED_ERR") {
|
||||||
error instanceof DOMException &&
|
|
||||||
error.name === "QUOTA_EXCEEDED_ERR"
|
|
||||||
) {
|
|
||||||
console.error("Storage quota exceeded, not caching new function calls");
|
console.error("Storage quota exceeded, not caching new function calls");
|
||||||
} else {
|
} else {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -66,13 +60,13 @@ class LocalStorageCacheAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
has(cacheName: string, key: string): boolean {
|
has(cacheName: string, key: string): boolean {
|
||||||
if (this.#dbLoaded()) {
|
// if (this.#dbLoaded()) {
|
||||||
return this.keys.has(this.#createKey(cacheName, key));
|
return this.keys.has(this.#createKey(cacheName, key));
|
||||||
}
|
// }
|
||||||
|
|
||||||
console.info("No database loaded");
|
// console.info("No database loaded");
|
||||||
|
|
||||||
return false;
|
// return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get<R>(
|
get<R>(
|
||||||
|
@ -84,18 +78,18 @@ class LocalStorageCacheAdapter {
|
||||||
value: R;
|
value: R;
|
||||||
}
|
}
|
||||||
| undefined {
|
| undefined {
|
||||||
if (this.#dbLoaded()) {
|
// if (this.#dbLoaded()) {
|
||||||
const item = localStorage.getItem(this.#createKey(cacheName, key));
|
const item = localStorage.getItem(this.#createKey(cacheName, key));
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
return deserialize(item) as {
|
return deserialize(item) as {
|
||||||
isPromise: boolean;
|
isPromise: boolean;
|
||||||
value: R;
|
value: R;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.info("No database loaded");
|
|
||||||
}
|
}
|
||||||
|
// } else {
|
||||||
|
// console.info("No database loaded");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +122,7 @@ const createHashKey = (...args: unknown[]) => {
|
||||||
return hashString(stringToHash);
|
return hashString(stringToHash);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cached = <T extends unknown[], R, TT>(
|
export const cached = <T extends unknown[], R, TT>(fn: (...args: T) => R, self?: ThisType<TT>): ((...args: T) => R) => {
|
||||||
fn: (...args: T) => R,
|
|
||||||
self?: ThisType<TT>,
|
|
||||||
): ((...args: T) => R) => {
|
|
||||||
const cacheName = hashString(fn.toString()).toString();
|
const cacheName = hashString(fn.toString()).toString();
|
||||||
|
|
||||||
return (...args: T) => {
|
return (...args: T) => {
|
||||||
|
@ -140,11 +131,7 @@ export const cached = <T extends unknown[], R, TT>(
|
||||||
const cachedValue = cache.get<R>(cacheName, cacheKey);
|
const cachedValue = cache.get<R>(cacheName, cacheKey);
|
||||||
|
|
||||||
if (cachedValue) {
|
if (cachedValue) {
|
||||||
return (
|
return (cachedValue.isPromise ? Promise.resolve(cachedValue.value) : cachedValue.value) as R;
|
||||||
cachedValue.isPromise
|
|
||||||
? Promise.resolve(cachedValue.value)
|
|
||||||
: cachedValue.value
|
|
||||||
) as R;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let newValue: R;
|
let newValue: R;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Suspense, type Component } from "solid-js";
|
import { Suspense, type Component } from "solid-js";
|
||||||
import { createAsync, type RoutePreloadFunc, type RouteSectionProps } from "@solidjs/router";
|
import { createAsync, type RoutePreloadFunc, type RouteSectionProps } from "@solidjs/router";
|
||||||
|
|
||||||
import { dmPartnerRecipientQuery, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db-queries";
|
import { dmPartnerRecipientQuery, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery, SELF_ID } from "~/db";
|
||||||
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
||||||
import { Heading } from "~/components/ui/heading";
|
import { Heading } from "~/components/ui/heading";
|
||||||
import { Grid } from "~/components/ui/grid";
|
import { Grid } from "~/components/ui/grid";
|
||||||
|
@ -15,7 +15,6 @@ import { DmMessagesPerRecipient } from "./dm-messages-per-recipients";
|
||||||
import { DmMessagesPerWeekday } from "./dm-messages-per-weekday";
|
import { DmMessagesPerWeekday } from "./dm-messages-per-weekday";
|
||||||
import type { MessageOverview } from "~/types";
|
import type { MessageOverview } from "~/types";
|
||||||
import { createMessageStatsSources } from "~/lib/messages";
|
import { createMessageStatsSources } from "~/lib/messages";
|
||||||
import { SELF_ID } from "~/db";
|
|
||||||
import { Flex } from "~/components/ui/flex";
|
import { Flex } from "~/components/ui/flex";
|
||||||
|
|
||||||
const getDmIdData = (dmId: number) => {
|
const getDmIdData = (dmId: number) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ChartData } from "chart.js";
|
import type { ChartData } from "chart.js";
|
||||||
import { Show, type Accessor, type Component } from "solid-js";
|
import { Show, type Accessor, type Component } from "solid-js";
|
||||||
import { WordCloudChart } from "~/components/ui/charts";
|
import { WordCloudChart } from "~/components/ui/charts";
|
||||||
import type { threadMostUsedWordsQuery } from "~/db-queries";
|
import type { threadMostUsedWordsQuery } from "~/db";
|
||||||
|
|
||||||
const maxWordSize = 100;
|
const maxWordSize = 100;
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,60 @@
|
||||||
import { useNavigate, type RouteSectionProps } from "@solidjs/router";
|
import { useNavigate, type RouteSectionProps } from "@solidjs/router";
|
||||||
import { createSignal, Show, type Component, type JSX } from "solid-js";
|
import { createSignal, type JSX, Show, type Component } from "solid-js";
|
||||||
|
|
||||||
import { Title } from "@solidjs/meta";
|
import { Title } from "@solidjs/meta";
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal } from "solid-js/web";
|
||||||
import { Flex } from "~/components/ui/flex";
|
import { Flex } from "~/components/ui/flex";
|
||||||
|
|
||||||
import {
|
import { Progress, ProgressLabel, ProgressValueLabel } from "~/components/ui/progress";
|
||||||
Progress,
|
import { loadDb } from "~/db/db-queries";
|
||||||
ProgressLabel,
|
|
||||||
ProgressValueLabel,
|
|
||||||
} from "~/components/ui/progress";
|
|
||||||
// import { db } from "~/db";
|
|
||||||
import { loadDb } from "~/db-queries";
|
|
||||||
import { decryptBackup } from "~/lib/decryptor";
|
import { decryptBackup } from "~/lib/decryptor";
|
||||||
|
import { createDropzone, createFileUploader } from "@solid-primitives/upload";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { TextField, TextFieldInput, TextFieldLabel } from "~/components/ui/text-field";
|
||||||
|
|
||||||
export const Home: Component<RouteSectionProps> = () => {
|
export const Home: Component<RouteSectionProps> = () => {
|
||||||
const [decryptionProgress, setDecryptionProgress] = createSignal<number>();
|
|
||||||
const [isLoadingDatabase, setIsLoadingDatabase] = createSignal(false);
|
|
||||||
const [passphrase, setPassphrase] = createSignal("");
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onFileChange: JSX.ChangeEventHandler<HTMLInputElement, Event> = (
|
const fileUploader = createFileUploader({
|
||||||
event,
|
accept: ".backup",
|
||||||
) => {
|
multiple: false,
|
||||||
const file = event.currentTarget.files?.[0];
|
});
|
||||||
|
|
||||||
|
const dropzone = createDropzone({
|
||||||
|
onDrop: (files) => {
|
||||||
|
const file = files.at(0);
|
||||||
|
|
||||||
|
if (file?.name.endsWith(".backup")) {
|
||||||
|
setBackupFile(file.file);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [passphrase, setPassphrase] = createSignal("");
|
||||||
|
const [backupFile, setBackupFile] = createSignal<File>();
|
||||||
|
|
||||||
|
const [decryptionProgress, setDecryptionProgress] = createSignal<number>();
|
||||||
|
const [loadingProgress, setLoadingProgress] = createSignal<number>();
|
||||||
|
// const [isLoadingDatabase, setIsLoadingDatabase] = createSignal(false);
|
||||||
|
|
||||||
|
const onSubmit: JSX.EventHandler<HTMLFormElement, SubmitEvent> = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const currentBackupFile = backupFile();
|
||||||
const currentPassphrase = passphrase();
|
const currentPassphrase = passphrase();
|
||||||
|
|
||||||
if (file && currentPassphrase) {
|
if (currentBackupFile && currentPassphrase) {
|
||||||
decryptBackup(file, currentPassphrase, setDecryptionProgress)
|
decryptBackup(currentBackupFile, currentPassphrase, setDecryptionProgress)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
setDecryptionProgress(undefined);
|
setDecryptionProgress(undefined);
|
||||||
setIsLoadingDatabase(true);
|
// setIsLoadingDatabase(true);
|
||||||
|
setLoadingProgress(0);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loadDb(result.database_statements);
|
loadDb(result.database_statements, (newValue) => (console.log(newValue), setLoadingProgress(newValue)));
|
||||||
|
|
||||||
setIsLoadingDatabase(false);
|
// setIsLoadingDatabase(false);
|
||||||
|
setLoadingProgress(undefined);
|
||||||
|
|
||||||
navigate("/overview");
|
navigate("/overview");
|
||||||
}, 0);
|
}, 0);
|
||||||
|
@ -53,9 +72,10 @@ export const Home: Component<RouteSectionProps> = () => {
|
||||||
flexDirection="col"
|
flexDirection="col"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
class="fixed inset-0 backdrop-blur-lg backdrop-filter gap-y-8"
|
class="fixed inset-0 gap-y-8 backdrop-blur-lg backdrop-filter"
|
||||||
classList={{
|
classList={{
|
||||||
hidden: decryptionProgress() === undefined && !isLoadingDatabase(),
|
// hidden: decryptionProgress() === undefined && !isLoadingDatabase(),
|
||||||
|
hidden: decryptionProgress() === undefined && loadingProgress() === undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={decryptionProgress() !== undefined}>
|
<Show when={decryptionProgress() !== undefined}>
|
||||||
|
@ -73,19 +93,62 @@ export const Home: Component<RouteSectionProps> = () => {
|
||||||
</div>
|
</div>
|
||||||
</Progress>
|
</Progress>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={isLoadingDatabase()}>
|
<Show when={loadingProgress() !== undefined}>
|
||||||
|
{/* <p class="font-bold text-2xl">Loading database</p>
|
||||||
|
<p class="text-muted-foreground">This can take some time</p> */}
|
||||||
<p class="font-bold text-2xl">Loading database</p>
|
<p class="font-bold text-2xl">Loading database</p>
|
||||||
|
<Progress
|
||||||
|
value={loadingProgress()}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={100}
|
||||||
|
getValueLabel={({ value }) => `${value}%`}
|
||||||
|
class="w-[300px] space-y-1"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<ProgressLabel>Loading...</ProgressLabel>
|
||||||
|
<ProgressValueLabel />
|
||||||
|
</div>
|
||||||
|
</Progress>
|
||||||
</Show>
|
</Show>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Portal>
|
</Portal>
|
||||||
<Title>Signal stats</Title>
|
<Title>Signal stats</Title>
|
||||||
<div>
|
<form class="flex flex-col gap-y-8 p-8" onSubmit={onSubmit}>
|
||||||
<input
|
<TextField onChange={(value) => setPassphrase(value)}>
|
||||||
type="password"
|
<TextFieldLabel>Passphrase</TextFieldLabel>
|
||||||
onChange={(event) => setPassphrase(event.currentTarget.value)}
|
<TextFieldInput type="password" class="max-w-md" />
|
||||||
/>
|
</TextField>
|
||||||
<input type="file" accept=".backup" onChange={onFileChange} />
|
<Flex
|
||||||
</div>
|
ref={dropzone.setRef}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
class="relative min-h-32 min-w-96 max-w-xl rounded-lg border-4 border-border border-dashed"
|
||||||
|
classList={{
|
||||||
|
"border-ring": dropzone.isDragging(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
fileUploader.selectFiles((files) => {
|
||||||
|
setBackupFile(files.at(0)?.file);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Select backup file
|
||||||
|
</Button>
|
||||||
|
<span
|
||||||
|
class="absolute bottom-2"
|
||||||
|
classList={{
|
||||||
|
"text-muted-foreground": !backupFile(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{backupFile() ? backupFile()?.name : "or drop the file here"}
|
||||||
|
</span>
|
||||||
|
</Flex>
|
||||||
|
<Button type="submit" class="max-w-72">
|
||||||
|
Decrypt and load backup
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,67 +1,48 @@
|
||||||
import type { RouteSectionProps } from "@solidjs/router";
|
import type { RouteSectionProps } from "@solidjs/router";
|
||||||
import { type Component, createResource, Show } from "solid-js";
|
import { type Component, createResource, Show } from "solid-js";
|
||||||
|
|
||||||
import {
|
import { allThreadsOverviewQuery, overallSentMessagesQuery, SELF_ID } from "~/db";
|
||||||
allThreadsOverviewQuery,
|
|
||||||
overallSentMessagesQuery,
|
|
||||||
} from "~/db-queries";
|
|
||||||
|
|
||||||
import { Title } from "@solidjs/meta";
|
import { Title } from "@solidjs/meta";
|
||||||
import { SELF_ID } from "~/db";
|
|
||||||
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
||||||
import { OverviewTable, type RoomOverview } from "./overview-table";
|
import { OverviewTable, type RoomOverview } from "./overview-table";
|
||||||
|
|
||||||
export const Overview: Component<RouteSectionProps> = () => {
|
export const Overview: Component<RouteSectionProps> = () => {
|
||||||
const [allSelfSentMessagesCount] = createResource(() =>
|
const [allSelfSentMessagesCount] = createResource(() => overallSentMessagesQuery(SELF_ID));
|
||||||
overallSentMessagesQuery(SELF_ID),
|
|
||||||
);
|
|
||||||
|
|
||||||
const [roomOverview] = createResource<RoomOverview[] | undefined>(
|
const [roomOverview] = createResource<RoomOverview[] | undefined>(async () => {
|
||||||
async () => {
|
return (await allThreadsOverviewQuery())?.map((row) => {
|
||||||
return (await allThreadsOverviewQuery())?.map((row) => {
|
const isGroup = row.title !== null;
|
||||||
const isGroup = row.title !== null;
|
|
||||||
|
|
||||||
let name = "";
|
let name = "";
|
||||||
|
|
||||||
if (row.title !== null) {
|
if (row.title !== null) {
|
||||||
name = row.title;
|
name = row.title;
|
||||||
} else {
|
} else {
|
||||||
name = getNameFromRecipient(
|
name = getNameFromRecipient(row.nickname_joined_name, row.system_joined_name, row.profile_joined_name);
|
||||||
row.nickname_joined_name,
|
}
|
||||||
row.system_joined_name,
|
|
||||||
row.profile_joined_name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
threadId: row.thread_id,
|
threadId: row.thread_id,
|
||||||
recipientId: row.recipient_id,
|
recipientId: row.recipient_id,
|
||||||
archived: Boolean(row.archived),
|
archived: Boolean(row.archived),
|
||||||
messageCount: row.message_count,
|
messageCount: row.message_count,
|
||||||
lastMessageDate: row.last_message_date
|
lastMessageDate: row.last_message_date ? new Date(row.last_message_date) : undefined,
|
||||||
? new Date(row.last_message_date)
|
name,
|
||||||
: undefined,
|
isGroup,
|
||||||
name,
|
};
|
||||||
isGroup,
|
});
|
||||||
};
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title>Signal statistics overview</Title>
|
<Title>Signal statistics overview</Title>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>All messages: {allSelfSentMessagesCount()?.messageCount as number}</p>
|
||||||
All messages: {allSelfSentMessagesCount()?.messageCount as number}
|
<Show when={!roomOverview.loading && roomOverview()} fallback="Loading...">
|
||||||
</p>
|
|
||||||
<Show
|
|
||||||
when={!roomOverview.loading && roomOverview()}
|
|
||||||
fallback="Loading..."
|
|
||||||
>
|
|
||||||
{(currentRoomOverview) => (
|
{(currentRoomOverview) => (
|
||||||
<OverviewTable data={currentRoomOverview()} />
|
console.log(currentRoomOverview()), (<OverviewTable data={currentRoomOverview()} />)
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue