Skip to content

Commit e12280a

Browse files
committed
feat: add React Native SDK preview
0 parents  commit e12280a

41 files changed

Lines changed: 9615 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
validate:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: pnpm/action-setup@v4
17+
with:
18+
version: 11.10.0
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: 24
22+
cache: pnpm
23+
- run: pnpm install --frozen-lockfile
24+
- run: pnpm validate

‎.gitignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
dist/
3+
coverage/
4+
.DS_Store
5+
*.tgz

‎.npmignore‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
assets/
2+
example/
3+
scripts/
4+
src/
5+
test/
6+
tsconfig.json
7+
tsup.config.ts
8+
vitest.config.ts

‎AGENTS.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SeatLayer React Native agent guide
2+
3+
This repository is the public React Native SDK. The private SeatLayer platform
4+
repository is not a dependency and must never be referenced from public docs,
5+
metadata or release manifests.
6+
7+
Before changing the bridge, read:
8+
9+
- `docs/bridge.md`
10+
- `src/bridge/envelope.ts`
11+
- `src/bridge/protocol.ts`
12+
- `src/bridge/client.ts`
13+
- `src/controller.ts`
14+
15+
Invariants:
16+
17+
1. Treat every enum, event name and payload as an open, forward-compatible set.
18+
2. Never interpolate bridge payloads as executable JavaScript.
19+
3. Register command correlation before sending.
20+
4. Drop late replies and stale per-event sequences.
21+
5. Fail incompatible protocol ranges before chart construction.
22+
6. Keep booking and secret keys on the integrator's trusted backend.
23+
7. Run `pnpm validate` before committing.
24+
25+
Canonical resources:
26+
27+
- Docs: https://docs.seatlayer.io/buyer-sdk/mobile/
28+
- Agent index: https://docs.seatlayer.io/llms.txt
29+
- AI Toolkit: https://github.com/seatlayer/seatlayer-ai-toolkit
30+
- Web SDK: https://github.com/seatlayer/seatlayer-sdk

‎CHANGELOG.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
3+
## 0.1.0
4+
5+
- Initial React Native public preview.
6+
- Typed iOS and Android WebView component.
7+
- Version-negotiated SeatLayer bridge with correlation, timeout and stale-event
8+
protection.
9+
- Selection, holds, best available, general admission, floors, zoom,
10+
accessibility controls and typed events.
11+
- Expo-compatible example and vendored `seatlayer-js@0.30.1` buyer bundle.

‎CONTRIBUTING.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Contributing
2+
3+
1. Install Node.js 20.19.4 or newer and pnpm 11.10.
4+
2. Run `pnpm install`.
5+
3. Make focused changes with tests.
6+
4. Run `pnpm validate`.
7+
5. Open a pull request describing behavior, compatibility and verification.
8+
9+
The WebView bridge is shared across SeatLayer mobile SDKs. Preserve envelope
10+
versioning, protocol negotiation, command correlation, per-event ordering,
11+
unknown-field tolerance and the server-side booking boundary.

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 SeatLayer
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.md‎

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# SeatLayer for React Native
2+
3+
[![npm](https://img.shields.io/npm/v/@seatlayer/react-native?label=%40seatlayer%2Freact-native)](https://www.npmjs.com/package/@seatlayer/react-native)
4+
[![React Native](https://img.shields.io/badge/React%20Native-%E2%89%A50.72-61DAFB.svg)](https://reactnative.dev/)
5+
[![Expo](https://img.shields.io/badge/Expo-compatible-000020.svg)](https://expo.dev/)
6+
[![License: MIT](https://img.shields.io/badge/license-MIT-111827.svg)](LICENSE)
7+
8+
The official React Native SDK for embedding interactive SeatLayer
9+
reserved-seating maps in iOS and Android apps. It provides typed selection,
10+
holds, best available, general admission, multi-floor controls, errors and
11+
events over a versioned WebView bridge.
12+
13+
[Developer docs](https://docs.seatlayer.io/buyer-sdk/mobile/) ·
14+
[Live demo](https://app.seatlayer.io/demo/play) ·
15+
[Website](https://seatlayer.io/developers/) ·
16+
[Web SDK](https://github.com/seatlayer/seatlayer-sdk) ·
17+
[Flutter SDK](https://pub.dev/packages/seatlayer) ·
18+
[iOS SDK](https://github.com/seatlayer/seatlayer-ios) ·
19+
[AI Toolkit](https://github.com/seatlayer/seatlayer-ai-toolkit)
20+
21+
> **Public preview:** Validate `0.1.x` using a SeatLayer test event and physical
22+
> iOS and Android devices before production rollout.
23+
24+
## Install
25+
26+
### Expo
27+
28+
```bash
29+
npm install @seatlayer/react-native
30+
npx expo install react-native-webview
31+
```
32+
33+
No custom native SeatLayer module is used, so this SDK works with Expo Go when
34+
the installed Expo SDK includes `react-native-webview`.
35+
36+
### React Native Community CLI
37+
38+
```bash
39+
npm install @seatlayer/react-native react-native-webview
40+
npx pod-install
41+
```
42+
43+
React Native autolinks `react-native-webview` on Android and iOS.
44+
45+
## Quick start
46+
47+
Give the map a definite height or a full-screen parent. Keep the configuration
48+
object stable so React rerenders do not reload the chart.
49+
50+
```tsx
51+
import React, { useEffect, useMemo } from 'react';
52+
import { View } from 'react-native';
53+
import {
54+
SeatLayerError,
55+
SeatLayerView,
56+
useSeatLayerController,
57+
} from '@seatlayer/react-native';
58+
59+
export function SeatMapScreen() {
60+
const controller = useSeatLayerController();
61+
const configuration = useMemo(
62+
() => ({
63+
event: 'ev_your_event_key',
64+
currency: 'USD',
65+
maxSelection: 8,
66+
}),
67+
[],
68+
);
69+
70+
useEffect(
71+
() =>
72+
controller.on('selectionChanged', (seats) => {
73+
console.log('Selected seats', seats);
74+
}),
75+
[controller],
76+
);
77+
78+
return (
79+
<View style={{ flex: 1 }}>
80+
<SeatLayerView
81+
style={{ flex: 1 }}
82+
controller={controller}
83+
configuration={configuration}
84+
onReady={(info) => {
85+
console.log(
86+
`SeatLayer ready: protocol=${info.protocolRevision} mode=${info.mode}`,
87+
);
88+
}}
89+
onLoadError={(error) => {
90+
console.error(error.code, error.message);
91+
}}
92+
/>
93+
</View>
94+
);
95+
}
96+
```
97+
98+
Drive checkout-related actions through the controller:
99+
100+
```tsx
101+
try {
102+
const hold = await controller.bestAvailable(4);
103+
if (hold) {
104+
// Send only the hold id to your trusted backend.
105+
await beginCheckoutOnServer(hold.holdId);
106+
}
107+
} catch (error) {
108+
if (error instanceof SeatLayerError) {
109+
showInventoryMessage(error.code, error.message);
110+
}
111+
}
112+
```
113+
114+
## Commands
115+
116+
`hold` · `resumeHold` · `extendHold` · `release` · `releaseLabels` ·
117+
`bestAvailable` · `holdGA` · `setSeatTier` · `getSelection` ·
118+
`getCurrentHold` · `getGAAreas` · `getFloors` · `setFloor` ·
119+
`setColorblindSafe` · `zoomIn` · `zoomOut` · `zoomToFit` · `destroy`
120+
121+
All asynchronous command failures reject with `SeatLayerError`. Inventory
122+
outcomes such as `sold_out`, `not_enough_together`, expired holds and conflicts
123+
remain distinct codes suitable for buyer-facing recovery.
124+
125+
## Events
126+
127+
Subscribe with `controller.on(name, listener)`. The returned function removes
128+
the listener.
129+
130+
```tsx
131+
useEffect(() => {
132+
const offHold = controller.on('holdChanged', persistOpenHold);
133+
const offExpired = controller.on('holdExpired', returnBuyerToSelection);
134+
const offError = controller.on('error', reportSeatLayerError);
135+
return () => {
136+
offHold();
137+
offExpired();
138+
offError();
139+
};
140+
}, [controller]);
141+
```
142+
143+
Events: `ready` · `selectionChanged` · `holdChanged` · `holdRestored` ·
144+
`holdExpired` · `error` · `hint` · `gaClick` · `seatHover` · `deckTap` ·
145+
`checkout` · `unknownEvent`
146+
147+
Unknown future events remain observable through `unknownEvent`; adding a bundle
148+
event does not crash an older app.
149+
150+
## Security boundary
151+
152+
The app selects and holds inventory. Your trusted backend validates payment,
153+
inspects the hold and creates the booking.
154+
155+
- Never ship a SeatLayer secret key in JavaScript, the app bundle or WebView.
156+
- Send only `holdId` and normal checkout context to your backend.
157+
- Calculate the amount from server-inspected hold items, not device input.
158+
- Use a stable order id as the booking reference for safe retries.
159+
- Do not enable arbitrary navigation inside the SDK WebView.
160+
161+
Read [holds and checkout](https://docs.seatlayer.io/buyer-sdk/holds-and-checkout/)
162+
before connecting a payment flow.
163+
164+
## How the bridge works
165+
166+
The npm package embeds the verified `seatlayer-js@0.30.1` bundle in generated
167+
inline HTML. This avoids the inconsistent local-file behavior of iOS and Android
168+
WebViews while keeping the SDK JavaScript independent of a runtime CDN download.
169+
Chart data and live inventory still come from the configured SeatLayer API.
170+
171+
The protocol guarantees:
172+
173+
- range-negotiated compatibility before rendering;
174+
- one response per command using correlation ids;
175+
- native command timeouts and late-reply rejection;
176+
- monotonic event ordering per event type; and
177+
- forward-compatible unknown events and fields.
178+
179+
See [the bridge contract](docs/bridge.md) for the wire-level details.
180+
181+
## Layout and lifecycle
182+
183+
- Use a fixed-height or full-screen parent; do not put the map inside a vertical
184+
`ScrollView`.
185+
- Keep `configuration` stable with `useMemo`.
186+
- Change `reloadKey` to deliberately rebuild the WebView.
187+
- `useSeatLayerController` disposes the controller automatically.
188+
- Persist an open `holdId` and call `resumeHold` after app restoration.
189+
190+
## Development
191+
192+
```bash
193+
pnpm install
194+
pnpm validate
195+
cd example && pnpm install && pnpm start
196+
```
197+
198+
`pnpm validate` regenerates the embedded document, type-checks, runs protocol
199+
tests, builds ESM/CommonJS/types, and validates the npm tarball.
200+
201+
## Related resources
202+
203+
- [React Native mobile guide](https://docs.seatlayer.io/buyer-sdk/mobile/)
204+
- [Buyer SDK installation](https://docs.seatlayer.io/buyer-sdk/install/)
205+
- [Complete checkout example](https://docs.seatlayer.io/examples/complete-checkout/)
206+
- [Agent-readable documentation](https://docs.seatlayer.io/llms.txt)
207+
- [SeatLayer GitHub organization](https://github.com/seatlayer)
208+
209+
## License
210+
211+
MIT © SeatLayer

‎SECURITY.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Security
2+
3+
Report security issues privately through
4+
[GitHub Security Advisories](https://github.com/seatlayer/seatlayer-react-native/security/advisories/new).
5+
Do not open a public issue for a suspected vulnerability.
6+
7+
The React Native SDK is a public-client integration. It may select and hold
8+
inventory, but it must never contain a SeatLayer secret key or create bookings
9+
directly. Booking belongs on a trusted backend after payment or order
10+
validation.

‎assets/index.html‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover">
6+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; connect-src https: wss: http://localhost:* ws://localhost:*; style-src 'unsafe-inline'; img-src data: blob: https:; font-src data: https:;">
7+
<title>SeatLayer</title>
8+
<style>
9+
html, body {
10+
margin: 0;
11+
padding: 0;
12+
height: 100%;
13+
width: 100%;
14+
overflow: hidden;
15+
background: transparent;
16+
-webkit-user-select: none;
17+
user-select: none;
18+
-webkit-touch-callout: none;
19+
-webkit-tap-highlight-color: transparent;
20+
overscroll-behavior: none;
21+
}
22+
#seatlayer-root {
23+
position: absolute;
24+
inset: 0;
25+
touch-action: none;
26+
}
27+
</style>
28+
<script>
29+
/*__SEATLAYER_BUNDLE__*/
30+
</script>
31+
</head>
32+
<body>
33+
<div id="seatlayer-root"></div>
34+
<script>
35+
(function () {
36+
if (!window.seatlayer || typeof window.seatlayer.startBridge !== 'function') {
37+
return;
38+
}
39+
window.__slHandle = window.seatlayer.startBridge({
40+
container: document.getElementById('seatlayer-root')
41+
});
42+
})();
43+
</script>
44+
</body>
45+
</html>

0 commit comments

Comments
 (0)