「復号化+リストア版」Apollo Client
の Reactive variables
データと SQLite
のデータを暗号化し バックアップ ➡︎ リストア する方法をご紹介しています。
開発環境(Expo Bare)
- “typescript”: “^5.2.2”
- “expo”: “~49.0.13”,
- “react”: “18.2.0”,
- “react-native”: “0.72.6”,
- “expo-router”: “^2.0.0”,
- “react-native-paper”: “^5.10.6”,
- “@apollo/client“: “^3.8.5”,
- “expo-sqlite”: “~11.3.3”,
- “crypto-js“: “^4.2.0”,
- “expo-sharing“: “~11.5.0”,
- “expo-file-system”: “~15.4.5”,
- “react-native-restart“: “^0.0.27”,
セットアップする
「暗号化+バックアップ版」をご参考ください。
バックアップする
リストアする
全体の流れ:一部の詳細条件は割愛しています
- リストア関数を作成する
// onRestore()
import RNRestart from 'react-native-restart'; const [backupDatas, setBackupDatas] = useState<string | undefined>(''); const [parsedBkupDatas, setParsedBkupDatas] = useState<BackupListType[]>([]); const onRestore = () => { if (!isStep1Done) { onStep1(); } else if (isStep1Done && !isStep2Done) { onStep2(); } else if (isStep1Done && isStep2Done && !isStep3Done) { onStep3(); } else if (isStep1Done && isStep2Done && isStep3Done && !isStep4Done) { onStep4(); } else onStep5(); };
- Step1バックアップファイルをピックアップする
// onStep1()
const onStep1 = async () => { const j = await jcommonMapper.pickBackupFile(); setBackupDatas(j); };
// pickBackupFile()
import {xor} from 'lodash'; import * as FileSystem from 'expo-file-system'; import * as DocumentPicker from 'expo-document-picker'; // 暗号化+バックアップ版 const BackupListProperties = [ 'rvCommon', 'preferredTheme', 'items', ]; pickBackupFile: async () => { const result = await DocumentPicker.getDocumentAsync({ type: 'application/json', }); if (result.canceled) return; if (result?.assets?.length) { const uri = result.assets.map(el => el.uri)[0]; const fileData = await FileSystem.readAsStringAsync(uri); const decryptedData = jcommonMapper.jsonParse(fileData); const j = decryptedData.map(el => Object.keys(el)[0]); const a = xor(j, BackupListProperties); const isTargetDatas = a.length === 0; if (isTargetDatas) { return fileData; } else return undefined; } },
- Step2対象テーブルをリセットする(SQLiteの場合)
// onStep2()
const db = jsqlMapper.opendb(); const clear = async () => await AsyncStorage.clear(); const onStep2 = async () => { await jcommonMapper.rvReset(clear); await jcommonMapper.dbReset(db); };
// rvReset()
rvReset: async ( storageClear: (callback?: Callback | undefined) => Promise<void>, ) => { try { await storageClear(); console.log('🥕 AsyncStorageClear(): done'); } catch (err) { return {isRvResetDone: false}; } return {isRvResetDone: true}; },
// dbReset()
dbReset: async (db: SQLiteDatabase) => { db.transaction( tx => { tx.executeSql('DROP TABLE IF EXISTS items'); tx.executeSql('"CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY NOT NULL AUTOINCREMENT, done INT, value TEXT);"'); }, err => { console.log('!@# dbReset/err:', err); jcommonMapper.isDbResetDone(false); crashlytics().recordError(new Error(err.toString()), 'dbReset'); }, () => jcommonMapper.isDbResetDone(true), ); },
- Step3オブジェクトファイルに変換する
// onStep3()
const onStep3 = () => { const c = jcommonMapper.jsonParse(backupDatas); setParsedBkupDatas(c); };
// jsonParse() – 複合化する
jsonParse: (fileData: string) => { /** Decrypt */ const ciphertextOfBackuped = fileData; const bytes = CryptoJS.AES.decrypt(ciphertextOfBackuped, 'secret key 123'); const decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); return decryptedData as BackupListType[]; },
- Step4リストアする
// onStep4()
const onStep4 = async () => { await jcommonMapper.restoreRvdatas(parsedBkupDatas); await jcommonMapper.restoreDbdatas(db, parsedBkupDatas); };
// restoreRvdatas()
restoreRvdatas: async (parsedBkupDatas: BackupListType[]) => { const re = await Promise.all( parsedBkupDatas.map(prd => { if (prd.rvCommon) { const _rvCommon = { ...prd.rvCommon, userId: rvCommonVar().userId, preferredTheme: rvCommonVar().preferredTheme. point: rvCommonVar().point, }; rvCommonVar({..._rvCommon}); } }), ); return !!re.length; },
// restoreDbdatas()
restoreDbdatas: async ( db: SQLiteDatabase, parsedBkupDatas: BackupListType[], ) => { const items = compact(flatten(parsedBkupDatas.map(el => el.items))); db.transaction( tx => { items.map(h => executeSql.insertItemsFromBkup( tx, h.done, h.value, ), ); }, err => { console.log('!@# restoreDbdatas/err:', err); jcommonMapper.isRestoreDbDatasDone(false); }, () => jcommonMapper.isRestoreDbDatasDone(true), ); },
- Step5アプリを再起動する
// onStep5()
const onStep5 = () => { RNRestart.Restart(); };
動作デモ
リリースする
※上記の詳細は以下のアプリに実装されています!
コメント