react-native-nitro-storage 0.5.9 → 0.7.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.
- package/CHANGELOG.md +39 -0
- package/README.md +193 -16
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +225 -76
- package/app.plugin.js +2 -0
- package/cpp/bindings/HybridStorage.cpp +29 -21
- package/cpp/bindings/HybridStorage.hpp +5 -0
- package/docs/secure-storage.md +4 -0
- package/lib/commonjs/index.js +19 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +19 -2
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js.map +1 -1
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/shared.js.map +1 -1
- package/lib/commonjs/storage-core.js +306 -10
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/commonjs/storage-events.js.map +1 -1
- package/lib/commonjs/storage-hooks.js +16 -1
- package/lib/commonjs/storage-hooks.js.map +1 -1
- package/lib/commonjs/testing.js +267 -0
- package/lib/commonjs/testing.js.map +1 -0
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +5 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js.map +1 -1
- package/lib/module/internal.js.map +1 -1
- package/lib/module/shared.js.map +1 -1
- package/lib/module/storage-core.js +307 -11
- package/lib/module/storage-core.js.map +1 -1
- package/lib/module/storage-events.js.map +1 -1
- package/lib/module/storage-hooks.js +15 -2
- package/lib/module/storage-hooks.js.map +1 -1
- package/lib/module/testing.js +188 -0
- package/lib/module/testing.js.map +1 -0
- package/lib/typescript/index.d.ts +28 -5
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +28 -5
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +1 -0
- package/lib/typescript/shared.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts +62 -3
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/storage-events.d.ts +1 -1
- package/lib/typescript/storage-events.d.ts.map +1 -1
- package/lib/typescript/storage-hooks.d.ts +18 -1
- package/lib/typescript/storage-hooks.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts +158 -0
- package/lib/typescript/testing.d.ts.map +1 -0
- package/package.json +11 -6
- package/src/index.ts +17 -4
- package/src/index.web.ts +17 -4
- package/src/indexeddb-backend.ts +1 -2
- package/src/internal.ts +1 -1
- package/src/shared.ts +1 -0
- package/src/storage-core.ts +463 -11
- package/src/storage-events.ts +3 -2
- package/src/storage-hooks.ts +42 -3
- package/src/testing.ts +270 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,45 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format follows Keep a Changelog and the project adheres to SemVer.
|
|
6
6
|
|
|
7
|
+
## 0.7.0 - 2026-07-30
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
|
|
11
|
+
- **Breaking change:** Android secure key discovery, existence checks, and cleanup now surface locked, unavailable, or invalidated biometric-store errors instead of treating inaccessible protected values as absent. Catch storage errors around these operations and use `isKeychainLockedError()` when retrying after device authentication is appropriate.
|
|
12
|
+
- Upgrade the validated package baseline to Expo SDK 57, React Native 0.86.2, and Nitro Modules/Nitrogen 0.36.4.
|
|
13
|
+
- Preserve each item/value relationship in heterogeneous `setBatch()` calls so TypeScript rejects values assigned to the wrong storage item.
|
|
14
|
+
- Serialize native key-index hydration with concurrent mutations so `has`, `size`, and key queries cannot remain stale after a racing write.
|
|
15
|
+
- Enforce Android biometric policy levels with distinct Keystore keys, propagate locked or invalidated biometric failures, and keep secure preference files excluded from backup.
|
|
16
|
+
- Preflight biometric store access before aggregate secure mutations and surface native commit or corruption-recovery failures.
|
|
17
|
+
|
|
18
|
+
## 0.6.0 - 2026-06-15
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Object-state ergonomics on `StorageItem<T>`: `item.merge(partial)` for shallow object updates, `item.reset()` to return to the default value, and `item.setOrDelete(value)` which deletes on `null`/`undefined` and sets otherwise.
|
|
23
|
+
- Scoped item factories `memoryItem`, `diskItem`, and `secureItem` so call sites no longer repeat `scope: StorageScope.X`.
|
|
24
|
+
- `createSetItem()` for set-membership state backed by storage, with `add`/`delete`/`has`/`toggle`/`values`/`size`/`clear`/`reset` and no-op-safe writes (no event/render churn when adding an existing member or deleting an absent one).
|
|
25
|
+
- Lifecycle helpers: `storage.clear(scope, { except })` to wipe a scope while preserving listed keys/items, per-item `group` config plus `storage.clearGroup(group)` and `storage.getGroupItems(group)` (which compose for "clear all except a group").
|
|
26
|
+
- Declarative legacy migration: `renameFrom` on items (and per-key on `createSecureAuthStorage`) copies a legacy key to the new key on first read and removes the legacy entry. `createSecureAuthStorage` also accepts `group` and `fallbackToCacheOnReadError`.
|
|
27
|
+
- Secure read resilience: `fallbackToCacheOnReadError` returns the last cached value when a secure read throws a locked-keychain error, plus an `onReadError` hook.
|
|
28
|
+
- Global expiration events: TTL expiry now emits a `"expire"` change event (memory and disk) routed through the event bus, with `storage.subscribeExpired(scope, listener)`.
|
|
29
|
+
- Hook ergonomics: `useStorage` now returns a third, render-stable `actions` element (`set`/`merge`/`reset`/`remove`/`setOrDelete`); new `useStorageValue` (read-only) and `useStorageActions` hooks.
|
|
30
|
+
- Dev introspection: `storage.findDuplicateKeys()` and `storage.getRegisteredKeys()` to audit accidental `(scope, key)` collisions at startup.
|
|
31
|
+
- New `react-native-nitro-storage/testing` entrypoint: a faithful in-memory implementation of the full public surface plus `createNitroStorageMock()` and `resetNitroStorageMock()` for Jest/Storybook without native modules.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Faster writes when nothing is subscribed: the native write/notify path now takes a lock-free fast path (per-scope atomic listener counts) and skips locking and copying the listener vector when a scope has no listeners. Applies to both iOS and Android via the shared C++ `HybridStorage`, and is thread-safe (verified under the C++ AddressSanitizer, ThreadSanitizer, and UndefinedBehaviorSanitizer suites).
|
|
36
|
+
|
|
37
|
+
### Breaking Changes
|
|
38
|
+
|
|
39
|
+
All new APIs are additive — existing code keeps working. These behavior and
|
|
40
|
+
type changes can affect advanced consumers:
|
|
41
|
+
|
|
42
|
+
- TTL expiry now emits a `"expire"` change event instead of `"remove"`. Previously, a disk/secure value expiring on read emitted `operation: "remove"` and an expiring memory value emitted no event at all. If you subscribe to storage events and branch on `operation === "remove"` to detect expiry, also handle `"expire"` (or use the new `storage.subscribeExpired()`).
|
|
43
|
+
- `StorageChangeOperation` gained the `"expire"` and `"clearGroup"` members. Exhaustive `switch` statements over a change event's `operation` need cases for the new members.
|
|
44
|
+
- `useStorage()` now returns a three-element tuple `[value, setter, actions]` (was two). Array destructuring such as `const [value, setStore] = useStorage(item)` is unaffected; only code that annotated the result with an explicit two-element tuple type needs to widen the annotation.
|
|
45
|
+
|
|
7
46
|
## 0.5.9 - 2026-06-11
|
|
8
47
|
|
|
9
48
|
### Fixed
|
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
[](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/actions/workflows/ci.yml)
|
|
6
6
|
[](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/blob/main/LICENSE)
|
|
7
7
|
[](https://reactnative.dev/)
|
|
8
|
-
[](https://docs.expo.dev/versions/latest/)
|
|
9
|
+
[](https://nitro.margelo.com/)
|
|
10
10
|
[](https://www.typescriptlang.org/)
|
|
11
11
|
|
|
12
12
|
Synchronous Memory, Disk, and Secure storage for React Native, Expo development
|
|
@@ -27,6 +27,10 @@ pagination, conflict resolution, or remote synchronization.
|
|
|
27
27
|
- [Expo Config](#expo-config)
|
|
28
28
|
- [Quick Start](#quick-start)
|
|
29
29
|
- [Typed Storage Items](#typed-storage-items)
|
|
30
|
+
- [Item Ergonomics](#item-ergonomics)
|
|
31
|
+
- [Set Items](#set-items)
|
|
32
|
+
- [Groups And Lifecycle](#groups-and-lifecycle)
|
|
33
|
+
- [Legacy Key Migration And Secure Resilience](#legacy-key-migration-and-secure-resilience)
|
|
30
34
|
- [React Hooks](#react-hooks)
|
|
31
35
|
- [Storage Scopes](#storage-scopes)
|
|
32
36
|
- [Secure Storage](#secure-storage)
|
|
@@ -34,10 +38,12 @@ pagination, conflict resolution, or remote synchronization.
|
|
|
34
38
|
- [Events And Observability](#events-and-observability)
|
|
35
39
|
- [Migrations And Transactions](#migrations-and-transactions)
|
|
36
40
|
- [Web Backends](#web-backends)
|
|
41
|
+
- [Testing](#testing)
|
|
37
42
|
- [Platform Support](#platform-support)
|
|
38
43
|
- [Documentation](#documentation)
|
|
39
44
|
- [Troubleshooting](#troubleshooting)
|
|
40
45
|
- [Development](#development)
|
|
46
|
+
- [License](#license)
|
|
41
47
|
|
|
42
48
|
## Install
|
|
43
49
|
|
|
@@ -47,13 +53,16 @@ bun add react-native-nitro-storage react-native-nitro-modules
|
|
|
47
53
|
|
|
48
54
|
Peer dependencies:
|
|
49
55
|
|
|
50
|
-
| Package | Version
|
|
51
|
-
| ---------------------------- |
|
|
52
|
-
| `react` | `>=18.2.0`
|
|
53
|
-
| `react-native` | `>=0.75.0`
|
|
54
|
-
| `react-native-nitro-modules` | `>=0.
|
|
56
|
+
| Package | Version |
|
|
57
|
+
| ---------------------------- | ------------------ |
|
|
58
|
+
| `react` | `>=18.2.0` |
|
|
59
|
+
| `react-native` | `>=0.75.0` |
|
|
60
|
+
| `react-native-nitro-modules` | `>=0.36.4 <0.37.0` |
|
|
55
61
|
|
|
56
|
-
Nitro peer requirement: `react-native-nitro-modules >=0.
|
|
62
|
+
Nitro peer requirement: `react-native-nitro-modules >=0.36.4 <0.37.0`.
|
|
63
|
+
|
|
64
|
+
Validated example baseline: Expo SDK 57, React Native 0.86.2, React 19.2.3,
|
|
65
|
+
and Nitro Modules 0.36.4.
|
|
57
66
|
|
|
58
67
|
For Expo development builds:
|
|
59
68
|
|
|
@@ -153,9 +162,108 @@ const didWrite = preferencesItem.setIfVersion(snapshot.version, {
|
|
|
153
162
|
});
|
|
154
163
|
```
|
|
155
164
|
|
|
156
|
-
The package
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
The package ships its own TypeScript types, so editors and AI tools catch
|
|
166
|
+
mistakes before they reach the runtime. It exports `StorageItem`,
|
|
167
|
+
`StorageItemConfig`, `StorageSetter`, `StorageActions`, `VersionedValue`,
|
|
168
|
+
`StorageBatchSetItem`, `StorageClearOptions`, `StorageKeyRef`, `SetItemConfig`,
|
|
169
|
+
`SetStorageItem`, plus web backend, event, secure-metadata, and capability types.
|
|
170
|
+
|
|
171
|
+
## Item Ergonomics
|
|
172
|
+
|
|
173
|
+
`merge`, `reset`, and `setOrDelete` cover the most common object-state edits
|
|
174
|
+
without re-reading or hand-writing compare-and-swap loops. Scoped factories
|
|
175
|
+
(`memoryItem`, `diskItem`, `secureItem`) drop the repeated `scope` field.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { diskItem, memoryItem } from "react-native-nitro-storage";
|
|
179
|
+
|
|
180
|
+
const config = diskItem<{ theme: "light" | "dark"; compact: boolean }>({
|
|
181
|
+
key: "config",
|
|
182
|
+
defaultValue: { theme: "light", compact: false },
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
config.merge({ compact: true }); // shallow object update
|
|
186
|
+
config.reset(); // back to the default value
|
|
187
|
+
const loginMethod = memoryItem<string | null>({
|
|
188
|
+
key: "loginMethod",
|
|
189
|
+
defaultValue: null,
|
|
190
|
+
});
|
|
191
|
+
loginMethod.setOrDelete(maybeMethod); // null/undefined deletes, value sets
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Set Items
|
|
195
|
+
|
|
196
|
+
`createSetItem()` models set-membership state (seen ids, dismissed prompts)
|
|
197
|
+
without hand-rolling `Record<string, true>` helpers. Adding an existing member
|
|
198
|
+
or deleting an absent one is a no-op, so subscribers do not re-render.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { createSetItem, StorageScope } from "react-native-nitro-storage";
|
|
202
|
+
|
|
203
|
+
const dismissedTips = createSetItem({
|
|
204
|
+
key: "dismissedTips",
|
|
205
|
+
scope: StorageScope.Disk,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
dismissedTips.add("welcome");
|
|
209
|
+
dismissedTips.has("welcome"); // true
|
|
210
|
+
dismissedTips.toggle("welcome"); // false (removed)
|
|
211
|
+
dismissedTips.values(); // string[]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Groups And Lifecycle
|
|
215
|
+
|
|
216
|
+
Tag items with a `group` to clear related state in one call, or keep specific
|
|
217
|
+
keys while wiping the rest of a scope. This replaces manual snapshot-and-restore
|
|
218
|
+
logout flows.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { secureItem, storage, StorageScope } from "react-native-nitro-storage";
|
|
222
|
+
|
|
223
|
+
const accessToken = secureItem<string>({
|
|
224
|
+
key: "accessToken",
|
|
225
|
+
defaultValue: "",
|
|
226
|
+
group: "session",
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Wipe everything tied to the session.
|
|
230
|
+
storage.clearGroup("session");
|
|
231
|
+
|
|
232
|
+
// Wipe Disk but keep a few opt-in preferences.
|
|
233
|
+
storage.clear(StorageScope.Disk, {
|
|
234
|
+
except: [apiEnvironmentItem, "onboardingComplete"],
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Legacy Key Migration And Secure Resilience
|
|
239
|
+
|
|
240
|
+
`renameFrom` migrates an old key to a new one on first read and deletes the
|
|
241
|
+
legacy entry. Secure items can fall back to the last cached value when the
|
|
242
|
+
keychain is locked instead of throwing.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import {
|
|
246
|
+
secureItem,
|
|
247
|
+
createSecureAuthStorage,
|
|
248
|
+
} from "react-native-nitro-storage";
|
|
249
|
+
|
|
250
|
+
const accessToken = secureItem<string>({
|
|
251
|
+
key: "accessToken",
|
|
252
|
+
namespace: "auth",
|
|
253
|
+
defaultValue: "",
|
|
254
|
+
renameFrom: "authToken", // copied + cleaned up on first read
|
|
255
|
+
fallbackToCacheOnReadError: true,
|
|
256
|
+
onReadError: (error) => reportSecureReadError(error),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const auth = createSecureAuthStorage(
|
|
260
|
+
{
|
|
261
|
+
accessToken: { renameFrom: "authToken" },
|
|
262
|
+
refreshToken: { renameFrom: "refreshToken" },
|
|
263
|
+
},
|
|
264
|
+
{ namespace: "auth", group: "session", fallbackToCacheOnReadError: true },
|
|
265
|
+
);
|
|
266
|
+
```
|
|
159
267
|
|
|
160
268
|
## React Hooks
|
|
161
269
|
|
|
@@ -188,6 +296,24 @@ const [compactMode] = useStorageSelector(
|
|
|
188
296
|
);
|
|
189
297
|
```
|
|
190
298
|
|
|
299
|
+
`useStorage` also returns a render-stable `actions` object as a third element,
|
|
300
|
+
and `useStorageValue` / `useStorageActions` split read and write concerns.
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
import {
|
|
304
|
+
useStorage,
|
|
305
|
+
useStorageActions,
|
|
306
|
+
useStorageValue,
|
|
307
|
+
} from "react-native-nitro-storage";
|
|
308
|
+
|
|
309
|
+
const [config, setConfig, actions] = useStorage(configItem);
|
|
310
|
+
actions.merge({ compact: true });
|
|
311
|
+
actions.reset();
|
|
312
|
+
|
|
313
|
+
const theme = useStorageValue(themeItem); // read-only, no setter
|
|
314
|
+
const tokenActions = useStorageActions(tokenItem); // { set, merge, reset, remove, setOrDelete }
|
|
315
|
+
```
|
|
316
|
+
|
|
191
317
|
## Storage Scopes
|
|
192
318
|
|
|
193
319
|
| Scope | Backing store | Use it for |
|
|
@@ -248,10 +374,20 @@ exporting secure values unless you are intentionally doing a short-lived
|
|
|
248
374
|
in-memory migration. `storage.export(StorageScope.Secure)` throws unless you
|
|
249
375
|
explicitly opt into `{ includeSecureValues: true }`.
|
|
250
376
|
|
|
377
|
+
On Android 11 and newer, `BiometricLevel.BiometryOnly` and
|
|
378
|
+
`BiometricLevel.BiometryOrPasscode` use separate Keystore policies. Android 10
|
|
379
|
+
and older support `BiometryOrPasscode`; `BiometryOnly` throws
|
|
380
|
+
`biometric_unavailable` because those releases cannot safely enforce the
|
|
381
|
+
biometric-only distinction. Secure existence, discovery, and cleanup operations
|
|
382
|
+
can also throw when a protected store is locked or its key is invalidated. Catch
|
|
383
|
+
those failures and use `isKeychainLockedError()` when authentication-aware retry
|
|
384
|
+
behavior is appropriate.
|
|
385
|
+
|
|
251
386
|
## Batch Operations
|
|
252
387
|
|
|
253
388
|
`getBatch()` preserves tuple value types, so IDEs infer each result from the
|
|
254
|
-
matching item.
|
|
389
|
+
matching item. `setBatch()` validates every item/value pair independently,
|
|
390
|
+
including heterogeneous batches.
|
|
255
391
|
|
|
256
392
|
```ts
|
|
257
393
|
import { getBatch, removeBatch, setBatch } from "react-native-nitro-storage";
|
|
@@ -303,6 +439,21 @@ Secure event observer values are redacted by default. Pass
|
|
|
303
439
|
`{ redactSecureValues: false }` only in trusted debug tooling where raw values
|
|
304
440
|
are safe to inspect.
|
|
305
441
|
|
|
442
|
+
TTL expiry emits a dedicated `"expire"` change event. Use
|
|
443
|
+
`storage.subscribeExpired()` to react to keys that lapse on read.
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
const unsubscribeExpired = storage.subscribeExpired(
|
|
447
|
+
StorageScope.Disk,
|
|
448
|
+
(event) => {
|
|
449
|
+
console.log("expired", event.key);
|
|
450
|
+
},
|
|
451
|
+
);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`storage.findDuplicateKeys()` and `storage.getRegisteredKeys()` help audit
|
|
455
|
+
accidental `(scope, key)` collisions; call them once at startup in development.
|
|
456
|
+
|
|
306
457
|
## Migrations And Transactions
|
|
307
458
|
|
|
308
459
|
```ts
|
|
@@ -324,15 +475,16 @@ registerMigration(2, ({ getRaw, setRaw, removeRaw }) => {
|
|
|
324
475
|
|
|
325
476
|
migrateToLatest(StorageScope.Disk);
|
|
326
477
|
|
|
327
|
-
runTransaction(() => {
|
|
328
|
-
|
|
329
|
-
|
|
478
|
+
runTransaction(StorageScope.Disk, (tx) => {
|
|
479
|
+
tx.setItem(themeItem, "dark");
|
|
480
|
+
tx.setItem(localeItem, "en-US");
|
|
330
481
|
});
|
|
331
482
|
|
|
332
483
|
migrateFromMMKV(mmkvInstance, themeItem);
|
|
333
484
|
```
|
|
334
485
|
|
|
335
|
-
|
|
486
|
+
`runTransaction(scope, callback)` rolls back every write made through the `tx`
|
|
487
|
+
context if the callback throws.
|
|
336
488
|
|
|
337
489
|
## Web Backends
|
|
338
490
|
|
|
@@ -355,6 +507,31 @@ setWebSecureStorageBackend(backend);
|
|
|
355
507
|
Browser storage cannot provide iOS Keychain or Android Keystore guarantees. Web
|
|
356
508
|
Secure scope is only as strong as the backend you configure.
|
|
357
509
|
|
|
510
|
+
## Testing
|
|
511
|
+
|
|
512
|
+
The `react-native-nitro-storage/testing` entrypoint is a faithful in-memory
|
|
513
|
+
implementation of the full public surface, so unit tests and Storybook run
|
|
514
|
+
without native modules. Mock the package with it, or use it directly.
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
import {
|
|
518
|
+
createNitroStorageMock,
|
|
519
|
+
resetNitroStorageMock,
|
|
520
|
+
} from "react-native-nitro-storage/testing";
|
|
521
|
+
|
|
522
|
+
// Jest: swap the real module for the in-memory implementation.
|
|
523
|
+
jest.mock("react-native-nitro-storage", () =>
|
|
524
|
+
require("react-native-nitro-storage/testing"),
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
beforeEach(() => {
|
|
528
|
+
resetNitroStorageMock();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Or build an isolated instance per test file.
|
|
532
|
+
const { storage, memoryItem } = createNitroStorageMock();
|
|
533
|
+
```
|
|
534
|
+
|
|
358
535
|
## Platform Support
|
|
359
536
|
|
|
360
537
|
| Platform | Status |
|