index.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import globalConfig from '@/config';
  2. import { stringMd5, rabbitEncryptString, rabbitDecryptString } from '@/utils/crypto';
  3. // 用一样的 key 和 iv 来加密 storage 的 key, 保证每次加密后的 storage 的 key 的结果一样,否则每次获取数据都需要把 storage 中的所有 key 解密一遍才知道哪个是我们需要的 key
  4. const storageKeyIV = stringMd5(`${globalConfig.app.title}_${globalConfig.app.version}`).slice(0, 8);
  5. export function encryptKey(key: string) {
  6. return rabbitEncryptString(key, globalConfig.security.storageEncryptionKey, storageKeyIV);
  7. }
  8. export function decryptKey(key: string) {
  9. return rabbitDecryptString(key, globalConfig.security.storageEncryptionKey, storageKeyIV);
  10. }
  11. export function encryptData(data: string) {
  12. return rabbitEncryptString(data, globalConfig.security.storageEncryptionKey);
  13. }
  14. export function decryptData(data: string) {
  15. return rabbitDecryptString(data, globalConfig.security.storageEncryptionKey);
  16. }
  17. export interface StorageOptions {
  18. /**
  19. * 过期时间(秒), 0 为永远不过期
  20. * @default 0
  21. */
  22. expire?: number;
  23. /**
  24. * 是否加密key
  25. * @default false
  26. */
  27. encryptKey?: boolean;
  28. /**
  29. * 是否加密数据
  30. * @default false
  31. */
  32. encryptData?: boolean;
  33. }
  34. interface StorageData<T = any> {
  35. data: T;
  36. expire: number;
  37. }
  38. /**
  39. * 是否处于可访问 localStorage 的浏览器环境。
  40. * 预渲染(SSG)在 Node 端执行,无 window/localStorage,此时所有 storage 操作应安全降级为空操作/返回 null,
  41. * 而非抛错。hydrate 后回到浏览器环境即恢复正常读写。
  42. */
  43. const canUseStorage = () => typeof window !== 'undefined' && !!window.localStorage;
  44. function shouldEncryptKey(functionOptions?: StorageOptions, instanceOptions?: StorageOptions) {
  45. if (globalConfig.security.enableStorageEncryption) {
  46. return functionOptions?.encryptKey ?? instanceOptions?.encryptKey ?? false;
  47. }
  48. return false;
  49. }
  50. function shouldEncryptData(funcOpts?: StorageOptions, instanceOpts?: StorageOptions) {
  51. if (globalConfig.security.enableStorageEncryption) {
  52. return funcOpts?.encryptData ?? instanceOpts?.encryptData ?? false;
  53. }
  54. return false;
  55. }
  56. /**
  57. * 创建 localStorage 工具实例
  58. * @param opts {StorageOptions} 本实例使用的全局配置
  59. * @returns
  60. */
  61. export function createLocalStorage({
  62. expire: confExpire = 0,
  63. encryptKey: confEncryptKey = false,
  64. encryptData: confEncryptData = false,
  65. }: StorageOptions = {}) {
  66. /**
  67. * 保存数据
  68. * @param key
  69. * @param data
  70. * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
  71. */
  72. function set<T = any>(key: string, data: T, opts?: StorageOptions) {
  73. if (!canUseStorage()) return;
  74. const expire = opts?.expire ?? confExpire;
  75. const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
  76. const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
  77. const storageData: StorageData<T> = {
  78. data,
  79. expire: expire !== 0 ? new Date().getTime() + expire * 1000 : 0,
  80. };
  81. const json = JSON.stringify(storageData);
  82. const finalKey = isEncryptKey ? encryptKey(key) : key;
  83. const finalData = isEncryptData ? encryptData(json) : json;
  84. window.localStorage.setItem(finalKey, finalData);
  85. }
  86. /**
  87. * 读取数据
  88. * @param key
  89. * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
  90. * @returns
  91. */
  92. function get<T = any>(key: string, opts?: StorageOptions) {
  93. if (!canUseStorage()) return null;
  94. const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
  95. const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
  96. const finalKey = isEncryptKey ? encryptKey(key) : key;
  97. let data = window.localStorage.getItem(finalKey);
  98. if (!data) return null;
  99. if (isEncryptData) {
  100. data = decryptData(data);
  101. }
  102. if (!data) return null;
  103. let storageData: StorageData | null = null;
  104. try {
  105. storageData = JSON.parse(data);
  106. } catch {
  107. // Prevent failure
  108. }
  109. if (storageData) {
  110. const { data, expire = 0 } = storageData;
  111. if (expire === 0 || expire >= Date.now()) return data as T;
  112. }
  113. return null;
  114. }
  115. /**
  116. * 删除数据
  117. * @param key
  118. * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
  119. */
  120. function remove(key: string, opts?: StorageOptions) {
  121. if (!canUseStorage()) return;
  122. const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
  123. const finalKey = isEncryptKey ? encryptKey(key) : key;
  124. window.localStorage.removeItem(finalKey);
  125. }
  126. /**
  127. * 清除 localStorage 中的所有数据。**慎用**
  128. */
  129. function clear() {
  130. if (!canUseStorage()) return;
  131. window.localStorage.clear();
  132. }
  133. return { set, get, remove, clear };
  134. }
  135. /**
  136. * 创建 sessionStorage 工具实例
  137. * @param opts {StorageOptions} 本实例使用的全局配置
  138. * @returns
  139. */
  140. export function createSessionStorage({
  141. expire: confExpire = 0,
  142. encryptKey: confEncryptKey = false,
  143. encryptData: confEncryptData = false,
  144. }: StorageOptions = {}) {
  145. /**
  146. * 保存数据
  147. * @param key
  148. * @param data
  149. * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
  150. */
  151. function set<T = any>(key: string, data: T, opts?: StorageOptions) {
  152. if (!canUseStorage()) return;
  153. const expire = opts?.expire ?? confExpire;
  154. const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
  155. const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
  156. const storageData: StorageData<T> = {
  157. data,
  158. expire: expire !== 0 ? new Date().getTime() + expire * 1000 : 0,
  159. };
  160. const json = JSON.stringify(storageData);
  161. const finalKey = isEncryptKey ? encryptKey(key) : key;
  162. const finalData = isEncryptData ? encryptData(json) : json;
  163. window.sessionStorage.setItem(finalKey, finalData);
  164. }
  165. /**
  166. * 读取数据
  167. * @param key
  168. * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
  169. * @returns
  170. */
  171. function get<T = any>(key: string, opts?: StorageOptions) {
  172. if (!canUseStorage()) return null;
  173. const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
  174. const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData });
  175. const finalKey = isEncryptKey ? encryptKey(key) : key;
  176. let data = window.sessionStorage.getItem(finalKey);
  177. if (!data) return null;
  178. if (isEncryptData) {
  179. data = decryptData(data);
  180. }
  181. if (!data) return null;
  182. let storageData: StorageData | null = null;
  183. try {
  184. storageData = JSON.parse(data);
  185. } catch {
  186. // Prevent failure
  187. }
  188. if (storageData) {
  189. const { data, expire = 0 } = storageData;
  190. if (expire === 0 || expire >= Date.now()) return data as T;
  191. }
  192. return null;
  193. }
  194. /**
  195. * 删除数据
  196. * @param key
  197. * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。
  198. */
  199. function remove(key: string, opts?: StorageOptions) {
  200. if (!canUseStorage()) return;
  201. const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey });
  202. const finalKey = isEncryptKey ? encryptKey(key) : key;
  203. window.sessionStorage.removeItem(finalKey);
  204. }
  205. /**
  206. * 清除 sessionStorage 中的所有数据。**慎用**
  207. */
  208. function clear() {
  209. if (!canUseStorage()) return;
  210. window.sessionStorage.clear();
  211. }
  212. return { set, get, remove, clear };
  213. }
  214. export const ls = createLocalStorage({
  215. expire: 60 * 60 * 24 * 7,
  216. encryptKey: true,
  217. encryptData: true,
  218. });
  219. export const ss = createSessionStorage({
  220. expire: 60 * 60 * 24 * 7,
  221. encryptKey: true,
  222. encryptData: true,
  223. });