@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/plugin.cjs.js
CHANGED
|
@@ -15,27 +15,41 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
15
15
|
super(...arguments);
|
|
16
16
|
this._cryptoCallbacks = {
|
|
17
17
|
getSecretStorageKey: async (opts) => {
|
|
18
|
-
var _a;
|
|
18
|
+
var _a, _b;
|
|
19
19
|
const keyId = Object.keys(opts.keys)[0];
|
|
20
20
|
if (!keyId)
|
|
21
21
|
return null;
|
|
22
|
-
//
|
|
23
|
-
|
|
22
|
+
// Exact match: only return the cached raw key for the key ID it was cached under.
|
|
23
|
+
// (bootstrapSecretStorage uses createSecretStorageKey for the new key, so this
|
|
24
|
+
// path is only reached for an already-established key — e.g. after recoverAndSetup.)
|
|
25
|
+
if (this.secretStorageKey && this.secretStorageKeyId === keyId) {
|
|
24
26
|
return [keyId, this.secretStorageKey];
|
|
25
27
|
}
|
|
26
|
-
//
|
|
28
|
+
// Derive from the current passphrase (set during recoverAndSetup)
|
|
27
29
|
if (this.recoveryPassphrase) {
|
|
28
30
|
const keyInfo = opts.keys[keyId];
|
|
29
31
|
if (keyInfo === null || keyInfo === void 0 ? void 0 : keyInfo.passphrase) {
|
|
30
32
|
const derived = await keyPassphrase.deriveRecoveryKeyFromPassphrase(this.recoveryPassphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations, (_a = keyInfo.passphrase.bits) !== null && _a !== void 0 ? _a : 256);
|
|
33
|
+
// Cache with the correct key ID for subsequent calls
|
|
31
34
|
this.secretStorageKey = derived;
|
|
35
|
+
this.secretStorageKeyId = keyId;
|
|
36
|
+
return [keyId, derived];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Fallback: derive from the OLD passphrase when bootstrapSecretStorage is
|
|
40
|
+
// migrating existing cross-signing / backup secrets into a new SSSS.
|
|
41
|
+
if (this.fallbackPassphrase) {
|
|
42
|
+
const keyInfo = opts.keys[keyId];
|
|
43
|
+
if (keyInfo === null || keyInfo === void 0 ? void 0 : keyInfo.passphrase) {
|
|
44
|
+
const derived = await keyPassphrase.deriveRecoveryKeyFromPassphrase(this.fallbackPassphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations, (_b = keyInfo.passphrase.bits) !== null && _b !== void 0 ? _b : 256);
|
|
32
45
|
return [keyId, derived];
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
return null;
|
|
36
49
|
},
|
|
37
|
-
cacheSecretStorageKey: (
|
|
50
|
+
cacheSecretStorageKey: (keyId, _keyInfo, key) => {
|
|
38
51
|
this.secretStorageKey = key;
|
|
52
|
+
this.secretStorageKeyId = keyId;
|
|
39
53
|
},
|
|
40
54
|
};
|
|
41
55
|
}
|
|
@@ -60,6 +74,12 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
60
74
|
return session;
|
|
61
75
|
}
|
|
62
76
|
async loginWithToken(options) {
|
|
77
|
+
// Stop any previously running client to avoid parallel instances that
|
|
78
|
+
// would deadlock on the shared IndexedDB crypto store.
|
|
79
|
+
if (this.client) {
|
|
80
|
+
this.client.stopClient();
|
|
81
|
+
this.client = undefined;
|
|
82
|
+
}
|
|
63
83
|
this.client = matrixJsSdk.createClient({
|
|
64
84
|
baseUrl: options.homeserverUrl,
|
|
65
85
|
accessToken: options.accessToken,
|
|
@@ -89,6 +109,19 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
89
109
|
}
|
|
90
110
|
localStorage.removeItem(SESSION_KEY);
|
|
91
111
|
}
|
|
112
|
+
async clearAllData() {
|
|
113
|
+
if (this.client) {
|
|
114
|
+
this.client.stopClient();
|
|
115
|
+
this.client = undefined;
|
|
116
|
+
}
|
|
117
|
+
// Reset all cached crypto state
|
|
118
|
+
this.secretStorageKey = undefined;
|
|
119
|
+
this.secretStorageKeyId = undefined;
|
|
120
|
+
this.recoveryPassphrase = undefined;
|
|
121
|
+
this.fallbackPassphrase = undefined;
|
|
122
|
+
localStorage.removeItem(SESSION_KEY);
|
|
123
|
+
await this.deleteCryptoStore();
|
|
124
|
+
}
|
|
92
125
|
async getSession() {
|
|
93
126
|
const raw = localStorage.getItem(SESSION_KEY);
|
|
94
127
|
if (!raw)
|
|
@@ -141,13 +174,21 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
141
174
|
}
|
|
142
175
|
}
|
|
143
176
|
});
|
|
144
|
-
this.client.on(matrixJsSdk.RoomEvent.Receipt, (
|
|
145
|
-
var _a;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
177
|
+
this.client.on(matrixJsSdk.RoomEvent.Receipt, (event, room) => {
|
|
178
|
+
var _a, _b;
|
|
179
|
+
const receiptContent = event.getContent();
|
|
180
|
+
for (const [eventId, receiptTypes] of Object.entries(receiptContent)) {
|
|
181
|
+
const mRead = (_a = receiptTypes['m.read']) !== null && _a !== void 0 ? _a : {};
|
|
182
|
+
for (const userId of Object.keys(mRead)) {
|
|
183
|
+
this.notifyListeners('receiptReceived', {
|
|
184
|
+
roomId: room.roomId,
|
|
185
|
+
eventId,
|
|
186
|
+
userId,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
149
190
|
// Re-emit own sent messages with updated read status
|
|
150
|
-
const myUserId = (
|
|
191
|
+
const myUserId = (_b = this.client) === null || _b === void 0 ? void 0 : _b.getUserId();
|
|
151
192
|
if (myUserId) {
|
|
152
193
|
const timeline = room.getLiveTimeline().getEvents();
|
|
153
194
|
// Walk backwards through recent events; stop after checking a reasonable batch
|
|
@@ -170,13 +211,14 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
170
211
|
});
|
|
171
212
|
});
|
|
172
213
|
this.client.on(matrixJsSdk.RoomMemberEvent.Typing, (_event, member) => {
|
|
173
|
-
var _a, _b;
|
|
174
214
|
const roomId = member === null || member === void 0 ? void 0 : member.roomId;
|
|
175
215
|
if (roomId) {
|
|
176
216
|
const room = this.client.getRoom(roomId);
|
|
177
217
|
if (room) {
|
|
178
|
-
const
|
|
179
|
-
|
|
218
|
+
const userIds = room
|
|
219
|
+
.getMembers()
|
|
220
|
+
.filter((m) => m.typing)
|
|
221
|
+
.map((m) => m.userId);
|
|
180
222
|
this.notifyListeners('typingChanged', { roomId, userIds });
|
|
181
223
|
}
|
|
182
224
|
}
|
|
@@ -295,10 +337,7 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
295
337
|
msgtype,
|
|
296
338
|
body: options.body || options.fileName || 'file',
|
|
297
339
|
url: mxcUrl,
|
|
298
|
-
info: {
|
|
299
|
-
mimetype: options.mimeType,
|
|
300
|
-
size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size,
|
|
301
|
-
},
|
|
340
|
+
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 })),
|
|
302
341
|
};
|
|
303
342
|
const res = await this.client.sendMessage(options.roomId, content);
|
|
304
343
|
return { eventId: res.event_id };
|
|
@@ -317,19 +356,35 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
317
356
|
return { eventId: res.event_id };
|
|
318
357
|
}
|
|
319
358
|
async editMessage(options) {
|
|
359
|
+
var _a, _b;
|
|
320
360
|
this.requireClient();
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
361
|
+
const msgtype = (_a = options.msgtype) !== null && _a !== void 0 ? _a : 'm.text';
|
|
362
|
+
const mediaTypes = ['m.image', 'm.audio', 'm.video', 'm.file'];
|
|
363
|
+
let newContent;
|
|
364
|
+
if (mediaTypes.includes(msgtype) && options.fileUri) {
|
|
365
|
+
const response = await fetch(options.fileUri);
|
|
366
|
+
const blob = await response.blob();
|
|
367
|
+
const uploadRes = await this.client.uploadContent(blob, {
|
|
368
|
+
name: options.fileName,
|
|
369
|
+
type: options.mimeType,
|
|
370
|
+
});
|
|
371
|
+
newContent = {
|
|
372
|
+
msgtype,
|
|
373
|
+
body: options.newBody || options.fileName || 'file',
|
|
374
|
+
url: uploadRes.content_uri,
|
|
375
|
+
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 })),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
newContent = {
|
|
380
|
+
msgtype,
|
|
326
381
|
body: options.newBody,
|
|
327
|
-
}
|
|
328
|
-
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const content = Object.assign(Object.assign({}, newContent), { body: `* ${options.newBody}`, 'm.new_content': newContent, 'm.relates_to': {
|
|
329
385
|
rel_type: 'm.replace',
|
|
330
386
|
event_id: options.eventId,
|
|
331
|
-
}
|
|
332
|
-
};
|
|
387
|
+
} });
|
|
333
388
|
const res = await this.client.sendMessage(options.roomId, content);
|
|
334
389
|
return { eventId: res.event_id };
|
|
335
390
|
}
|
|
@@ -351,10 +406,7 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
351
406
|
msgtype,
|
|
352
407
|
body: options.body || options.fileName || 'file',
|
|
353
408
|
url: uploadRes.content_uri,
|
|
354
|
-
info: {
|
|
355
|
-
mimetype: options.mimeType,
|
|
356
|
-
size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size,
|
|
357
|
-
},
|
|
409
|
+
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 })),
|
|
358
410
|
'm.relates_to': {
|
|
359
411
|
'm.in_reply_to': {
|
|
360
412
|
event_id: options.replyToEventId,
|
|
@@ -572,18 +624,31 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
572
624
|
}
|
|
573
625
|
// ── Device Management ──────────────────────────────────
|
|
574
626
|
async getDevices() {
|
|
575
|
-
var _a;
|
|
627
|
+
var _a, _b;
|
|
576
628
|
this.requireClient();
|
|
577
629
|
const res = await this.client.getDevices();
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
630
|
+
const crypto = this.client.getCrypto();
|
|
631
|
+
const myUserId = (_a = this.client.getUserId()) !== null && _a !== void 0 ? _a : '';
|
|
632
|
+
const devices = await Promise.all(((_b = res.devices) !== null && _b !== void 0 ? _b : []).map(async (d) => {
|
|
633
|
+
var _a, _b, _c, _d;
|
|
634
|
+
let isCrossSigningVerified;
|
|
635
|
+
if (crypto) {
|
|
636
|
+
try {
|
|
637
|
+
const status = await crypto.getDeviceVerificationStatus(myUserId, d.device_id);
|
|
638
|
+
isCrossSigningVerified = (_a = status === null || status === void 0 ? void 0 : status.crossSigningVerified) !== null && _a !== void 0 ? _a : false;
|
|
639
|
+
}
|
|
640
|
+
catch (_e) {
|
|
641
|
+
// ignore — crypto may not be ready
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
581
645
|
deviceId: d.device_id,
|
|
582
|
-
displayName: (
|
|
583
|
-
lastSeenTs: (
|
|
584
|
-
lastSeenIp: (
|
|
585
|
-
|
|
586
|
-
|
|
646
|
+
displayName: (_b = d.display_name) !== null && _b !== void 0 ? _b : undefined,
|
|
647
|
+
lastSeenTs: (_c = d.last_seen_ts) !== null && _c !== void 0 ? _c : undefined,
|
|
648
|
+
lastSeenIp: (_d = d.last_seen_ip) !== null && _d !== void 0 ? _d : undefined,
|
|
649
|
+
isCrossSigningVerified,
|
|
650
|
+
};
|
|
651
|
+
}));
|
|
587
652
|
return { devices };
|
|
588
653
|
}
|
|
589
654
|
async deleteDevice(options) {
|
|
@@ -606,12 +671,48 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
606
671
|
}
|
|
607
672
|
// ── Encryption ──────────────────────────────────────────
|
|
608
673
|
async initializeCrypto() {
|
|
674
|
+
var _a, _b;
|
|
609
675
|
this.requireClient();
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
676
|
+
const cryptoOpts = { cryptoDatabasePrefix: 'matrix-js-sdk' };
|
|
677
|
+
try {
|
|
678
|
+
await this.client.initRustCrypto(cryptoOpts);
|
|
679
|
+
}
|
|
680
|
+
catch (e) {
|
|
681
|
+
// After logout + re-login the server issues a new deviceId, but the
|
|
682
|
+
// shared IndexedDB crypto store still references the old one.
|
|
683
|
+
// Delete the stale store and retry so crypto initialises cleanly.
|
|
684
|
+
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")) {
|
|
685
|
+
await this.deleteCryptoStore();
|
|
686
|
+
await this.client.initRustCrypto(cryptoOpts);
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
throw e;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// Flush the initial /keys/query request that initRustCrypto enqueues.
|
|
693
|
+
// Without this, any call to getIdentity (e.g. via getCrossSigningStatus)
|
|
694
|
+
// will spin-wait and emit periodic WARN logs until sync processes it.
|
|
695
|
+
const crypto = this.client.getCrypto();
|
|
696
|
+
if ((_b = crypto === null || crypto === void 0 ? void 0 : crypto.outgoingRequestsManager) === null || _b === void 0 ? void 0 : _b.doProcessOutgoingRequests) {
|
|
697
|
+
await crypto.outgoingRequestsManager.doProcessOutgoingRequests();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
async deleteCryptoStore() {
|
|
701
|
+
if (typeof indexedDB === 'undefined')
|
|
702
|
+
return;
|
|
703
|
+
try {
|
|
704
|
+
const dbs = await indexedDB.databases();
|
|
705
|
+
await Promise.all(dbs
|
|
706
|
+
.filter((db) => { var _a; return (_a = db.name) === null || _a === void 0 ? void 0 : _a.startsWith('matrix-js-sdk'); })
|
|
707
|
+
.map((db) => new Promise((resolve) => {
|
|
708
|
+
const req = indexedDB.deleteDatabase(db.name);
|
|
709
|
+
req.onsuccess = () => resolve();
|
|
710
|
+
req.onerror = () => resolve();
|
|
711
|
+
})));
|
|
712
|
+
}
|
|
713
|
+
catch (_a) {
|
|
714
|
+
// indexedDB.databases() not available in all environments
|
|
715
|
+
}
|
|
615
716
|
}
|
|
616
717
|
async getEncryptionStatus() {
|
|
617
718
|
this.requireClient();
|
|
@@ -694,12 +795,39 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
694
795
|
var _a;
|
|
695
796
|
const crypto = await this.ensureCrypto();
|
|
696
797
|
const keyInfo = await crypto.createRecoveryKeyFromPassphrase(options === null || options === void 0 ? void 0 : options.passphrase);
|
|
798
|
+
// Pre-cache the new key bytes. secretStorageKeyId will be set by
|
|
799
|
+
// cacheSecretStorageKey once bootstrapSecretStorage writes the new key
|
|
800
|
+
// into SSSS and the SDK calls back.
|
|
697
801
|
this.secretStorageKey = keyInfo.privateKey;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
802
|
+
this.secretStorageKeyId = undefined;
|
|
803
|
+
// If the caller provides the same or old passphrase, keep it so
|
|
804
|
+
// getSecretStorageKey can derive the key for the SDK.
|
|
805
|
+
if (options === null || options === void 0 ? void 0 : options.passphrase) {
|
|
806
|
+
this.recoveryPassphrase = options.passphrase;
|
|
807
|
+
}
|
|
808
|
+
// If the caller knows the OLD passphrase, keep it as fallbackPassphrase so
|
|
809
|
+
// that getSecretStorageKey can decrypt the existing SSSS during
|
|
810
|
+
// bootstrapSecretStorage's migration of cross-signing / backup secrets.
|
|
811
|
+
if (options === null || options === void 0 ? void 0 : options.existingPassphrase) {
|
|
812
|
+
this.fallbackPassphrase = options.existingPassphrase;
|
|
813
|
+
}
|
|
814
|
+
try {
|
|
815
|
+
const bootstrapPromise = crypto.bootstrapSecretStorage({
|
|
816
|
+
createSecretStorageKey: async () => keyInfo,
|
|
817
|
+
setupNewSecretStorage: true,
|
|
818
|
+
setupNewKeyBackup: true,
|
|
819
|
+
});
|
|
820
|
+
// Guard against SDK hanging when it can't retrieve the old SSSS key
|
|
821
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
822
|
+
setTimeout(() => reject(new Error('bootstrapSecretStorage timed out — the old SSSS key could not be retrieved')), 30000);
|
|
823
|
+
});
|
|
824
|
+
await Promise.race([bootstrapPromise, timeoutPromise]);
|
|
825
|
+
}
|
|
826
|
+
finally {
|
|
827
|
+
// Always clear transient crypto state so it doesn't bleed into subsequent calls.
|
|
828
|
+
this.fallbackPassphrase = undefined;
|
|
829
|
+
this.recoveryPassphrase = undefined;
|
|
830
|
+
}
|
|
703
831
|
return { recoveryKey: (_a = keyInfo.encodedPrivateKey) !== null && _a !== void 0 ? _a : '' };
|
|
704
832
|
}
|
|
705
833
|
async isRecoveryEnabled() {
|
|
@@ -728,8 +856,23 @@ class MatrixWeb extends core.WebPlugin {
|
|
|
728
856
|
await crypto.loadSessionBackupPrivateKeyFromSecretStorage();
|
|
729
857
|
}
|
|
730
858
|
catch (e) {
|
|
731
|
-
|
|
859
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
860
|
+
if (msg.includes('decryption key does not match')) {
|
|
861
|
+
// The passphrase is correct (SSSS decrypted fine), but the backup key
|
|
862
|
+
// stored in SSSS doesn't match the server's current backup. This happens
|
|
863
|
+
// when another client re-created the backup without updating SSSS, or
|
|
864
|
+
// vice-versa. Auto-fix by creating a new backup that matches the SSSS key.
|
|
865
|
+
// recoveryPassphrase / secretStorageKey are still set, so the
|
|
866
|
+
// getSecretStorageKey callback can decrypt the existing SSSS.
|
|
867
|
+
await crypto.bootstrapSecretStorage({
|
|
868
|
+
setupNewKeyBackup: true,
|
|
869
|
+
});
|
|
870
|
+
await crypto.checkKeyBackupAndEnable();
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
// Different error — clear state and throw
|
|
732
874
|
this.secretStorageKey = undefined;
|
|
875
|
+
this.secretStorageKeyId = undefined;
|
|
733
876
|
this.recoveryPassphrase = undefined;
|
|
734
877
|
throw e;
|
|
735
878
|
}
|