@tagea/capacitor-matrix 0.0.2 → 0.1.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 +190 -48
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +190 -48
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +190 -48
- 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
|
|
@@ -291,10 +332,7 @@ var capacitorCapMatrix = (function (exports, core, matrixJsSdk, recoveryKey, key
|
|
|
291
332
|
msgtype,
|
|
292
333
|
body: options.body || options.fileName || 'file',
|
|
293
334
|
url: mxcUrl,
|
|
294
|
-
info: {
|
|
295
|
-
mimetype: options.mimeType,
|
|
296
|
-
size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size,
|
|
297
|
-
},
|
|
335
|
+
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
336
|
};
|
|
299
337
|
const res = await this.client.sendMessage(options.roomId, content);
|
|
300
338
|
return { eventId: res.event_id };
|
|
@@ -313,19 +351,35 @@ var capacitorCapMatrix = (function (exports, core, matrixJsSdk, recoveryKey, key
|
|
|
313
351
|
return { eventId: res.event_id };
|
|
314
352
|
}
|
|
315
353
|
async editMessage(options) {
|
|
354
|
+
var _a, _b;
|
|
316
355
|
this.requireClient();
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
356
|
+
const msgtype = (_a = options.msgtype) !== null && _a !== void 0 ? _a : 'm.text';
|
|
357
|
+
const mediaTypes = ['m.image', 'm.audio', 'm.video', 'm.file'];
|
|
358
|
+
let newContent;
|
|
359
|
+
if (mediaTypes.includes(msgtype) && options.fileUri) {
|
|
360
|
+
const response = await fetch(options.fileUri);
|
|
361
|
+
const blob = await response.blob();
|
|
362
|
+
const uploadRes = await this.client.uploadContent(blob, {
|
|
363
|
+
name: options.fileName,
|
|
364
|
+
type: options.mimeType,
|
|
365
|
+
});
|
|
366
|
+
newContent = {
|
|
367
|
+
msgtype,
|
|
368
|
+
body: options.newBody || options.fileName || 'file',
|
|
369
|
+
url: uploadRes.content_uri,
|
|
370
|
+
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 })),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
newContent = {
|
|
375
|
+
msgtype,
|
|
322
376
|
body: options.newBody,
|
|
323
|
-
}
|
|
324
|
-
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const content = Object.assign(Object.assign({}, newContent), { body: `* ${options.newBody}`, 'm.new_content': newContent, 'm.relates_to': {
|
|
325
380
|
rel_type: 'm.replace',
|
|
326
381
|
event_id: options.eventId,
|
|
327
|
-
}
|
|
328
|
-
};
|
|
382
|
+
} });
|
|
329
383
|
const res = await this.client.sendMessage(options.roomId, content);
|
|
330
384
|
return { eventId: res.event_id };
|
|
331
385
|
}
|
|
@@ -347,10 +401,7 @@ var capacitorCapMatrix = (function (exports, core, matrixJsSdk, recoveryKey, key
|
|
|
347
401
|
msgtype,
|
|
348
402
|
body: options.body || options.fileName || 'file',
|
|
349
403
|
url: uploadRes.content_uri,
|
|
350
|
-
info: {
|
|
351
|
-
mimetype: options.mimeType,
|
|
352
|
-
size: (_b = options.fileSize) !== null && _b !== void 0 ? _b : blob.size,
|
|
353
|
-
},
|
|
404
|
+
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
405
|
'm.relates_to': {
|
|
355
406
|
'm.in_reply_to': {
|
|
356
407
|
event_id: options.replyToEventId,
|
|
@@ -568,18 +619,31 @@ var capacitorCapMatrix = (function (exports, core, matrixJsSdk, recoveryKey, key
|
|
|
568
619
|
}
|
|
569
620
|
// ── Device Management ──────────────────────────────────
|
|
570
621
|
async getDevices() {
|
|
571
|
-
var _a;
|
|
622
|
+
var _a, _b;
|
|
572
623
|
this.requireClient();
|
|
573
624
|
const res = await this.client.getDevices();
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
625
|
+
const crypto = this.client.getCrypto();
|
|
626
|
+
const myUserId = (_a = this.client.getUserId()) !== null && _a !== void 0 ? _a : '';
|
|
627
|
+
const devices = await Promise.all(((_b = res.devices) !== null && _b !== void 0 ? _b : []).map(async (d) => {
|
|
628
|
+
var _a, _b, _c, _d;
|
|
629
|
+
let isCrossSigningVerified;
|
|
630
|
+
if (crypto) {
|
|
631
|
+
try {
|
|
632
|
+
const status = await crypto.getDeviceVerificationStatus(myUserId, d.device_id);
|
|
633
|
+
isCrossSigningVerified = (_a = status === null || status === void 0 ? void 0 : status.crossSigningVerified) !== null && _a !== void 0 ? _a : false;
|
|
634
|
+
}
|
|
635
|
+
catch (_e) {
|
|
636
|
+
// ignore — crypto may not be ready
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
577
640
|
deviceId: d.device_id,
|
|
578
|
-
displayName: (
|
|
579
|
-
lastSeenTs: (
|
|
580
|
-
lastSeenIp: (
|
|
581
|
-
|
|
582
|
-
|
|
641
|
+
displayName: (_b = d.display_name) !== null && _b !== void 0 ? _b : undefined,
|
|
642
|
+
lastSeenTs: (_c = d.last_seen_ts) !== null && _c !== void 0 ? _c : undefined,
|
|
643
|
+
lastSeenIp: (_d = d.last_seen_ip) !== null && _d !== void 0 ? _d : undefined,
|
|
644
|
+
isCrossSigningVerified,
|
|
645
|
+
};
|
|
646
|
+
}));
|
|
583
647
|
return { devices };
|
|
584
648
|
}
|
|
585
649
|
async deleteDevice(options) {
|
|
@@ -602,12 +666,48 @@ var capacitorCapMatrix = (function (exports, core, matrixJsSdk, recoveryKey, key
|
|
|
602
666
|
}
|
|
603
667
|
// ── Encryption ──────────────────────────────────────────
|
|
604
668
|
async initializeCrypto() {
|
|
669
|
+
var _a, _b;
|
|
605
670
|
this.requireClient();
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
671
|
+
const cryptoOpts = { cryptoDatabasePrefix: 'matrix-js-sdk' };
|
|
672
|
+
try {
|
|
673
|
+
await this.client.initRustCrypto(cryptoOpts);
|
|
674
|
+
}
|
|
675
|
+
catch (e) {
|
|
676
|
+
// After logout + re-login the server issues a new deviceId, but the
|
|
677
|
+
// shared IndexedDB crypto store still references the old one.
|
|
678
|
+
// Delete the stale store and retry so crypto initialises cleanly.
|
|
679
|
+
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")) {
|
|
680
|
+
await this.deleteCryptoStore();
|
|
681
|
+
await this.client.initRustCrypto(cryptoOpts);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
throw e;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Flush the initial /keys/query request that initRustCrypto enqueues.
|
|
688
|
+
// Without this, any call to getIdentity (e.g. via getCrossSigningStatus)
|
|
689
|
+
// will spin-wait and emit periodic WARN logs until sync processes it.
|
|
690
|
+
const crypto = this.client.getCrypto();
|
|
691
|
+
if ((_b = crypto === null || crypto === void 0 ? void 0 : crypto.outgoingRequestsManager) === null || _b === void 0 ? void 0 : _b.doProcessOutgoingRequests) {
|
|
692
|
+
await crypto.outgoingRequestsManager.doProcessOutgoingRequests();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
async deleteCryptoStore() {
|
|
696
|
+
if (typeof indexedDB === 'undefined')
|
|
697
|
+
return;
|
|
698
|
+
try {
|
|
699
|
+
const dbs = await indexedDB.databases();
|
|
700
|
+
await Promise.all(dbs
|
|
701
|
+
.filter((db) => { var _a; return (_a = db.name) === null || _a === void 0 ? void 0 : _a.startsWith('matrix-js-sdk'); })
|
|
702
|
+
.map((db) => new Promise((resolve) => {
|
|
703
|
+
const req = indexedDB.deleteDatabase(db.name);
|
|
704
|
+
req.onsuccess = () => resolve();
|
|
705
|
+
req.onerror = () => resolve();
|
|
706
|
+
})));
|
|
707
|
+
}
|
|
708
|
+
catch (_a) {
|
|
709
|
+
// indexedDB.databases() not available in all environments
|
|
710
|
+
}
|
|
611
711
|
}
|
|
612
712
|
async getEncryptionStatus() {
|
|
613
713
|
this.requireClient();
|
|
@@ -690,12 +790,39 @@ var capacitorCapMatrix = (function (exports, core, matrixJsSdk, recoveryKey, key
|
|
|
690
790
|
var _a;
|
|
691
791
|
const crypto = await this.ensureCrypto();
|
|
692
792
|
const keyInfo = await crypto.createRecoveryKeyFromPassphrase(options === null || options === void 0 ? void 0 : options.passphrase);
|
|
793
|
+
// Pre-cache the new key bytes. secretStorageKeyId will be set by
|
|
794
|
+
// cacheSecretStorageKey once bootstrapSecretStorage writes the new key
|
|
795
|
+
// into SSSS and the SDK calls back.
|
|
693
796
|
this.secretStorageKey = keyInfo.privateKey;
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
797
|
+
this.secretStorageKeyId = undefined;
|
|
798
|
+
// If the caller provides the same or old passphrase, keep it so
|
|
799
|
+
// getSecretStorageKey can derive the key for the SDK.
|
|
800
|
+
if (options === null || options === void 0 ? void 0 : options.passphrase) {
|
|
801
|
+
this.recoveryPassphrase = options.passphrase;
|
|
802
|
+
}
|
|
803
|
+
// If the caller knows the OLD passphrase, keep it as fallbackPassphrase so
|
|
804
|
+
// that getSecretStorageKey can decrypt the existing SSSS during
|
|
805
|
+
// bootstrapSecretStorage's migration of cross-signing / backup secrets.
|
|
806
|
+
if (options === null || options === void 0 ? void 0 : options.existingPassphrase) {
|
|
807
|
+
this.fallbackPassphrase = options.existingPassphrase;
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
const bootstrapPromise = crypto.bootstrapSecretStorage({
|
|
811
|
+
createSecretStorageKey: async () => keyInfo,
|
|
812
|
+
setupNewSecretStorage: true,
|
|
813
|
+
setupNewKeyBackup: true,
|
|
814
|
+
});
|
|
815
|
+
// Guard against SDK hanging when it can't retrieve the old SSSS key
|
|
816
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
817
|
+
setTimeout(() => reject(new Error('bootstrapSecretStorage timed out — the old SSSS key could not be retrieved')), 30000);
|
|
818
|
+
});
|
|
819
|
+
await Promise.race([bootstrapPromise, timeoutPromise]);
|
|
820
|
+
}
|
|
821
|
+
finally {
|
|
822
|
+
// Always clear transient crypto state so it doesn't bleed into subsequent calls.
|
|
823
|
+
this.fallbackPassphrase = undefined;
|
|
824
|
+
this.recoveryPassphrase = undefined;
|
|
825
|
+
}
|
|
699
826
|
return { recoveryKey: (_a = keyInfo.encodedPrivateKey) !== null && _a !== void 0 ? _a : '' };
|
|
700
827
|
}
|
|
701
828
|
async isRecoveryEnabled() {
|
|
@@ -724,8 +851,23 @@ var capacitorCapMatrix = (function (exports, core, matrixJsSdk, recoveryKey, key
|
|
|
724
851
|
await crypto.loadSessionBackupPrivateKeyFromSecretStorage();
|
|
725
852
|
}
|
|
726
853
|
catch (e) {
|
|
727
|
-
|
|
854
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
855
|
+
if (msg.includes('decryption key does not match')) {
|
|
856
|
+
// The passphrase is correct (SSSS decrypted fine), but the backup key
|
|
857
|
+
// stored in SSSS doesn't match the server's current backup. This happens
|
|
858
|
+
// when another client re-created the backup without updating SSSS, or
|
|
859
|
+
// vice-versa. Auto-fix by creating a new backup that matches the SSSS key.
|
|
860
|
+
// recoveryPassphrase / secretStorageKey are still set, so the
|
|
861
|
+
// getSecretStorageKey callback can decrypt the existing SSSS.
|
|
862
|
+
await crypto.bootstrapSecretStorage({
|
|
863
|
+
setupNewKeyBackup: true,
|
|
864
|
+
});
|
|
865
|
+
await crypto.checkKeyBackupAndEnable();
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
// Different error — clear state and throw
|
|
728
869
|
this.secretStorageKey = undefined;
|
|
870
|
+
this.secretStorageKeyId = undefined;
|
|
729
871
|
this.recoveryPassphrase = undefined;
|
|
730
872
|
throw e;
|
|
731
873
|
}
|