@betterreviews/react-native 1.0.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 (41) hide show
  1. package/LICENSE +145 -0
  2. package/README.md +189 -0
  3. package/SECURITY.md +238 -0
  4. package/dist/index.d.mts +581 -0
  5. package/dist/index.d.ts +581 -0
  6. package/dist/index.js +2384 -0
  7. package/dist/index.mjs +2346 -0
  8. package/package.json +78 -0
  9. package/src/BetterReviewsProvider.tsx +62 -0
  10. package/src/ProductContentBlock.tsx +143 -0
  11. package/src/StarRating.tsx +85 -0
  12. package/src/WebViewHost.tsx +164 -0
  13. package/src/bridge.ts +48 -0
  14. package/src/client/createBetterReviewsClient.ts +211 -0
  15. package/src/client/types.ts +101 -0
  16. package/src/icons/BRIcons.tsx +176 -0
  17. package/src/index.ts +74 -0
  18. package/src/minSdkVersion.ts +52 -0
  19. package/src/sections/FeaturesSection.tsx +69 -0
  20. package/src/sections/ReviewsSummarySection.tsx +47 -0
  21. package/src/telemetry.ts +52 -0
  22. package/src/theme/applyTheme.ts +72 -0
  23. package/src/theme/widgetTheme.ts +67 -0
  24. package/src/webviewMessage.ts +23 -0
  25. package/src/widget/ReviewWidget.tsx +230 -0
  26. package/src/widget/WidgetContext.tsx +43 -0
  27. package/src/widget/components/FilterToolbar.tsx +146 -0
  28. package/src/widget/components/MediaGallery.tsx +53 -0
  29. package/src/widget/components/PulseSection.tsx +69 -0
  30. package/src/widget/components/RatingStars.tsx +40 -0
  31. package/src/widget/components/ReviewCard.tsx +114 -0
  32. package/src/widget/components/SortDrawer.tsx +49 -0
  33. package/src/widget/components/StaleListOverlay.tsx +51 -0
  34. package/src/widget/components/VoteButtons.tsx +55 -0
  35. package/src/widget/hooks/useReviewDetail.ts +55 -0
  36. package/src/widget/hooks/useReviewList.ts +136 -0
  37. package/src/widget/hooks/useReviewSummary.ts +24 -0
  38. package/src/widget/hooks/useVote.ts +68 -0
  39. package/src/widget/styles.ts +393 -0
  40. package/src/widget/util.ts +21 -0
  41. package/src/widget/viewer/MediaReviewViewer.tsx +350 -0
package/LICENSE ADDED
@@ -0,0 +1,145 @@
1
+ BetterReviews Proprietary License (BRPL) v1
2
+ Copyright (c) 2026 BetterReviews ("Licensor")
3
+
4
+ 1. DEFINITIONS
5
+
6
+ "Software" means the `@betterreviews/react-native` npm package, including
7
+ its source code, generated artifacts, type definitions, and accompanying
8
+ documentation.
9
+
10
+ "Authorized Partner" means an entity that has executed a written
11
+ integration agreement with Licensor that explicitly names this Software
12
+ and grants right of use. As of the date of this LICENSE, the sole
13
+ Authorized Partner is Reactiv ("Reactiv Technologies Ltd." or its
14
+ successor entity).
15
+
16
+ "Production Use" means embedding or executing the Software in any
17
+ application distributed to end users, whether commercial or non-
18
+ commercial, paid or free.
19
+
20
+ "Evaluation Use" means executing the Software (a) against the BetterReviews
21
+ public mock endpoint with synthetic data only, or (b) against a
22
+ Licensor-issued sandbox credential, in either case strictly for the
23
+ purpose of evaluating the Software for a future integration agreement.
24
+
25
+ 2. GRANT OF LICENSE
26
+
27
+ 2.1. To Authorized Partners. Subject to the terms of this LICENSE and
28
+ the partner's written integration agreement, Licensor grants each
29
+ Authorized Partner a non-exclusive, non-transferable, non-sublicensable,
30
+ revocable license to install, execute, and embed the Software for
31
+ Production Use solely within the scope defined in that agreement.
32
+
33
+ 2.2. To Evaluators. Licensor grants any entity a non-exclusive,
34
+ non-transferable, non-sublicensable, revocable license to install and
35
+ execute the Software for Evaluation Use only. Evaluation Use does not
36
+ authorize Production Use, redistribution, modification, or any use
37
+ beyond pre-integration evaluation.
38
+
39
+ 3. RESTRICTIONS
40
+
41
+ You may not, and shall not permit any third party to:
42
+
43
+ (a) redistribute, sublicense, sell, lend, rent, or otherwise transfer
44
+ the Software, in source or compiled form, to any party other than
45
+ end users of an Authorized Partner's application;
46
+
47
+ (b) modify, adapt, translate, or create derivative works of the
48
+ Software, except for the minimum integration code required to wire
49
+ the Software into the Authorized Partner's application, and except
50
+ for bug fixes contributed back to Licensor under section 5;
51
+
52
+ (c) reverse engineer, decompile, or disassemble the Software, except
53
+ to the extent that applicable law expressly permits such activity
54
+ notwithstanding this restriction;
55
+
56
+ (d) remove, alter, or obscure any copyright, trademark, license, or
57
+ attribution notices contained in the Software;
58
+
59
+ (e) use the Software to build, train, or evaluate a product that
60
+ competes with the BetterReviews review-collection or review-display
61
+ platform;
62
+
63
+ (f) use the Software in any manner that would cause the Software to
64
+ become subject to an open-source license that requires disclosure
65
+ of any derivative work;
66
+
67
+ (g) extend Production Use beyond the scope granted in the partner's
68
+ written integration agreement.
69
+
70
+ 4. OWNERSHIP
71
+
72
+ The Software is licensed, not sold. Licensor retains all right, title,
73
+ and interest in and to the Software, including all intellectual
74
+ property rights. No rights are granted to any trademark, service mark,
75
+ or trade name of Licensor except as expressly stated in a separate
76
+ written agreement.
77
+
78
+ 5. CONTRIBUTIONS
79
+
80
+ If you submit a bug report, feature request, patch, or other
81
+ contribution to the Software, you grant Licensor a worldwide,
82
+ perpetual, irrevocable, royalty-free, sublicensable license to use,
83
+ modify, distribute, and incorporate that contribution into the
84
+ Software under any terms Licensor chooses, including under this
85
+ LICENSE.
86
+
87
+ 6. TERMINATION
88
+
89
+ 6.1. This LICENSE terminates automatically upon (a) any material
90
+ breach by you of these terms, or (b) termination or expiration of
91
+ the underlying written integration agreement, whichever occurs first.
92
+
93
+ 6.2. Licensor may terminate this LICENSE for any reason with thirty
94
+ (30) days' written notice. On termination, you must cease all use
95
+ of the Software and remove the Software from any application under
96
+ your control within thirty (30) days.
97
+
98
+ 6.3. Sections 3, 4, 5, 7, 8, and 9 survive termination.
99
+
100
+ 7. CONFIDENTIALITY
101
+
102
+ The Software and any non-public information about its internal
103
+ structure, behavior, or roadmap is confidential to Licensor. You
104
+ shall protect such information with at least the same degree of care
105
+ you use for your own confidential information of similar
106
+ sensitivity, and in no event with less than reasonable care. This
107
+ obligation does not apply to information that is or becomes
108
+ publicly available through no fault of yours.
109
+
110
+ 8. WARRANTY DISCLAIMER
111
+
112
+ THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE," WITHOUT WARRANTY
113
+ OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
114
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
115
+ NON-INFRINGEMENT, AND ACCURACY OF DATA. LICENSOR DOES NOT WARRANT
116
+ THAT THE SOFTWARE WILL OPERATE UNINTERRUPTED OR ERROR-FREE.
117
+
118
+ 9. LIMITATION OF LIABILITY
119
+
120
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL
121
+ LICENSOR BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
122
+ CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF PROFITS, REVENUE,
123
+ DATA, USE, OR GOODWILL, ARISING OUT OF OR IN CONNECTION WITH THIS
124
+ LICENSE OR THE SOFTWARE, WHETHER BASED IN CONTRACT, TORT (INCLUDING
125
+ NEGLIGENCE), STRICT LIABILITY, OR ANY OTHER THEORY, EVEN IF
126
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LICENSOR'S TOTAL
127
+ AGGREGATE LIABILITY UNDER THIS LICENSE SHALL NOT EXCEED ONE
128
+ HUNDRED U.S. DOLLARS ($100).
129
+
130
+ 10. GOVERNING LAW
131
+
132
+ This LICENSE is governed by the laws of England and Wales, without
133
+ regard to its conflict-of-laws principles. The courts located in
134
+ London, England shall have exclusive jurisdiction over any dispute
135
+ arising out of or relating to this LICENSE.
136
+
137
+ 11. ENTIRE AGREEMENT
138
+
139
+ This LICENSE, together with any executed written integration
140
+ agreement between you and Licensor, constitutes the entire
141
+ agreement between the parties with respect to the Software and
142
+ supersedes all prior or contemporaneous oral or written
143
+ communications, proposals, and representations.
144
+
145
+ For licensing inquiries, contact: legal@betterreviews.app
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # @betterreviews/react-native
2
+
3
+ Native React Native renderer for BetterReviews mobile PDP content. Consumes the `betterreviews_reactiv.*` Shopify metafield namespace and renders product content blocks themed to the merchant.
4
+
5
+ ## Authorized Partner
6
+
7
+ This package is licensed for use by **Reactiv** under a written integration agreement with BetterReviews. See [LICENSE](./LICENSE).
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ yarn add @betterreviews/react-native \
13
+ react-native-webview react-native-svg react-native-gesture-handler
14
+ ```
15
+
16
+ Peer dependencies your host app must provide: `react ≥18`, `react-native ≥0.74`, `react-native-webview ≥13`, `react-native-svg ≥15`, and `react-native-gesture-handler ≥2.16`. `valibot` is bundled as a direct dependency — you don't install it.
17
+
18
+ **`react-native-gesture-handler` setup (required by `ReviewWidget`'s media viewer):** import it as the **very first line** of your app entry (`index.js`/`index.ts`) and wrap your app root in `GestureHandlerRootView`:
19
+
20
+ ```tsx
21
+ import 'react-native-gesture-handler'; // must be first
22
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
23
+
24
+ export default function Root() {
25
+ return <GestureHandlerRootView style={{ flex: 1 }}>{/* app */}</GestureHandlerRootView>;
26
+ }
27
+ ```
28
+
29
+ > **npm users:** React Native 0.74 ships a mismatched `@types/react` peer range, so a plain `npm install` may fail with `ERESOLVE`. This is an upstream React Native quirk, not specific to this package — install with `npm install --legacy-peer-deps`, or use Yarn (which is more lenient). Yarn is recommended for React Native projects.
30
+
31
+ ## Quick start
32
+
33
+ ```tsx
34
+ import {
35
+ BetterReviewsProvider,
36
+ ProductContentBlock,
37
+ type Theme,
38
+ type Config,
39
+ type ProductContentBlockSchema,
40
+ } from '@betterreviews/react-native';
41
+
42
+ function App() {
43
+ // Partner host app fetches the three metafield bodies from Shopify
44
+ // (storefront API or partner-backend proxy) for the active product.
45
+ const theme: Theme | null = useFetchedTheme(productId);
46
+ const config: Config | null = useFetchedConfig(productId);
47
+ const block: ProductContentBlockSchema | null = useFetchedBlock(productId);
48
+
49
+ return (
50
+ <BetterReviewsProvider
51
+ theme={theme}
52
+ config={config}
53
+ onTelemetryEvent={(event) => {
54
+ // Forward to partner's own observability stack.
55
+ partnerAnalytics.log(event);
56
+ }}
57
+ >
58
+ <ProductContentBlock block={block} />
59
+ </BetterReviewsProvider>
60
+ );
61
+ }
62
+ ```
63
+
64
+ ## Render gates
65
+
66
+ `<ProductContentBlock>` renders nothing (returns `null`) when any of these are true:
67
+
68
+ - `config.product_content_block_enabled === false` — merchant explicitly disabled this product
69
+ - `config.min_sdk_version` declared and current SDK below floor — emits `betterreviews.fetch.failure` telemetry
70
+ - `block` is `null` / `undefined`
71
+ - The block envelope fails top-level schema validation — emits `betterreviews.schema.violation` telemetry
72
+
73
+ Per-section validation uses tolerant-reader semantics: an individual malformed section is dropped (with telemetry) while the rest render.
74
+
75
+ ## Telemetry events
76
+
77
+ | Event | When |
78
+ |---|---|
79
+ | `betterreviews.fetch.failure` | Mount blocked by `min_sdk_version` floor (`error_code: "sdk_below_floor"`) |
80
+ | `betterreviews.schema.violation` | Envelope or per-section validation failed |
81
+
82
+ Future versions will emit `betterreviews.fetch.success`, `betterreviews.signature.invalid`, and `betterreviews.webview.error` (the last lands with the WebView host in Card C.12b).
83
+
84
+ ## Theming
85
+
86
+ The widgets are **neutral by default** — a black CTA on zinc grayscale. No brand color appears unless the host opts in by passing a `theme` to `<BetterReviewsProvider>` (`background_color`, `text_color`, `accent_color`, `corner_style`, `font_family`). The only non-grayscale defaults are the **gold stars/bars** and the green **"Verified Buyer"** badge (universal review conventions, fixed in v1).
87
+
88
+ ## StarRating (aggregate badge)
89
+
90
+ `<StarRating>` is the compact rating badge (stars + score + review count) for near the product title — the RN equivalent of the storefront `br-star-rating` block. The host supplies the aggregate (`average` + `total`, typically from the `betterreviews.summary` metafield it already fetches); the badge does **not** call the API.
91
+
92
+ ```tsx
93
+ import { StarRating } from '@betterreviews/react-native';
94
+
95
+ <StarRating average={4.5} total={128} onPress={scrollToReviews} />
96
+ ```
97
+
98
+ Star color comes from `<BetterReviewsProvider>` theme (gold fallback outside a provider); `onPress` makes it a button (e.g. scroll to the `ReviewWidget`); it renders nothing when `total` is 0 (`hideWhenEmpty`, default on).
99
+
100
+ ## ReviewWidget (review browsing + voting)
101
+
102
+ `<ReviewWidget>` renders the full review-browsing surface — paginated list, rating/photo/search filters, sort, read-more, a full-screen media viewer, and helpful/unhelpful voting.
103
+
104
+ The package owns the UI + pagination/sort/filter state. **Your host app owns transport and auth** via an injected `Fetcher`: it prepends the API base URL, injects the widget `token`, and returns parsed JSON (throwing on a non-2xx status). No token ever lives inside this package or your app binary's SDK code — keep it in your host's secure config / backend proxy.
105
+
106
+ ```tsx
107
+ import {
108
+ ReviewWidget,
109
+ createBetterReviewsClient,
110
+ type Fetcher,
111
+ } from '@betterreviews/react-native';
112
+
113
+ const fetcher: Fetcher = async ({ path, query, method = 'GET', body, signal }) => {
114
+ const params = new URLSearchParams();
115
+ for (const [k, v] of Object.entries(query ?? {})) if (v !== undefined) params.set(k, String(v));
116
+ params.set('token', getWidgetTokenFromSecureConfig()); // host-injected; never hardcode
117
+ const res = await fetch(`${API_BASE}${path}?${params}`, {
118
+ method, signal,
119
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
120
+ body: body ? JSON.stringify(body) : undefined,
121
+ });
122
+ if (!res.ok) throw new Error(`widget request failed: HTTP ${res.status}`); // never log the URL — the token is in it
123
+ return res.json();
124
+ };
125
+
126
+ const client = createBetterReviewsClient({ fetcher, storeId, productId });
127
+
128
+ // Theme + telemetry come from <BetterReviewsProvider> context (NOT props).
129
+ <BetterReviewsProvider theme={theme} onTelemetryEvent={partnerAnalytics.log}>
130
+ <ReviewWidget client={client} onWriteReview={openReviewChat} />
131
+ </BetterReviewsProvider>
132
+ ```
133
+
134
+ Notes:
135
+ - **`ReviewWidget` renders inline** — it owns no scroll container, so drop it into your product-page `ScrollView` as one section (e.g. below the product info and `ProductContentBlock`) and it scrolls with the page. Paging is a "Load more" button (no virtualization). The sort drawer and full-screen media viewer are `Modal` overlays, so they work regardless.
136
+ - `onWriteReview` is host-owned (open your chat WebView / nav). The CTA hides if omitted. Never pass server-controlled strings to `Linking.openURL`.
137
+ - Voting persists in-memory by default; pass `voteStateStore` (e.g. AsyncStorage-backed) to persist "already voted" across launches. Store only review id → direction; never review content or the token.
138
+ - The widget requires the `GestureHandlerRootView` root wrap above.
139
+
140
+ ## Security obligations on the host
141
+
142
+ See [SECURITY.md § "What you must do (the host)"](./SECURITY.md) for the contract every embedding host app must honor — credential storage, GDPR cascade, Shopify Level 2 data scope, cache TTL ceiling, Info.plist permissions, logging discipline.
143
+
144
+ ## Versioning
145
+
146
+ This package follows semver. The `betterreviews_reactiv.*` metafield schema (generated JSON Schemas at `elixir/priv/reactiv_schemas/v1/`) follows additive-only compatibility with a 90-day deprecation window — see `schemas/betterreviews-reactiv/COMPATIBILITY.md` at the BetterReviews repo root.
147
+
148
+ ## WebView surface
149
+
150
+ For the customer-facing chat flow (`/review/chat`), use `WebViewHost`:
151
+
152
+ ```tsx
153
+ import { WebViewHost } from '@betterreviews/react-native';
154
+
155
+ <WebViewHost
156
+ url={`https://api.betterreviews.app/review/chat?store_id=${storeId}&product_id=${productId}&token=${token}`}
157
+ onMessage={(event) => console.log('message from chat', event.nativeEvent.data)}
158
+ onError={(event) => console.warn('webview error', event.nativeEvent)}
159
+ onClose={() => dismissChat()} // fires when the page posts {type:'close'} (e.g. "Back to store")
160
+ />
161
+ ```
162
+
163
+ The component intentionally exposes only `{ url, onMessage, onError }`. All underlying `WebView` props (cookie scope, content-inset behavior, keyboard handling, media playback) are locked to the baseline validated by the Tier 2 WebView test (`docs/proposals/reactiv-webview-tier2-test-2026-05-18.md`). Future recovery patches stay surgical.
164
+
165
+ `react-native-webview ≥13.0.0` is a peer dep. Add it to your host app:
166
+
167
+ ```bash
168
+ yarn add react-native-webview
169
+ ```
170
+
171
+ ## Bridge config
172
+
173
+ `config.bridge` declares the merchant's preference for native bridge surfaces:
174
+
175
+ - `"off"` (default): everything renders in-WebView.
176
+ - `"auto"`: native if available, fall back to in-WebView.
177
+ - `"required"`: native; if not available, render nothing.
178
+
179
+ v1 of this package supports `"off"` only. `"auto"` and `"required"` resolve to `"off"` behavior and emit a `betterreviews.fetch.failure` telemetry event with `error_code: "bridge_not_implemented"` so partner observability can surface the unhonored intent. Native bridge implementations land post-soft-launch (Card C.14).
180
+
181
+ ## Not yet shipped
182
+
183
+ - Native (non-WebView) bridge surfaces for the chat flow (Card C.14 — post-soft-launch)
184
+ - Per-surface theming of the widget's fixed neutrals (star/verified/muted/border/scrim) — a later additive `theme` schema bump
185
+ - HMAC signature verification (forward-compat — added when a bidirectional channel emerges)
186
+
187
+ ## License
188
+
189
+ Proprietary. See [LICENSE](./LICENSE). For licensing inquiries: `legal@betterreviews.app`.
package/SECURITY.md ADDED
@@ -0,0 +1,238 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Email `security@betterreviews.app` with a description of the issue, steps
6
+ to reproduce, and any proof-of-concept code. Encrypt sensitive details
7
+ with our PGP key if you prefer (fingerprint: PENDING — published at
8
+ https://betterreviews.app/.well-known/security.txt).
9
+
10
+ **Do not file a public GitHub issue or open a PR for security issues.**
11
+
12
+ We commit to:
13
+
14
+ - Acknowledge receipt within 24 hours.
15
+ - Provide an initial assessment within 72 hours.
16
+ - Coordinate disclosure timing with you. We aim to patch within 14 days
17
+ for critical issues, 30 days for high, 90 days for medium and low.
18
+ - Credit you in our advisory if you wish, after the fix ships.
19
+
20
+ We will not pursue legal action against good-faith security research
21
+ that follows this policy. Specifically, we permit:
22
+
23
+ - Testing against the BetterReviews public mock endpoint
24
+ (`https://api.betterreviews.app/api/v1/reactiv/mock/*`) with synthetic
25
+ data only.
26
+ - Testing against your own sandbox credential.
27
+ - Decompiling or reverse-engineering this package for the sole purpose
28
+ of identifying a vulnerability you intend to report under this policy.
29
+
30
+ We do not permit testing that:
31
+
32
+ - Uses real production credentials issued to a different partner.
33
+ - Accesses, modifies, or attempts to access another merchant's data.
34
+ - Causes service disruption (DoS, fuzzing at scale, etc.).
35
+ - Persists data exfiltrated from BetterReviews beyond what is needed
36
+ to demonstrate the issue.
37
+
38
+ ## Supported versions
39
+
40
+ | Version | Supported |
41
+ |---------|---------------|
42
+ | 1.x | Yes |
43
+ | < 1.0 | Pre-release — no security commitments. Upgrade. |
44
+
45
+ When a v2 ships, v1 remains supported with security patches for 12
46
+ months from the v2 release date.
47
+
48
+ ## What BetterReviews commits to (the wire)
49
+
50
+ The wire between this package and `api.betterreviews.app` is designed
51
+ fail-closed. Specifically:
52
+
53
+ - **HMAC signatures.** Every outbound state-change webhook from
54
+ BetterReviews to a partner endpoint is signed HMAC-SHA256. The
55
+ package verifies signatures using `Plug.Crypto.secure_compare`-
56
+ equivalent constant-time comparison; a missing or invalid signature
57
+ rejects the message.
58
+ - **Per-merchant credentials.** The `reactiv_app_id` issued to a
59
+ partner is scoped per merchant. A leaked credential exposes one
60
+ merchant's content stream, not all merchants.
61
+ - **No PII at rest in the package.** The package does not persist
62
+ customer email, name, IP, or any conversation transcript locally.
63
+ Review content fetched from BetterReviews is rendered and discarded
64
+ on view-change. If your host app caches review content, that is your
65
+ responsibility under the BR data-handling addendum.
66
+ - **Tolerant-reader pattern.** Unknown JSON fields are ignored, never
67
+ echoed back to BR or forwarded to logs. Unknown section types in
68
+ product content blocks are skipped, logged locally, and reported via
69
+ the telemetry hook (see "Observability" below).
70
+ - **WKWebView origin pinning.** The package's WebView host for
71
+ `/review/chat` pins the origin to `*.betterreviews.app` and
72
+ `*.betterreviews.ngrok-free.dev` (development only). Cross-origin
73
+ navigations are blocked.
74
+ - **Schema validation.** Content blocks are validated against the
75
+ generated JSON Schema at `elixir/priv/reactiv_schemas/v1/` before
76
+ render. Schema mismatches are dropped, not partially rendered.
77
+ - **Storage of partner credentials.** BetterReviews stores partner
78
+ credentials encrypted at rest via `PiiVault` (AES-GCM with per-tenant
79
+ key derivation). Plaintext credentials never appear in logs, audit
80
+ rows, or error messages.
81
+
82
+ ## What you must do (the host)
83
+
84
+ Integrating this package into your host app shifts certain obligations
85
+ to you. The integration is not safe unless you do these:
86
+
87
+ ### 1. Store credentials in iOS Keychain / Android Keystore
88
+
89
+ The `reactiv_app_id` and any merchant-specific tokens must be stored
90
+ in the platform-native secure enclave, never in `AsyncStorage`,
91
+ `UserDefaults`, or shared preferences. Example: use
92
+ `react-native-keychain` with `ACCESSIBLE_WHEN_UNLOCKED_THIS_DEVICE_ONLY`.
93
+
94
+ ### 2. Honor the GDPR cascade
95
+
96
+ When a customer requests deletion under GDPR Article 17, you MUST:
97
+
98
+ - Stop calling `/api/v1/reactiv/*` endpoints with that customer's
99
+ identifiers within 30 days of the request.
100
+ - Purge any locally cached review content tied to that customer within
101
+ the same window.
102
+ - Forward the request to BetterReviews via
103
+ `POST /api/v1/reactiv/customer-data-erasure` with the customer's
104
+ email hash (SHA-256 of lowercased email).
105
+
106
+ A separate Data Processing Addendum covers BetterReviews' obligations
107
+ on the upstream side.
108
+
109
+ ### 3. Honor the Shopify Level 2 protected data scope
110
+
111
+ BetterReviews holds Shopify Level 2 protected customer data approval.
112
+ Any review content you render through this package may include data
113
+ classified as protected customer data under Shopify's policies. You
114
+ must ensure your host app's Shopify data-handling commitments cover
115
+ the data your app receives via this package, or terminate display when
116
+ the merchant's Shopify access is revoked.
117
+
118
+ ### 4. Honor the cache TTL ceiling
119
+
120
+ Review content fetched from BetterReviews may not be cached longer
121
+ than 1 hour without re-fetch. This is enforced upstream by the
122
+ `Cache-Control: max-age=3600` header on every `/api/v1/reactiv/content`
123
+ response. The package respects this header on its own internal cache;
124
+ if your host app adds its own cache layer, it must respect the same
125
+ ceiling. (This bound is set by the BetterReviews GDPR remediation lag
126
+ requirement — see the schema doc § 11 gap 8.)
127
+
128
+ ### 5. Configure Info.plist permissions
129
+
130
+ The chat surface uses the iOS native photo picker. Your host app's
131
+ `Info.plist` must declare:
132
+
133
+ - `NSCameraUsageDescription`
134
+ - `NSPhotoLibraryUsageDescription`
135
+ - `NSMicrophoneUsageDescription` (only if you enable the
136
+ voice-input experiment)
137
+
138
+ Without these, the photo step in the chat surface silently fails on
139
+ iOS.
140
+
141
+ ### 6. Do not log review content, customer emails, or partner
142
+ credentials
143
+
144
+ The package emits structured telemetry events via the configured
145
+ telemetry hook (see "Observability"). Those events are pre-redacted.
146
+ If you add your own logs around the package, do not log raw event
147
+ payloads — they may contain review content (untrusted user input) or
148
+ customer identifiers.
149
+
150
+ ### 7. Use mock-mode for development
151
+
152
+ Do not use a production partner credential during development. The
153
+ package supports a mock-mode flag that routes all calls to
154
+ `https://api.betterreviews.app/api/v1/reactiv/mock/*`, which returns
155
+ synthetic data and accepts any well-formed signature. Configure it
156
+ via the `BR_REACTIV_MOCK_MODE=true` environment variable, or
157
+ programmatically via the `mockMode: true` package option.
158
+
159
+ ### 8. Widget read/vote fetcher surface (`ReviewWidget`)
160
+
161
+ `ReviewWidget` reads and votes via the public widget API (`/api/widget/...`),
162
+ authenticated by a widget `token` query param that **your injected `Fetcher`
163
+ supplies** — the token never lives in this package or your binary's SDK code.
164
+ Obligations:
165
+
166
+ - **Inject the token host-side, from secure config — never a `const TOKEN =
167
+ '...'` literal, never committed.** The widget read token is the public,
168
+ store-scoped HMAC token; it is rotated only by a global secret rotation that
169
+ affects every surface, so treat a leak as high-impact and keep it out of the
170
+ app binary (inject from your backend / secure store).
171
+ - **Never log the resolved request URL or the raw fetcher error.** The token
172
+ rides in the query string — a logged URL or an `Error` whose message includes
173
+ the URL leaks it. Throw a coarse message (`HTTP <status>`), not the URL.
174
+ - **Render review content (`body`/`title`/`author`/`tags`/`merchant_reply`) in
175
+ `<Text>` only — never a WebView, `react-native-render-html`, or any HTML
176
+ renderer.** Review text is untrusted, user-authored input; RN `<Text>` renders
177
+ it inert, an HTML renderer does not.
178
+ - **Do not pass server-controlled strings to `Linking.openURL`.** Your
179
+ `onWriteReview` handler is host-owned; keep it to your own URLs.
180
+ - The SDK already drops any media URL that is not `https://` and never sends
181
+ `product_id`-less detail/vote calls (the server scopes detail/vote to the
182
+ product). Don't bypass the client by reading the raw response shape yourself.
183
+
184
+ ## Observability
185
+
186
+ The package emits the following telemetry event types via the
187
+ configured `onTelemetryEvent` hook. All events are pre-redacted; no
188
+ customer PII appears in any payload.
189
+
190
+ | Event | When | Payload (redacted) |
191
+ |---|---|---|
192
+ | `betterreviews.fetch.success` | Successful content fetch | `{block_id, latency_ms, cache_hit}` |
193
+ | `betterreviews.fetch.failure` | Failed content fetch | `{block_id, error_code, retry_attempt}` |
194
+ | `betterreviews.signature.invalid` | HMAC verification failed | `{endpoint, source_ip_hash}` |
195
+ | `betterreviews.schema.violation` | Content/response failed schema validation (one event per response — bad list rows are dropped, the rest render) | `{schema_version, violation_path, dropped_count}` |
196
+ | `betterreviews.webview.error` | WebView navigation error | `{error_code, url_origin}` |
197
+
198
+ Forward these events to your own observability stack. BetterReviews
199
+ also receives an aggregate signal via the heartbeat endpoint (no
200
+ per-event forwarding from your side).
201
+
202
+ ## Cryptographic primitives
203
+
204
+ | Use | Primitive |
205
+ |---|---|
206
+ | Outbound webhook signing | HMAC-SHA256 |
207
+ | Constant-time signature compare | `Plug.Crypto.secure_compare` (Elixir side); `crypto.timingSafeEqual` (Node side); the package uses the platform-native equivalent on RN |
208
+ | TLS | TLS 1.3 required for all `*.betterreviews.app` endpoints |
209
+ | PII at rest | AES-256-GCM via PiiVault (server side); package does not persist PII |
210
+ | Email hash | SHA-256 of `lowercase(email)` |
211
+
212
+ ## Out-of-scope assets
213
+
214
+ The following are intentionally not covered by this policy and should
215
+ not be the target of security research:
216
+
217
+ - Third-party services BetterReviews uses (Anthropic, OpenAI, AWS,
218
+ Cloudflare, Shopify). Report vulnerabilities in those to the vendor
219
+ directly.
220
+ - Sample apps, demo stores, or fixtures distributed alongside this
221
+ package. They exist to demonstrate integration patterns and may
222
+ contain intentionally simplified code.
223
+ - The mock endpoint's synthetic data. By design, it accepts any
224
+ well-formed signature so partner-side dev does not require real
225
+ credentials.
226
+
227
+ ## Contact
228
+
229
+ - Vulnerability reports: `security@betterreviews.app`
230
+ - General partner security questions:
231
+ `partner-security@betterreviews.app`
232
+ - BetterReviews security lead: PENDING (publish name + LinkedIn once
233
+ team grows).
234
+
235
+ ## Acknowledgements
236
+
237
+ We thank the following researchers for responsible disclosure
238
+ (populated post-launch).