Skip to content

Commit 68f1401

Browse files
Merge pull request #1 from seatlayer/codex/react-native-0.2.0
Release React Native SDK 0.2.0
2 parents 93239c7 + 986ac49 commit 68f1401

14 files changed

Lines changed: 527 additions & 920 deletions

File tree

.github/workflows/publish.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,13 @@ jobs:
2828
run: |
2929
package_version="$(node -p "require('./package.json').version")"
3030
test "${GITHUB_REF_NAME#v}" = "$package_version"
31+
- name: Verify deployed hosted runtime
32+
run: |
33+
runtime="$(sed -n "s/.*seatLayerHostedWebVersion = '\(.*\)';/\1/p" src/types.ts)"
34+
metadata="$(curl --fail --silent --show-error "https://cdn.seatlayer.io/seatlayer-js@$runtime/release.json")"
35+
METADATA="$metadata" RUNTIME="$runtime" node -e \
36+
'const m=JSON.parse(process.env.METADATA); if(m.version!==process.env.RUNTIME || m.promotable!==true) process.exit(1)'
37+
curl --fail --silent --show-error --output /dev/null \
38+
"https://cdn.seatlayer.io/seatlayer-js@$runtime/mobile.html"
3139
- run: pnpm validate
3240
- run: npm publish --access public

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## 0.2.0
4+
5+
- Uses the pinned hosted `seatlayer-js@0.66.0/mobile.html` document at
6+
`https://cdn.seatlayer.io`; buyer access tokens must be minted for that exact
7+
allowed origin. Private configuration fails closed unless the bridge advertises
8+
`native-access-provider`.
9+
- Adds programmatic selection/category controls, exact-count validators, typed
10+
validity/access events, selected-object unavailability, and view-mode parity.
11+
- Reloads the WebView when configuration identity changes without serializing
12+
credentials into React keys; callback-only rerenders no longer restart the
13+
handshake.
14+
- Removes the unused legacy inline-document generation pipeline and reports the
15+
production dependency as `seatLayerHostedWebVersion`.
16+
317
## 0.1.3
418

519
- Updated the vendored buyer runtime to `seatlayer-js@0.59.0` (sha256

README.md

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ events over a versioned WebView bridge.
2020
[Native Android SDK](https://github.com/seatlayer/seatlayer-android) ·
2121
[AI Toolkit](https://github.com/seatlayer/seatlayer-ai-toolkit)
2222

23-
> **Public preview:** Validate `0.1.x` using a SeatLayer test event and physical
23+
> **Public preview:** Validate `0.2.x` using a SeatLayer test event and physical
2424
> iOS and Android devices before production rollout.
2525
2626
## Install
@@ -117,8 +117,11 @@ try {
117117

118118
`hold` · `resumeHold` · `extendHold` · `release` · `releaseLabels` ·
119119
`bestAvailable` · `holdGA` · `setSeatTier` · `getSelection` ·
120-
`getCurrentHold` · `getGAAreas` · `getFloors` · `setFloor` ·
121-
`setColorblindSafe` · `zoomIn` · `zoomOut` · `zoomToFit` · `destroy`
120+
`selectObjects` · `deselectObjects` · `clearSelection` · `selectCategories` ·
121+
`deselectCategories` · `setSelectableObjects` · `setMaxSelection` ·
122+
`getSelectionValidity` · `refreshAccess` · `getCurrentHold` · `getGAAreas` ·
123+
`getFloors` · `setFloor` · `setColorblindSafe` · `setViewMode` ·
124+
`getViewMode` · `zoomIn` · `zoomOut` · `zoomToFit` · `destroy`
122125

123126
All asynchronous command failures reject with `SeatLayerError`. Inventory
124127
outcomes such as `sold_out`, `not_enough_together`, expired holds and conflicts
@@ -143,8 +146,10 @@ useEffect(() => {
143146
```
144147

145148
Events: `ready` · `selectionChanged` · `holdChanged` · `holdRestored` ·
146-
`holdExpired` · `error` · `hint` · `gaClick` · `seatHover` · `deckTap` ·
147-
`checkout` · `unknownEvent`
149+
`holdExpired` · `selectionValidityChanged` · `selectionValid` ·
150+
`selectionInvalid` · `selectionLimit` · `accessExpired` · `accessUnavailable` ·
151+
`selectedObjectsUnavailable` · `error` · `hint` · `gaClick` · `seatHover` ·
152+
`deckTap` · `checkout` · `unknownEvent`
148153

149154
Unknown future events remain observable through `unknownEvent`; adding a bundle
150155
event does not crash an older app.
@@ -165,10 +170,15 @@ before connecting a payment flow.
165170

166171
## How the bridge works
167172

168-
The npm package embeds the verified `seatlayer-js@0.59.0` bundle (sha256 `89bc29fb…`) in generated
169-
inline HTML. This avoids the inconsistent local-file behavior of iOS and Android
170-
WebViews while keeping the SDK JavaScript independent of a runtime CDN download.
171-
Chart data and live inventory still come from the configured SeatLayer API.
173+
The SDK loads the immutable hosted `seatlayer-js@0.66.0/mobile.html` document
174+
from `https://cdn.seatlayer.io`. This gives every platform the mintable HTTPS
175+
origin `https://cdn.seatlayer.io` and lets the browser runtime load its pinned
176+
lazy assets. Buyer-access sessions must be minted for that exact allowed origin;
177+
tokens never belong in a URL, JavaScript source, or app logs.
178+
179+
Configure `buyerAccessTokenProvider` with a function that calls your own backend
180+
mint endpoint. Private configuration and selection policies fail closed if the
181+
loaded runtime does not advertise the required bridge capabilities.
172182

173183
The protocol guarantees:
174184

@@ -197,8 +207,9 @@ pnpm validate
197207
cd example && pnpm install && pnpm start
198208
```
199209

200-
`pnpm validate` regenerates the embedded document, type-checks, runs protocol
201-
tests, builds ESM/CommonJS/types, and validates the npm tarball.
210+
`pnpm validate` type-checks, runs protocol tests, builds ESM/CommonJS/types,
211+
and validates the npm tarball. Production loads the exact hosted runtime; the
212+
package no longer generates or ships an unused inline Web document.
202213

203214
## Related resources
204215

assets/index.html

Lines changed: 0 additions & 45 deletions
This file was deleted.

assets/seatlayer.js

Lines changed: 0 additions & 807 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@seatlayer/react-native",
3-
"version": "0.1.3",
3+
"version": "0.2.0",
44
"description": "Official React Native SDK for interactive SeatLayer reserved-seating maps on iOS and Android.",
55
"license": "MIT",
66
"homepage": "https://docs.seatlayer.io/buyer-sdk/mobile/",
@@ -35,14 +35,12 @@
3535
],
3636
"sideEffects": false,
3737
"scripts": {
38-
"generate:web": "node scripts/generate-web-document.mjs",
39-
"prebuild": "pnpm generate:web",
4038
"build": "tsup",
4139
"typecheck": "tsc --noEmit",
4240
"test": "vitest run",
4341
"test:watch": "vitest",
4442
"lint:package": "publint && attw --pack .",
45-
"validate": "pnpm generate:web && pnpm typecheck && pnpm test && pnpm build && pnpm lint:package",
43+
"validate": "pnpm typecheck && pnpm test && pnpm build && pnpm lint:package",
4644
"prepublishOnly": "pnpm validate"
4745
},
4846
"peerDependencies": {

scripts/generate-web-document.mjs

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/SeatLayerView.tsx

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useLayoutEffect, useMemo, useRef } from 'react';
1+
import React, { useLayoutEffect, useRef } from 'react';
22
import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
33
import {
44
WebView,
@@ -9,12 +9,9 @@ import {
99
import type { BridgeTransport } from './bridge/client';
1010
import { encodeEnvelope, type Envelope } from './bridge/envelope';
1111
import { SeatLayerError } from './errors';
12-
import { seatLayerWebDocument } from './generated/webDocument';
13-
import type { ReadyInfo, SeatLayerConfiguration } from './types';
12+
import { seatLayerMobileOrigin, seatLayerMobilePageUrl, type ReadyInfo, type SeatLayerConfiguration } from './types';
1413
import type { SeatLayerController } from './controller';
1514

16-
const documentBaseUrl = 'https://seatlayer.local/';
17-
1815
export interface SeatLayerViewProps {
1916
controller: SeatLayerController;
2017
configuration: SeatLayerConfiguration;
@@ -38,6 +35,17 @@ const NativeWebView = WebView as unknown as React.ForwardRefExoticComponent<
3835
WebViewProps & React.RefAttributes<WebViewHandle>
3936
>;
4037

38+
const configurationIds = new WeakMap<object, number>();
39+
let nextConfigurationId = 0;
40+
41+
function configurationIdentity(configuration: object): number {
42+
const existing = configurationIds.get(configuration);
43+
if (existing !== undefined) return existing;
44+
nextConfigurationId += 1;
45+
configurationIds.set(configuration, nextConfigurationId);
46+
return nextConfigurationId;
47+
}
48+
4149
export function SeatLayerView({
4250
controller,
4351
configuration,
@@ -49,22 +57,27 @@ export function SeatLayerView({
4957
onLoadError,
5058
}: SeatLayerViewProps): React.ReactElement {
5159
const webView = useRef<WebViewHandle | null>(null);
52-
const configurationKey = useMemo(
53-
() => JSON.stringify(configuration),
54-
[configuration],
55-
);
56-
const viewKey = `${configurationKey}:${String(reloadKey ?? '')}`;
60+
const onReadyRef = useRef(onReady);
61+
const onLoadErrorRef = useRef(onLoadError);
62+
onReadyRef.current = onReady;
63+
onLoadErrorRef.current = onLoadError;
64+
// Credentials and provider functions must never be serialized into a React
65+
// key. Depending on the configuration object reloads safely when either is
66+
// replaced, without putting the token in a string, log, or native view key.
67+
const viewKey = `${configurationIdentity(configuration)}:${String(
68+
reloadKey ?? 'seatlayer',
69+
)}`;
5770

5871
useLayoutEffect(() => {
5972
const transport = new ReactNativeWebViewTransport(() => webView.current);
6073
let active = true;
6174
controller.beginHandshake(transport, configuration).then(
6275
(info) => {
63-
if (active) onReady?.(info);
76+
if (active) onReadyRef.current?.(info);
6477
},
6578
(error: unknown) => {
6679
if (!active) return;
67-
onLoadError?.(
80+
onLoadErrorRef.current?.(
6881
error instanceof SeatLayerError
6982
? error
7083
: SeatLayerError.transport('SeatLayer handshake failed.', error),
@@ -78,10 +91,12 @@ export function SeatLayerView({
7891
false,
7992
);
8093
};
81-
}, [configurationKey, controller, onLoadError, onReady, reloadKey]);
94+
}, [configuration, controller, reloadKey]);
8295

8396
const onMessage = (event: WebViewMessageEvent): void => {
84-
controller.ingestRaw(event.nativeEvent.data);
97+
if (event.nativeEvent.url === seatLayerMobilePageUrl) {
98+
controller.ingestRaw(event.nativeEvent.data);
99+
}
85100
};
86101

87102
const reportLoadFailure = (message: string): void => {
@@ -95,8 +110,8 @@ export function SeatLayerView({
95110
testID={testID}
96111
accessibilityLabel={accessibilityLabel}
97112
style={[styles.webView, style]}
98-
source={{ html: seatLayerWebDocument, baseUrl: documentBaseUrl }}
99-
originWhitelist={['*']}
113+
source={{ uri: seatLayerMobilePageUrl }}
114+
originWhitelist={[seatLayerMobileOrigin]}
100115
javaScriptEnabled
101116
domStorageEnabled
102117
mixedContentMode="never"
@@ -118,9 +133,7 @@ export function SeatLayerView({
118133
)
119134
}
120135
onShouldStartLoadWithRequest={(request) =>
121-
request.url === 'about:blank' ||
122-
request.url.startsWith(documentBaseUrl) ||
123-
request.url.startsWith('data:text/html')
136+
request.url === seatLayerMobilePageUrl
124137
}
125138
/>
126139
);

0 commit comments

Comments
 (0)