react-native-krds-game-sdk
v0.1.1
Published
React Native SDK for embedding KRDS web games inside client applications.
Maintainers
Readme
react-native-krds-game-sdk
React Native SDK for embedding KRDS web games inside client applications.
React Native Client
↓
react-native-krds-game-sdk
↓
KrdsGameView
↓
react-native-webview
↓
Web Content1. Overview
KrdsGameView renders KRDS web content inside a React Native application and takes care of
everything a host application would otherwise have to reimplement:
- loads a URL in a WebView with JavaScript and DOM storage enabled,
- shows a loading indicator until the content is ready,
- turns native WebView failures into one typed
GameWebViewErrorshape, - carries structured messages between the web game and React Native,
- keeps callbacks referentially stable so parent re-renders do not restart the WebView.
The SDK deliberately contains no game logic. It is a transport and rendering layer, which is what allows game IDs, configuration, authentication and native modules to be added later without changing the public API. See Architecture in the development guide for the planned phases.
Licensing — this is commercial software. Use in production requires a valid KRDS subscription; see LICENSE.
2. Requirements
| Requirement | Version | | -------------------- | -------------------------------- | | React Native | >= 0.71 (New Architecture ready) | | React | >= 18 | | react-native-webview | >= 13 |
react, react-native and react-native-webview are peer dependencies. They are provided by
the client application and are never bundled into this package — bundling them would create
duplicate copies of React or of the native WebView module at runtime.
3. Installation
npm install react-native-krds-game-sdk react-native-webviewyarn add react-native-krds-game-sdk react-native-webviewFor Expo projects, install the WebView version matched to your SDK release:
npx expo install react-native-webview4. iOS setup
react-native-webview contains native code, so the pods have to be installed:
cd ios && pod installNo changes are required for https:// content. Two cases need extra configuration:
Loading plain http:// URLs (local development servers) — App Transport Security blocks them.
Add an exception to ios/<App>/Info.plist, scoped as narrowly as possible:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>Games that use the camera or microphone — declare the usage descriptions the platform requires, otherwise the application is terminated when the permission is requested:
<key>NSCameraUsageDescription</key>
<string>Used by games that require the camera.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used by games that require the microphone.</string>5. Android setup
Autolinking wires up the WebView, so there is nothing to register manually.
Confirm the internet permission is present in android/app/src/main/AndroidManifest.xml (it is part
of the default React Native template):
<uses-permission android:name="android.permission.INTERNET" />To load plain http:// URLs during development, enable cleartext traffic on the <application>
element — preferably only in the debug manifest:
<application android:usesCleartextTraffic="true" ... >react-native-webview requires minSdkVersion 24 or higher, which is the React Native default.
Jest setup in the client application
This SDK resolves to its CommonJS build under Jest, so it needs no configuration. Its peer
dependency react-native-webview does: it publishes untranspiled ES modules and reaches for its
native module at import time. Any test that renders a screen containing KrdsGameView therefore
needs both of these in the client application.
// jest.config.js
module.exports = {
preset: '@react-native/jest-preset',
setupFiles: ['<rootDir>/jest.setup.js'],
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|react-native-webview)/)',
],
};// jest.setup.js
jest.mock('react-native-webview', () => {
const React = require('react');
const { View } = require('react-native');
const WebView = React.forwardRef((props, ref) => {
React.useImperativeHandle(ref, () => ({
reload: jest.fn(),
injectJavaScript: jest.fn(),
postMessage: jest.fn(),
stopLoading: jest.fn(),
}));
return React.createElement(View, { testID: 'mock-webview', ...props });
});
return { __esModule: true, WebView, default: WebView };
});With the WebView mocked, the SDK's callbacks can be driven directly in tests:
const webView = renderer.root.findByProps({ testID: 'mock-webview' });
act(() => {
webView.props.onMessage({
nativeEvent: { data: JSON.stringify({ type: 'GAME_STARTED' }) },
});
});6. Basic usage
import { KrdsGameView } from 'react-native-krds-game-sdk';
import type { GameMessage, GameWebViewError } from 'react-native-krds-game-sdk';
import { StyleSheet, View } from 'react-native';
export function GameScreen() {
return (
<View style={styles.container}>
<KrdsGameView
url="https://example.com"
onLoad={() => {
console.log('Game loaded');
}}
onError={(error: GameWebViewError) => {
console.log('Game error', error.code, error.message);
}}
onMessage={(message: GameMessage) => {
console.log('Game message', message.type, message.data);
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
});KrdsGameView fills its parent (flex: 1). Give it a sized container, or pass style.
Imperative API
import { useRef } from 'react';
import { KrdsGameView } from 'react-native-krds-game-sdk';
import type { KrdsGameViewRef } from 'react-native-krds-game-sdk';
const gameRef = useRef<KrdsGameViewRef>(null);
gameRef.current?.reload();
gameRef.current?.postMessage({ type: 'PAUSE_GAME' });
<KrdsGameView ref={gameRef} url="https://example.com" />;7. Props
| Prop | Type | Default | Description |
| ----------------------- | ----------------------------------- | ---------- | -------------------------------------------------------------------- |
| url | string | required | Absolute http(s) URL of the web content. |
| onLoad | () => void | — | Content finished loading successfully. |
| onLoadStart | () => void | — | A navigation started, including reloads. |
| onLoadEnd | () => void | — | A navigation ended, successfully or not. |
| onError | (error: GameWebViewError) => void | — | A normalized error occurred. |
| onMessage | (message: GameMessage) => void | — | A well-formed message arrived from the web content. |
| style | StyleProp<ViewStyle> | flex: 1 | Style of the container view. |
| showsLoadingIndicator | boolean | true | Show the built-in loading indicator. |
| loadingIndicatorColor | string | platform | Color of the built-in indicator. |
| renderLoading | () => ReactElement \| null | — | Replaces the built-in indicator with a custom loading state. |
| javaScriptEnabled | boolean | true | Enable JavaScript in the web content. |
| domStorageEnabled | boolean | true | Enable localStorage / sessionStorage. |
| webViewProps | KrdsGameWebViewProps | — | Escape hatch forwarded to react-native-webview (see caveat below). |
| testID | string | — | Test identifier on the container view. |
Applied to the WebView by default, overridable through webViewProps:
allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true.
webViewPropscouples your code toreact-native-webview's own API rather than to the SDK's contract. Props the SDK owns (source, the loading/error/message handlers, the injected bridge script) are removed from its type, because overriding them would break loading state, error reporting or messaging. Prefer the dedicated props above; treatwebViewPropsas temporary until the SDK exposes a first-class prop for what you need.
Ref
| Method | Description |
| ---------------------- | ----------------------------------------- |
| reload() | Reloads the current URL. |
| postMessage(message) | Sends a GameMessage to the web content. |
8. Events and callbacks
Loading a URL produces this sequence:
onLoadStart → (loading indicator visible) → onLoad → onLoadEnd
onLoadStart → onError → onLoadEndonLoadEndfires for both outcomes; use it to dismiss your own progress UI.onErrormay fire without ending the load — an HTTP error on a sub-resource, or a malformed message, is reported while the view stays usable. Branch onerror.isFatal.- All callbacks are optional. Passing inline arrow functions is fine: they are internally stabilized, so a parent re-render never rebuilds the WebView handlers.
Game lifecycle events (GAME_STARTED, GAME_COMPLETED, …) are not callbacks — they arrive as
messages through onMessage. The vocabulary is published as GAME_EVENTS:
import { GAME_EVENTS } from 'react-native-krds-game-sdk';
import type { GameMessage, GameResult } from 'react-native-krds-game-sdk';
const handleMessage = (message: GameMessage) => {
switch (message.type) {
case GAME_EVENTS.GAME_STARTED:
analytics.track('game_started');
break;
case GAME_EVENTS.GAME_COMPLETED: {
const result = message.data as GameResult;
grantReward(result.reward);
break;
}
default:
// Unknown types are forwarded as-is, so new web events do not require an app update.
break;
}
};9. Web ↔ React Native messaging
The SDK injects window.KrdsGameBridge into the page before its own scripts run. Games use that
object instead of window.ReactNativeWebView directly, so the transport can change later — to a
native module, for example — without the games being rewritten.
Web → React Native
window.KrdsGameBridge.postMessage({
type: 'GAME_COMPLETED',
data: { score: 100, reward: 50 },
});Every message must be a JSON object with a non-empty type string. data is optional and entirely
defined by the game. Well-formed messages reach onMessage; anything else is reported through
onError as a non-fatal MESSAGE_PARSE_FAILED.
A page that has not been updated for the bridge can still use the underlying transport directly:
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'GAME_STARTED' }));React Native → web
gameRef.current?.postMessage({ type: 'PAUSE_GAME', data: { reason: 'app_backgrounded' } });The page receives it either through a listener:
const unsubscribe = window.KrdsGameBridge.addMessageListener((message) => {
if (message.type === 'PAUSE_GAME') {
pause();
}
});or as a DOM event:
window.addEventListener('krds:message', (event) => {
console.log(event.detail.type, event.detail.data);
});Typing payloads
GameMessage.data is unknown because the payload belongs to the game, not to the SDK. Narrow it
in your own code:
const handleMessage = (message: GameMessage<GameResult>) => {
console.log(message.data?.score);
};10. Error handling
Every failure is normalized into GameWebViewError, so client applications never touch
react-native-webview internals:
interface GameWebViewError {
code: GameErrorCode;
message: string; // for logs — do not parse
url?: string;
statusCode?: number;
isFatal: boolean;
}| Code | Cause | isFatal |
| ---------------------- | ---------------------------------------------- | --------- |
| INVALID_URL | url is not an absolute http(s) URL | true |
| WEBVIEW_LOAD_FAILED | Offline, DNS, TLS or timeout failure | true |
| WEBVIEW_HTTP_ERROR | Server responded 4xx/5xx (statusCode is set) | true |
| WEBVIEW_TERMINATED | Web content process crashed or was reclaimed | true |
| MESSAGE_PARSE_FAILED | Message was not JSON, or had no type string | false |
Branch on code, never on message. Messages are for logs and may be reworded in any release.
import { GAME_ERROR_CODES } from 'react-native-krds-game-sdk';
const handleError = (error: GameWebViewError) => {
if (!error.isFatal) {
logger.warn('KRDS game warning', error);
return;
}
if (error.code === GAME_ERROR_CODES.WEBVIEW_TERMINATED) {
gameRef.current?.reload();
return;
}
showRetryScreen(error);
};Development
Building, testing or publishing this SDK itself — not just using it — see DEVELOPMENT.md.
License
Commercial. See LICENSE.
