added persist:
* auto login if credentials exists * storing messages in indexdb ($mol_db)
This commit is contained in:
parent
3b55ecc693
commit
e90ae849f3
1 changed files with 111 additions and 9 deletions
120
xmpp.view.ts
120
xmpp.view.ts
|
|
@ -22,6 +22,24 @@ namespace $.$$ {
|
||||||
nick: string // my nickname in this room
|
nick: string // my nickname in this room
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $mol_db schema for persisted messages.
|
||||||
|
type Xmpp_db_schema = {
|
||||||
|
Messages: {
|
||||||
|
Key: [ string, string ] // [account_bare_jid, msg_id]
|
||||||
|
Doc: {
|
||||||
|
account: string
|
||||||
|
peer: string
|
||||||
|
id: string
|
||||||
|
from: string
|
||||||
|
to: string
|
||||||
|
body: string
|
||||||
|
time: number
|
||||||
|
nick?: string
|
||||||
|
}
|
||||||
|
Indexes: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Media_type = 'image' | 'audio' | 'link' | null
|
type Media_type = 'image' | 'audio' | 'link' | null
|
||||||
|
|
||||||
function media_type(url: string): Media_type {
|
function media_type(url: string): Media_type {
|
||||||
|
|
@ -616,20 +634,29 @@ private _handle_message(el: Element) {
|
||||||
// — covers servers that replay offline messages without proper <delay>.
|
// — covers servers that replay offline messages without proper <delay>.
|
||||||
private _connect_at = 0
|
private _connect_at = 0
|
||||||
|
|
||||||
|
// IndexedDB for persistent message history (via $mol_db).
|
||||||
|
private _db: $mol_db_database<Xmpp_db_schema> | null = null
|
||||||
|
private _db_init: Promise<$mol_db_database<Xmpp_db_schema>> | null = null
|
||||||
|
private _loading_persisted = false
|
||||||
|
|
||||||
|
private _auto_connect_tried = false
|
||||||
|
|
||||||
@ $mol_mem
|
@ $mol_mem
|
||||||
status(next?: 'disconnected' | 'connecting' | 'connected') { return next ?? 'disconnected' }
|
status(next?: 'disconnected' | 'connecting' | 'connected') { return next ?? 'disconnected' }
|
||||||
|
|
||||||
@ $mol_mem
|
@ $mol_mem
|
||||||
my_jid(next?: string) { return next ?? '' }
|
my_jid(next?: string) { return next ?? '' }
|
||||||
|
|
||||||
@ $mol_mem
|
// Persisted in localStorage so the user doesn't have to re-enter on reload.
|
||||||
server(next?: string) { return next ?? '' }
|
server(next?: string) {
|
||||||
|
return (this.$.$mol_state_local.value('xmpp_server', next) as string | null) ?? ''
|
||||||
@ $mol_mem
|
}
|
||||||
jid(next?: string) { return next ?? '' }
|
jid(next?: string) {
|
||||||
|
return (this.$.$mol_state_local.value('xmpp_jid', next) as string | null) ?? ''
|
||||||
@ $mol_mem
|
}
|
||||||
password(next?: string) { return next ?? '' }
|
password(next?: string) {
|
||||||
|
return (this.$.$mol_state_local.value('xmpp_password', next) as string | null) ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
@ $mol_mem
|
@ $mol_mem
|
||||||
error_text(next?: string) { return next ?? '' }
|
error_text(next?: string) { return next ?? '' }
|
||||||
|
|
@ -714,7 +741,10 @@ private _handle_message(el: Element) {
|
||||||
// ── Pages ─────────────────────────────────────────────────────────────
|
// ── Pages ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
pages() {
|
pages() {
|
||||||
if (this.status() !== 'connected') return [this.Login_page()]
|
if (this.status() !== 'connected') {
|
||||||
|
this._maybe_auto_connect()
|
||||||
|
return [this.Login_page()]
|
||||||
|
}
|
||||||
const pages: $mol_view[] = [this.Roster_page()]
|
const pages: $mol_view[] = [this.Roster_page()]
|
||||||
const peer = this.$.$mol_state_arg.value('chat')
|
const peer = this.$.$mol_state_arg.value('chat')
|
||||||
if (peer) {
|
if (peer) {
|
||||||
|
|
@ -725,6 +755,21 @@ private _handle_message(el: Element) {
|
||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On first render, if we have saved credentials and aren't connecting/connected, kick off auto-connect.
|
||||||
|
private _maybe_auto_connect() {
|
||||||
|
if (this._auto_connect_tried) return
|
||||||
|
if (this.status() !== 'disconnected') return
|
||||||
|
const url = this.server().trim()
|
||||||
|
const jid = this.jid().trim()
|
||||||
|
const pw = this.password()
|
||||||
|
if (!url || !jid || !pw) return
|
||||||
|
this._auto_connect_tried = true
|
||||||
|
// Defer to escape the current reactive fiber — do_connect mutates memos.
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
if (this.status() === 'disconnected') this.do_connect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ── Login ─────────────────────────────────────────────────────────────
|
// ── Login ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
connecting() { return this.status() === 'connecting' }
|
connecting() { return this.status() === 'connecting' }
|
||||||
|
|
@ -751,6 +796,7 @@ private _handle_message(el: Element) {
|
||||||
this.my_jid(bound_jid)
|
this.my_jid(bound_jid)
|
||||||
this.status('connected')
|
this.status('connected')
|
||||||
this._connect_at = Date.now()
|
this._connect_at = Date.now()
|
||||||
|
void this._load_persisted()
|
||||||
}
|
}
|
||||||
conn.on_bookmarks = bookmarks => {
|
conn.on_bookmarks = bookmarks => {
|
||||||
const my_nick = this.my_jid().split('@')[0]
|
const my_nick = this.my_jid().split('@')[0]
|
||||||
|
|
@ -1322,6 +1368,62 @@ private _handle_message(el: Element) {
|
||||||
if (msg.time < cur) this._oldest_time.set(msg.from, msg.time)
|
if (msg.time < cur) this._oldest_time.set(msg.from, msg.time)
|
||||||
}
|
}
|
||||||
this.messages_ver(this.messages_ver() + 1)
|
this.messages_ver(this.messages_ver() + 1)
|
||||||
|
if (!this._loading_persisted) void this._persist_msg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Persistence (IndexedDB via $mol_db) ──────────────────────────────
|
||||||
|
|
||||||
|
private _ensure_db(): Promise<$mol_db_database<Xmpp_db_schema>> {
|
||||||
|
if (this._db) return Promise.resolve(this._db)
|
||||||
|
if (this._db_init) return this._db_init
|
||||||
|
this._db_init = this.$.$mol_db<Xmpp_db_schema>('xmpp',
|
||||||
|
mig => { mig.store_make('Messages') },
|
||||||
|
).then(db => { this._db = db; return db })
|
||||||
|
return this._db_init
|
||||||
|
}
|
||||||
|
|
||||||
|
private _peer_of(msg: Xmpp_message, account: string): string {
|
||||||
|
if (msg.nick !== undefined) return msg.from
|
||||||
|
return msg.from === account ? msg.to : msg.from
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _persist_msg(msg: Xmpp_message) {
|
||||||
|
const account = this.my_jid().split('/')[0]
|
||||||
|
if (!account) return
|
||||||
|
try {
|
||||||
|
const db = await this._ensure_db()
|
||||||
|
const peer = this._peer_of(msg, account)
|
||||||
|
const { Messages } = db.change('Messages').stores
|
||||||
|
const doc: Xmpp_db_schema['Messages']['Doc'] = {
|
||||||
|
account, peer,
|
||||||
|
id: msg.id, from: msg.from, to: msg.to, body: msg.body, time: msg.time,
|
||||||
|
...(msg.nick !== undefined ? { nick: msg.nick } : {}),
|
||||||
|
}
|
||||||
|
await Messages.put(doc, [account, msg.id])
|
||||||
|
} catch (e) { console.warn('[xmpp] persist failed', e) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _load_persisted() {
|
||||||
|
const account = this.my_jid().split('/')[0]
|
||||||
|
if (!account) return
|
||||||
|
try {
|
||||||
|
const db = await this._ensure_db()
|
||||||
|
const range = this.$.$mol_dom_context.IDBKeyRange.bound([account, ''], [account, ''])
|
||||||
|
const docs = await db.read('Messages').Messages.select(range, 100_000)
|
||||||
|
if (!docs?.length) return
|
||||||
|
this._loading_persisted = true
|
||||||
|
try {
|
||||||
|
for (const doc of docs) {
|
||||||
|
this._add_message({
|
||||||
|
id: doc.id, from: doc.from, to: doc.to,
|
||||||
|
body: doc.body, time: doc.time,
|
||||||
|
...(doc.nick !== undefined ? { nick: doc.nick } : {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this._loading_persisted = false
|
||||||
|
}
|
||||||
|
} catch (e) { console.warn('[xmpp] load persisted failed', e) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private _maybe_load_history(jid: string) {
|
private _maybe_load_history(jid: string) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue