Jeremy Kahn e962c403a5
feat(profile) [closes #219] Import and export user profile (#220)
* feat(profile): implement profile export
* feat(profile): load user-selected file
* feat(profile): import loaded profile
* feat(profile): validate public/private keys
* refactor(settings): remove unnecessary theme functions
* refactor(settings): use theme hook
* feat(settings): show error alert if profile export fails
2023-12-10 19:43:32 -06:00

81 lines
2.3 KiB
TypeScript

import { saveAs } from 'file-saver'
import { UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption'
import {
isSerializedUserSettings,
serializationService,
} from 'services/Serialization/Serialization'
class InvalidFileError extends Error {
message = 'InvalidFileError: File could not be imported'
}
const encryptionTestTarget = 'chitchatter'
export class SettingsService {
exportSettings = async (userSettings: UserSettings) => {
const serializedUserSettings =
await serializationService.serializeUserSettings(userSettings)
const blob = new Blob([JSON.stringify(serializedUserSettings)], {
type: 'application/json;charset=utf-8',
})
saveAs(blob, `chitchatter-profile-${userSettings.userId}.json`)
}
importSettings = async (file: File) => {
const fileReader = new FileReader()
const promise = new Promise<UserSettings>((resolve, reject) => {
fileReader.addEventListener('loadend', async evt => {
try {
const fileReaderResult = evt.target?.result
if (typeof fileReaderResult !== 'string') {
throw new Error()
}
const parsedFileResult = JSON.parse(fileReaderResult)
if (!isSerializedUserSettings(parsedFileResult)) {
throw new Error()
}
const deserializedUserSettings =
await serializationService.deserializeUserSettings(parsedFileResult)
const encryptedString = await encryptionService.encryptString(
deserializedUserSettings.publicKey,
encryptionTestTarget
)
const decryptedString = await encryptionService.decryptString(
deserializedUserSettings.privateKey,
encryptedString
)
// NOTE: This determines whether the public and private keys match
// and are compatible with Chitchatter.
if (decryptedString !== encryptionTestTarget) {
throw new Error()
}
resolve(deserializedUserSettings)
} catch (e) {
const err = new InvalidFileError()
console.error(err)
reject(err)
}
})
fileReader.readAsText(file.slice())
})
return promise
}
}
export const settingsService = new SettingsService()