Skip to content
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, or OchesoHost.postMessage (Android and Flutter). You answer by calling window.OchesoApp.receive(message) in the page.
  • Messages are the same envelope the web loader uses: { och: 1, wid, type, data }. The widget says hello with its wid; reply init with that wid, 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 widgetWhat 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.
loginNobody 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 · collapseIgnore: the webview is already full screen.
To the widgetWhen
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.
logoutYour 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.