xref: /expo/home/storage/LocalStorage.ts (revision 0738eb43)
1import AsyncStorage from '@react-native-async-storage/async-storage';
2import mapValues from 'lodash/mapValues';
3
4import * as Kernel from '../kernel/Kernel';
5import { SessionObject } from '../redux/SessionReducer';
6import { HistoryItem } from '../types';
7import addListenerWithNativeCallback from '../utils/addListenerWithNativeCallback';
8
9type Settings = Record<string, any>;
10
11const Keys = mapValues(
12  {
13    Session: 'session',
14    History: 'history',
15    Settings: 'settings',
16  },
17  (value) => `Exponent.${value}`
18);
19
20async function getSettingsAsync(): Promise<Settings> {
21  const json = await AsyncStorage.getItem(Keys.Settings);
22  if (!json) {
23    return {};
24  }
25
26  try {
27    return JSON.parse(json);
28  } catch {
29    return {};
30  }
31}
32
33async function updateSettingsAsync(updatedSettings: Partial<Settings>): Promise<void> {
34  const currentSettings = await getSettingsAsync();
35  const newSettings = {
36    ...currentSettings,
37    ...updatedSettings,
38  };
39
40  await AsyncStorage.setItem(Keys.Settings, JSON.stringify(newSettings));
41}
42
43async function getSessionAsync() {
44  let results = await Kernel.getSessionAsync();
45  if (!results) {
46    // NOTE(2018-11-8): we are migrating to storing all session keys
47    // using the Kernel module instead of AsyncStorage, but we need to
48    // continue to check the old location for a little while
49    // until all clients in use have migrated over
50    const json = await AsyncStorage.getItem(Keys.Session);
51    if (json) {
52      try {
53        results = JSON.parse(json);
54        await saveSessionAsync(results as SessionObject);
55        await AsyncStorage.removeItem(Keys.Session);
56      } catch {
57        return null;
58      }
59    }
60  }
61
62  return results;
63}
64
65async function saveSessionAsync(session: SessionObject): Promise<void> {
66  await Kernel.setSessionAsync(session as any);
67}
68
69async function getHistoryAsync(): Promise<HistoryItem[]> {
70  const jsonHistory = await AsyncStorage.getItem(Keys.History);
71  if (jsonHistory) {
72    try {
73      return JSON.parse(jsonHistory);
74    } catch (e) {
75      console.error(e);
76    }
77  }
78  return [];
79}
80
81async function saveHistoryAsync(history: HistoryItem[]): Promise<void> {
82  await AsyncStorage.setItem(Keys.History, JSON.stringify(history));
83}
84
85async function clearHistoryAsync(): Promise<void> {
86  await AsyncStorage.removeItem(Keys.History);
87}
88
89async function removeSessionAsync(): Promise<void> {
90  await Kernel.removeSessionAsync();
91}
92
93// adds a hook for native code to query Home's history.
94// needed for routing push notifications in Home.
95addListenerWithNativeCallback('ExponentKernel.getHistoryUrlForExperienceId', async (event) => {
96  const { experienceId } = event; // scopeKey
97  let history = await getHistoryAsync();
98  history = history.sort((item1, item2) => {
99    // date descending -- we want to pick the most recent experience with this id,
100    // in case there are multiple (e.g. somebody was developing against various URLs of the
101    // same app)
102    const item2time = item2.time ? item2.time : 0;
103    const item1time = item1.time ? item1.time : 0;
104    return item2time - item1time;
105  });
106  // TODO(wschurman): only check for scope key in the future when most manifests contain it
107  // TODO(wschurman): update for new manifest2 format (Manifest)
108  const historyItem = history.find(
109    (item) =>
110      item.manifest &&
111      (item.manifest.id === experienceId ||
112        ('scopeKey' in item.manifest && item.manifest.scopeKey === experienceId))
113  );
114  if (historyItem) {
115    return { url: historyItem.url };
116  }
117  return {};
118});
119
120export default {
121  clearHistoryAsync,
122  getSessionAsync,
123  getHistoryAsync,
124  getSettingsAsync,
125  saveHistoryAsync,
126  saveSessionAsync,
127  removeSessionAsync,
128  updateSettingsAsync,
129};
130