-
-
Notifications
You must be signed in to change notification settings - Fork 392
/
factory.js
501 lines (419 loc) · 15.4 KB
/
factory.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
import FakeWebCrypto from './fake_web_crypto.js'
import { AppContext } from './AppContext.js'
import { VaultsContext } from './VaultsContext.js'
import * as Applications from './Applications.js'
import * as Defaults from './Defaults.js'
import * as Utils from './Utils.js'
import { createItemParams, createNoteParams, createTagParams } from './Items.js'
export const TenSecondTimeout = 10_000
export const TwentySecondTimeout = 20_000
export const ThirtySecondTimeout = 30_000
export const SixtySecondTimeout = 60_000
export const syncOptions = {
checkIntegrity: true,
awaitAll: true,
}
export async function createAndInitSimpleAppContext(
{ registerUser, environment } = {
registerUser: false,
environment: Environment.Web,
},
) {
const application = await createInitAppWithFakeCrypto(environment)
const email = UuidGenerator.GenerateUuid()
const password = UuidGenerator.GenerateUuid()
const newPassword = randomString()
if (registerUser) {
await registerUserToApplication({
application,
email,
password,
})
}
return {
application,
email,
password,
newPassword,
}
}
export async function createAppContextWithFakeCrypto(identifier, email, password) {
return createAppContext({ identifier, crypto: new FakeWebCrypto(), email, password })
}
export async function createAppContextWithRealCrypto(identifier) {
return createAppContext({ identifier, crypto: new SNWebCrypto() })
}
export async function createAppContext({ identifier, crypto, email, password, host } = {}) {
const context = new AppContext({ identifier, crypto, email, password, host })
await context.initialize()
return context
}
export async function createVaultsContextWithRealCrypto(identifier) {
return createVaultsContext({ identifier, crypto: new SNWebCrypto() })
}
export async function createVaultsContextWithFakeCrypto(identifier) {
return createVaultsContext({ identifier, crypto: new FakeWebCrypto() })
}
export async function createVaultsContext({ identifier, crypto, email, password, host } = {}) {
const context = new VaultsContext({ identifier, crypto, email, password, host })
await context.initialize()
return context
}
export function disableIntegrityAutoHeal(application) {
application.sync.emitOutOfSyncRemotePayloads = () => {
console.warn('Integrity self-healing is disabled for this test')
}
}
export async function safeDeinit(application) {
return Utils.safeDeinit(application)
}
export function getDefaultHost() {
return Defaults.getDefaultHost()
}
export function createApplicationWithFakeCrypto(identifier, environment, platform, host) {
return Applications.createApplicationWithFakeCrypto(identifier, environment, platform, host)
}
export function createApplicationWithRealCrypto(identifier, environment, platform, host) {
return Applications.createApplicationWithRealCrypto(identifier, environment, platform, host)
}
export async function createAppWithRandNamespace(environment, platform) {
return Applications.createAppWithRandNamespace(environment, platform)
}
export async function createInitAppWithFakeCrypto(environment, platform) {
return Applications.createInitAppWithFakeCrypto(environment, platform)
}
export async function createInitAppWithFakeCryptoWithOptions({ environment, platform, identifier }) {
const application = Applications.createApplicationWithOptions({ identifier, environment, platform })
await Applications.initializeApplication(application)
return application
}
export async function createInitAppWithRealCrypto(environment, platform) {
return Applications.createInitAppWithRealCrypto(environment, platform)
}
export async function createAndInitializeApplication(namespace, environment, platform, host, crypto) {
return Applications.createAndInitializeApplication(namespace, environment, platform, host, crypto)
}
export async function initializeApplication(application) {
return Applications.initializeApplication(application)
}
export function registerUserToApplication({ application, email, password, ephemeral, mergeLocal = true }) {
if (!email) email = Utils.generateUuid()
if (!password) password = Utils.generateUuid()
return application.register(email, password, ephemeral, mergeLocal)
}
export async function setOldVersionPasscode({ application, passcode, version }) {
const identifier = await application.encryption.crypto.generateUUID()
const operator = application.dependencies.get(TYPES.EncryptionOperators).operatorForVersion(version)
const key = await operator.createRootKey(identifier, passcode, KeyParamsOrigination.PasscodeCreate)
await application.encryption.setNewRootKeyWrapper(key)
await application.user.rewriteItemsKeys()
await application.sync.sync(syncOptions)
}
/**
* Using application.register will always use latest version of protocol.
* To use older version, use this method.
*/
export async function registerOldUser({ application, email, password, version }) {
if (!email) email = Utils.generateUuid()
if (!password) password = Utils.generateUuid()
const operator = application.dependencies.get(TYPES.EncryptionOperators).operatorForVersion(version)
const accountKey = await operator.createRootKey(email, password, KeyParamsOrigination.Registration)
const response = await application.dependencies.get(TYPES.UserApiService).register({
email: email,
serverPassword: accountKey.serverPassword,
keyParams: accountKey.keyParams,
})
/** Mark all existing items as dirty. */
await application.mutator.changeItems(application.items.items, (m) => {
m.dirty = true
})
await application.sessions.handleSuccessAuthResponse(response, accountKey)
application.notifyEvent(ApplicationEvent.SignedIn)
await application.sync.sync({
mode: SyncMode.DownloadFirst,
...syncOptions,
})
await application.dependencies.get(TYPES.DecryptErroredPayloads).execute()
}
export function createStorageItemPayload(contentType) {
return new DecryptedPayload(createItemParams(contentType))
}
export function createNotePayload(title, text = undefined, dirty = true) {
return new DecryptedPayload(createNoteParams({ title, text, dirty }))
}
export function createNote(title, text = undefined, dirty = true) {
return new SNNote(new DecryptedPayload(createNoteParams({ title, text, dirty })))
}
export function createStorageItemTagPayload(tagParams = {}) {
return new DecryptedPayload(createTagParams(tagParams))
}
export function itemToStoragePayload(item) {
return new DecryptedPayload(item)
}
export function createMappedNote(application, title, text, dirty = true) {
const payload = createNotePayload(title, text, dirty)
return application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
}
export async function createMappedTag(application, tagParams = {}) {
const payload = createStorageItemTagPayload(tagParams)
return application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
}
export async function createSyncedNote(application, title, text) {
const payload = createNotePayload(title, text)
const item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged)
await application.mutator.setItemDirty(item)
await application.sync.sync(syncOptions)
const note = application.items.findItem(payload.uuid)
return note
}
export async function getStoragePayloadsOfType(application, type) {
const rawPayloads = await application.storage.getAllRawPayloads()
return rawPayloads
.filter((rp) => rp.content_type === type)
.map((rp) => {
return new CreatePayload(rp)
})
}
export async function createManyMappedNotes(application, count) {
const createdNotes = []
for (let i = 0; i < count; i++) {
const note = await createMappedNote(application)
await application.mutator.setItemDirty(note)
createdNotes.push(note)
}
return createdNotes
}
export async function loginToApplication({
application,
email,
password,
ephemeral,
strict = false,
mergeLocal = true,
awaitSync = true,
}) {
return application.signIn(email, password, strict, ephemeral, mergeLocal, awaitSync)
}
export async function awaitFunctionInvokation(object, functionName) {
return new Promise((resolve) => {
const original = object[functionName]
object[functionName] = async function () {
const result = original.apply(this, arguments)
resolve(result)
return result
}
})
}
/**
* Signing out of an application deinits it.
* A new one must be created.
*/
export async function signOutApplicationAndReturnNew(application) {
const isRealCrypto = application.crypto instanceof SNWebCrypto
await application.user.signOut()
if (isRealCrypto) {
return createInitAppWithRealCrypto()
} else {
return createInitAppWithFakeCrypto()
}
}
export async function signOutAndBackIn(application, email, password) {
const isRealCrypto = application.crypto instanceof SNWebCrypto
await application.user.signOut()
const newApplication = isRealCrypto ? await createInitAppWithRealCrypto() : await createInitAppWithFakeCrypto()
await this.loginToApplication({
application: newApplication,
email,
password,
})
return newApplication
}
export async function restartApplication(application) {
const id = application.identifier
await safeDeinit(application)
const newApplication = await createAndInitializeApplication(id)
return newApplication
}
export async function storagePayloadCount(application) {
const payloads = await application.storage.getAllRawPayloads()
return payloads.length
}
/**
* The number of seconds between changes before a server creates a new revision.
* Controlled via docker/syncing-server-js.env
*/
export const ServerRevisionFrequency = 2.1
export const ServerRevisionCreationDelay = 1.0
export function yesterday() {
return new Date(new Date().setDate(new Date().getDate() - 1))
}
export function dateToMicroseconds(date) {
return date.getTime() * 1_000
}
export function tomorrow() {
return new Date(new Date().setDate(new Date().getDate() + 1))
}
export async function sleep(seconds, reason, dontLog = false) {
if (!dontLog) {
console.log('[Factory] Sleeping for reason', reason)
}
return Utils.sleep(seconds)
}
export function shuffleArray(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[a[i], a[j]] = [a[j], a[i]]
}
return a
}
export function randomString(length = 10) {
let result = ''
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
export function generateUuidish() {
return this.randomString(32)
}
export function randomArrayValue(array) {
return array[Math.floor(Math.random() * array.length)]
}
export async function expectThrowsAsync(method, errorMessage) {
let error = null
try {
await method()
} catch (err) {
error = err
}
const expect = chai.expect
expect(error).to.be.an('Error')
if (errorMessage) {
expect(error.message)
.to.be.a('string')
.and.satisfy((msg) => msg.startsWith(errorMessage))
}
}
export function ignoreChallenges(application) {
application.setLaunchCallback({
receiveChallenge() {
/** no-op */
},
})
}
export function handlePasswordChallenges(application, password) {
application.setLaunchCallback({
receiveChallenge: (challenge) => {
const values = challenge.prompts.map((prompt) =>
CreateChallengeValue(
prompt,
prompt.validation === ChallengeValidation.ProtectionSessionDuration
? UnprotectedAccessSecondsDuration.OneMinute
: password,
),
)
application.submitValuesForChallenge(challenge, values)
},
})
}
export async function createTags(application, hierarchy, parent = undefined, resultAccumulator = undefined) {
const result = resultAccumulator || {}
const promises = Object.entries(hierarchy).map(async ([key, value]) => {
let tag = await application.mutator.findOrCreateTag(key)
result[key] = tag
if (parent) {
await application.mutator.setTagParent(parent, tag)
}
if (value === true) {
return
}
await createTags(application, value, tag, result)
})
await Promise.all(promises)
return result
}
export function pinNote(application, note) {
return application.mutator.changeItem(note, (mutator) => {
mutator.pinned = true
})
}
export async function insertItemWithOverride(application, contentType, content, needsSync = false, errorDecrypting) {
const item = await application.mutator.createItem(contentType, content, needsSync)
if (errorDecrypting) {
const encrypted = new EncryptedPayload({
...item.payload.ejected(),
content: '004:...',
errorDecrypting,
})
await application.payloads.emitPayload(encrypted)
} else {
const decrypted = new DecryptedPayload({
...item.payload.ejected(),
})
await application.payloads.emitPayload(decrypted)
}
return application.items.findAnyItem(item.uuid)
}
export async function alternateUuidForItem(application, uuid) {
const item = application.items.findItem(uuid)
const payload = new DecryptedPayload(item)
const results = await PayloadsByAlternatingUuid(payload, application.payloads.getMasterCollection())
await application.payloads.emitPayloads(results, PayloadEmitSource.LocalChanged)
await application.sync.persistPayloads(results)
return application.items.findItem(results[0].uuid)
}
export async function markDirtyAndSyncItem(application, itemToLookupUuidFor) {
const item = application.items.findItem(itemToLookupUuidFor.uuid)
if (!item) {
throw Error('Attempting to save non-inserted item')
}
if (!item.dirty) {
await application.mutator.changeItem(item, undefined, MutationType.NoUpdateUserTimestamps)
}
await application.sync.sync()
}
export async function changePayloadTimeStampAndSync(application, payload, timestamp, contentOverride, syncOptions) {
await changePayloadTimeStamp(application, payload, timestamp, contentOverride)
await application.sync.sync(syncOptions)
return application.items.findAnyItem(payload.uuid)
}
export async function changePayloadTimeStamp(application, payload, timestamp, contentOverride) {
payload = application.payloads.collection.find(payload.uuid)
const changedPayload = new DecryptedPayload({
...payload,
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
content: {
...payload.content,
...contentOverride,
},
updated_at_timestamp: timestamp,
})
await application.mutator.emitItemFromPayload(changedPayload)
return application.items.findAnyItem(payload.uuid)
}
export async function changePayloadUpdatedAt(application, payload, updatedAt) {
const latestPayload = application.payloads.collection.find(payload.uuid)
const changedPayload = new DecryptedPayload({
...latestPayload.ejected(),
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
updated_at: updatedAt,
})
return application.mutator.emitItemFromPayload(changedPayload)
}
export async function changePayloadTimeStampDeleteAndSync(application, payload, timestamp, syncOptions) {
payload = application.payloads.collection.find(payload.uuid)
const changedPayload = new DeletedPayload({
...payload,
content: undefined,
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
deleted: true,
updated_at_timestamp: timestamp,
})
await application.payloads.emitPayload(changedPayload)
await application.sync.sync(syncOptions)
}