@wireapp/core 46.23.14 → 46.24.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/lib/Account.d.ts +7 -8
- package/lib/Account.d.ts.map +1 -1
- package/lib/Account.js +21 -23
- package/lib/client/ClientService.js +1 -1
- package/lib/conversation/ConversationService/ConversationService.d.ts +12 -8
- package/lib/conversation/ConversationService/ConversationService.d.ts.map +1 -1
- package/lib/conversation/ConversationService/ConversationService.js +11 -13
- package/lib/conversation/ConversationService/ConversationService.test.js +5 -11
- package/lib/messagingProtocols/common.types.d.ts +9 -0
- package/lib/messagingProtocols/common.types.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIService.types.d.ts +2 -2
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIService.types.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIService.types.js +2 -1
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.js +6 -5
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.test.js +20 -15
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceInternal.d.ts +9 -3
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceInternal.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceInternal.js +30 -12
- package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.js +7 -2
- package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.test.js +0 -34
- package/lib/messagingProtocols/mls/EventHandler/events/welcomeMessage/welcomeMessage.test.js +2 -2
- package/lib/messagingProtocols/mls/MLSService/MLSService.d.ts +16 -31
- package/lib/messagingProtocols/mls/MLSService/MLSService.d.ts.map +1 -1
- package/lib/messagingProtocols/mls/MLSService/MLSService.js +74 -171
- package/lib/messagingProtocols/mls/MLSService/MLSService.test.js +93 -151
- package/lib/messagingProtocols/mls/types.d.ts +0 -8
- package/lib/messagingProtocols/mls/types.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper/CoreCryptoWrapper.d.ts +4 -13
- package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper/CoreCryptoWrapper.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper/CoreCryptoWrapper.js +79 -62
- package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CryptoClient.types.d.ts +0 -2
- package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CryptoClient.types.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts +5 -3
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.js +14 -14
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.js +3 -1
- package/lib/messagingProtocols/proteus/ProteusService/WithMockedGenerics.test.js +3 -0
- package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.test.js +3 -0
- package/lib/secretStore/secretKeyGenerator.d.ts +1 -0
- package/lib/secretStore/secretKeyGenerator.d.ts.map +1 -1
- package/lib/secretStore/secretKeyGenerator.js +3 -1
- package/lib/test/StoreHelper.d.ts +2 -0
- package/lib/test/StoreHelper.d.ts.map +1 -0
- package/lib/test/StoreHelper.js +27 -0
- package/package.json +6 -6
|
@@ -40,57 +40,100 @@ const coreCryptoLogger = {
|
|
|
40
40
|
logFunctions[level].call(logger, { message, context });
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const getKey = async (generateSecretKey, keyName, keySize) => {
|
|
44
|
+
return await generateSecretKey(keyName, keySize);
|
|
45
|
+
};
|
|
46
|
+
const migrateOnceAndGetKey = async (generateSecretKey, coreCryptoDbName) => {
|
|
47
|
+
const coreCryptoNewKeyId = 'corecrypto-key-v2';
|
|
46
48
|
const coreCryptoKeyId = 'corecrypto-key';
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
// We retrieve the old key if it exists or generate a new one
|
|
50
|
+
const keyOld = await getKey(generateSecretKey, coreCryptoKeyId, 16);
|
|
51
|
+
// We retrieve the new key if it exists or generate a new one
|
|
52
|
+
const keyNew = await getKey(generateSecretKey, coreCryptoNewKeyId, 32);
|
|
53
|
+
// If we dont retreive any key, we throw an error
|
|
54
|
+
// This should not happen since we generate a new key if it does not exist
|
|
55
|
+
if (!keyNew || !keyOld) {
|
|
56
|
+
throw new Error('Key not found and could not be generated');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* If the old key is freshly generated we dont need to migrate and return the new key
|
|
60
|
+
* If the old key exists and the new key is freshly generated, we need to migrate and then return the new key (This should only happen once !!!!)
|
|
61
|
+
* If the old key exists and the new key exists we return the new key
|
|
62
|
+
*/
|
|
63
|
+
if (!keyOld.freshlyGenerated && keyNew.freshlyGenerated) {
|
|
64
|
+
// Create the new key in the format used by coreCrypto
|
|
65
|
+
const databaseKey = new core_crypto_1.DatabaseKey(keyNew.key);
|
|
66
|
+
// Run the migration
|
|
67
|
+
await (0, core_crypto_1.migrateDatabaseKeyTypeToBytes)(coreCryptoDbName, bazinga64_1.Encoder.toBase64(keyOld.key).asString, databaseKey);
|
|
68
|
+
// delete the old key, it will be freshly generated in the next call and ensure we dont run the migration again
|
|
69
|
+
await keyOld.deleteKey();
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
key: new core_crypto_1.DatabaseKey(keyNew.key),
|
|
73
|
+
deleteKey: keyNew.deleteKey,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
async function buildClient(storeEngine, { generateSecretKey, nbPrekeys, onNewPrekeys }, { wasmFilePath }) {
|
|
77
|
+
return (
|
|
78
|
+
// We need to initialize the coreCrypto package with the path to the wasm file
|
|
79
|
+
// before we can use it. This is a one time operation and should be done
|
|
80
|
+
// before we create the CoreCrypto instance.
|
|
81
|
+
(0, core_crypto_1.initWasmModule)(wasmFilePath)
|
|
82
|
+
.then(async (output) => {
|
|
83
|
+
logger.log('info', 'CoreCrypto initialized', { output });
|
|
84
|
+
const coreCryptoDbName = `corecrypto-${storeEngine.storeName}`;
|
|
85
|
+
// New key format used by coreCrypto
|
|
86
|
+
let key;
|
|
87
|
+
try {
|
|
88
|
+
key = await migrateOnceAndGetKey(generateSecretKey, coreCryptoDbName);
|
|
55
89
|
}
|
|
56
|
-
|
|
57
|
-
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof secretKeyGenerator_1.CorruptedKeyError) {
|
|
92
|
+
// If we are dealing with a corrupted key, we wipe the key and the coreCrypto DB to start fresh
|
|
93
|
+
await (0, idb_1.deleteDB)(coreCryptoDbName);
|
|
94
|
+
key = await migrateOnceAndGetKey(generateSecretKey, coreCryptoDbName);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
58
99
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
100
|
+
const coreCrypto = await core_crypto_1.CoreCrypto.deferredInit({
|
|
101
|
+
databaseName: coreCryptoDbName,
|
|
102
|
+
key: key.key,
|
|
103
|
+
});
|
|
104
|
+
(0, core_crypto_1.setLogger)(coreCryptoLogger);
|
|
105
|
+
(0, core_crypto_1.setMaxLogLevel)(core_crypto_1.CoreCryptoLogLevel.Info);
|
|
106
|
+
return new CoreCryptoWrapper(coreCrypto, { nbPrekeys, onNewPrekeys, onWipe: key.deleteKey });
|
|
107
|
+
})
|
|
108
|
+
// if the coreCrypto initialization fails, can not use the crypto client and throw an error
|
|
109
|
+
.catch(error => {
|
|
110
|
+
logger.error('error', 'CoreCrypto initialization failed', { error });
|
|
111
|
+
throw error;
|
|
112
|
+
}));
|
|
68
113
|
}
|
|
69
114
|
class CoreCryptoWrapper {
|
|
70
115
|
coreCrypto;
|
|
71
|
-
config;
|
|
72
116
|
prekeyTracker;
|
|
73
117
|
version;
|
|
74
118
|
constructor(coreCrypto, config) {
|
|
75
119
|
this.coreCrypto = coreCrypto;
|
|
76
|
-
this.
|
|
77
|
-
this.version = core_crypto_1.CoreCrypto.version();
|
|
120
|
+
this.version = (0, core_crypto_1.version)();
|
|
78
121
|
this.prekeyTracker = new PrekeysTracker_1.PrekeyTracker(this, config);
|
|
79
122
|
}
|
|
80
123
|
getNativeClient() {
|
|
81
124
|
return this.coreCrypto;
|
|
82
125
|
}
|
|
83
126
|
encrypt(sessions, plainText) {
|
|
84
|
-
return this.coreCrypto.proteusEncryptBatched(sessions, plainText);
|
|
127
|
+
return this.coreCrypto.transaction(cx => cx.proteusEncryptBatched(sessions, plainText));
|
|
85
128
|
}
|
|
86
129
|
decrypt(sessionId, message) {
|
|
87
|
-
return this.coreCrypto.proteusDecrypt(sessionId, message);
|
|
130
|
+
return this.coreCrypto.transaction(cx => cx.proteusDecrypt(sessionId, message));
|
|
88
131
|
}
|
|
89
132
|
init(nbInitialPrekeys) {
|
|
90
133
|
if (nbInitialPrekeys) {
|
|
91
134
|
this.prekeyTracker.setInitialState(nbInitialPrekeys);
|
|
92
135
|
}
|
|
93
|
-
return this.coreCrypto.proteusInit();
|
|
136
|
+
return this.coreCrypto.transaction(cx => cx.proteusInit());
|
|
94
137
|
}
|
|
95
138
|
async create(nbPrekeys, entropy) {
|
|
96
139
|
if (entropy) {
|
|
@@ -101,7 +144,7 @@ class CoreCryptoWrapper {
|
|
|
101
144
|
for (let id = 0; id < nbPrekeys; id++) {
|
|
102
145
|
prekeys.push(await this.newPrekey());
|
|
103
146
|
}
|
|
104
|
-
const lastPrekeyBytes = await this.coreCrypto.proteusLastResortPrekey();
|
|
147
|
+
const lastPrekeyBytes = await this.coreCrypto.transaction(cx => cx.proteusLastResortPrekey());
|
|
105
148
|
const lastPrekey = bazinga64_1.Encoder.toBase64(lastPrekeyBytes).asString;
|
|
106
149
|
const lastPrekeyId = core_crypto_1.CoreCrypto.proteusLastResortPrekeyId();
|
|
107
150
|
return {
|
|
@@ -117,25 +160,25 @@ class CoreCryptoWrapper {
|
|
|
117
160
|
}
|
|
118
161
|
async sessionFromMessage(sessionId, message) {
|
|
119
162
|
await this.consumePrekey(); // we need to mark a prekey as consumed since if we create a session from a message, it means the sender has consumed one of our prekeys
|
|
120
|
-
return this.coreCrypto.proteusSessionFromMessage(sessionId, message);
|
|
163
|
+
return this.coreCrypto.transaction(cx => cx.proteusSessionFromMessage(sessionId, message));
|
|
121
164
|
}
|
|
122
165
|
sessionFromPrekey(sessionId, prekey) {
|
|
123
|
-
return this.coreCrypto.proteusSessionFromPrekey(sessionId, prekey);
|
|
166
|
+
return this.coreCrypto.transaction(cx => cx.proteusSessionFromPrekey(sessionId, prekey));
|
|
124
167
|
}
|
|
125
168
|
sessionExists(sessionId) {
|
|
126
169
|
return this.coreCrypto.proteusSessionExists(sessionId);
|
|
127
170
|
}
|
|
128
171
|
saveSession(sessionId) {
|
|
129
|
-
return this.coreCrypto.proteusSessionSave(sessionId);
|
|
172
|
+
return this.coreCrypto.transaction(cx => cx.proteusSessionSave(sessionId));
|
|
130
173
|
}
|
|
131
174
|
deleteSession(sessionId) {
|
|
132
|
-
return this.coreCrypto.proteusSessionDelete(sessionId);
|
|
175
|
+
return this.coreCrypto.transaction(cx => cx.proteusSessionDelete(sessionId));
|
|
133
176
|
}
|
|
134
177
|
consumePrekey() {
|
|
135
178
|
return this.prekeyTracker.consumePrekey();
|
|
136
179
|
}
|
|
137
180
|
async newPrekey() {
|
|
138
|
-
const { id, pkb } = await this.coreCrypto.proteusNewPrekeyAuto();
|
|
181
|
+
const { id, pkb } = await this.coreCrypto.transaction(cx => cx.proteusNewPrekeyAuto());
|
|
139
182
|
return { id, key: bazinga64_1.Encoder.toBase64(pkb).asString };
|
|
140
183
|
}
|
|
141
184
|
async debugBreakSession(sessionId) {
|
|
@@ -145,36 +188,10 @@ class CoreCryptoWrapper {
|
|
|
145
188
|
200, 16, 166, 184, 70, 21, 81, 43, 80, 21, 231, 182, 142, 51, 220, 131, 162, 11, 255, 162, 74, 78, 162, 95, 156,
|
|
146
189
|
131, 48, 203, 5, 77, 122, 4, 246,
|
|
147
190
|
];
|
|
148
|
-
await this.coreCrypto.proteusSessionFromPrekey(sessionId, Uint8Array.from(fakePrekey));
|
|
149
|
-
}
|
|
150
|
-
async debugResetIdentity() {
|
|
151
|
-
await this.coreCrypto.wipe();
|
|
191
|
+
await this.coreCrypto.transaction(cx => cx.proteusSessionFromPrekey(sessionId, Uint8Array.from(fakePrekey)));
|
|
152
192
|
}
|
|
153
193
|
async migrateFromCryptobox(dbName) {
|
|
154
|
-
return this.coreCrypto.proteusCryptoboxMigrate(dbName);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Will call the callback once corecrypto is ready.
|
|
158
|
-
* @param callback - Function to be called once corecrypto is ready.
|
|
159
|
-
* @see https://github.com/wireapp/wire-web-packages/pull/4972
|
|
160
|
-
*/
|
|
161
|
-
onReady(callback) {
|
|
162
|
-
if (!this.coreCrypto.isLocked()) {
|
|
163
|
-
return callback();
|
|
164
|
-
}
|
|
165
|
-
return new Promise(resolve => {
|
|
166
|
-
const intervalId = setInterval(async () => {
|
|
167
|
-
if (!this.coreCrypto.isLocked()) {
|
|
168
|
-
clearInterval(intervalId);
|
|
169
|
-
await callback();
|
|
170
|
-
return resolve();
|
|
171
|
-
}
|
|
172
|
-
}, 100);
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
async wipe() {
|
|
176
|
-
await this.config.onWipe();
|
|
177
|
-
await this.onReady(() => this.coreCrypto.wipe());
|
|
194
|
+
return this.coreCrypto.transaction(cx => cx.proteusCryptoboxMigrate(dbName));
|
|
178
195
|
}
|
|
179
196
|
}
|
|
180
197
|
exports.CoreCryptoWrapper = CoreCryptoWrapper;
|
|
@@ -30,11 +30,9 @@ export interface CryptoClient<T = unknown> {
|
|
|
30
30
|
deleteSession(sessionId: string): Promise<void>;
|
|
31
31
|
newPrekey(): Promise<PreKey>;
|
|
32
32
|
debugBreakSession(sessionId: string): void;
|
|
33
|
-
debugResetIdentity(): Promise<void>;
|
|
34
33
|
/**
|
|
35
34
|
* Will migrate the database from a different client type
|
|
36
35
|
*/
|
|
37
36
|
migrateFromCryptobox?(dbName: string): Promise<void>;
|
|
38
|
-
wipe(): Promise<void>;
|
|
39
37
|
}
|
|
40
38
|
//# sourceMappingURL=CryptoClient.types.d.ts.map
|
package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CryptoClient.types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CryptoClient.types.d.ts","sourceRoot":"","sources":["../../../../../src/messagingProtocols/proteus/ProteusService/CryptoClient/CryptoClient.types.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAC,MAAM,EAAC,MAAM,8BAA8B,CAAC;AAEpD,MAAM,MAAM,cAAc,GAAG;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,CAAC;AAErE,oBAAY,gBAAgB;IAC1B,WAAW,IAAA;IACX,SAAS,IAAA;CACV;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,eAAe,IAAI,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACrF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAErE;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACzE,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChF,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C
|
|
1
|
+
{"version":3,"file":"CryptoClient.types.d.ts","sourceRoot":"","sources":["../../../../../src/messagingProtocols/proteus/ProteusService/CryptoClient/CryptoClient.types.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAC,MAAM,EAAC,MAAM,8BAA8B,CAAC;AAEpD,MAAM,MAAM,cAAc,GAAG;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,CAAC;AAErE,oBAAY,gBAAgB;IAC1B,WAAW,IAAA;IACX,SAAS,IAAA;CACV;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,eAAe,IAAI,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACrF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAErE;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACzE,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChF,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C;;OAEG;IACH,oBAAoB,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD"}
|
|
@@ -20,11 +20,13 @@ export declare class ProteusService {
|
|
|
20
20
|
private readonly apiClient;
|
|
21
21
|
private readonly cryptoClient;
|
|
22
22
|
private readonly config;
|
|
23
|
+
private readonly storeEngine;
|
|
23
24
|
private readonly messageService;
|
|
24
25
|
private readonly logger;
|
|
25
|
-
|
|
26
|
+
private readonly dbName;
|
|
27
|
+
constructor(apiClient: APIClient, cryptoClient: CryptoClient, config: ProteusServiceConfig, storeEngine: CRUDEngine);
|
|
26
28
|
handleOtrMessageAddEvent(event: ConversationOtrMessageAddEvent): Promise<HandledEventPayload>;
|
|
27
|
-
initClient(
|
|
29
|
+
initClient(context: Context): Promise<void>;
|
|
28
30
|
createClient(entropy?: Uint8Array): Promise<import("./CryptoClient").InitialPrekeys>;
|
|
29
31
|
/**
|
|
30
32
|
* Get the fingerprint of the local client.
|
|
@@ -50,6 +52,6 @@ export declare class ProteusService {
|
|
|
50
52
|
consumePrekey(): Promise<void>;
|
|
51
53
|
deleteSession(userId: QualifiedId, clientId: string): Promise<void>;
|
|
52
54
|
encrypt(plainText: Uint8Array, recipients: QualifiedUserPreKeyBundleMap | QualifiedUserClients): Promise<EncryptionResult>;
|
|
53
|
-
wipe(
|
|
55
|
+
wipe(): Promise<void>;
|
|
54
56
|
}
|
|
55
57
|
//# sourceMappingURL=ProteusService.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProteusService.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/proteus/ProteusService/ProteusService.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,mCAAmC,CAAC;AACjE,OAAO,KAAK,EAAC,MAAM,EAAE,OAAO,EAAC,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAGL,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAC,8BAA8B,EAAC,MAAM,+BAA+B,CAAC;AAClF,OAAO,KAAK,EAAC,WAAW,EAAE,4BAA4B,EAAC,MAAM,8BAA8B,CAAC;AAG5F,OAAO,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAI5C,OAAO,KAAK,EACV,mCAAmC,EACnC,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,wBAAwB,CAAC;AAIhC,OAAO,EAEL,iCAAiC,EAEjC,UAAU,EACV,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAY1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wEAAwE;IACxE,QAAQ,EAAE,sBAAsB,CAAC;IACjC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,sGAAsG;IACtG,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;AAEF,qBAAa,cAAc;
|
|
1
|
+
{"version":3,"file":"ProteusService.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/proteus/ProteusService/ProteusService.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,mCAAmC,CAAC;AACjE,OAAO,KAAK,EAAC,MAAM,EAAE,OAAO,EAAC,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAGL,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAC,8BAA8B,EAAC,MAAM,+BAA+B,CAAC;AAClF,OAAO,KAAK,EAAC,WAAW,EAAE,4BAA4B,EAAC,MAAM,8BAA8B,CAAC;AAG5F,OAAO,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAI5C,OAAO,KAAK,EACV,mCAAmC,EACnC,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,wBAAwB,CAAC;AAIhC,OAAO,EAEL,iCAAiC,EAEjC,UAAU,EACV,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAY1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wEAAwE;IACxE,QAAQ,EAAE,sBAAsB,CAAC;IACjC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,sGAAsG;IACtG,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;AAEF,qBAAa,cAAc;IAMvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR9B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwD;IAC/E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAGb,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,oBAAoB,EAC5B,WAAW,EAAE,UAAU;IAM7B,wBAAwB,CAAC,KAAK,EAAE,8BAA8B,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAO7F,UAAU,CAAC,OAAO,EAAE,OAAO;IA4BjC,YAAY,CAAC,OAAO,CAAC,EAAE,UAAU;IAIxC;;OAEG;IACI,mBAAmB;IAInB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIxE;;;;;;OAMG;IACU,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAQ3E,kBAAkB,CAAC,gBAAgB,EAAE,eAAe,GAAG,OAAO,CAAC,iCAAiC,CAAC;IAwC9G;;;OAGG;IACU,sBAAsB,CAAC,EAClC,cAAc,EACd,cAAc,GACf,EAAE,mCAAmC,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAoD5D,WAAW,CAAC,EACvB,OAAO,EACP,cAAc,EACd,UAAU,EACV,UAAU,EACV,OAAO,EACP,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,OAAO,CAAC,UAAU,CAAC;IA6CpC,OAAO,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM;IAuB9E,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM;IAQ7C,OAAO,CAClB,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,4BAA4B,GAAG,oBAAoB,GAC9D,OAAO,CAAC,gBAAgB,CAAC;IAiBtB,IAAI;CAGX"}
|
|
@@ -37,13 +37,17 @@ class ProteusService {
|
|
|
37
37
|
apiClient;
|
|
38
38
|
cryptoClient;
|
|
39
39
|
config;
|
|
40
|
+
storeEngine;
|
|
40
41
|
messageService;
|
|
41
42
|
logger = commons_1.LogFactory.getLogger('@wireapp/core/ProteusService');
|
|
42
|
-
|
|
43
|
+
dbName;
|
|
44
|
+
constructor(apiClient, cryptoClient, config, storeEngine) {
|
|
43
45
|
this.apiClient = apiClient;
|
|
44
46
|
this.cryptoClient = cryptoClient;
|
|
45
47
|
this.config = config;
|
|
48
|
+
this.storeEngine = storeEngine;
|
|
46
49
|
this.messageService = new MessageService_1.MessageService(this.apiClient, this);
|
|
50
|
+
this.dbName = storeEngine.storeName;
|
|
47
51
|
}
|
|
48
52
|
async handleOtrMessageAddEvent(event) {
|
|
49
53
|
return (0, events_1.handleOtrMessageAdd)({
|
|
@@ -51,23 +55,22 @@ class ProteusService {
|
|
|
51
55
|
proteusService: this,
|
|
52
56
|
});
|
|
53
57
|
}
|
|
54
|
-
async initClient(
|
|
55
|
-
const dbName = storeEngine.storeName;
|
|
58
|
+
async initClient(context) {
|
|
56
59
|
if (context.domain) {
|
|
57
60
|
// We want sessions to be fully qualified from now on
|
|
58
|
-
if (!cryptoMigrationStateStore_1.cryptoMigrationStore.qualifiedSessions.isReady(dbName)) {
|
|
61
|
+
if (!cryptoMigrationStateStore_1.cryptoMigrationStore.qualifiedSessions.isReady(this.dbName)) {
|
|
59
62
|
this.logger.info(`Migrating existing session ids to qualified ids.`);
|
|
60
|
-
await (0, sessionIdMigrator_1.migrateToQualifiedSessionIds)(storeEngine, context.domain);
|
|
61
|
-
cryptoMigrationStateStore_1.cryptoMigrationStore.qualifiedSessions.markAsReady(dbName);
|
|
63
|
+
await (0, sessionIdMigrator_1.migrateToQualifiedSessionIds)(this.storeEngine, context.domain);
|
|
64
|
+
cryptoMigrationStateStore_1.cryptoMigrationStore.qualifiedSessions.markAsReady(this.dbName);
|
|
62
65
|
this.logger.info(`Successfully migrated session ids to qualified ids.`);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
|
-
if (!cryptoMigrationStateStore_1.cryptoMigrationStore.coreCrypto.isReady(dbName) && this.cryptoClient.migrateFromCryptobox) {
|
|
68
|
+
if (!cryptoMigrationStateStore_1.cryptoMigrationStore.coreCrypto.isReady(this.dbName) && this.cryptoClient.migrateFromCryptobox) {
|
|
66
69
|
this.logger.info(`Migrating from cryptobox to corecrypto.`);
|
|
67
70
|
try {
|
|
68
71
|
const startTime = Date.now();
|
|
69
|
-
await this.cryptoClient.migrateFromCryptobox(dbName);
|
|
70
|
-
cryptoMigrationStateStore_1.cryptoMigrationStore.coreCrypto.markAsReady(dbName);
|
|
72
|
+
await this.cryptoClient.migrateFromCryptobox(this.dbName);
|
|
73
|
+
cryptoMigrationStateStore_1.cryptoMigrationStore.coreCrypto.markAsReady(this.dbName);
|
|
71
74
|
this.logger.info(`Successfully migrated from cryptobox to corecrypto (took ${Date.now() - startTime}ms).`);
|
|
72
75
|
}
|
|
73
76
|
catch (error) {
|
|
@@ -271,11 +274,8 @@ class ProteusService {
|
|
|
271
274
|
failed,
|
|
272
275
|
};
|
|
273
276
|
}
|
|
274
|
-
async wipe(
|
|
275
|
-
|
|
276
|
-
await (0, identityClearer_1.deleteIdentity)(storeEngine);
|
|
277
|
-
}
|
|
278
|
-
return this.cryptoClient.wipe();
|
|
277
|
+
async wipe() {
|
|
278
|
+
await (0, identityClearer_1.deleteIdentity)(this.storeEngine);
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
exports.ProteusService = ProteusService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProteusService.mocks.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/proteus/ProteusService/ProteusService.mocks.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAE9C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"ProteusService.mocks.d.ts","sourceRoot":"","sources":["../../../../src/messagingProtocols/proteus/ProteusService/ProteusService.mocks.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAE9C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAKhD,eAAO,MAAM,mBAAmB,QAAa,OAAO,CAClD,CAAC,cAAc,EAAE;IAAC,SAAS,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAC,CAAC,CAwBrE,CAAC"}
|
|
@@ -24,6 +24,7 @@ const api_client_1 = require("@wireapp/api-client");
|
|
|
24
24
|
const CoreCryptoWrapper_1 = require("./CryptoClient/CoreCryptoWrapper/CoreCryptoWrapper");
|
|
25
25
|
const ProteusService_1 = require("./ProteusService");
|
|
26
26
|
const PayloadHelper_1 = require("../../../test/PayloadHelper");
|
|
27
|
+
const StoreHelper_1 = require("../../../test/StoreHelper");
|
|
27
28
|
const buildProteusService = async () => {
|
|
28
29
|
const apiClient = new api_client_1.APIClient({ urls: api_client_1.APIClient.BACKEND.STAGING });
|
|
29
30
|
apiClient.context = {
|
|
@@ -32,9 +33,10 @@ const buildProteusService = async () => {
|
|
|
32
33
|
clientId: (0, PayloadHelper_1.getUUID)(),
|
|
33
34
|
};
|
|
34
35
|
const cryptoClient = new CoreCryptoWrapper_1.CoreCryptoWrapper({}, {});
|
|
36
|
+
const storeEngine = await (0, StoreHelper_1.createMemoryEngine)('proteus-service-test');
|
|
35
37
|
const proteusService = new ProteusService_1.ProteusService(apiClient, cryptoClient, {
|
|
36
38
|
nbPrekeys: 0,
|
|
37
|
-
});
|
|
39
|
+
}, storeEngine);
|
|
38
40
|
return [proteusService, { apiClient, cryptoClient }];
|
|
39
41
|
};
|
|
40
42
|
exports.buildProteusService = buildProteusService;
|
|
@@ -72,6 +72,9 @@ const prepareProteusService = async () => {
|
|
|
72
72
|
.mockImplementation(data => Promise.resolve(data));
|
|
73
73
|
return proteusService;
|
|
74
74
|
};
|
|
75
|
+
afterAll(() => {
|
|
76
|
+
jest.clearAllTimers();
|
|
77
|
+
});
|
|
75
78
|
describe('sendGenericMessage', () => {
|
|
76
79
|
describe('targetted messages', () => {
|
|
77
80
|
it(`indicates when sending was canceled`, async () => {
|
|
@@ -59,6 +59,9 @@ describe('SessionHandler', () => {
|
|
|
59
59
|
beforeAll(() => {
|
|
60
60
|
jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockImplementation(generatePrekeys);
|
|
61
61
|
});
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
jest.clearAllTimers();
|
|
64
|
+
});
|
|
62
65
|
describe('constructSessionId', () => {
|
|
63
66
|
describe('constructs a session ID', () => {
|
|
64
67
|
it('without a domain', () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secretKeyGenerator.d.ts","sourceRoot":"","sources":["../../src/secretStore/secretKeyGenerator.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAEhD,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,UAAU,CAAC;IAChB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"secretKeyGenerator.d.ts","sourceRoot":"","sources":["../../src/secretStore/secretKeyGenerator.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAEhD,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,UAAU,CAAC;IAChB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,KAAK,EACL,OAAY,EACZ,SAAS,GACV,EAAE;IACD,wGAAwG;IACxG,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;CAChC,GAAG,OAAO,CAAC,YAAY,CAAC,CAgCxB"}
|
|
@@ -27,6 +27,7 @@ exports.CorruptedKeyError = CorruptedKeyError;
|
|
|
27
27
|
* Will generate (or retrieve) a secret key from the database.
|
|
28
28
|
*/
|
|
29
29
|
async function generateSecretKey({ keyId, keySize = 16, secretsDb, }) {
|
|
30
|
+
let freshlyGenerated = false;
|
|
30
31
|
try {
|
|
31
32
|
let key;
|
|
32
33
|
try {
|
|
@@ -48,8 +49,9 @@ async function generateSecretKey({ keyId, keySize = 16, secretsDb, }) {
|
|
|
48
49
|
}, true, ['encrypt', 'decrypt']);
|
|
49
50
|
key = new Uint8Array(await crypto.subtle.exportKey('raw', key));
|
|
50
51
|
await secretsDb.saveSecretValue(keyId, key);
|
|
52
|
+
freshlyGenerated = true;
|
|
51
53
|
}
|
|
52
|
-
return { key, deleteKey: () => secretsDb.deleteSecretValue(keyId) };
|
|
54
|
+
return { key, deleteKey: () => secretsDb.deleteSecretValue(keyId), freshlyGenerated };
|
|
53
55
|
}
|
|
54
56
|
catch (error) {
|
|
55
57
|
throw error;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StoreHelper.d.ts","sourceRoot":"","sources":["../../src/test/StoreHelper.ts"],"names":[],"mappings":"AAqBA,wBAAsB,kBAAkB,CAAC,SAAS,SAAuB,gBAIxE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Wire
|
|
4
|
+
* Copyright (C) 2022 Wire Swiss GmbH
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* This program is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU General Public License
|
|
17
|
+
* along with this program. If not, see http://www.gnu.org/licenses/.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.createMemoryEngine = createMemoryEngine;
|
|
22
|
+
const { MemoryEngine } = require('@wireapp/store-engine');
|
|
23
|
+
async function createMemoryEngine(storeName = `temp-${Date.now()}`) {
|
|
24
|
+
const engine = new MemoryEngine();
|
|
25
|
+
await engine.init(storeName);
|
|
26
|
+
return engine;
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"./lib/cryptography/AssetCryptography/crypto.node": "./lib/cryptography/AssetCryptography/crypto.browser.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@wireapp/api-client": "^27.56.
|
|
14
|
+
"@wireapp/api-client": "^27.56.1",
|
|
15
15
|
"@wireapp/commons": "^5.4.2",
|
|
16
|
-
"@wireapp/core-crypto": "
|
|
16
|
+
"@wireapp/core-crypto": "7.0.1",
|
|
17
17
|
"@wireapp/cryptobox": "12.8.0",
|
|
18
18
|
"@wireapp/priority-queue": "^2.1.11",
|
|
19
19
|
"@wireapp/promise-queue": "^2.3.12",
|
|
20
20
|
"@wireapp/protocol-messaging": "1.52.0",
|
|
21
|
-
"@wireapp/store-engine": "5.1.11",
|
|
21
|
+
"@wireapp/store-engine": "^5.1.11",
|
|
22
22
|
"axios": "1.7.9",
|
|
23
|
-
"bazinga64": "^6.
|
|
23
|
+
"bazinga64": "^6.4.0",
|
|
24
24
|
"deepmerge-ts": "6.0.0",
|
|
25
25
|
"hash.js": "1.1.7",
|
|
26
26
|
"http-status-codes": "2.3.0",
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
"test:coverage": "jest --coverage",
|
|
62
62
|
"watch": "tsc --watch"
|
|
63
63
|
},
|
|
64
|
-
"version": "46.
|
|
65
|
-
"gitHead": "
|
|
64
|
+
"version": "46.24.0",
|
|
65
|
+
"gitHead": "47ba145ff0a7401ee8dfec9d600878785f85620b"
|
|
66
66
|
}
|