syzygy-services-rn 1.0.0 → 1.2.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 (90) hide show
  1. package/.eslintrc.json +47 -0
  2. package/.github/workflows/release.yml +12 -5
  3. package/.prettierrc +9 -0
  4. package/CHANGELOG.md +51 -0
  5. package/CONTRACT_TESTS.md +118 -0
  6. package/README.md +76 -14
  7. package/dist/__tests__/ContractCompliance.test.d.ts +13 -0
  8. package/dist/__tests__/ContractCompliance.test.d.ts.map +1 -0
  9. package/dist/__tests__/ContractCompliance.test.js +286 -0
  10. package/dist/__tests__/ContractCompliance.test.js.map +1 -0
  11. package/dist/__tests__/analytics/AnalyticsProvider.test.js +19 -2
  12. package/dist/__tests__/analytics/AnalyticsProvider.test.js.map +1 -1
  13. package/dist/__tests__/auth/AuthProvider.test.js +126 -0
  14. package/dist/__tests__/auth/AuthProvider.test.js.map +1 -1
  15. package/dist/__tests__/crashreporting/CrashReporter.test.js +37 -0
  16. package/dist/__tests__/crashreporting/CrashReporter.test.js.map +1 -1
  17. package/dist/__tests__/deviceservices/DeviceProvider.test.js +6 -0
  18. package/dist/__tests__/deviceservices/DeviceProvider.test.js.map +1 -1
  19. package/dist/__tests__/networking/NetworkClient.test.js +86 -0
  20. package/dist/__tests__/networking/NetworkClient.test.js.map +1 -1
  21. package/dist/__tests__/networking/RetryIntegration.test.d.ts +2 -0
  22. package/dist/__tests__/networking/RetryIntegration.test.d.ts.map +1 -0
  23. package/dist/__tests__/networking/RetryIntegration.test.js +319 -0
  24. package/dist/__tests__/networking/RetryIntegration.test.js.map +1 -0
  25. package/dist/__tests__/persistence/StorageProvider.test.js +34 -0
  26. package/dist/__tests__/persistence/StorageProvider.test.js.map +1 -1
  27. package/dist/__tests__/pushnotifications/PushProvider.test.js +42 -0
  28. package/dist/__tests__/pushnotifications/PushProvider.test.js.map +1 -1
  29. package/dist/__tests__/remoteconfig/RemoteConfigProvider.test.js +82 -0
  30. package/dist/__tests__/remoteconfig/RemoteConfigProvider.test.js.map +1 -1
  31. package/dist/__tests__/websocket/WebSocketProvider.test.js +195 -0
  32. package/dist/__tests__/websocket/WebSocketProvider.test.js.map +1 -1
  33. package/dist/analytics/AnalyticsProvider.d.ts +1 -0
  34. package/dist/analytics/AnalyticsProvider.d.ts.map +1 -1
  35. package/dist/analytics/AnalyticsProvider.js +5 -2
  36. package/dist/analytics/AnalyticsProvider.js.map +1 -1
  37. package/dist/auth/AuthProvider.d.ts +13 -0
  38. package/dist/auth/AuthProvider.d.ts.map +1 -1
  39. package/dist/auth/AuthProvider.js +20 -0
  40. package/dist/auth/AuthProvider.js.map +1 -1
  41. package/dist/crashreporting/CrashReporter.d.ts +33 -0
  42. package/dist/crashreporting/CrashReporter.d.ts.map +1 -1
  43. package/dist/crashreporting/CrashReporter.js +28 -1
  44. package/dist/crashreporting/CrashReporter.js.map +1 -1
  45. package/dist/deviceservices/DeviceProvider.d.ts +1 -0
  46. package/dist/deviceservices/DeviceProvider.d.ts.map +1 -1
  47. package/dist/deviceservices/DeviceProvider.js +10 -2
  48. package/dist/deviceservices/DeviceProvider.js.map +1 -1
  49. package/dist/networking/NetworkClient.d.ts +47 -3
  50. package/dist/networking/NetworkClient.d.ts.map +1 -1
  51. package/dist/networking/NetworkClient.js +63 -6
  52. package/dist/networking/NetworkClient.js.map +1 -1
  53. package/dist/persistence/StorageProvider.d.ts +4 -0
  54. package/dist/persistence/StorageProvider.d.ts.map +1 -1
  55. package/dist/persistence/StorageProvider.js +19 -1
  56. package/dist/persistence/StorageProvider.js.map +1 -1
  57. package/dist/pushnotifications/PushProvider.d.ts +72 -0
  58. package/dist/pushnotifications/PushProvider.d.ts.map +1 -1
  59. package/dist/pushnotifications/PushProvider.js +51 -0
  60. package/dist/pushnotifications/PushProvider.js.map +1 -1
  61. package/dist/remoteconfig/RemoteConfigProvider.d.ts +8 -0
  62. package/dist/remoteconfig/RemoteConfigProvider.d.ts.map +1 -1
  63. package/dist/remoteconfig/RemoteConfigProvider.js +7 -0
  64. package/dist/remoteconfig/RemoteConfigProvider.js.map +1 -1
  65. package/dist/websocket/WebSocketProvider.d.ts +79 -4
  66. package/dist/websocket/WebSocketProvider.d.ts.map +1 -1
  67. package/dist/websocket/WebSocketProvider.js +106 -5
  68. package/dist/websocket/WebSocketProvider.js.map +1 -1
  69. package/package.json +2 -2
  70. package/src/__tests__/ContractCompliance.test.ts +341 -0
  71. package/src/__tests__/analytics/AnalyticsProvider.test.ts +21 -2
  72. package/src/__tests__/auth/AuthProvider.test.ts +152 -0
  73. package/src/__tests__/crashreporting/CrashReporter.test.ts +42 -0
  74. package/src/__tests__/deviceservices/DeviceProvider.test.ts +7 -0
  75. package/src/__tests__/networking/NetworkClient.test.ts +102 -0
  76. package/src/__tests__/networking/RetryIntegration.test.ts +388 -0
  77. package/src/__tests__/persistence/StorageProvider.test.ts +38 -0
  78. package/src/__tests__/pushnotifications/PushProvider.test.ts +58 -1
  79. package/src/__tests__/remoteconfig/RemoteConfigProvider.test.ts +97 -0
  80. package/src/__tests__/websocket/WebSocketProvider.test.ts +254 -0
  81. package/src/analytics/AnalyticsProvider.ts +5 -2
  82. package/src/auth/AuthProvider.ts +22 -0
  83. package/src/crashreporting/CrashReporter.ts +61 -1
  84. package/src/deviceservices/DeviceProvider.ts +10 -2
  85. package/src/networking/NetworkClient.ts +121 -8
  86. package/src/persistence/StorageProvider.ts +25 -1
  87. package/src/pushnotifications/PushProvider.ts +96 -0
  88. package/src/remoteconfig/RemoteConfigProvider.ts +14 -0
  89. package/src/websocket/WebSocketProvider.ts +138 -5
  90. package/syzygy.yml +2 -2
package/.eslintrc.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "root": true,
3
+ "parser": "@typescript-eslint/parser",
4
+ "parserOptions": {
5
+ "ecmaVersion": 2020,
6
+ "sourceType": "module",
7
+ "ecmaFeatures": { "jsx": true }
8
+ },
9
+ "plugins": ["@typescript-eslint", "react", "react-hooks", "react-native", "import"],
10
+ "extends": [
11
+ "eslint:recommended",
12
+ "plugin:@typescript-eslint/recommended",
13
+ "plugin:react/recommended",
14
+ "plugin:react-hooks/recommended",
15
+ // Must always be last: disables ESLint formatting rules that would
16
+ // otherwise conflict with Prettier's output.
17
+ "eslint-config-prettier"
18
+ ],
19
+ "env": {
20
+ "es6": true,
21
+ "node": true,
22
+ "react-native/react-native": true
23
+ },
24
+ "settings": {
25
+ "react": { "version": "detect" }
26
+ },
27
+ "rules": {
28
+ "@typescript-eslint/no-unused-vars": [
29
+ "warn",
30
+ { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
31
+ ],
32
+ "@typescript-eslint/no-explicit-any": "warn",
33
+ "react/prop-types": "off",
34
+ "react/react-in-jsx-scope": "off",
35
+ "react-native/no-inline-styles": "warn",
36
+ "react-native/no-unused-styles": "warn",
37
+ "import/order": [
38
+ "warn",
39
+ {
40
+ "groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
41
+ "alphabetize": { "order": "asc", "caseInsensitive": true },
42
+ "newlines-between": "always"
43
+ }
44
+ ]
45
+ },
46
+ "ignorePatterns": ["dist", "node_modules", "lib", "build"]
47
+ }
@@ -21,10 +21,17 @@ jobs:
21
21
  contents: read
22
22
  steps:
23
23
  - uses: actions/checkout@v4
24
- - uses: actions/setup-node@v4
24
+ - name: Set up Node
25
+ uses: actions/setup-node@v4
25
26
  with:
26
27
  node-version: '24'
27
- registry-url: 'https://registry.npmjs.org'
28
- package-manager-cache: false
29
- - run: npm ci
30
- - run: npm publish --access=public --provenance
28
+ - name: Install dependencies
29
+ run: npm ci
30
+ - name: Lint
31
+ run: npm run lint
32
+ - name: Type check
33
+ run: npm run typecheck
34
+ - name: Test
35
+ run: npm test
36
+ - name: Publish to npm
37
+ run: npm publish --access=public --provenance
package/.prettierrc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "trailingComma": "all",
5
+ "tabWidth": 2,
6
+ "useTabs": false,
7
+ "printWidth": 100,
8
+ "arrowParens": "always"
9
+ }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [1.2.0] - 2026-09-19
11
+
12
+ ### Fixed
13
+ - `DeviceProvider`: concurrent `getDeviceId()` calls now share one UUID generation via single-flight Promise memoization
14
+
15
+ ### Changed
16
+ - npm publish gated behind lint + typecheck + test in release.yml
17
+ - `StorageProvider.set(value, key)` argument order documented — Foundation v1.2.0 TODO noted
18
+ - Canonical backoff policy: 500ms base, 2.0× multiplier, full jitter, 8 000ms cap, max 3 retries
19
+ - Foundation dependency updated to ^1.2.0
20
+
21
+ ## [1.1.0] - 2026-09-13
22
+
23
+ ### Added
24
+
25
+ - DeviceServices: `NodeDeviceProvider` now uses storage key `"syzygy.device.uuid"` (was `"syzygy.device.id"`) — UUID is generated once and persisted via `StorageProvider`, never regenerated if already stored
26
+ - Persistence: `InMemoryStorageProvider.get()` now throws `TypeError` with a descriptive message (key name, stored type, requested type) when the stored value's type does not match the key's declared type; type detection uses `defaultValue` as a runtime type hint
27
+ - Networking: `FetchNetworkClient` accepts an optional `logger?: LoggerProtocol` — logs outgoing request (method, URL, safe headers, body size), successful response (status, elapsed ms, body size), and errors; `undefined` logger incurs zero overhead
28
+ - Analytics: `ConsoleAnalyticsProvider` and `InMemoryAnalyticsProvider` now inject `sessionId` into every tracked event's `properties`; session ID resets on `reset()`
29
+ - CrashReporting: `CrashReporter` interface and all implementations gain `leaveBreadcrumb(message, metadata?)` and `clearBreadcrumbs()`; `InMemoryCrashReporter` stores up to 20 breadcrumbs in a circular buffer and includes a snapshot in every `CrashRecord`
30
+ - WebSocket: `onBinaryMessage(handler)` callback on `WebSocketProvider` interface and `InMemoryWebSocketProvider`, returning `Uint8Array` frames — aligns with Android `binaryMessages: Flow<ByteArray>` contract
31
+ - WebSocket: `simulateBinaryMessage(data)` helper on `InMemoryWebSocketProvider` for test-time binary frame injection
32
+ - WebSocket: binary stream tests covering `onBinaryMessage` delivery, unsubscribe, multi-handler fan-out, empty frames, and mixed text+binary ordering (Android parity)
33
+ - Networking: injectable `backoffClock: BackoffClock` option on `FetchNetworkClient` so retry back-off timing is deterministic in tests without global `setTimeout` stubbing — mirrors Android injectable-clock pattern
34
+ - Networking: deterministic retry backoff tests using injectable `backoffClock` (exact delay values, partial recovery, no-sleep on first attempt, no-sleep on 4xx)
35
+ - Networking retry integration tests with injectable `backoffClock`/timer
36
+ - Auth: real token-refresh flow wired to NetworkClient with auto-refresh on expired JWT
37
+ - RemoteConfig: cache TTL (cacheTtlSeconds, default 3600) for NetworkRemoteConfigProvider
38
+ - WebSocket binary send tests (ArrayBuffer, binary receive callback, mixed text+binary)
39
+ - PushNotifications: JSDoc FCM/APNs integration guide and NotificationPayload factory helpers
40
+ - PushNotifications: README section with FCM/APNs integration steps and factory helper examples
41
+ - Contract compliance tests verifying every service implements its Foundation protocol
42
+ - Networking: `FetchNetworkClient.dispose()` — marks the client as disposed; subsequent `execute()` calls throw `NetworkError` (code `unknown`); calling `dispose()` multiple times is a safe no-op
43
+ - WebSocket: `dispose(): void` added to `WebSocketProvider` interface, `NativeWebSocketProvider`, and `InMemoryWebSocketProvider` — closes the connection, clears all registered handlers, and prevents further `connect()` / `send()` / `sendBinary()` calls (each throws `WebSocketError`); calling `dispose()` multiple times is a safe no-op
44
+ - Added `canUseBiometric()` and `authenticateWithBiometric(reason)` stub methods to `JWTAuthProvider` — returns `false`/`AuthState.Unauthenticated` with doc comments explaining real platform wiring (react-native-biometrics)
45
+ - Added `CONTRACT_TESTS.md` — canonical set of behaviour assertions every Services implementation must satisfy, organised by module
46
+
47
+ ### Changed
48
+
49
+ - SleepFn renamed to BackoffClock for cross-platform naming consistency
50
+
51
+ ### Fixed
52
+
53
+ - README version badge corrected to 1.1.0
54
+ - README banner updated to match cross-repo standard
55
+
8
56
  ## [1.0.0] - 2026-09-12
9
57
 
10
58
  ### Added
@@ -20,4 +68,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
68
  - CrashReporter interface and console crash logging stub
21
69
  - WebSocketProvider interface and native WebSocket implementation stub
22
70
 
71
+ [Unreleased]: https://github.com/Syzygy-Hub/syzygy-services-rn/compare/1.2.0...HEAD
72
+ [1.2.0]: https://github.com/Syzygy-Hub/syzygy-services-rn/compare/1.1.0...1.2.0
73
+ [1.1.0]: https://github.com/Syzygy-Hub/syzygy-services-rn/compare/1.0.0...1.1.0
23
74
  [1.0.0]: https://github.com/Syzygy-Hub/syzygy-services-rn/releases/tag/1.0.0
@@ -0,0 +1,118 @@
1
+ # Syzygy Services — Contract Tests Reference
2
+
3
+ This document defines the canonical set of behaviour assertions every Syzygy Services
4
+ implementation must satisfy. It serves as the source of truth for cross-platform contract
5
+ compliance.
6
+
7
+ ## How to use this document
8
+
9
+ Each section lists the minimum test cases required per module. Tests marked ✅ are already
10
+ implemented in this repo's test suite; tests marked 🔲 are not yet implemented.
11
+
12
+ ---
13
+
14
+ ## Networking
15
+
16
+ | Test | Status |
17
+ |------|--------|
18
+ | Successful GET request returns 200 body | ✅ |
19
+ | Retries on 5xx responses up to maxRetries | ✅ |
20
+ | Does NOT retry on 4xx responses | ✅ |
21
+ | Retry delays use injected BackoffClock (not real sleep) | ✅ |
22
+ | Recovers on Nth attempt after N-1 failures | ✅ |
23
+ | Logger receives request method, URL, headers (Authorization stripped) | ✅ |
24
+ | Logger receives response status, body size, elapsed time | ✅ |
25
+ | dispose() cancels in-flight requests | ✅ |
26
+ | dispose() is idempotent | ✅ |
27
+
28
+ ## Persistence
29
+
30
+ | Test | Status |
31
+ |------|--------|
32
+ | Stores and retrieves a value by key | ✅ |
33
+ | Returns undefined for missing key | ✅ |
34
+ | Type mismatch throws descriptive error with key name, stored type, requested type | ✅ |
35
+ | Secure storage stores and retrieves | ✅ |
36
+
37
+ ## Auth
38
+
39
+ | Test | Status |
40
+ |------|--------|
41
+ | Initial state is unauthenticated | ✅ |
42
+ | setToken() transitions to authenticated | ✅ |
43
+ | clearTokens() transitions to unauthenticated | ✅ |
44
+ | refresh() POSTs to refreshUrl, updates stored tokens | ✅ |
45
+ | refresh() on expired JWT auto-refreshes before request | ✅ |
46
+ | refresh() failure transitions to unauthenticated | ✅ |
47
+ | canUseBiometric() returns false on stub | ✅ |
48
+ | authenticateWithBiometric() returns unauthenticated on stub | ✅ |
49
+
50
+ ## FileManagement
51
+
52
+ | Test | Status |
53
+ |------|--------|
54
+ | Writes and reads a file | ✅ |
55
+ | Returns null for missing file | ✅ |
56
+ | Deletes a file | ✅ |
57
+
58
+ ## PushNotifications
59
+
60
+ | Test | Status |
61
+ |------|--------|
62
+ | Registers for notifications without throwing | ✅ |
63
+ | createAlertNotification() creates correct payload | ✅ |
64
+ | createSilentNotification() creates correct payload | ✅ |
65
+
66
+ ## DeviceServices
67
+
68
+ | Test | Status |
69
+ |------|--------|
70
+ | deviceId() returns consistent UUID across calls | ✅ |
71
+ | deviceId() is stored under key "syzygy.device.uuid" | ✅ |
72
+
73
+ ## RemoteConfig
74
+
75
+ | Test | Status |
76
+ |------|--------|
77
+ | fetchConfig() returns values | ✅ |
78
+ | Cache hit: second fetch within TTL does not call network | ✅ |
79
+ | Cache miss: fetch after TTL calls network | ✅ |
80
+
81
+ ## Analytics
82
+
83
+ | Test | Status |
84
+ |------|--------|
85
+ | track() records event | ✅ |
86
+ | Session ID injected into every event | ✅ |
87
+ | reset() regenerates session ID | ✅ |
88
+
89
+ ## CrashReporting
90
+
91
+ | Test | Status |
92
+ |------|--------|
93
+ | recordCrash() records crash | ✅ |
94
+ | leaveBreadcrumb() records breadcrumb | ✅ |
95
+ | Breadcrumb buffer limited to 20 | ✅ |
96
+ | clearBreadcrumbs() empties buffer | ✅ |
97
+ | Breadcrumbs included in crash record | ✅ |
98
+
99
+ ## WebSocket
100
+
101
+ | Test | Status |
102
+ |------|--------|
103
+ | Connects successfully | ✅ |
104
+ | Sends and receives text messages | ✅ |
105
+ | onBinaryMessage() callback accessible via interface type | ✅ |
106
+ | simulateBinaryMessage() emits to binary callback | ✅ |
107
+ | dispose() closes connection | ✅ |
108
+ | dispose() is idempotent | ✅ |
109
+
110
+ ## Idempotency Tests
111
+
112
+ | Module | Scenario | Status |
113
+ |--------|----------|--------|
114
+ | networking | `dispose()` called twice does not throw | ❌ (test not yet implemented) |
115
+ | websocket | `disconnect()` called before `connect()` does not throw | ❌ (test not yet implemented) |
116
+ | websocket | `disconnect()` called twice does not throw | ❌ (test not yet implemented) |
117
+ | auth | `signOut()` called when already signed out does not throw | ❌ (test not yet implemented) |
118
+ | persistence | `clear()` called on empty store does not throw | ❌ (test not yet implemented) |
package/README.md CHANGED
@@ -1,12 +1,8 @@
1
- ![CI](https://github.com/Syzygy-Hub/syzygy-services-rn/actions/workflows/ci.yml/badge.svg)
2
- ![version](https://img.shields.io/badge/version-1.0.0-blue)
3
- ![platform](https://img.shields.io/badge/platform-React%20Native%20%7C%20TypeScript-61DAFB)
4
- ![license](https://img.shields.io/badge/license-MIT-green)
1
+ [![React Native](https://img.shields.io/badge/React%20Native-TypeScript-61DAFB?style=flat)](https://reactnative.dev) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-3178C6?logo=typescript&logoColor=white&style=flat)](https://www.typescriptlang.org) [![CI](https://img.shields.io/github/actions/workflow/status/Syzygy-Hub/syzygy-services-rn/ci.yml?label=ci&style=flat)](https://github.com/Syzygy-Hub/syzygy-services-rn/actions/workflows/ci.yml) [![Version](https://img.shields.io/badge/version-1.2.0-D85A30?style=flat)](https://github.com/Syzygy-Hub/syzygy-services-rn/releases) [![License](https://img.shields.io/badge/License-MIT-green?style=flat)](LICENSE)
5
2
 
6
3
  <picture>
7
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Syzygy-Hub/.github/main/assets/syzygy-banner-dark.png">
8
- <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Syzygy-Hub/.github/main/assets/syzygy-banner-light.png">
9
- <img alt="Syzygy Banner" src="https://raw.githubusercontent.com/Syzygy-Hub/.github/main/assets/syzygy-banner-light.png">
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Syzygy-Hub/.github/main/brand/assets/banners/syzygy-banner-dark-1200.png">
5
+ <img src="https://raw.githubusercontent.com/Syzygy-Hub/.github/main/brand/assets/banners/syzygy-banner-light-1200.png" alt="Syzygy" width="600">
10
6
  </picture>
11
7
 
12
8
  # syzygy-services-rn
@@ -19,14 +15,14 @@ Concrete I/O service implementations for the Syzygy React Native ecosystem — n
19
15
  |--------|-----------|----------------|-------------|
20
16
  | `networking` | `NetworkClient` | `FetchNetworkClient` | HTTP GET/POST via global `fetch` |
21
17
  | `persistence` | `StorageProvider` | `InMemoryStorageProvider` | Key-value storage (swap with AsyncStorage) |
22
- | `auth` | `AuthProvider` | `JWTAuthProvider` | JWT token storage and refresh stub |
18
+ | `auth` | `AuthProvider` | `JWTAuthProvider` | JWT token storage and refresh |
23
19
  | `filemanagement` | `FileProvider` | `NodeFileProvider` | File read/write/delete (swap with RNFS) |
24
- | `pushnotifications` | `PushProvider` | `InMemoryPushProvider` | Push token registration stub |
20
+ | `pushnotifications` | `PushProvider` | `InMemoryPushProvider` | Push token registration |
25
21
  | `deviceservices` | `DeviceProvider` | `NodeDeviceProvider` | Hostname, platform, OS version |
26
- | `remoteconfig` | `RemoteConfigProvider` | `InMemoryRemoteConfigProvider` | In-memory remote config store |
27
- | `analytics` | `AnalyticsProvider` | `ConsoleAnalyticsProvider` | Console-based event logging stub |
28
- | `crashreporting` | `CrashReporter` | `ConsoleCrashReporter` | Console-based crash/error logging stub |
29
- | `websocket` | `WebSocketProvider` | `NativeWebSocketProvider` | Native WebSocket implementation stub |
22
+ | `remoteconfig` | `RemoteConfigProvider` | `InMemoryRemoteConfigProvider`, `NetworkRemoteConfigProvider` | In-memory remote config store |
23
+ | `analytics` | `ExtendedAnalyticsProvider` | `ConsoleAnalyticsProvider`, `InMemoryAnalyticsProvider` | Console-based event logging |
24
+ | `crashreporting` | `CrashReporter` | `ConsoleCrashReporter`, `InMemoryCrashReporter` | Console-based crash/error logging |
25
+ | `websocket` | `WebSocketProvider` | `NativeWebSocketProvider`, `InMemoryWebSocketProvider` | Native WebSocket implementation |
30
26
 
31
27
  ## Installation
32
28
 
@@ -42,7 +38,7 @@ npm install syzygy-services-rn
42
38
 
43
39
  ## Dependencies
44
40
 
45
- - `syzygy-foundation-rn` ^1.1.0
41
+ - `syzygy-foundation-rn` ^1.2.0
46
42
 
47
43
  ## Ecosystem
48
44
 
@@ -53,6 +49,72 @@ npm install syzygy-services-rn
53
49
  | `syzygy-foundation-rn` | foundation | Primitives, utilities, base types |
54
50
  | `syzygy-services-rn` | services | I/O service interfaces and implementations |
55
51
 
52
+ ## Push Notifications
53
+
54
+ `syzygy-services-rn` ships `InMemoryPushProvider` as a zero-dependency stub and documents the steps required to wire up a real FCM/APNs push layer.
55
+
56
+ ### FCM (Android + iOS via Firebase Cloud Messaging)
57
+
58
+ 1. Install `@react-native-firebase/app` and `@react-native-firebase/messaging`.
59
+ 2. Add `google-services.json` (Android) and `GoogleService-Info.plist` (iOS) to your project roots following the Firebase Console setup wizard.
60
+ 3. Request permission and register the token:
61
+ ```ts
62
+ import messaging from '@react-native-firebase/messaging';
63
+
64
+ const granted = await messaging().requestPermission();
65
+ if (granted) {
66
+ const token = await messaging().getToken();
67
+ pushProvider.registerToken(token);
68
+ }
69
+ ```
70
+ 4. Handle foreground messages:
71
+ ```ts
72
+ messaging().onMessage(async (remoteMessage) => {
73
+ const payload = createNotificationPayload(
74
+ remoteMessage.notification?.title ?? '',
75
+ remoteMessage.notification?.body ?? '',
76
+ remoteMessage.data ?? {},
77
+ );
78
+ pushProvider.simulateNotification(payload); // or forward to your UI
79
+ });
80
+ ```
81
+ 5. Handle background / quit-state messages with `messaging().setBackgroundMessageHandler()`.
82
+
83
+ ### APNs (iOS native push)
84
+
85
+ 1. Enable the **Push Notifications** capability in Xcode (Signing & Capabilities tab).
86
+ 2. Create an APNs Authentication Key (.p8) or APNs certificate in the Apple Developer portal and upload it to your backend or Firebase console.
87
+ 3. Retrieve the APNs device token:
88
+ ```ts
89
+ import { getAPNSToken } from '@react-native-firebase/messaging';
90
+ const apnsToken = await getAPNSToken();
91
+ ```
92
+ 4. Exchange the APNs token for an FCM token when using Firebase as the delivery layer.
93
+ 5. Handle incoming APNs notifications via `UNUserNotificationCenterDelegate` (native) or the Firebase `onMessage` / `onNotificationOpenedApp` callbacks.
94
+
95
+ ### Notification payload factory helpers
96
+
97
+ ```ts
98
+ import {
99
+ createNotificationPayload,
100
+ createAlertNotification,
101
+ createMessageNotification,
102
+ createSilentNotification,
103
+ } from 'syzygy-services-rn';
104
+
105
+ // Generic payload
106
+ const payload = createNotificationPayload('Title', 'Body', { key: 'value' });
107
+
108
+ // Alert notification
109
+ const alert = createAlertNotification('Alert', 'Something happened');
110
+
111
+ // Chat message notification with deep-link thread id
112
+ const msg = createMessageNotification('Alice', 'Hey!', 'thread-42');
113
+
114
+ // Silent (data-only) push for background processing
115
+ const silent = createSilentNotification({ action: 'sync' });
116
+ ```
117
+
56
118
  ## License
57
119
 
58
120
  MIT
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Contract compliance tests.
3
+ *
4
+ * Verifies that every concrete service class:
5
+ * 1. Can be instantiated without throwing.
6
+ * 2. Implements (structurally) the expected Foundation protocol.
7
+ * 3. Returns the expected type from its primary method.
8
+ *
9
+ * These tests are intentionally lightweight — they guard against accidental
10
+ * API breakage rather than testing behaviour (covered by the per-module suites).
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=ContractCompliance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContractCompliance.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ContractCompliance.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}