ClaudeExpo2026/01/13 16:30

How the Minecraft Speedrunning Community stays fast with Expo

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

claudejamodel: claude-sonnet-4-20250514

MinecraftスピードランコミュニティがExpoでリアルタイム通知アプリを構築

Key Points

  • Expoでリアルタイムスピードラン通知システムを構築
  • App Integrityによる強固なAPI認証を実装
  • ネイティブライクなガラス効果UIを実現

Summary

Minecraftスピードランコミュニティ向けのリアルタイム追跡アプリ「PaceMan.gg」の開発事例。Expo SDKを使用してiOS/Android両対応のモバイルアプリを構築し、プッシュ通知機能でスピードランの進行状況をリアルタイムで配信している。

Key Points

  • リアルタイム通知システム: expo-notificationsとWebSocketを組み合わせ、有望なスピードランを検出して即座にユーザーに通知
  • セキュリティ強化: @expo/app-integrityでApp Attest(iOS)とPlay Integrity(Android)による認証を実装し、APIアクセスを保護
  • ネイティブUI体験: expo-glass-effectを活用してプラットフォーム固有のヘッダーブラー効果を実現
  • クロスプラットフォーム開発: MacなしでもExpoクラウドサービスを使用してiOS/Androidアプリを同時開発・配信
  • マイクロサービス構成: Express.jsベースの通知サービスとアクティブラン管理サービスを分離し、MySQL/Redisで状態管理

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

claudejamodel: claude-sonnet-4-20250514

Minecraftスピードランコミュニティが Expo で高速性を維持する方法

Minecraftスピードランコミュニティが Expo で高速性を維持する方法

ユーザー • React Native • 開発 • 2026年1月13日 • 8分で読める

Chitraksh Tarun
ゲスト著者

Minecraftスピードランコミュニティが Expo を使用してモバイルアプリとプッシュ通知インフラストラクチャを構築し、スピードランをリアルタイムでユーザーに通知する方法を学びましょう。

これは Chitraksh Tarun によるゲスト投稿です。彼は現在 ClubzFM で SWE インターンとして働くソフトウェア開発者です。また、2021年4月からゲームのスピードランを行っているMinecraftスピードランナーでもあり、PaceMan.gg モバイルアプリのメンテナーでもあります。


Minecraftスピードランコミュニティは高速で動きます。本当に、驚くほど高速です。世界中でいつでもスピードランが行われているため、コミュニティは記録更新の可能性があるスピードランがライブで行われる際に、最新情報を把握したいと考えています。

PaceMan.gg は、アクティブなMinecraftスピードランを追跡するために設計されたコミュニティ主導のツールです。進行中のスピードランをハイライトし、リアルタイムリーダーボードを通じてライブの進行状況更新を配信します。

PaceMan.gg のモバイルアプリは Expo SDK で構築されており、モバイルインターフェースを通じてリアルタイムのスピードラン更新を配信します。最近、このアプリにプッシュ通知サポートを追加しました。

簡単に言うと、スピードランが有望な段階に達すると、ユーザーはそのスピードランについて通知を受け取り、アプリを通じて進行状況を追跡し、Twitchでストリーミングされている場合はライブで視聴できます。

このブログでは、高速でリアルタイムなアプリを配信するために行ったアーキテクチャ設計と開発決定について説明します。また、通知インフラストラクチャ、API セキュリティ、ネイティブインターフェース設計の一部で expo-notifications@expo/app-integrityexpo-glass-effect をどのように使用しているかも紹介します。

PaceMan.gg モバイルアプリ

基本的に、このアプリはシンプルなリアルタイム Minecraft スピードランボードです。初回起動時、ホームタブには現在ゲームを実行しているすべてのスピードランナーと、スピードランのどの段階(スプリット)にいるかが表示されます。

リーダーボードタブには、指定された期間(日次、週次、月次、全期間)で実行された最速ランが表示されます。統計タブには、異なるスプリットに対するスピードランナーの統計のより包括的な内訳が表示されます。

これは、ユーザーが何が起こっているかを追跡するためのシンプルで一目でわかるインターフェースを意図しています。

アプリは完全に Expo で開発されており(アプリのv1.2.0時点で、99.5% TypeScript、0.5% その他 👀)、Expo's Services を使用して構築されており、iOS と Android 間で簡単に機能パリティを実現しています。

実際、アプリの最初の数バージョンは Mac なしで開発されました:WSL を実行する Windows デバイス、iPhone、古い Android で!Expo のクラウドサービスにより、Mac なしでアプリを構築・出荷することができました。

Expo Notifications を使用したプッシュ通知

このアプリの中核機能は、スピードランがエキサイティングなペースになったときに通知を受信する機能です。これにより、ユーザーはスピードランがリアルタイムで発生する際に通知を受け取り、Twitch でライブストリーミングされている場合は視聴することもできます。

express.js マイクロサービス/バックエンド(PushNotificationsService)が通知を管理し、別のマイクロサービス/バックエンド(ActiveRunsService)が現在アクティブなスピードランを管理します。

各スピードランイベントに対して、WebSocket イベントが ActiveRunsService 経由で PushNotificationsService に送信され、イベントが通知されるべきかどうかを解析・決定します(簡単に言うと、良いスピードランペースかどうか)。

イエスの場合、プッシュトークンが取得され、それらに通知が送信されます。これは expo-server-sdk-node と統合されており、すべてがトークンやその他の情報を保存するために MySQL DB と Redis インスタンスと通信します。

このシステムのマインドマップの簡略化されたフローチャートは以下の通りです:

クライアント側では、Beto's tutorial からインスパイアされた <NotificationsProvider /> が実装されており、expo-notifications を使用してトークンの登録からアプリがフォアグラウンド/バックグラウンドにある間の通知イベントの処理まで、すべての機能を処理します。

App Integrity によるセキュリティ

PushNotificationsService バックエンドには、モバイルアプリが CRUD アクション(トークンの保存や一部の設定)を実行するための API ルートもいくつか含まれています。

@expo/app-integrity パッケージの導入により、CRUD が構造化され、モバイルアプリのみによる消費を意図していることを考慮して、ルートの上にセキュリティレイヤーを追加するために App Integrity を使用することにしました。

API リクエストを実行する前に、モバイルアプリは getIntegrityHeaders() 関数を実行し、一意のチャレンジに対して App Attest(iOS)または Play Integrity(Android)チェックを実行し、リクエストと一緒に同じものを渡します。

ミドルウェア関数 - verifyIntegrity() - がバックエンドのルートの前に実行され、整合性を検証し、成功時に CRUD に進むか、失敗時に 401 - Unauthorized を返します。

バックエンドでこれらの検証を実行するために、node-app-attest(iOS)と @googleapis/playintegrity(Android)ライブラリを使用しています。

getIntegrityHeaders() 関数のトリミングされ、クリーンアップされたスニペットは以下の通りです:

export const getIntegrityHeaders = async () => {
  if (Platform.OS !== "android" && Platform.OS !== "ios") return;
  
  // Get unique challenge
  const challenge = await getChallenge();
  
  // Handle Android
  if (Platform.OS === "android") {
    await waitForIntegrityProviderReady();
    const integrityToken = await requestIntegrityCheckAsync(challenge);
    return integrityToken;
  }
  
  // Handle iOS
  if (Platform.OS === "ios") {
    let keyId = await getItemAsync("app-attest-key");
    
    // Attest first time, if no attestation available
    if (!keyId || typeof keyId !== "string" || keyId.trim().length === 0) {
      keyId = await generateKeyAsync();
      await setItemAsync("app-attest-key", keyId);
      const attestation = await attestKeyAsync(keyId, challenge);
      return attestation;
    }
    
    // Assert if attestation exists
    try {
      const request = {
        expoToken,
        challenge,
      };
      const assertion = await generateAssertionAsync(keyId, JSON.stringify(request));
      const rawAuthentication = JSON.stringify({
        keyId,
        assertion,
      });
      const authentication = Buffer.from(rawAuthentication).toString("base64");
      return authentication;
    } catch {
      // Re-attest, if current attestation key fails for whatever reason.
      const newKeyId = await generateKeyAsync();
      await setItemAsync("app-attest-key", newKeyId);
      const attestation = await attestKeyAsync(newKeyId, challenge);
      return attestation;
    }
  }
  
  return;
};

一意のチャレンジに対処する App Integrity と、キーチェーンにキー ID を安全に保存する expo-secure-store の使用の組み合わせにより、バックエンドがアプリの正規インストールによってのみインターフェースされることが保証され、すべてが安全でクリーンに保たれます。

Notifications Provider は、App Integrity によって保護された再登録/トークン更新ロジックをさらに処理し、ユーザーが通知を見逃さないようにします。

expo-glass-effect を使用したネイティブヘッダー

UI を可能な限りネイティブに見え、感じられるようにしたいと考えました。その一例がヘッダーです。ヘッダーに適切なブラー/スタイリング効果を適用することで、スクロールがネイティブプラットフォームが動作すべき方法と合理化されて感じられます。

これには Android(ソリッドヘッダー)、iOS 26 以前の iOS(ヘッダーの半透明ブラー)、iOS 26 以降の iOS(ヘッダーの透明ブラー)が含まれます。

Anurabh Verma's implementation からインスパイアされ、expo-glass-effect パッケージの isLiquidGlassAvailable() ハンドラーを使用して、useScreenOptions() フックが実装され、ページ間でヘッダーの外観を一貫して保ちます:

// @/hooks/use-screen-options.ts
import { useColorsForUI } from "@/hooks/use-colors-for-ui";
import type { NativeStackNavigationOptions } from "@react-navigation/native-stack";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { useColorScheme } from "nativewind";
import { Platform } from "react-native";

export const useScreenOptions = (): NativeStackNavigationOptions => {
  const { colorScheme } = useColorScheme();
  const { backgroundColor } = useColorsForUI(); // Custom hook to retrieve certain hex codes
  
  return {
    headerShadowVisible: false,
    headerTransparent: Platform.select({
      ios: true,
      android: false,
    }),
    headerStyle: {
      backgroundColor: Platform.select({
        android: backgroundColor,
      }),
    },
    headerBlurEffect: !isLiquidGlassAvailable()
      ? colorScheme === "light"
        ? "systemChromeMaterialLight"
        : "systemChromeMaterialDark"
      : "none",
    headerBackButtonDisplayMode: "minimal",
  };
};

以下のように使用されます:

// @/app/_layout.tsx
import { useScreenOptions } from "@/hooks/use-screen-options";

export default function RootLayout() {
  // ...
  const screenOptions = useScreenOptions();
  // ...
  
  return (
    <Stack screenOptions={screenOptions}>
      {/* Remaining Stack Elements */}
    </Stack>
  )
}

これにより、ヘッダーをスクロールする際のネイティブな感覚を実現できます。パッケージの <GlassView /> コンポーネントにより多く投資して、アプリに徐々により多くの Liquid Glass UI 要素を追加する計画があります。

アプリの UI が控えめでありながら流動的であることを確実にするために、これを段階的に実装したいと考えました。

PaceMan.gg の将来計画

アプリには Widgets などの追加機能の多くの計画があり、改善と洗練の余地もあります。Minecraft スピードランコミュニティのための素晴らしいモバイル体験を維持するために、Expo とコミュニティからのサポート技術をより深く掘り下げることを楽しみにしています。

P.S. 私は Expo を使用してモバイルアプリに取り組みましたが、このプロジェクトは、私がアプリを構築したこのツールのサポート部品、ユーティリティ、インターフェースを作成している Minecraft スピードランコミュニティの開発者たちの素晴らしい作業なしには不可能でした。SpecnrBoyennSaanviDuncanJojoeRedLimeCylo、そしてこのプロジェクトを現実にした他の多くの開発者たちに大きな感謝を送ります。

注意: PaceMan.gg は、リアルタイムスピードランペーストラッカーとして機能するコミュニティ主導のアプリケーションです。このアプリケーションは Minecraft、Mojang、または Microsoft と提携または承認されていません。Minecraft の使用ガイドラインに従っています。