Developers · More
Mobile apps
In an app, open the widget's own page in a webview and add
?app=1. A few lines of bridge code let the widget sign your users in with your app's login and tell you what happens.How it works
- Load the frame's address directly, full screen:
https://www.ocheso.com/embed/en/t/weekly-blitz?app=1. Use/embed/fr/…for French. Studio's Install page and Embedding list the views. ?app=1tells the widget it's in an app: it talks to your code through the native bridge, and hides Google sign-in, which Google doesn't allow in webviews.- The widget posts JSON strings to whichever bridge it finds:
ReactNativeWebView.postMessage,webkit.messageHandlers.ocheso, orOchesoHost.postMessage(Android and Flutter). You answer by callingwindow.OchesoApp.receive(message)in the page. - Messages are the same envelope the web loader uses:
{ och: 1, wid, type, data }. The widget sayshellowith itswid; replyinitwith thatwid, and keep using it.
Without your login, players can still sign in inside the app with an emailed 6-digit code. The bridge is what lets your own users skip that.
The code
Each version answers hello with init and need-identity with a player token from your server, made exactly as for the web (Player sign-in, step 2).
// pubspec.yaml: webview_flutter: ^4.10.0
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class OchesoTournament extends StatefulWidget {
const OchesoTournament({super.key, required this.fetchToken});
/// Asks YOUR server for a player token (ochp_…) for the signed-in user.
/// Never put the secret key in the app.
final Future<String?> Function() fetchToken;
@override
State<OchesoTournament> createState() => _OchesoTournamentState();
}
class _OchesoTournamentState extends State<OchesoTournament> {
String? _wid; // the frame's widget id, from its hello
late final WebViewController _controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
// The frame posts here: hello, need-identity, event (join, gameStart, gameEnd), error.
..addJavaScriptChannel('OchesoHost', onMessageReceived: (m) {
final msg = jsonDecode(m.message) as Map<String, dynamic>;
switch (msg['type']) {
case 'hello': // answer with init: until then the frame accepts nothing from the app
_wid = msg['wid'] as String;
_send('init', {'proto': 1, 'loader': 'flutter', 'features': [], 'visible': true, 'identify': true});
case 'need-identity':
_sendIdentity();
}
})
..loadRequest(Uri.parse('https://www.ocheso.com/embed/en/t/weekly-blitz?app=1'));
Future<void> _send(String type, Map<String, dynamic> data) {
final msg = jsonEncode({'och': 1, 'wid': _wid, 'type': type, 'data': data});
return _controller.runJavaScript('window.OchesoApp && window.OchesoApp.receive($msg)');
}
Future<void> _sendIdentity() async => _send('identity', {'token': await widget.fetchToken()});
@override
Widget build(BuildContext context) => WebViewWidget(controller: _controller);
}Messages
| From the widget | What to do |
|---|---|
| hello { wid, view, title } | Reply init with that wid. Required. |
| need-identity { reason } | Reply identity { token } from your server, or { token: null } when nobody is signed in. |
| login | Nobody is signed in and the player pressed Sign in: show your app's sign-in. |
| open { url } | Open the www.ocheso.com address in the system browser. |
| event { name, detail } | join, gameStart, gameEnd, as on the web. Optional. |
| error { code, message } | Log it. Optional. |
| resize · expand · collapse | Ignore: the webview is already full screen. |
| To the widget | When |
|---|---|
| init { proto: 1, loader, features: [], visible, identify } | Once, in answer to hello. identify: true says you can sign users in. |
| identity { token } | In answer to need-identity. |
| logout | Your user signed out of your app. |
| theme { mode?, vars? } | To follow your app's dark mode: { mode: "dark" }. vars take the theming keys. |
| navigate { view, params? } | To switch view from a native control. |
Good to know
- Sounds start after the player's first tap: phones don't play audio a page starts by itself.
- Allowed domains don't apply in an app: it's your own webview, with no other site around it.
- Keep local storage on (Android:
domStorageEnabled). The widget keeps the player's session there; without it they sign in again each time. - The widget is built for 360px-wide screens and up, and follows the system's light or dark mode unless you send a theme.