@tagea/capacitor-matrix 0.0.2 → 0.2.0
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/README.md +66 -37
- package/android/src/main/kotlin/de/tremaze/capacitor/matrix/CapMatrix.kt +10 -13
- package/android/src/main/kotlin/de/tremaze/capacitor/matrix/CapMatrixPlugin.kt +18 -2
- package/dist/docs.json +131 -2
- package/dist/esm/definitions.d.ts +40 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +5 -0
- package/dist/esm/web.js +194 -51
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +194 -51
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +194 -51
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapMatrixPlugin/CapMatrix.swift +18 -7
- package/ios/Sources/CapMatrixPlugin/CapMatrixPlugin.swift +19 -2
- package/package.json +3 -2
package/dist/esm/web.js
CHANGED
|
@@ -8,27 +8,41 @@ export class MatrixWeb extends WebPlugin {
|
|
|
8
8
|
super(...arguments);
|
|
9
9
|
this._cryptoCallbacks = {
|
|
10
10
|
getSecretStorageKey: async (opts) => {
|
|
11
|
-
var _a;
|
|
11
|
+
var _a, _b;
|
|
12
12
|
const keyId = Object.keys(opts.keys)[0];
|
|
13
13
|
if (!keyId)
|
|
14
14
|
return null;
|
|
15
|
-
//
|
|
16
|
-
|
|
15
|
+
// Exact match: only return the cached raw key for the key ID it was cached under.
|
|
16
|
+
// (bootstrapSecretStorage uses createSecretStorageKey for the new key, so this
|
|
17
|
+
// path is only reached for an already-established key — e.g. after recoverAndSetup.)
|
|
18
|
+
if (this.secretStorageKey && this.secretStorageKeyId === keyId) {
|
|
17
19
|
return [keyId, this.secretStorageKey];
|
|
18
20
|
}
|
|
19
|
-
//
|
|
21
|
+
// Derive from the current passphrase (set during recoverAndSetup)
|
|
20
22
|
if (this.recoveryPassphrase) {
|
|
21
23
|
const keyInfo = opts.keys[keyId];
|
|
22
24
|
if (keyInfo === null || keyInfo === void 0 ? void 0 : keyInfo.passphrase) {
|
|
23
25
|
const derived = await deriveRecoveryKeyFromPassphrase(this.recoveryPassphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations, (_a = keyInfo.passphrase.bits) !== null && _a !== void 0 ? _a : 256);
|
|
26
|
+
// Cache with the correct key ID for subsequent calls
|
|
24
27
|
this.secretStorageKey = derived;
|
|
28
|
+
this.secretStorageKeyId = keyId;
|
|
29
|
+
return [keyId, derived];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Fallback: derive from the OLD passphrase when bootstrapSecretStorage is
|
|
33
|
+
// migrating existing cross-signing / backup secrets into a new SSSS.
|
|
34
|
+
if (this.fallbackPassphrase) {
|
|
35
|
+
const keyInfo = opts.keys[keyId];
|
|
36
|
+
if (keyInfo === null || keyInfo === void 0 ? void 0 : keyInfo.passphrase) {
|
|
37
|
+
const derived = await deriveRecoveryKeyFromPassphrase(this.fallbackPassphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations, (_b = keyInfo.passphrase.bits) !== null && _b !== void 0 ? _b : 256);
|
|
25
38
|
return [keyId, derived];
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
return null;
|
|
29
42
|
},
|
|
30
|
-
cacheSecretStorageKey: (
|
|
43
|
+
cacheSecretStorageKey: (keyId, _keyInfo, key) => {
|
|
31
44
|
this.secretStorageKey = key;
|
|
45
|
+
this.secretStorageKeyId = keyId;
|
|
32
46
|
},
|
|
33
47
|
};
|
|
34
48
|
}
|
|
@@ -53,6 +67,12 @@ export class MatrixWeb extends WebPlugin {
|
|
|
53
67
|
return session;
|
|
54
68
|
}
|
|
55
69
|
async loginWithToken(options) {
|
|
70
|
+
// Stop any previously running client to avoid parallel instances that
|
|
71
|
+
// would deadlock on the shared IndexedDB crypto store.
|
|
72
|
+
if (this.client) {
|
|
73
|
+
this.client.stopClient();
|
|
74
|
+
this.client = undefined;
|
|
75
|
+
}
|
|
56
76
|
this.client = createClient({
|
|
57
77
|
baseUrl: options.homeserverUrl,
|
|
58
78
|
accessToken: options.accessToken,
|
|
@@ -82,6 +102,19 @@ export class MatrixWeb extends WebPlugin {
|
|
|
82
102
|
}
|
|
83
103
|
localStorage.removeItem(SESSION_KEY);
|
|
84
104
|
}
|
|
105
|
+
async clearAllData() {
|
|
106
|
+
if (this.client) {
|
|
107
|
+
this.client.stopClient();
|
|
108
|
+
this.client = undefined;
|
|
109
|
+
}
|
|
110
|
+
// Reset all cached crypto state
|
|
111
|
+
this.secretStorageKey = undefined;
|
|
112
|
+
this.secretStorageKeyId = undefined;
|
|
113
|
+
this.recoveryPassphrase = undefined;
|
|
114
|
+
this.fallbackPassphrase = undefined;
|
|
115
|
+
localStorage.removeItem(SESSION_KEY);
|
|
116
|
+
await this.deleteCryptoStore();
|
|
117
|
+
}
|
|
85
118
|
async getSession() {
|
|
86
119
|
const raw = localStorage.getItem(SESSION_KEY);
|
|
87
120
|
if (!raw)
|
|
@@ -134,13 +167,21 @@ export class MatrixWeb extends WebPlugin {
|
|
|
134
167
|
}
|
|
135
168
|
}
|
|
136
169
|
});
|
|
137
|
-
this.client.on(RoomEvent.Receipt, (
|
|
138
|
-
var _a;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
this.client.on(RoomEvent.Receipt, (event, room) => {
|
|
171
|
+
var _a, _b;
|
|
172
|
+
const receiptContent = event.getContent();
|
|
173
|
+
for (const [eventId, receiptTypes] of Object.entries(receiptContent)) {
|
|
174
|
+
const mRead = (_a = receiptTypes['m.read']) !== null && _a !== void 0 ? _a : {};
|
|
175
|
+
for (const userId of Object.keys(mRead)) {
|
|
176
|
+
this.notifyListeners('receiptReceived', {
|
|
177
|
+
roomId: room.roomId,
|
|
178
|
+
eventId,
|
|
179
|
+
userId,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
142
183
|
// Re-emit own sent messages with updated read status
|
|
143
|
-
const myUserId = (
|
|
184
|
+
const myUserId = (_b = this.client) === null || _b === void 0 ? void 0 : _b.getUserId();
|
|
144
185
|
if (myUserId) {
|
|
145
186
|
const timeline = room.getLiveTimeline().getEvents();
|
|
146
187
|
// Walk backwards through recent events; stop after checking a reasonable batch
|
|
@@ -163,13 +204,14 @@ export class MatrixWeb extends WebPlugin {
|
|
|
163
204
|
});
|
|
164
205
|
});
|
|
165
206
|
this.client.on(RoomMemberEvent.Typing, (_event, member) => {
|
|
166
|
-
var _a, _b;
|
|
167
207
|
const roomId = member === null || member === void 0 ? void 0 : member.roomId;
|
|
168
208
|
if (roomId) {
|
|
169
209
|
const room = this.client.getRoom(roomId);
|
|
170
210
|
if (room) {
|
|
171
|
-
const
|
|
172
|
-
|
|
211
|
+
const userIds = room
|
|
212
|
+
.getMembers()
|
|
213
|
+
.filter((m) => m.typing)
|
|
214
|
+
.map((m) => m.userId);
|
|
173
215
|
this.notifyListeners('typingChanged', { roomId, userIds });
|
|
174
216
|
}
|
|
175
217
|
}
|
|
@@ -288,10 +330,7 @@ export class MatrixWeb extends WebPlugin {
|
|
|
288
330
|
msgtype,
|
|
289
331
|
body: options.body || options.fileName || 'file',
|
|
290
332
|
url: mxcUrl,
|
|
291
|
-
info: {
|
|
292
|
-
mimetype: options.mimeType,
|
|
293
|
-
size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size,
|
|
294
|
-
},
|
|
333
|
+
info: Object.assign(Object.assign(Object.assign({ mimetype: options.mimeType, size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size }, (options.duration !== undefined && { duration: options.duration })), (options.width !== undefined && { w: options.width })), (options.height !== undefined && { h: options.height })),
|
|
295
334
|
};
|
|
296
335
|
const res = await this.client.sendMessage(options.roomId, content);
|
|
297
336
|
return { eventId: res.event_id };
|
|
@@ -310,19 +349,35 @@ export class MatrixWeb extends WebPlugin {
|
|
|
310
349
|
return { eventId: res.event_id };
|
|
311
350
|
}
|
|
312
351
|
async editMessage(options) {
|
|
352
|
+
var _a, _b;
|
|
313
353
|
this.requireClient();
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
354
|
+
const msgtype = (_a = options.msgtype) !== null && _a !== void 0 ? _a : 'm.text';
|
|
355
|
+
const mediaTypes = ['m.image', 'm.audio', 'm.video', 'm.file'];
|
|
356
|
+
let newContent;
|
|
357
|
+
if (mediaTypes.includes(msgtype) && options.fileUri) {
|
|
358
|
+
const response = await fetch(options.fileUri);
|
|
359
|
+
const blob = await response.blob();
|
|
360
|
+
const uploadRes = await this.client.uploadContent(blob, {
|
|
361
|
+
name: options.fileName,
|
|
362
|
+
type: options.mimeType,
|
|
363
|
+
});
|
|
364
|
+
newContent = {
|
|
365
|
+
msgtype,
|
|
366
|
+
body: options.newBody || options.fileName || 'file',
|
|
367
|
+
url: uploadRes.content_uri,
|
|
368
|
+
info: Object.assign(Object.assign(Object.assign({ mimetype: options.mimeType, size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size }, (options.duration !== undefined && { duration: options.duration })), (options.width !== undefined && { w: options.width })), (options.height !== undefined && { h: options.height })),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
newContent = {
|
|
373
|
+
msgtype,
|
|
319
374
|
body: options.newBody,
|
|
320
|
-
}
|
|
321
|
-
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const content = Object.assign(Object.assign({}, newContent), { body: `* ${options.newBody}`, 'm.new_content': newContent, 'm.relates_to': {
|
|
322
378
|
rel_type: 'm.replace',
|
|
323
379
|
event_id: options.eventId,
|
|
324
|
-
}
|
|
325
|
-
};
|
|
380
|
+
} });
|
|
326
381
|
const res = await this.client.sendMessage(options.roomId, content);
|
|
327
382
|
return { eventId: res.event_id };
|
|
328
383
|
}
|
|
@@ -344,10 +399,7 @@ export class MatrixWeb extends WebPlugin {
|
|
|
344
399
|
msgtype,
|
|
345
400
|
body: options.body || options.fileName || 'file',
|
|
346
401
|
url: uploadRes.content_uri,
|
|
347
|
-
info: {
|
|
348
|
-
mimetype: options.mimeType,
|
|
349
|
-
size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size,
|
|
350
|
-
},
|
|
402
|
+
info: Object.assign(Object.assign(Object.assign({ mimetype: options.mimeType, size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size }, (options.duration !== undefined && { duration: options.duration })), (options.width !== undefined && { w: options.width })), (options.height !== undefined && { h: options.height })),
|
|
351
403
|
'm.relates_to': {
|
|
352
404
|
'm.in_reply_to': {
|
|
353
405
|
event_id: options.replyToEventId,
|
|
@@ -565,18 +617,31 @@ export class MatrixWeb extends WebPlugin {
|
|
|
565
617
|
}
|
|
566
618
|
// ── Device Management ──────────────────────────────────
|
|
567
619
|
async getDevices() {
|
|
568
|
-
var _a;
|
|
620
|
+
var _a, _b;
|
|
569
621
|
this.requireClient();
|
|
570
622
|
const res = await this.client.getDevices();
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
623
|
+
const crypto = this.client.getCrypto();
|
|
624
|
+
const myUserId = (_a = this.client.getUserId()) !== null && _a !== void 0 ? _a : '';
|
|
625
|
+
const devices = await Promise.all(((_b = res.devices) !== null && _b !== void 0 ? _b : []).map(async (d) => {
|
|
626
|
+
var _a, _b, _c, _d;
|
|
627
|
+
let isCrossSigningVerified;
|
|
628
|
+
if (crypto) {
|
|
629
|
+
try {
|
|
630
|
+
const status = await crypto.getDeviceVerificationStatus(myUserId, d.device_id);
|
|
631
|
+
isCrossSigningVerified = (_a = status === null || status === void 0 ? void 0 : status.crossSigningVerified) !== null && _a !== void 0 ? _a : false;
|
|
632
|
+
}
|
|
633
|
+
catch (_e) {
|
|
634
|
+
// ignore — crypto may not be ready
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
574
638
|
deviceId: d.device_id,
|
|
575
|
-
displayName: (
|
|
576
|
-
lastSeenTs: (
|
|
577
|
-
lastSeenIp: (
|
|
578
|
-
|
|
579
|
-
|
|
639
|
+
displayName: (_b = d.display_name) !== null && _b !== void 0 ? _b : undefined,
|
|
640
|
+
lastSeenTs: (_c = d.last_seen_ts) !== null && _c !== void 0 ? _c : undefined,
|
|
641
|
+
lastSeenIp: (_d = d.last_seen_ip) !== null && _d !== void 0 ? _d : undefined,
|
|
642
|
+
isCrossSigningVerified,
|
|
643
|
+
};
|
|
644
|
+
}));
|
|
580
645
|
return { devices };
|
|
581
646
|
}
|
|
582
647
|
async deleteDevice(options) {
|
|
@@ -599,12 +664,48 @@ export class MatrixWeb extends WebPlugin {
|
|
|
599
664
|
}
|
|
600
665
|
// ── Encryption ──────────────────────────────────────────
|
|
601
666
|
async initializeCrypto() {
|
|
667
|
+
var _a, _b;
|
|
602
668
|
this.requireClient();
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
669
|
+
const cryptoOpts = { cryptoDatabasePrefix: 'matrix-js-sdk' };
|
|
670
|
+
try {
|
|
671
|
+
await this.client.initRustCrypto(cryptoOpts);
|
|
672
|
+
}
|
|
673
|
+
catch (e) {
|
|
674
|
+
// After logout + re-login the server issues a new deviceId, but the
|
|
675
|
+
// shared IndexedDB crypto store still references the old one.
|
|
676
|
+
// Delete the stale store and retry so crypto initialises cleanly.
|
|
677
|
+
if ((_a = e === null || e === void 0 ? void 0 : e.message) === null || _a === void 0 ? void 0 : _a.includes("account in the store doesn't match")) {
|
|
678
|
+
await this.deleteCryptoStore();
|
|
679
|
+
await this.client.initRustCrypto(cryptoOpts);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
throw e;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// Flush the initial /keys/query request that initRustCrypto enqueues.
|
|
686
|
+
// Without this, any call to getIdentity (e.g. via getCrossSigningStatus)
|
|
687
|
+
// will spin-wait and emit periodic WARN logs until sync processes it.
|
|
688
|
+
const crypto = this.client.getCrypto();
|
|
689
|
+
if ((_b = crypto === null || crypto === void 0 ? void 0 : crypto.outgoingRequestsManager) === null || _b === void 0 ? void 0 : _b.doProcessOutgoingRequests) {
|
|
690
|
+
await crypto.outgoingRequestsManager.doProcessOutgoingRequests();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async deleteCryptoStore() {
|
|
694
|
+
if (typeof indexedDB === 'undefined')
|
|
695
|
+
return;
|
|
696
|
+
try {
|
|
697
|
+
const dbs = await indexedDB.databases();
|
|
698
|
+
await Promise.all(dbs
|
|
699
|
+
.filter((db) => { var _a; return (_a = db.name) === null || _a === void 0 ? void 0 : _a.startsWith('matrix-js-sdk'); })
|
|
700
|
+
.map((db) => new Promise((resolve) => {
|
|
701
|
+
const req = indexedDB.deleteDatabase(db.name);
|
|
702
|
+
req.onsuccess = () => resolve();
|
|
703
|
+
req.onerror = () => resolve();
|
|
704
|
+
})));
|
|
705
|
+
}
|
|
706
|
+
catch (_a) {
|
|
707
|
+
// indexedDB.databases() not available in all environments
|
|
708
|
+
}
|
|
608
709
|
}
|
|
609
710
|
async getEncryptionStatus() {
|
|
610
711
|
this.requireClient();
|
|
@@ -687,12 +788,39 @@ export class MatrixWeb extends WebPlugin {
|
|
|
687
788
|
var _a;
|
|
688
789
|
const crypto = await this.ensureCrypto();
|
|
689
790
|
const keyInfo = await crypto.createRecoveryKeyFromPassphrase(options === null || options === void 0 ? void 0 : options.passphrase);
|
|
791
|
+
// Pre-cache the new key bytes. secretStorageKeyId will be set by
|
|
792
|
+
// cacheSecretStorageKey once bootstrapSecretStorage writes the new key
|
|
793
|
+
// into SSSS and the SDK calls back.
|
|
690
794
|
this.secretStorageKey = keyInfo.privateKey;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
795
|
+
this.secretStorageKeyId = undefined;
|
|
796
|
+
// If the caller provides the same or old passphrase, keep it so
|
|
797
|
+
// getSecretStorageKey can derive the key for the SDK.
|
|
798
|
+
if (options === null || options === void 0 ? void 0 : options.passphrase) {
|
|
799
|
+
this.recoveryPassphrase = options.passphrase;
|
|
800
|
+
}
|
|
801
|
+
// If the caller knows the OLD passphrase, keep it as fallbackPassphrase so
|
|
802
|
+
// that getSecretStorageKey can decrypt the existing SSSS during
|
|
803
|
+
// bootstrapSecretStorage's migration of cross-signing / backup secrets.
|
|
804
|
+
if (options === null || options === void 0 ? void 0 : options.existingPassphrase) {
|
|
805
|
+
this.fallbackPassphrase = options.existingPassphrase;
|
|
806
|
+
}
|
|
807
|
+
try {
|
|
808
|
+
const bootstrapPromise = crypto.bootstrapSecretStorage({
|
|
809
|
+
createSecretStorageKey: async () => keyInfo,
|
|
810
|
+
setupNewSecretStorage: true,
|
|
811
|
+
setupNewKeyBackup: true,
|
|
812
|
+
});
|
|
813
|
+
// Guard against SDK hanging when it can't retrieve the old SSSS key
|
|
814
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
815
|
+
setTimeout(() => reject(new Error('bootstrapSecretStorage timed out — the old SSSS key could not be retrieved')), 30000);
|
|
816
|
+
});
|
|
817
|
+
await Promise.race([bootstrapPromise, timeoutPromise]);
|
|
818
|
+
}
|
|
819
|
+
finally {
|
|
820
|
+
// Always clear transient crypto state so it doesn't bleed into subsequent calls.
|
|
821
|
+
this.fallbackPassphrase = undefined;
|
|
822
|
+
this.recoveryPassphrase = undefined;
|
|
823
|
+
}
|
|
696
824
|
return { recoveryKey: (_a = keyInfo.encodedPrivateKey) !== null && _a !== void 0 ? _a : '' };
|
|
697
825
|
}
|
|
698
826
|
async isRecoveryEnabled() {
|
|
@@ -721,8 +849,23 @@ export class MatrixWeb extends WebPlugin {
|
|
|
721
849
|
await crypto.loadSessionBackupPrivateKeyFromSecretStorage();
|
|
722
850
|
}
|
|
723
851
|
catch (e) {
|
|
724
|
-
|
|
852
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
853
|
+
if (msg.includes('decryption key does not match')) {
|
|
854
|
+
// The passphrase is correct (SSSS decrypted fine), but the backup key
|
|
855
|
+
// stored in SSSS doesn't match the server's current backup. This happens
|
|
856
|
+
// when another client re-created the backup without updating SSSS, or
|
|
857
|
+
// vice-versa. Auto-fix by creating a new backup that matches the SSSS key.
|
|
858
|
+
// recoveryPassphrase / secretStorageKey are still set, so the
|
|
859
|
+
// getSecretStorageKey callback can decrypt the existing SSSS.
|
|
860
|
+
await crypto.bootstrapSecretStorage({
|
|
861
|
+
setupNewKeyBackup: true,
|
|
862
|
+
});
|
|
863
|
+
await crypto.checkKeyBackupAndEnable();
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
// Different error — clear state and throw
|
|
725
867
|
this.secretStorageKey = undefined;
|
|
868
|
+
this.secretStorageKeyId = undefined;
|
|
726
869
|
this.recoveryPassphrase = undefined;
|
|
727
870
|
throw e;
|
|
728
871
|
}
|