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