@metamask/profile-sync-controller 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/dist/controllers/authentication/AuthenticationController.cjs +0 -1
  3. package/dist/controllers/authentication/AuthenticationController.cjs.map +1 -1
  4. package/dist/controllers/authentication/AuthenticationController.d.cts.map +1 -1
  5. package/dist/controllers/authentication/AuthenticationController.d.mts.map +1 -1
  6. package/dist/controllers/authentication/AuthenticationController.mjs +0 -1
  7. package/dist/controllers/authentication/AuthenticationController.mjs.map +1 -1
  8. package/dist/controllers/user-storage/UserStorageController.cjs +55 -22
  9. package/dist/controllers/user-storage/UserStorageController.cjs.map +1 -1
  10. package/dist/controllers/user-storage/UserStorageController.d.cts +32 -15
  11. package/dist/controllers/user-storage/UserStorageController.d.cts.map +1 -1
  12. package/dist/controllers/user-storage/UserStorageController.d.mts +32 -15
  13. package/dist/controllers/user-storage/UserStorageController.d.mts.map +1 -1
  14. package/dist/controllers/user-storage/UserStorageController.mjs +56 -23
  15. package/dist/controllers/user-storage/UserStorageController.mjs.map +1 -1
  16. package/dist/controllers/user-storage/__fixtures__/mockMessenger.cjs +171 -0
  17. package/dist/controllers/user-storage/__fixtures__/mockMessenger.cjs.map +1 -0
  18. package/dist/controllers/user-storage/__fixtures__/mockMessenger.d.cts +102 -0
  19. package/dist/controllers/user-storage/__fixtures__/mockMessenger.d.cts.map +1 -0
  20. package/dist/controllers/user-storage/__fixtures__/mockMessenger.d.mts +102 -0
  21. package/dist/controllers/user-storage/__fixtures__/mockMessenger.d.mts.map +1 -0
  22. package/dist/controllers/user-storage/__fixtures__/mockMessenger.mjs +166 -0
  23. package/dist/controllers/user-storage/__fixtures__/mockMessenger.mjs.map +1 -0
  24. package/dist/controllers/user-storage/__fixtures__/mockResponses.cjs +7 -6
  25. package/dist/controllers/user-storage/__fixtures__/mockResponses.cjs.map +1 -1
  26. package/dist/controllers/user-storage/__fixtures__/mockResponses.d.cts.map +1 -1
  27. package/dist/controllers/user-storage/__fixtures__/mockResponses.d.mts.map +1 -1
  28. package/dist/controllers/user-storage/__fixtures__/mockResponses.mjs +7 -6
  29. package/dist/controllers/user-storage/__fixtures__/mockResponses.mjs.map +1 -1
  30. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.cjs +20 -2
  31. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.cjs.map +1 -1
  32. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.d.cts +8 -1
  33. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.d.cts.map +1 -1
  34. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.d.mts +8 -1
  35. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.d.mts.map +1 -1
  36. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.mjs +17 -1
  37. package/dist/controllers/user-storage/network-syncing/__fixtures__/mockNetwork.mjs.map +1 -1
  38. package/dist/controllers/user-storage/network-syncing/add-network-utils.cjs +35 -0
  39. package/dist/controllers/user-storage/network-syncing/add-network-utils.cjs.map +1 -0
  40. package/dist/controllers/user-storage/network-syncing/add-network-utils.d.cts +21 -0
  41. package/dist/controllers/user-storage/network-syncing/add-network-utils.d.cts.map +1 -0
  42. package/dist/controllers/user-storage/network-syncing/add-network-utils.d.mts +21 -0
  43. package/dist/controllers/user-storage/network-syncing/add-network-utils.d.mts.map +1 -0
  44. package/dist/controllers/user-storage/network-syncing/add-network-utils.mjs +30 -0
  45. package/dist/controllers/user-storage/network-syncing/add-network-utils.mjs.map +1 -0
  46. package/dist/controllers/user-storage/network-syncing/controller-integration.cjs +157 -29
  47. package/dist/controllers/user-storage/network-syncing/controller-integration.cjs.map +1 -1
  48. package/dist/controllers/user-storage/network-syncing/controller-integration.d.cts +45 -3
  49. package/dist/controllers/user-storage/network-syncing/controller-integration.d.cts.map +1 -1
  50. package/dist/controllers/user-storage/network-syncing/controller-integration.d.mts +45 -3
  51. package/dist/controllers/user-storage/network-syncing/controller-integration.d.mts.map +1 -1
  52. package/dist/controllers/user-storage/network-syncing/controller-integration.mjs +154 -28
  53. package/dist/controllers/user-storage/network-syncing/controller-integration.mjs.map +1 -1
  54. package/dist/controllers/user-storage/network-syncing/services.cjs +9 -4
  55. package/dist/controllers/user-storage/network-syncing/services.cjs.map +1 -1
  56. package/dist/controllers/user-storage/network-syncing/services.d.cts.map +1 -1
  57. package/dist/controllers/user-storage/network-syncing/services.d.mts.map +1 -1
  58. package/dist/controllers/user-storage/network-syncing/services.mjs +10 -5
  59. package/dist/controllers/user-storage/network-syncing/services.mjs.map +1 -1
  60. package/dist/controllers/user-storage/network-syncing/sync-all.cjs +10 -28
  61. package/dist/controllers/user-storage/network-syncing/sync-all.cjs.map +1 -1
  62. package/dist/controllers/user-storage/network-syncing/sync-all.d.cts +5 -8
  63. package/dist/controllers/user-storage/network-syncing/sync-all.d.cts.map +1 -1
  64. package/dist/controllers/user-storage/network-syncing/sync-all.d.mts +5 -8
  65. package/dist/controllers/user-storage/network-syncing/sync-all.d.mts.map +1 -1
  66. package/dist/controllers/user-storage/network-syncing/sync-all.mjs +9 -26
  67. package/dist/controllers/user-storage/network-syncing/sync-all.mjs.map +1 -1
  68. package/dist/controllers/user-storage/network-syncing/sync-mutations.cjs +1 -1
  69. package/dist/controllers/user-storage/network-syncing/sync-mutations.cjs.map +1 -1
  70. package/dist/controllers/user-storage/network-syncing/sync-mutations.d.cts +1 -1
  71. package/dist/controllers/user-storage/network-syncing/sync-mutations.d.cts.map +1 -1
  72. package/dist/controllers/user-storage/network-syncing/sync-mutations.d.mts +1 -1
  73. package/dist/controllers/user-storage/network-syncing/sync-mutations.d.mts.map +1 -1
  74. package/dist/controllers/user-storage/network-syncing/sync-mutations.mjs +1 -1
  75. package/dist/controllers/user-storage/network-syncing/sync-mutations.mjs.map +1 -1
  76. package/dist/controllers/user-storage/network-syncing/types.cjs.map +1 -1
  77. package/dist/controllers/user-storage/network-syncing/types.d.cts +1 -4
  78. package/dist/controllers/user-storage/network-syncing/types.d.cts.map +1 -1
  79. package/dist/controllers/user-storage/network-syncing/types.d.mts +1 -4
  80. package/dist/controllers/user-storage/network-syncing/types.d.mts.map +1 -1
  81. package/dist/controllers/user-storage/network-syncing/types.mjs.map +1 -1
  82. package/dist/controllers/user-storage/network-syncing/update-network-utils.cjs +126 -0
  83. package/dist/controllers/user-storage/network-syncing/update-network-utils.cjs.map +1 -0
  84. package/dist/controllers/user-storage/network-syncing/update-network-utils.d.cts +56 -0
  85. package/dist/controllers/user-storage/network-syncing/update-network-utils.d.cts.map +1 -0
  86. package/dist/controllers/user-storage/network-syncing/update-network-utils.d.mts +56 -0
  87. package/dist/controllers/user-storage/network-syncing/update-network-utils.d.mts.map +1 -0
  88. package/dist/controllers/user-storage/network-syncing/update-network-utils.mjs +119 -0
  89. package/dist/controllers/user-storage/network-syncing/update-network-utils.mjs.map +1 -0
  90. package/dist/controllers/user-storage/services.cjs +49 -1
  91. package/dist/controllers/user-storage/services.cjs.map +1 -1
  92. package/dist/controllers/user-storage/services.d.cts +8 -0
  93. package/dist/controllers/user-storage/services.d.cts.map +1 -1
  94. package/dist/controllers/user-storage/services.d.mts +8 -0
  95. package/dist/controllers/user-storage/services.d.mts.map +1 -1
  96. package/dist/controllers/user-storage/services.mjs +47 -0
  97. package/dist/controllers/user-storage/services.mjs.map +1 -1
  98. package/dist/sdk/__fixtures__/mock-userstorage.cjs +29 -6
  99. package/dist/sdk/__fixtures__/mock-userstorage.cjs.map +1 -1
  100. package/dist/sdk/__fixtures__/mock-userstorage.d.cts +4 -6
  101. package/dist/sdk/__fixtures__/mock-userstorage.d.cts.map +1 -1
  102. package/dist/sdk/__fixtures__/mock-userstorage.d.mts +4 -6
  103. package/dist/sdk/__fixtures__/mock-userstorage.d.mts.map +1 -1
  104. package/dist/sdk/__fixtures__/mock-userstorage.mjs +6 -6
  105. package/dist/sdk/__fixtures__/mock-userstorage.mjs.map +1 -1
  106. package/dist/sdk/authentication-jwt-bearer/flow-siwe.mjs +1 -2
  107. package/dist/sdk/authentication-jwt-bearer/flow-siwe.mjs.map +1 -1
  108. package/dist/sdk/user-storage.cjs +62 -9
  109. package/dist/sdk/user-storage.cjs.map +1 -1
  110. package/dist/sdk/user-storage.d.cts +5 -1
  111. package/dist/sdk/user-storage.d.cts.map +1 -1
  112. package/dist/sdk/user-storage.d.mts +5 -1
  113. package/dist/sdk/user-storage.d.mts.map +1 -1
  114. package/dist/sdk/user-storage.mjs +62 -9
  115. package/dist/sdk/user-storage.mjs.map +1 -1
  116. package/dist/shared/encryption/cache.cjs +12 -14
  117. package/dist/shared/encryption/cache.cjs.map +1 -1
  118. package/dist/shared/encryption/cache.d.cts +4 -3
  119. package/dist/shared/encryption/cache.d.cts.map +1 -1
  120. package/dist/shared/encryption/cache.d.mts +4 -3
  121. package/dist/shared/encryption/cache.d.mts.map +1 -1
  122. package/dist/shared/encryption/cache.mjs +11 -13
  123. package/dist/shared/encryption/cache.mjs.map +1 -1
  124. package/dist/shared/encryption/constants.cjs +18 -0
  125. package/dist/shared/encryption/constants.cjs.map +1 -0
  126. package/dist/shared/encryption/constants.d.cts +8 -0
  127. package/dist/shared/encryption/constants.d.cts.map +1 -0
  128. package/dist/shared/encryption/constants.d.mts +8 -0
  129. package/dist/shared/encryption/constants.d.mts.map +1 -0
  130. package/dist/shared/encryption/constants.mjs +15 -0
  131. package/dist/shared/encryption/constants.mjs.map +1 -0
  132. package/dist/shared/encryption/encryption.cjs +49 -25
  133. package/dist/shared/encryption/encryption.cjs.map +1 -1
  134. package/dist/shared/encryption/encryption.d.cts +2 -0
  135. package/dist/shared/encryption/encryption.d.cts.map +1 -1
  136. package/dist/shared/encryption/encryption.d.mts +2 -0
  137. package/dist/shared/encryption/encryption.d.mts.map +1 -1
  138. package/dist/shared/encryption/encryption.mjs +38 -14
  139. package/dist/shared/encryption/encryption.mjs.map +1 -1
  140. package/package.json +11 -6
@@ -0,0 +1,119 @@
1
+ import { RpcEndpointType } from "@metamask/network-controller";
2
+ import { setDifference } from "../utils.mjs";
3
+ /**
4
+ * Will map the network configuration we want to update into something valid that `updateNetwork` accepts
5
+ * Exported for testability
6
+ *
7
+ * @param props - properties
8
+ * @param props.originalNetworkConfiguration - original network configuration we will override
9
+ * @param props.newNetworkConfiguration - new network configuration
10
+ * @returns NetworkConfiguration to dispatch to `NetworkController:updateNetwork`
11
+ */
12
+ export const getMappedNetworkConfiguration = (props) => {
13
+ const { originalNetworkConfiguration, newNetworkConfiguration } = props;
14
+ // Map of URL <> clientId (url is unique)
15
+ const originalRPCUrlMap = new Map(originalNetworkConfiguration.rpcEndpoints.map((r) => [r.url, r.networkClientId]));
16
+ const updateNetworkConfig = newNetworkConfiguration;
17
+ updateNetworkConfig.rpcEndpoints = updateNetworkConfig.rpcEndpoints.map((r) => {
18
+ const originalRPCClientId = originalRPCUrlMap.get(r.url);
19
+ // This is an existing RPC, so use the clients networkClientId
20
+ if (originalRPCClientId) {
21
+ r.networkClientId = originalRPCClientId;
22
+ return r;
23
+ }
24
+ // This is a new RPC, so remove the remote networkClientId
25
+ r.networkClientId = undefined;
26
+ return r;
27
+ });
28
+ return updateNetworkConfig;
29
+ };
30
+ /**
31
+ * Will insert any missing infura RPCs, as we cannot remove infura RPC
32
+ * Exported for testability
33
+ * @param props - properties
34
+ * @param props.originalNetworkConfiguration - original network configuration
35
+ * @param props.updateNetworkConfiguration - the updated network configuration to use when dispatching `NetworkController:updateNetwork`
36
+ * @returns mutates and returns the updateNetworkConfiguration
37
+ */
38
+ export const appendMissingInfuraNetworks = (props) => {
39
+ const { originalNetworkConfiguration, updateNetworkConfiguration } = props;
40
+ // Ensure we have not removed any infura networks (and add them back if they were removed)
41
+ const origInfuraRPCMap = new Map(originalNetworkConfiguration.rpcEndpoints
42
+ .filter((r) => r.type === RpcEndpointType.Infura)
43
+ .map((r) => [r.networkClientId, r]));
44
+ const newInfuraRPCMap = new Map(updateNetworkConfiguration.rpcEndpoints
45
+ .filter((r) => r.type === RpcEndpointType.Infura && r.networkClientId)
46
+ .map((r) => [r.networkClientId, r]));
47
+ const missingOrigInfuraRPCs = setDifference(new Set(origInfuraRPCMap.keys()), new Set(newInfuraRPCMap.keys()));
48
+ if (missingOrigInfuraRPCs.size > 0) {
49
+ const missingRPCs = [];
50
+ missingOrigInfuraRPCs.forEach((clientId) => {
51
+ missingRPCs.push(origInfuraRPCMap.get(clientId));
52
+ });
53
+ updateNetworkConfiguration.rpcEndpoints.unshift(...missingRPCs);
54
+ }
55
+ return updateNetworkConfiguration;
56
+ };
57
+ /**
58
+ * The `NetworkController:updateNetwork` method will require us to pass in a `replacementSelectedRpcEndpointIndex` if the selected RPC is removed or modified
59
+ * @param props - properties
60
+ * @param props.originalNetworkConfiguration - the original network configuration
61
+ * @param props.updateNetworkConfiguration - the new network configuration we will use to update
62
+ * @param props.selectedNetworkClientId - the NetworkController's selected network id.
63
+ * @returns the new RPC index if it needs modification
64
+ */
65
+ export const getNewRPCIndex = (props) => {
66
+ const { originalNetworkConfiguration, updateNetworkConfiguration, selectedNetworkClientId, } = props;
67
+ const isRPCInNewList = updateNetworkConfiguration.rpcEndpoints.some((r) => r.networkClientId === selectedNetworkClientId);
68
+ const isRPCInOldList = originalNetworkConfiguration.rpcEndpoints.some((r) => r.networkClientId === selectedNetworkClientId);
69
+ const getAnyRPCIndex = () => Math.max(updateNetworkConfiguration.rpcEndpoints.findIndex((r) => Boolean(r.networkClientId)), 0);
70
+ // We have removed the selected RPC, so we must point to a new RPC index
71
+ if (isRPCInOldList && !isRPCInNewList) {
72
+ // Try finding an existing index, or default to first RPC.
73
+ const newIndex = getAnyRPCIndex();
74
+ return newIndex;
75
+ }
76
+ // We have updated the selected RPC, so we must point to the same RPC index (or a new one)
77
+ if (isRPCInOldList && isRPCInNewList) {
78
+ const existingIndex = updateNetworkConfiguration.rpcEndpoints.findIndex((r) => r.networkClientId === selectedNetworkClientId);
79
+ /* istanbul ignore next - the `getAnyRPCIndex` should not be reachable since this is an existing network */
80
+ return existingIndex !== -1 ? existingIndex : getAnyRPCIndex();
81
+ }
82
+ return undefined;
83
+ };
84
+ /**
85
+ * create the correct `NetworkController:updateNetwork` parameters
86
+ * @param props - properties
87
+ * @param props.originalNetworkConfiguration - original config
88
+ * @param props.newNetworkConfiguration - new config (from remote)
89
+ * @param props.selectedNetworkClientId - the current selected network client id
90
+ * @returns parameters to be used for `NetworkController:updateNetwork` call
91
+ */
92
+ export const createUpdateNetworkProps = (props) => {
93
+ const { originalNetworkConfiguration, newNetworkConfiguration, selectedNetworkClientId, } = props;
94
+ // The `NetworkController:updateNetwork` has a strict set of rules to follow
95
+ // New RPCs that we are adding must not have a networkClientId
96
+ // Existing RPCs must point to the correct networkClientId (so we must convert and use this client clientIds set)
97
+ // Removing RPCs are omitted from the list
98
+ // We cannot remove infura RPCs - so ensure that they stay populated
99
+ // If we are removing a selected RPC - then we need to provide `replacementSelectedRpcEndpointIndex` to an index in the new list
100
+ // If we are updating a selected RPC - then we need to provide `replacementSelectedRpcEndpointIndex` to the index in the new list
101
+ const mappedNetworkConfiguration = getMappedNetworkConfiguration({
102
+ originalNetworkConfiguration,
103
+ newNetworkConfiguration,
104
+ });
105
+ appendMissingInfuraNetworks({
106
+ originalNetworkConfiguration,
107
+ updateNetworkConfiguration: mappedNetworkConfiguration,
108
+ });
109
+ const updatedRPCIndex = getNewRPCIndex({
110
+ originalNetworkConfiguration,
111
+ updateNetworkConfiguration: mappedNetworkConfiguration,
112
+ selectedNetworkClientId,
113
+ });
114
+ return {
115
+ updateNetworkFields: mappedNetworkConfiguration,
116
+ newSelectedRpcEndpointIndex: updatedRPCIndex,
117
+ };
118
+ };
119
+ //# sourceMappingURL=update-network-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-network-utils.mjs","sourceRoot":"","sources":["../../../../src/controllers/user-storage/network-syncing/update-network-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n RpcEndpointType,\n type UpdateNetworkFields,\n type NetworkConfiguration,\n} from '@metamask/network-controller';\n\nimport { setDifference } from '../utils';\n\n/**\n * Will map the network configuration we want to update into something valid that `updateNetwork` accepts\n * Exported for testability\n *\n * @param props - properties\n * @param props.originalNetworkConfiguration - original network configuration we will override\n * @param props.newNetworkConfiguration - new network configuration\n * @returns NetworkConfiguration to dispatch to `NetworkController:updateNetwork`\n */\nexport const getMappedNetworkConfiguration = (props: {\n originalNetworkConfiguration: NetworkConfiguration;\n newNetworkConfiguration: NetworkConfiguration;\n}): UpdateNetworkFields => {\n const { originalNetworkConfiguration, newNetworkConfiguration } = props;\n\n // Map of URL <> clientId (url is unique)\n const originalRPCUrlMap = new Map(\n originalNetworkConfiguration.rpcEndpoints.map(\n (r) => [r.url, r.networkClientId] as const,\n ),\n );\n\n const updateNetworkConfig = newNetworkConfiguration as UpdateNetworkFields;\n\n updateNetworkConfig.rpcEndpoints = updateNetworkConfig.rpcEndpoints.map(\n (r) => {\n const originalRPCClientId = originalRPCUrlMap.get(r.url);\n\n // This is an existing RPC, so use the clients networkClientId\n if (originalRPCClientId) {\n r.networkClientId = originalRPCClientId;\n return r;\n }\n\n // This is a new RPC, so remove the remote networkClientId\n r.networkClientId = undefined;\n return r;\n },\n );\n\n return updateNetworkConfig;\n};\n\n/**\n * Will insert any missing infura RPCs, as we cannot remove infura RPC\n * Exported for testability\n * @param props - properties\n * @param props.originalNetworkConfiguration - original network configuration\n * @param props.updateNetworkConfiguration - the updated network configuration to use when dispatching `NetworkController:updateNetwork`\n * @returns mutates and returns the updateNetworkConfiguration\n */\nexport const appendMissingInfuraNetworks = (props: {\n originalNetworkConfiguration: NetworkConfiguration;\n updateNetworkConfiguration: UpdateNetworkFields;\n}) => {\n const { originalNetworkConfiguration, updateNetworkConfiguration } = props;\n\n // Ensure we have not removed any infura networks (and add them back if they were removed)\n const origInfuraRPCMap = new Map(\n originalNetworkConfiguration.rpcEndpoints\n .filter((r) => r.type === RpcEndpointType.Infura)\n .map((r) => [r.networkClientId, r] as const),\n );\n const newInfuraRPCMap = new Map(\n updateNetworkConfiguration.rpcEndpoints\n .filter((r) => r.type === RpcEndpointType.Infura && r.networkClientId)\n .map((r) => [r.networkClientId as string, r]),\n );\n const missingOrigInfuraRPCs = setDifference(\n new Set(origInfuraRPCMap.keys()),\n new Set(newInfuraRPCMap.keys()),\n );\n\n if (missingOrigInfuraRPCs.size > 0) {\n const missingRPCs: UpdateNetworkFields['rpcEndpoints'] = [];\n missingOrigInfuraRPCs.forEach((clientId) => {\n missingRPCs.push(\n origInfuraRPCMap.get(\n clientId,\n ) as UpdateNetworkFields['rpcEndpoints'][number],\n );\n });\n\n updateNetworkConfiguration.rpcEndpoints.unshift(...missingRPCs);\n }\n\n return updateNetworkConfiguration;\n};\n\n/**\n * The `NetworkController:updateNetwork` method will require us to pass in a `replacementSelectedRpcEndpointIndex` if the selected RPC is removed or modified\n * @param props - properties\n * @param props.originalNetworkConfiguration - the original network configuration\n * @param props.updateNetworkConfiguration - the new network configuration we will use to update\n * @param props.selectedNetworkClientId - the NetworkController's selected network id.\n * @returns the new RPC index if it needs modification\n */\nexport const getNewRPCIndex = (props: {\n originalNetworkConfiguration: NetworkConfiguration;\n updateNetworkConfiguration: UpdateNetworkFields;\n selectedNetworkClientId: string;\n}) => {\n const {\n originalNetworkConfiguration,\n updateNetworkConfiguration,\n selectedNetworkClientId,\n } = props;\n\n const isRPCInNewList = updateNetworkConfiguration.rpcEndpoints.some(\n (r) => r.networkClientId === selectedNetworkClientId,\n );\n const isRPCInOldList = originalNetworkConfiguration.rpcEndpoints.some(\n (r) => r.networkClientId === selectedNetworkClientId,\n );\n\n const getAnyRPCIndex = () =>\n Math.max(\n updateNetworkConfiguration.rpcEndpoints.findIndex((r) =>\n Boolean(r.networkClientId),\n ),\n 0,\n );\n\n // We have removed the selected RPC, so we must point to a new RPC index\n if (isRPCInOldList && !isRPCInNewList) {\n // Try finding an existing index, or default to first RPC.\n const newIndex = getAnyRPCIndex();\n return newIndex;\n }\n\n // We have updated the selected RPC, so we must point to the same RPC index (or a new one)\n if (isRPCInOldList && isRPCInNewList) {\n const existingIndex = updateNetworkConfiguration.rpcEndpoints.findIndex(\n (r) => r.networkClientId === selectedNetworkClientId,\n );\n /* istanbul ignore next - the `getAnyRPCIndex` should not be reachable since this is an existing network */\n return existingIndex !== -1 ? existingIndex : getAnyRPCIndex();\n }\n\n return undefined;\n};\n\n/**\n * create the correct `NetworkController:updateNetwork` parameters\n * @param props - properties\n * @param props.originalNetworkConfiguration - original config\n * @param props.newNetworkConfiguration - new config (from remote)\n * @param props.selectedNetworkClientId - the current selected network client id\n * @returns parameters to be used for `NetworkController:updateNetwork` call\n */\nexport const createUpdateNetworkProps = (props: {\n originalNetworkConfiguration: NetworkConfiguration;\n newNetworkConfiguration: NetworkConfiguration;\n selectedNetworkClientId: string;\n}) => {\n const {\n originalNetworkConfiguration,\n newNetworkConfiguration,\n selectedNetworkClientId,\n } = props;\n\n // The `NetworkController:updateNetwork` has a strict set of rules to follow\n // New RPCs that we are adding must not have a networkClientId\n // Existing RPCs must point to the correct networkClientId (so we must convert and use this client clientIds set)\n // Removing RPCs are omitted from the list\n // We cannot remove infura RPCs - so ensure that they stay populated\n // If we are removing a selected RPC - then we need to provide `replacementSelectedRpcEndpointIndex` to an index in the new list\n // If we are updating a selected RPC - then we need to provide `replacementSelectedRpcEndpointIndex` to the index in the new list\n\n const mappedNetworkConfiguration = getMappedNetworkConfiguration({\n originalNetworkConfiguration,\n newNetworkConfiguration,\n });\n\n appendMissingInfuraNetworks({\n originalNetworkConfiguration,\n updateNetworkConfiguration: mappedNetworkConfiguration,\n });\n\n const updatedRPCIndex = getNewRPCIndex({\n originalNetworkConfiguration,\n updateNetworkConfiguration: mappedNetworkConfiguration,\n selectedNetworkClientId,\n });\n\n return {\n updateNetworkFields: mappedNetworkConfiguration,\n newSelectedRpcEndpointIndex: updatedRPCIndex,\n };\n};\n"]}
@@ -26,9 +26,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.deleteUserStorageAllFeatureEntries = exports.batchDeleteUserStorage = exports.deleteUserStorage = exports.batchUpsertUserStorage = exports.upsertUserStorage = exports.getUserStorageAllFeatureEntries = exports.getUserStorage = exports.USER_STORAGE_ENDPOINT = exports.USER_STORAGE_API = void 0;
29
+ exports.deleteUserStorageAllFeatureEntries = exports.batchDeleteUserStorage = exports.deleteUserStorage = exports.batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries = exports.batchUpsertUserStorage = exports.upsertUserStorage = exports.getUserStorageAllFeatureEntries = exports.getUserStorage = exports.USER_STORAGE_ENDPOINT = exports.USER_STORAGE_API = void 0;
30
30
  const loglevel_1 = __importDefault(require("loglevel"));
31
31
  const encryption_1 = __importStar(require("../../shared/encryption/index.cjs"));
32
+ const constants_1 = require("../../shared/encryption/constants.cjs");
32
33
  const env_1 = require("../../shared/env.cjs");
33
34
  const storage_schema_1 = require("../../shared/storage-schema.cjs");
34
35
  const ENV_URLS = (0, env_1.getEnvUrls)(env_1.Env.PRD);
@@ -60,10 +61,16 @@ async function getUserStorage(opts) {
60
61
  }
61
62
  const userStorage = await userStorageResponse.json();
62
63
  const encryptedData = userStorage?.Data ?? null;
64
+ /* istanbul ignore if - this is an edge case where our endpoint returns invalid JSON payload */
63
65
  if (!encryptedData) {
64
66
  return null;
65
67
  }
66
68
  const decryptedData = await encryption_1.default.decryptString(encryptedData, opts.storageKey, nativeScryptCrypto);
69
+ // Re-encrypt and re-upload the entry if the salt is random
70
+ const salt = encryption_1.default.getSalt(encryptedData);
71
+ if (salt.toString() !== constants_1.SHARED_SALT.toString()) {
72
+ await upsertUserStorage(decryptedData, opts);
73
+ }
67
74
  return decryptedData;
68
75
  }
69
76
  catch (e) {
@@ -100,18 +107,32 @@ async function getUserStorageAllFeatureEntries(opts) {
100
107
  return null;
101
108
  }
102
109
  const decryptedData = [];
110
+ const reEncryptedEntries = [];
103
111
  for (const entry of userStorage) {
112
+ /* istanbul ignore if - unreachable if statement, but kept as edge case */
104
113
  if (!entry.Data) {
105
114
  continue;
106
115
  }
107
116
  try {
108
117
  const data = await encryption_1.default.decryptString(entry.Data, opts.storageKey, nativeScryptCrypto);
109
118
  decryptedData.push(data);
119
+ // Re-encrypt the entry if the salt is different from the shared one
120
+ const salt = encryption_1.default.getSalt(entry.Data);
121
+ if (salt.toString() !== constants_1.SHARED_SALT.toString()) {
122
+ reEncryptedEntries.push([
123
+ entry.HashedKey,
124
+ await encryption_1.default.encryptString(data, opts.storageKey, nativeScryptCrypto),
125
+ ]);
126
+ }
110
127
  }
111
128
  catch {
112
129
  // do nothing
113
130
  }
114
131
  }
132
+ // Re-upload the re-encrypted entries
133
+ if (reEncryptedEntries.length) {
134
+ await batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(reEncryptedEntries, opts);
135
+ }
115
136
  return decryptedData;
116
137
  }
117
138
  catch (e) {
@@ -178,6 +199,33 @@ async function batchUpsertUserStorage(data, opts) {
178
199
  }
179
200
  }
180
201
  exports.batchUpsertUserStorage = batchUpsertUserStorage;
202
+ /**
203
+ * User Storage Service - Set multiple storage entries for one specific feature.
204
+ * You cannot use this method to set multiple features at once.
205
+ *
206
+ * @param encryptedData - data to store, in the form of an array of [hashedKey, encryptedData] pairs
207
+ * @param opts - storage options
208
+ */
209
+ async function batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(encryptedData, opts) {
210
+ if (!encryptedData.length) {
211
+ return;
212
+ }
213
+ const { bearerToken, path } = opts;
214
+ const url = new URL(`${exports.USER_STORAGE_ENDPOINT}/${path}`);
215
+ const formattedData = Object.fromEntries(encryptedData);
216
+ const res = await fetch(url.toString(), {
217
+ method: 'PUT',
218
+ headers: {
219
+ 'Content-Type': 'application/json',
220
+ Authorization: `Bearer ${bearerToken}`,
221
+ },
222
+ body: JSON.stringify({ data: formattedData }),
223
+ });
224
+ if (!res.ok) {
225
+ throw new Error('user-storage - unable to batch upsert data');
226
+ }
227
+ }
228
+ exports.batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries = batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries;
181
229
  /**
182
230
  * User Storage Service - Delete Storage Entry.
183
231
  *
@@ -1 +1 @@
1
- {"version":3,"file":"services.cjs","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"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","sourcesContent":["import log from 'loglevel';\n\nimport encryption, { createSHA256Hash } from '../../shared/encryption';\nimport { Env, getEnvUrls } from '../../shared/env';\nimport type {\n UserStoragePathWithFeatureAndKey,\n UserStoragePathWithFeatureOnly,\n} from '../../shared/storage-schema';\nimport { createEntryPath } from '../../shared/storage-schema';\nimport type { NativeScrypt } from '../../shared/types/encryption';\n\nconst ENV_URLS = getEnvUrls(Env.PRD);\n\nexport const USER_STORAGE_API: string = ENV_URLS.userStorageApiUrl;\nexport const USER_STORAGE_ENDPOINT = `${USER_STORAGE_API}/api/v1/userstorage`;\n\n/**\n * This is the Server Response shape for a feature entry.\n */\nexport type GetUserStorageResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n};\n\nexport type GetUserStorageAllFeatureEntriesResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n}[];\n\nexport type UserStorageBaseOptions = {\n bearerToken: string;\n storageKey: string;\n nativeScryptCrypto?: NativeScrypt;\n};\n\nexport type UserStorageOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureAndKey;\n};\n\nexport type UserStorageAllFeatureEntriesOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureOnly;\n};\n\nexport type UserStorageBatchUpsertOptions = UserStorageAllFeatureEntriesOptions;\n\n/**\n * User Storage Service - Get Storage Entry.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorage(\n opts: UserStorageOptions,\n): Promise<string | null> {\n try {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates entry does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageResponse | null =\n await userStorageResponse.json();\n const encryptedData = userStorage?.Data ?? null;\n\n if (!encryptedData) {\n return null;\n }\n\n const decryptedData = await encryption.decryptString(\n encryptedData,\n opts.storageKey,\n nativeScryptCrypto,\n );\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Get all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<string[] | null> {\n try {\n const { bearerToken, path, nativeScryptCrypto } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates feature does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageAllFeatureEntriesResponse | null =\n await userStorageResponse.json();\n\n if (!Array.isArray(userStorage)) {\n return null;\n }\n\n const decryptedData: string[] = [];\n\n for (const entry of userStorage) {\n if (!entry.Data) {\n continue;\n }\n\n try {\n const data = await encryption.decryptString(\n entry.Data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n decryptedData.push(data);\n } catch {\n // do nothing\n }\n }\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Set Storage Entry.\n *\n * @param data - data to store\n * @param opts - storage options\n */\nexport async function upsertUserStorage(\n data: string,\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData = await encryption.encryptString(\n data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to upsert data');\n }\n}\n\n/**\n * User Storage Service - Set multiple storage entries for one specific feature.\n * You cannot use this method to set multiple features at once.\n *\n * @param data - data to store, in the form of an array of [entryKey, entryValue] pairs\n * @param opts - storage options\n */\nexport async function batchUpsertUserStorage(\n data: [string, string][],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData: string[][] = [];\n\n for (const d of data) {\n encryptedData.push([\n createSHA256Hash(d[0] + storageKey),\n await encryption.encryptString(d[1], opts.storageKey, nativeScryptCrypto),\n ]);\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const formattedData = Object.fromEntries(encryptedData);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: formattedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch upsert data');\n }\n}\n\n/**\n * User Storage Service - Delete Storage Entry.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorage(\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey } = opts;\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature/entry not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n\n/**\n * User Storage Service - Delete multiple storage entries for one specific feature.\n * You cannot use this method to delete multiple features at once.\n *\n * @param data - data to delete, in the form of an array entryKey[]\n * @param opts - storage options\n */\nexport async function batchDeleteUserStorage(\n data: string[],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey } = opts;\n\n const encryptedData: string[] = [];\n\n for (const d of data) {\n encryptedData.push(createSHA256Hash(d + storageKey));\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n // eslint-disable-next-line @typescript-eslint/naming-convention\n body: JSON.stringify({ batch_delete: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch delete data');\n }\n}\n\n/**\n * User Storage Service - Delete all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<void> {\n const { bearerToken, path } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n"]}
1
+ {"version":3,"file":"services.cjs","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"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","sourcesContent":["import log from 'loglevel';\n\nimport encryption, { createSHA256Hash } from '../../shared/encryption';\nimport { SHARED_SALT } from '../../shared/encryption/constants';\nimport { Env, getEnvUrls } from '../../shared/env';\nimport type {\n UserStoragePathWithFeatureAndKey,\n UserStoragePathWithFeatureOnly,\n} from '../../shared/storage-schema';\nimport { createEntryPath } from '../../shared/storage-schema';\nimport type { NativeScrypt } from '../../shared/types/encryption';\n\nconst ENV_URLS = getEnvUrls(Env.PRD);\n\nexport const USER_STORAGE_API: string = ENV_URLS.userStorageApiUrl;\nexport const USER_STORAGE_ENDPOINT = `${USER_STORAGE_API}/api/v1/userstorage`;\n\n/**\n * This is the Server Response shape for a feature entry.\n */\nexport type GetUserStorageResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n};\n\nexport type GetUserStorageAllFeatureEntriesResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n}[];\n\nexport type UserStorageBaseOptions = {\n bearerToken: string;\n storageKey: string;\n nativeScryptCrypto?: NativeScrypt;\n};\n\nexport type UserStorageOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureAndKey;\n};\n\nexport type UserStorageAllFeatureEntriesOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureOnly;\n};\n\nexport type UserStorageBatchUpsertOptions = UserStorageAllFeatureEntriesOptions;\n\n/**\n * User Storage Service - Get Storage Entry.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorage(\n opts: UserStorageOptions,\n): Promise<string | null> {\n try {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates entry does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageResponse | null =\n await userStorageResponse.json();\n const encryptedData = userStorage?.Data ?? null;\n\n /* istanbul ignore if - this is an edge case where our endpoint returns invalid JSON payload */\n if (!encryptedData) {\n return null;\n }\n\n const decryptedData = await encryption.decryptString(\n encryptedData,\n opts.storageKey,\n nativeScryptCrypto,\n );\n\n // Re-encrypt and re-upload the entry if the salt is random\n const salt = encryption.getSalt(encryptedData);\n if (salt.toString() !== SHARED_SALT.toString()) {\n await upsertUserStorage(decryptedData, opts);\n }\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Get all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<string[] | null> {\n try {\n const { bearerToken, path, nativeScryptCrypto } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates feature does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageAllFeatureEntriesResponse | null =\n await userStorageResponse.json();\n\n if (!Array.isArray(userStorage)) {\n return null;\n }\n\n const decryptedData: string[] = [];\n const reEncryptedEntries: [string, string][] = [];\n\n for (const entry of userStorage) {\n /* istanbul ignore if - unreachable if statement, but kept as edge case */\n if (!entry.Data) {\n continue;\n }\n\n try {\n const data = await encryption.decryptString(\n entry.Data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n decryptedData.push(data);\n\n // Re-encrypt the entry if the salt is different from the shared one\n const salt = encryption.getSalt(entry.Data);\n if (salt.toString() !== SHARED_SALT.toString()) {\n reEncryptedEntries.push([\n entry.HashedKey,\n await encryption.encryptString(\n data,\n opts.storageKey,\n nativeScryptCrypto,\n ),\n ]);\n }\n } catch {\n // do nothing\n }\n }\n\n // Re-upload the re-encrypted entries\n if (reEncryptedEntries.length) {\n await batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(\n reEncryptedEntries,\n opts,\n );\n }\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Set Storage Entry.\n *\n * @param data - data to store\n * @param opts - storage options\n */\nexport async function upsertUserStorage(\n data: string,\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData = await encryption.encryptString(\n data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to upsert data');\n }\n}\n\n/**\n * User Storage Service - Set multiple storage entries for one specific feature.\n * You cannot use this method to set multiple features at once.\n *\n * @param data - data to store, in the form of an array of [entryKey, entryValue] pairs\n * @param opts - storage options\n */\nexport async function batchUpsertUserStorage(\n data: [string, string][],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData: string[][] = [];\n\n for (const d of data) {\n encryptedData.push([\n createSHA256Hash(d[0] + storageKey),\n await encryption.encryptString(d[1], opts.storageKey, nativeScryptCrypto),\n ]);\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const formattedData = Object.fromEntries(encryptedData);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: formattedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch upsert data');\n }\n}\n\n/**\n * User Storage Service - Set multiple storage entries for one specific feature.\n * You cannot use this method to set multiple features at once.\n *\n * @param encryptedData - data to store, in the form of an array of [hashedKey, encryptedData] pairs\n * @param opts - storage options\n */\nexport async function batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(\n encryptedData: [string, string][],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!encryptedData.length) {\n return;\n }\n\n const { bearerToken, path } = opts;\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const formattedData = Object.fromEntries(encryptedData);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: formattedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch upsert data');\n }\n}\n\n/**\n * User Storage Service - Delete Storage Entry.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorage(\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey } = opts;\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature/entry not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n\n/**\n * User Storage Service - Delete multiple storage entries for one specific feature.\n * You cannot use this method to delete multiple features at once.\n *\n * @param data - data to delete, in the form of an array entryKey[]\n * @param opts - storage options\n */\nexport async function batchDeleteUserStorage(\n data: string[],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey } = opts;\n\n const encryptedData: string[] = [];\n\n for (const d of data) {\n encryptedData.push(createSHA256Hash(d + storageKey));\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n // eslint-disable-next-line @typescript-eslint/naming-convention\n body: JSON.stringify({ batch_delete: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch delete data');\n }\n}\n\n/**\n * User Storage Service - Delete all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<void> {\n const { bearerToken, path } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n"]}
@@ -54,6 +54,14 @@ export declare function upsertUserStorage(data: string, opts: UserStorageOptions
54
54
  * @param opts - storage options
55
55
  */
56
56
  export declare function batchUpsertUserStorage(data: [string, string][], opts: UserStorageBatchUpsertOptions): Promise<void>;
57
+ /**
58
+ * User Storage Service - Set multiple storage entries for one specific feature.
59
+ * You cannot use this method to set multiple features at once.
60
+ *
61
+ * @param encryptedData - data to store, in the form of an array of [hashedKey, encryptedData] pairs
62
+ * @param opts - storage options
63
+ */
64
+ export declare function batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(encryptedData: [string, string][], opts: UserStorageBatchUpsertOptions): Promise<void>;
57
65
  /**
58
66
  * User Storage Service - Delete Storage Entry.
59
67
  *
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.cts","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gCAAgC,EAChC,8BAA8B,EAC/B,wCAAoC;AAErC,OAAO,KAAK,EAAE,YAAY,EAAE,0CAAsC;AAIlE,eAAO,MAAM,gBAAgB,EAAE,MAAmC,CAAC;AACnE,eAAO,MAAM,qBAAqB,QAA2C,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IAEnC,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uCAAuC,GAAG;IAEpD,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,EAAE,CAAC;AAEJ,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,YAAY,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GAAG;IACxD,IAAI,EAAE,gCAAgC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG,sBAAsB,GAAG;IACzE,IAAI,EAAE,8BAA8B,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,mCAAmC,CAAC;AAEhF;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0CxB;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAoD1B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACxB,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AACH,wBAAsB,kCAAkC,CACtD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,IAAI,CAAC,CAmBf"}
1
+ {"version":3,"file":"services.d.cts","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,gCAAgC,EAChC,8BAA8B,EAC/B,wCAAoC;AAErC,OAAO,KAAK,EAAE,YAAY,EAAE,0CAAsC;AAIlE,eAAO,MAAM,gBAAgB,EAAE,MAAmC,CAAC;AACnE,eAAO,MAAM,qBAAqB,QAA2C,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IAEnC,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uCAAuC,GAAG;IAEpD,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,EAAE,CAAC;AAEJ,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,YAAY,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GAAG;IACxD,IAAI,EAAE,gCAAgC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG,sBAAsB,GAAG;IACzE,IAAI,EAAE,8BAA8B,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,mCAAmC,CAAC;AAEhF;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiDxB;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CA2E1B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACxB,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED;;;;;;GAMG;AACH,wBAAsB,0DAA0D,CAC9E,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACjC,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AACH,wBAAsB,kCAAkC,CACtD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,IAAI,CAAC,CAmBf"}
@@ -54,6 +54,14 @@ export declare function upsertUserStorage(data: string, opts: UserStorageOptions
54
54
  * @param opts - storage options
55
55
  */
56
56
  export declare function batchUpsertUserStorage(data: [string, string][], opts: UserStorageBatchUpsertOptions): Promise<void>;
57
+ /**
58
+ * User Storage Service - Set multiple storage entries for one specific feature.
59
+ * You cannot use this method to set multiple features at once.
60
+ *
61
+ * @param encryptedData - data to store, in the form of an array of [hashedKey, encryptedData] pairs
62
+ * @param opts - storage options
63
+ */
64
+ export declare function batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(encryptedData: [string, string][], opts: UserStorageBatchUpsertOptions): Promise<void>;
57
65
  /**
58
66
  * User Storage Service - Delete Storage Entry.
59
67
  *
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.mts","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gCAAgC,EAChC,8BAA8B,EAC/B,wCAAoC;AAErC,OAAO,KAAK,EAAE,YAAY,EAAE,0CAAsC;AAIlE,eAAO,MAAM,gBAAgB,EAAE,MAAmC,CAAC;AACnE,eAAO,MAAM,qBAAqB,QAA2C,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IAEnC,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uCAAuC,GAAG;IAEpD,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,EAAE,CAAC;AAEJ,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,YAAY,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GAAG;IACxD,IAAI,EAAE,gCAAgC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG,sBAAsB,GAAG;IACzE,IAAI,EAAE,8BAA8B,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,mCAAmC,CAAC;AAEhF;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0CxB;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAoD1B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACxB,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AACH,wBAAsB,kCAAkC,CACtD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,IAAI,CAAC,CAmBf"}
1
+ {"version":3,"file":"services.d.mts","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,gCAAgC,EAChC,8BAA8B,EAC/B,wCAAoC;AAErC,OAAO,KAAK,EAAE,YAAY,EAAE,0CAAsC;AAIlE,eAAO,MAAM,gBAAgB,EAAE,MAAmC,CAAC;AACnE,eAAO,MAAM,qBAAqB,QAA2C,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IAEnC,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uCAAuC,GAAG;IAEpD,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACd,EAAE,CAAC;AAEJ,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,YAAY,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GAAG;IACxD,IAAI,EAAE,gCAAgC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG,sBAAsB,GAAG;IACzE,IAAI,EAAE,8BAA8B,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,mCAAmC,CAAC;AAEhF;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiDxB;AAED;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CA2E1B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACxB,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED;;;;;;GAMG;AACH,wBAAsB,0DAA0D,CAC9E,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACjC,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AACH,wBAAsB,kCAAkC,CACtD,IAAI,EAAE,mCAAmC,GACxC,OAAO,CAAC,IAAI,CAAC,CAmBf"}
@@ -7,6 +7,7 @@ function $importDefault(module) {
7
7
  import $log from "loglevel";
8
8
  const log = $importDefault($log);
9
9
  import encryption, { createSHA256Hash } from "../../shared/encryption/index.mjs";
10
+ import { SHARED_SALT } from "../../shared/encryption/constants.mjs";
10
11
  import { Env, getEnvUrls } from "../../shared/env.mjs";
11
12
  import { createEntryPath } from "../../shared/storage-schema.mjs";
12
13
  const ENV_URLS = getEnvUrls(Env.PRD);
@@ -38,10 +39,16 @@ export async function getUserStorage(opts) {
38
39
  }
39
40
  const userStorage = await userStorageResponse.json();
40
41
  const encryptedData = userStorage?.Data ?? null;
42
+ /* istanbul ignore if - this is an edge case where our endpoint returns invalid JSON payload */
41
43
  if (!encryptedData) {
42
44
  return null;
43
45
  }
44
46
  const decryptedData = await encryption.decryptString(encryptedData, opts.storageKey, nativeScryptCrypto);
47
+ // Re-encrypt and re-upload the entry if the salt is random
48
+ const salt = encryption.getSalt(encryptedData);
49
+ if (salt.toString() !== SHARED_SALT.toString()) {
50
+ await upsertUserStorage(decryptedData, opts);
51
+ }
45
52
  return decryptedData;
46
53
  }
47
54
  catch (e) {
@@ -77,18 +84,32 @@ export async function getUserStorageAllFeatureEntries(opts) {
77
84
  return null;
78
85
  }
79
86
  const decryptedData = [];
87
+ const reEncryptedEntries = [];
80
88
  for (const entry of userStorage) {
89
+ /* istanbul ignore if - unreachable if statement, but kept as edge case */
81
90
  if (!entry.Data) {
82
91
  continue;
83
92
  }
84
93
  try {
85
94
  const data = await encryption.decryptString(entry.Data, opts.storageKey, nativeScryptCrypto);
86
95
  decryptedData.push(data);
96
+ // Re-encrypt the entry if the salt is different from the shared one
97
+ const salt = encryption.getSalt(entry.Data);
98
+ if (salt.toString() !== SHARED_SALT.toString()) {
99
+ reEncryptedEntries.push([
100
+ entry.HashedKey,
101
+ await encryption.encryptString(data, opts.storageKey, nativeScryptCrypto),
102
+ ]);
103
+ }
87
104
  }
88
105
  catch {
89
106
  // do nothing
90
107
  }
91
108
  }
109
+ // Re-upload the re-encrypted entries
110
+ if (reEncryptedEntries.length) {
111
+ await batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(reEncryptedEntries, opts);
112
+ }
92
113
  return decryptedData;
93
114
  }
94
115
  catch (e) {
@@ -152,6 +173,32 @@ export async function batchUpsertUserStorage(data, opts) {
152
173
  throw new Error('user-storage - unable to batch upsert data');
153
174
  }
154
175
  }
176
+ /**
177
+ * User Storage Service - Set multiple storage entries for one specific feature.
178
+ * You cannot use this method to set multiple features at once.
179
+ *
180
+ * @param encryptedData - data to store, in the form of an array of [hashedKey, encryptedData] pairs
181
+ * @param opts - storage options
182
+ */
183
+ export async function batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(encryptedData, opts) {
184
+ if (!encryptedData.length) {
185
+ return;
186
+ }
187
+ const { bearerToken, path } = opts;
188
+ const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);
189
+ const formattedData = Object.fromEntries(encryptedData);
190
+ const res = await fetch(url.toString(), {
191
+ method: 'PUT',
192
+ headers: {
193
+ 'Content-Type': 'application/json',
194
+ Authorization: `Bearer ${bearerToken}`,
195
+ },
196
+ body: JSON.stringify({ data: formattedData }),
197
+ });
198
+ if (!res.ok) {
199
+ throw new Error('user-storage - unable to batch upsert data');
200
+ }
201
+ }
155
202
  /**
156
203
  * User Storage Service - Delete Storage Entry.
157
204
  *
@@ -1 +1 @@
1
- {"version":3,"file":"services.mjs","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"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","sourcesContent":["import log from 'loglevel';\n\nimport encryption, { createSHA256Hash } from '../../shared/encryption';\nimport { Env, getEnvUrls } from '../../shared/env';\nimport type {\n UserStoragePathWithFeatureAndKey,\n UserStoragePathWithFeatureOnly,\n} from '../../shared/storage-schema';\nimport { createEntryPath } from '../../shared/storage-schema';\nimport type { NativeScrypt } from '../../shared/types/encryption';\n\nconst ENV_URLS = getEnvUrls(Env.PRD);\n\nexport const USER_STORAGE_API: string = ENV_URLS.userStorageApiUrl;\nexport const USER_STORAGE_ENDPOINT = `${USER_STORAGE_API}/api/v1/userstorage`;\n\n/**\n * This is the Server Response shape for a feature entry.\n */\nexport type GetUserStorageResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n};\n\nexport type GetUserStorageAllFeatureEntriesResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n}[];\n\nexport type UserStorageBaseOptions = {\n bearerToken: string;\n storageKey: string;\n nativeScryptCrypto?: NativeScrypt;\n};\n\nexport type UserStorageOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureAndKey;\n};\n\nexport type UserStorageAllFeatureEntriesOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureOnly;\n};\n\nexport type UserStorageBatchUpsertOptions = UserStorageAllFeatureEntriesOptions;\n\n/**\n * User Storage Service - Get Storage Entry.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorage(\n opts: UserStorageOptions,\n): Promise<string | null> {\n try {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates entry does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageResponse | null =\n await userStorageResponse.json();\n const encryptedData = userStorage?.Data ?? null;\n\n if (!encryptedData) {\n return null;\n }\n\n const decryptedData = await encryption.decryptString(\n encryptedData,\n opts.storageKey,\n nativeScryptCrypto,\n );\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Get all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<string[] | null> {\n try {\n const { bearerToken, path, nativeScryptCrypto } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates feature does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageAllFeatureEntriesResponse | null =\n await userStorageResponse.json();\n\n if (!Array.isArray(userStorage)) {\n return null;\n }\n\n const decryptedData: string[] = [];\n\n for (const entry of userStorage) {\n if (!entry.Data) {\n continue;\n }\n\n try {\n const data = await encryption.decryptString(\n entry.Data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n decryptedData.push(data);\n } catch {\n // do nothing\n }\n }\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Set Storage Entry.\n *\n * @param data - data to store\n * @param opts - storage options\n */\nexport async function upsertUserStorage(\n data: string,\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData = await encryption.encryptString(\n data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to upsert data');\n }\n}\n\n/**\n * User Storage Service - Set multiple storage entries for one specific feature.\n * You cannot use this method to set multiple features at once.\n *\n * @param data - data to store, in the form of an array of [entryKey, entryValue] pairs\n * @param opts - storage options\n */\nexport async function batchUpsertUserStorage(\n data: [string, string][],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData: string[][] = [];\n\n for (const d of data) {\n encryptedData.push([\n createSHA256Hash(d[0] + storageKey),\n await encryption.encryptString(d[1], opts.storageKey, nativeScryptCrypto),\n ]);\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const formattedData = Object.fromEntries(encryptedData);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: formattedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch upsert data');\n }\n}\n\n/**\n * User Storage Service - Delete Storage Entry.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorage(\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey } = opts;\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature/entry not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n\n/**\n * User Storage Service - Delete multiple storage entries for one specific feature.\n * You cannot use this method to delete multiple features at once.\n *\n * @param data - data to delete, in the form of an array entryKey[]\n * @param opts - storage options\n */\nexport async function batchDeleteUserStorage(\n data: string[],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey } = opts;\n\n const encryptedData: string[] = [];\n\n for (const d of data) {\n encryptedData.push(createSHA256Hash(d + storageKey));\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n // eslint-disable-next-line @typescript-eslint/naming-convention\n body: JSON.stringify({ batch_delete: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch delete data');\n }\n}\n\n/**\n * User Storage Service - Delete all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<void> {\n const { bearerToken, path } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n"]}
1
+ {"version":3,"file":"services.mjs","sourceRoot":"","sources":["../../../src/controllers/user-storage/services.ts"],"names":[],"mappings":"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","sourcesContent":["import log from 'loglevel';\n\nimport encryption, { createSHA256Hash } from '../../shared/encryption';\nimport { SHARED_SALT } from '../../shared/encryption/constants';\nimport { Env, getEnvUrls } from '../../shared/env';\nimport type {\n UserStoragePathWithFeatureAndKey,\n UserStoragePathWithFeatureOnly,\n} from '../../shared/storage-schema';\nimport { createEntryPath } from '../../shared/storage-schema';\nimport type { NativeScrypt } from '../../shared/types/encryption';\n\nconst ENV_URLS = getEnvUrls(Env.PRD);\n\nexport const USER_STORAGE_API: string = ENV_URLS.userStorageApiUrl;\nexport const USER_STORAGE_ENDPOINT = `${USER_STORAGE_API}/api/v1/userstorage`;\n\n/**\n * This is the Server Response shape for a feature entry.\n */\nexport type GetUserStorageResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n};\n\nexport type GetUserStorageAllFeatureEntriesResponse = {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n HashedKey: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n Data: string;\n}[];\n\nexport type UserStorageBaseOptions = {\n bearerToken: string;\n storageKey: string;\n nativeScryptCrypto?: NativeScrypt;\n};\n\nexport type UserStorageOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureAndKey;\n};\n\nexport type UserStorageAllFeatureEntriesOptions = UserStorageBaseOptions & {\n path: UserStoragePathWithFeatureOnly;\n};\n\nexport type UserStorageBatchUpsertOptions = UserStorageAllFeatureEntriesOptions;\n\n/**\n * User Storage Service - Get Storage Entry.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorage(\n opts: UserStorageOptions,\n): Promise<string | null> {\n try {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates entry does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageResponse | null =\n await userStorageResponse.json();\n const encryptedData = userStorage?.Data ?? null;\n\n /* istanbul ignore if - this is an edge case where our endpoint returns invalid JSON payload */\n if (!encryptedData) {\n return null;\n }\n\n const decryptedData = await encryption.decryptString(\n encryptedData,\n opts.storageKey,\n nativeScryptCrypto,\n );\n\n // Re-encrypt and re-upload the entry if the salt is random\n const salt = encryption.getSalt(encryptedData);\n if (salt.toString() !== SHARED_SALT.toString()) {\n await upsertUserStorage(decryptedData, opts);\n }\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Get all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n * @returns The storage entry, or null if fails to find entry\n */\nexport async function getUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<string[] | null> {\n try {\n const { bearerToken, path, nativeScryptCrypto } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n // Acceptable error - since indicates feature does not exist.\n if (userStorageResponse.status === 404) {\n return null;\n }\n\n if (userStorageResponse.status !== 200) {\n throw new Error('Unable to get User Storage');\n }\n\n const userStorage: GetUserStorageAllFeatureEntriesResponse | null =\n await userStorageResponse.json();\n\n if (!Array.isArray(userStorage)) {\n return null;\n }\n\n const decryptedData: string[] = [];\n const reEncryptedEntries: [string, string][] = [];\n\n for (const entry of userStorage) {\n /* istanbul ignore if - unreachable if statement, but kept as edge case */\n if (!entry.Data) {\n continue;\n }\n\n try {\n const data = await encryption.decryptString(\n entry.Data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n decryptedData.push(data);\n\n // Re-encrypt the entry if the salt is different from the shared one\n const salt = encryption.getSalt(entry.Data);\n if (salt.toString() !== SHARED_SALT.toString()) {\n reEncryptedEntries.push([\n entry.HashedKey,\n await encryption.encryptString(\n data,\n opts.storageKey,\n nativeScryptCrypto,\n ),\n ]);\n }\n } catch {\n // do nothing\n }\n }\n\n // Re-upload the re-encrypted entries\n if (reEncryptedEntries.length) {\n await batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(\n reEncryptedEntries,\n opts,\n );\n }\n\n return decryptedData;\n } catch (e) {\n log.error('Failed to get user storage', e);\n return null;\n }\n}\n\n/**\n * User Storage Service - Set Storage Entry.\n *\n * @param data - data to store\n * @param opts - storage options\n */\nexport async function upsertUserStorage(\n data: string,\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData = await encryption.encryptString(\n data,\n opts.storageKey,\n nativeScryptCrypto,\n );\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to upsert data');\n }\n}\n\n/**\n * User Storage Service - Set multiple storage entries for one specific feature.\n * You cannot use this method to set multiple features at once.\n *\n * @param data - data to store, in the form of an array of [entryKey, entryValue] pairs\n * @param opts - storage options\n */\nexport async function batchUpsertUserStorage(\n data: [string, string][],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey, nativeScryptCrypto } = opts;\n\n const encryptedData: string[][] = [];\n\n for (const d of data) {\n encryptedData.push([\n createSHA256Hash(d[0] + storageKey),\n await encryption.encryptString(d[1], opts.storageKey, nativeScryptCrypto),\n ]);\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const formattedData = Object.fromEntries(encryptedData);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: formattedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch upsert data');\n }\n}\n\n/**\n * User Storage Service - Set multiple storage entries for one specific feature.\n * You cannot use this method to set multiple features at once.\n *\n * @param encryptedData - data to store, in the form of an array of [hashedKey, encryptedData] pairs\n * @param opts - storage options\n */\nexport async function batchUpsertUserStorageWithAlreadyHashedAndEncryptedEntries(\n encryptedData: [string, string][],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!encryptedData.length) {\n return;\n }\n\n const { bearerToken, path } = opts;\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const formattedData = Object.fromEntries(encryptedData);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n body: JSON.stringify({ data: formattedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch upsert data');\n }\n}\n\n/**\n * User Storage Service - Delete Storage Entry.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorage(\n opts: UserStorageOptions,\n): Promise<void> {\n const { bearerToken, path, storageKey } = opts;\n const encryptedPath = createEntryPath(path, storageKey);\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${encryptedPath}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature/entry not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n\n/**\n * User Storage Service - Delete multiple storage entries for one specific feature.\n * You cannot use this method to delete multiple features at once.\n *\n * @param data - data to delete, in the form of an array entryKey[]\n * @param opts - storage options\n */\nexport async function batchDeleteUserStorage(\n data: string[],\n opts: UserStorageBatchUpsertOptions,\n): Promise<void> {\n if (!data.length) {\n return;\n }\n\n const { bearerToken, path, storageKey } = opts;\n\n const encryptedData: string[] = [];\n\n for (const d of data) {\n encryptedData.push(createSHA256Hash(d + storageKey));\n }\n\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const res = await fetch(url.toString(), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n // eslint-disable-next-line @typescript-eslint/naming-convention\n body: JSON.stringify({ batch_delete: encryptedData }),\n });\n\n if (!res.ok) {\n throw new Error('user-storage - unable to batch delete data');\n }\n}\n\n/**\n * User Storage Service - Delete all storage entries for a specific feature.\n *\n * @param opts - User Storage Options\n */\nexport async function deleteUserStorageAllFeatureEntries(\n opts: UserStorageAllFeatureEntriesOptions,\n): Promise<void> {\n const { bearerToken, path } = opts;\n const url = new URL(`${USER_STORAGE_ENDPOINT}/${path}`);\n\n const userStorageResponse = await fetch(url.toString(), {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (userStorageResponse.status === 404) {\n throw new Error('user-storage - feature not found');\n }\n\n if (!userStorageResponse.ok) {\n throw new Error('user-storage - unable to delete data');\n }\n}\n"]}
@@ -1,26 +1,49 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
29
  exports.handleMockUserStorageDeleteAllFeatureEntries = exports.handleMockUserStorageDelete = exports.handleMockUserStorageBatchDelete = exports.handleMockUserStoragePut = exports.handleMockUserStorageGetAllFeatureEntries = exports.handleMockUserStorageGet = exports.MOCK_STORAGE_RESPONSE = exports.MOCK_NOTIFICATIONS_DATA_ENCRYPTED = exports.MOCK_NOTIFICATIONS_DATA = exports.MOCK_STORAGE_KEY = void 0;
7
30
  const nock_1 = __importDefault(require("nock"));
8
- const encryption_1 = __importDefault(require("../../shared/encryption/index.cjs"));
31
+ const encryption_1 = __importStar(require("../../shared/encryption/index.cjs"));
9
32
  const env_1 = require("../../shared/env.cjs");
10
33
  const storage_schema_1 = require("../../shared/storage-schema.cjs");
11
34
  const user_storage_1 = require("../user-storage.cjs");
12
35
  // Example mock notifications storage entry (wildcard)
13
36
  const MOCK_STORAGE_URL = (0, user_storage_1.STORAGE_URL)(env_1.Env.DEV, `${storage_schema_1.USER_STORAGE_FEATURE_NAMES.notifications}.notification_settings`);
14
37
  const MOCK_STORAGE_URL_ALL_FEATURE_ENTRIES = (0, user_storage_1.STORAGE_URL)(env_1.Env.DEV, storage_schema_1.USER_STORAGE_FEATURE_NAMES.notifications);
15
- exports.MOCK_STORAGE_KEY = 'MOCK_STORAGE_KEY';
38
+ exports.MOCK_STORAGE_KEY = (0, encryption_1.createSHA256Hash)('mockStorageKey');
16
39
  // TODO: Either fix this lint violation or explain why it's necessary to ignore.
17
40
  // eslint-disable-next-line @typescript-eslint/naming-convention
18
- exports.MOCK_NOTIFICATIONS_DATA = { is_compact: false };
19
- const MOCK_NOTIFICATIONS_DATA_ENCRYPTED = async () => await encryption_1.default.encryptString(JSON.stringify(exports.MOCK_NOTIFICATIONS_DATA), exports.MOCK_STORAGE_KEY);
41
+ exports.MOCK_NOTIFICATIONS_DATA = '{ is_compact: false }';
42
+ const MOCK_NOTIFICATIONS_DATA_ENCRYPTED = async (data) => await encryption_1.default.encryptString(data ?? exports.MOCK_NOTIFICATIONS_DATA, exports.MOCK_STORAGE_KEY);
20
43
  exports.MOCK_NOTIFICATIONS_DATA_ENCRYPTED = MOCK_NOTIFICATIONS_DATA_ENCRYPTED;
21
- const MOCK_STORAGE_RESPONSE = async () => ({
44
+ const MOCK_STORAGE_RESPONSE = async (data) => ({
22
45
  HashedKey: '8485d2c14c333ebca415140a276adaf546619b0efc204586b73a5d400a18a5e2',
23
- Data: await (0, exports.MOCK_NOTIFICATIONS_DATA_ENCRYPTED)(),
46
+ Data: await (0, exports.MOCK_NOTIFICATIONS_DATA_ENCRYPTED)(data),
24
47
  });
25
48
  exports.MOCK_STORAGE_RESPONSE = MOCK_STORAGE_RESPONSE;
26
49
  const handleMockUserStorageGet = async (mockReply) => {