同じ招待URLを開いた人どうしで、名前とアイコン付きの「いまここ」と 待ち合わせ場所を地図上に共有できる、アカウント不要のリアルタイムWebサービスです。
名前とアイコンを選び、ルームを作成するか、16文字のルーム番号で参加します。
| PC | スマートフォン |
|---|---|
![]() |
![]() |
参加者の現在地、待ち合わせ場所、道路に沿った徒歩ルートを同じ地図で確認できます。 PCは地図とメニューを横並びにし、スマートフォンは開閉できるボトムシートへ情報をまとめます。
| PC | スマートフォン |
|---|---|
![]() |
![]() |
GPSを許可しない場合も地図で現在地を指定できます。初回共有の前に、共有する情報・共有相手・削除タイミングを確認します。
| 共有前の確認 | スマートフォンで地図指定 |
|---|---|
![]() |
![]() |
ホストは地図を直接操作するほか、施設名・駅名・住所を検索できます。候補は仮ピンで確認し、場所名と集合時刻を付けてから共有します。
| 地図で指定 | 場所を検索 | 内容を確認 |
|---|---|---|
![]() |
![]() |
![]() |
自分の共有位置から待ち合わせ場所までの徒歩時間・距離と、道路に沿った経路を表示します。
入退室と待ち合わせ変更は匿名識別ID付きのシステムログとして残ります。文章入力は設けず、6種類のアニマルスタンプ、定型絵文字、内蔵絵文字キーボードから選んだ絵文字1つを送信します。
ルーム全体の削除と、この端末だけに保存した情報の消去を分けています。TOPから利用規約・プライバシーポリシー・外部送信情報を確認できます。
利用者向け文書は開発時のひな形です。本番公開前に運営者情報・委託先・ログ設定を実態へ合わせ、専門家の確認を受けてください。
- 80bitの秘密を持つ16文字のルーム番号(4文字区切り)または招待URLで、一時的なルームを作成・参加
- 名前と6種類のオリジナルPOPアニマルアイコンからプロフィールを選んで参加
- ブラウザのGeolocationを、明示操作の後だけ共有
- 位置許可を使わず、地図タップで現在地を指定して共有
- GPS・地図指定の初回共有前に、共有項目・共有相手・削除時期を確認
- 参加者の位置、精度、接続状態を名前付きマーカーで同期
- ホストが待ち合わせピン、場所名、時刻を設定
- 施設名・駅名・住所を明示的に検索し、候補を地図中央へ合わせてから待ち合わせ場所に設定
- 自分の現在地から待ち合わせ場所まで、道路に沿った徒歩ルートを表示
- 徒歩時間・距離、ルート全体表示、外部徒歩ナビへの導線
- 生成アニマルスタンプ6種と、定型6種または内蔵絵文字キーボードから選んだ絵文字1つ
- 入退室・待ち合わせ変更を記録する、匿名識別ID付きのシステムログ
- 同じルーム内で参加者を見分ける6文字の匿名識別ID
- 切断・通常退出後もresume tokenにより同じ参加者として復帰し、最新プロフィールを反映
- ホスト退出時の自動引き継ぎ
- 位置共有停止時・切断時に、サーバー上の公開座標を削除
- GPSエラーや位置権限の取り消し時にも、公開座標の削除を要求
- ホストが全員を退出させ、現在のルームデータを即時削除
- この端末だけに保存したプロフィール・復帰情報を画面から消去
- PC / スマートフォン対応(スワイプ・ハンドル・要約欄から開閉できるボトムシート)
- スマートフォンの最初の画面内に主要ボタンを収め、入力エラーを該当欄の近くに表示
- ルームコード横の色と文言で、接続中・再接続中・オフラインを区別
- スマートフォンでも「招待」「退出」「待ち合わせ解除」を常に見つけられる配置
- スマートフォンの上部ヘッダーを2段に整理し、ブランド・招待・退出と、ルーム番号・参加状況を分離
- iPhoneの上部セーフエリア、320px幅、44px以上のタップ領域に対応
- TOPの名前欄を簡潔な「なまえ」にし、アイコンを「ねこ、あおい鳥、…」の順に表示
- 退出・待ち合わせ解除の前に確認画面を表示し、意図しない操作を防止
- 地図上の自分名は「あなた」、参加者欄は入力した名前を主表示して「あなた」チップを併記
- 現在地は地図を直接タップ、待ち合わせ場所は小さな赤い中央旗または直接タップで指定可能
- スマートフォンの待ち合わせ選択は「中央表示の2行案内」「中央を使う」「キャンセル」の順に整理
- スマートフォンのGPS案内を省略せず表示し、現在地操作アイコンと参加者アバターの形・中央位置を補正
- ルーム番号を押すと、4文字区切りの16文字をクリップボードへコピー
- ROOMラベル・ルーム番号・コピーアイコンを、PCとスマートフォンのヘッダー内で上下中央に整列
- 地図タイルをカラーで表示し、街区・道路・駅などを見分けやすく表示
- 集合時刻までの残り時間を薄いグレーで表示し、60分超は「X時間 X分」へ変換(地図上は時刻だけを表示)
- 日本語表示はZen Maru Gothicを使用
- 待ち合わせ設定中の矢印キー操作、ダイアログのフォーカス保持、Escapeキーに対応
- 未読スタンプ数をタブへ表示し、既存の履歴は未読として数えない
- 自由な文章入力を使わず、生成アニマルスタンプ6種と定型絵文字、または内蔵絵文字キーボードから選んだ絵文字1つだけを送信
- 距離は「直線」と「道路に沿った経路」を明確に区別し、根拠のない徒歩時間を表示しない
- 主要操作を44px以上にし、フォーカス表示、文字コントラスト、読み上げ用の状態通知を改善
Node.js 22以上を推奨します。
npm install
npm run devhttp://localhost:5173 を開きます。
ブラウザの位置情報は localhost またはHTTPSの安全なコンテキストでのみ使えます。
同じWi‑Fiの別端末から http://192.168... を開く場合、地図とルーム通信は動いても
GPS共有はブラウザに拒否されます。実機でGPSを試す場合はHTTPSトンネルまたは
TLS付きホストを使ってください。地図指定での位置共有はHTTPでも利用できます。
画面では、GPSが使えない原因を次のように区別して表示します。
- 通常HTTPで開かれている
- このサイトの位置情報権限が拒否されている
- 端末側の位置情報サービスを確認する必要がある
- 測位不能またはタイムアウト
- ブラウザ非対応、または埋め込み表示のPermissions Policyによる拒否
設定画面から権限を変更した場合も、Permissions APIに対応するブラウザでは画面へ反映します。 GPS取得中はキャンセルでき、測位エラー後は古い監視を解除して「GPSで共有」から再試行できます。
待ち合わせ場所の設定中に施設名・駅名・住所を入力し、検索 を押すと候補を最大5件表示します。
候補を選んだ段階では共有せず、地図中央の旗で位置を確認・微調整してから、既存の確認画面を経て
ルームへ共有します。入力中の自動検索は行いません。
ブラウザは同一オリジンの POST /api/place-search だけを呼び、NodeサーバーまたはCloudflareの
Geocoding CoordinatorがNominatimへ中継します。同じ検索の統合、キャッシュ、全体1.1秒間隔、
同時待機数・応答サイズ・タイムアウトの制限をサーバー側で適用します。検索語はルームデータや
メッセージ履歴へ保存しませんが、検索提供者とホスティング事業者のログ対象になり得ます。
公開Nominatimは小規模な利用を前提とし、オートコンプリートを禁じています。公開運用では
Nominatim Usage Policy を確認し、
識別可能なUser-Agentと公開URLを設定してください。利用量が増える場合は、互換APIを提供する
契約事業者またはセルフホストへ GEOCODING_BASE_URL を切り替えます。
GEOCODING_USER_AGENT="Imakoko/0.2 (+https://example.com/contact)"
GEOCODING_REFERER="https://example.com/"
GEOCODING_BASE_URL="https://nominatim.openstreetmap.org/search"
GEOCODING_PROVIDER_NAME="Nominatim / OpenStreetMap"
GEOCODING_PROVIDER_URL="https://nominatim.openstreetmap.org/"
VITE_PLACE_SEARCH_PROVIDER="OpenStreetMap Foundation(Nominatim)"検索提供者を変更するときは、API応答用の GEOCODING_PROVIDER_* と、利用者向け文書へ表示する
VITE_PLACE_SEARCH_PROVIDER も実際の委託先へ合わせて変更し、プライバシーポリシーを再確認します。
Node版の公開Nominatim向けレート制限は1プロセス内で調整します。公開Nominatimを使うNode版は 単一replicaで運用してください。複数replicaやサーバーレスへ分散する場合は、全体制限を1つの Durable Objectで調整するCloudflare版、共有キュー、または契約事業者の割当量に対応した構成を使います。
自分の共有位置と待ち合わせ場所がそろうと、ブラウザは同一オリジンの
POST /api/route を呼びます。Nodeサーバーが徒歩ルーティングへ問い合わせ、
成功した道路経路だけを青い二重線で表示します。取得できない場合、直線を徒歩ルートとして
見せることはせず、理由と再試行ボタンを表示します。
既定値はFOSSGISが運営するOpenStreetMap徒歩ルーターです。公開サービスへの負荷を抑えるため、 自分の経路だけを取得し、同一リクエストの統合、10分キャッシュ、全体1リクエスト/秒の制限を サーバー側で行います。FOSSGISの利用条件に従い、画面内に提供元と「地図を修正」リンクを 表示しています。
公開運用では、識別可能なUser-Agentと公開URLを設定し、利用量が増える場合はセルフホストまたは 契約ルーティングAPIへ切り替えてください。
ROUTING_USER_AGENT="Imakoko/0.2 (+https://example.com/contact)"
ROUTING_REFERER="https://example.com/"
ROUTING_BASE_URL="https://routing.openstreetmap.de/routed-foot/route/v1/driving"ルート検索時は、始点と終点がルーティング提供者へ送られます。ブラウザから提供者へ直接送信せず、 このNodeサーバーで中継し、アプリ側では履歴として永続化しません。既定のFOSSGISサービスは ルート要求をサーバーログへ保存すると説明しているため、公開前に同サービスのプライバシー説明を 確認し、要件に合わなければセルフホストへ切り替えてください。
「画面から見えなくなる時刻」と「保存先から削除する時刻」を分けずに運用できるよう、 現在の実装では次の期限を適用します。
| データ | 削除・失効のタイミング |
|---|---|
| ルーム内の名前、アイコン、識別ID、待ち合わせ、スタンプ・絵文字・システムログ等 | ホストの「ルームのデータを削除して終了」、作成後12時間、または全員切断から30分のうち最も早い時点 |
| 公開中の現在地 | 共有停止、退出・切断、GPSエラー、位置権限の取り消し、またはルーム削除時 |
| スタンプ・絵文字・入退室/待ち合わせ変更ログ | 合わせて直近50件を保持し、ルーム削除時に全件削除 |
| ブラウザ内の徒歩ルート | メモリ上で最長5分 |
| サーバー内の徒歩ルートキャッシュ | 最長10分。Cloudflare版も期限後の物理削除をアラームで実行 |
| サーバー内の場所検索キャッシュ | Node版・Cloudflare版とも、通常結果を最長24時間、空結果を最長10分保持。検索語はルーム状態へ保存しない |
| 端末内の入室中の印 | 通常退出時、ルーム終了・失効の検知時、または端末データ消去時に削除。保存から12時間で失効して以後利用せず、期限超過レコードは次回アクセス時に削除 |
| 端末内のルーム別再接続情報 | 通常退出後も保持。ルーム終了・失効の検知時、再入室時の無効確認時、または端末データ消去時に削除。保存から12時間で失効して以後利用せず、期限超過レコードは次回アクセス時に削除 |
| 端末内の表示名・アイコン | 「この端末の保存データを消去」またはブラウザのサイトデータ削除まで |
| 端末内の利用規約等の同意記録 | 規約版と同意日時を保存。「この端末の保存データを消去」またはブラウザのサイトデータ削除まで |
| ホスティング・上流サービスのアクセスログ | 各サービスの本番設定に依存。公開前に保持期間と削除方法を確定 |
Node版のルーム状態はメモリ上だけなので、プロセス再起動時は上記より早く消えます。 Cloudflare版はDurable Objectsへ一時保存し、期限・アラームで削除します。削除後に同じルーム番号や 招待URLを開くと、古い内容や復帰トークンを引き継がない、新しい空のルームとして再利用されます。 ルーム番号自体を失効させる仕様ではないため、ルーム番号と招待URLはパスワードと同じように 管理してください。旧形式のルームは有効期限中だけ招待URLから参加でき、5文字の旧表示から 元の秘密を復元できないため番号入力の対象にはしません。
TOPフッターで、利用規約・プライバシーポリシー・外部送信情報を確認できます。 ルームを作成・参加するには、TOPの「利用規約等に同意します」へチェックを入れる必要があります。 初期状態はひな形であり、画面にも「公開前の確認が必要です」と表示します。
.env.example を参照し、デプロイ先のビルド環境に次の公開情報を設定してください。
- 運営者名、住所、代表者または「該当なし」
- 問い合わせ先
- 合意管轄
- 本番のホスティング事業者
- 制定日
実際の委託先、ログ設定、問い合わせ・削除対応と文書が一致し、専門家の確認が完了した後にだけ
VITE_LEGAL_REVIEWED=true を設定します。これはViteのビルド時設定であり、秘密情報は入れません。
スタンプ・絵文字機能でも、特定の利用者間の通信をサーバーが媒介します。日本で公開運用する前に、 電気通信事業の届出・登録の要否を管轄の総合通信局へ確認し、通信の秘密を扱う運用権限、 不適切利用の問い合わせ・削除手順、事故対応手順を定めてください。付属文書は一般的なひな形で、 個別の法律相談を置き換えるものではありません。
npm run verify
npm start既定では http://localhost:8787 で、ビルド済みフロントエンドと
/ws WebSocketを同じNodeプロセスから配信します。PORT と HOST に対応しています。
docker build -t imakoko .
docker run --rm -p 8787:8787 imakokoWebSocketを維持できるNode/Docker対応ホスティングが必要です。公開環境ではHTTPS/WSSを 終端するリバースプロキシを使用してください。
WebSocketは既定で同一Originからの接続だけを受け入れます。別Originのフロントエンドを
許可する場合だけ、ALLOWED_ORIGINS にカンマ区切りの完全なOriginを設定してください。
無制限の * は公開環境では使用しないでください。
既存Node/Docker版を残したまま、Cloudflare Workers + Durable Objectsで動かせる
追加構成を cloudflare/ に用意しています。静的Reactアプリ、
同一オリジンの /ws、/api/route、/api/place-search を1つのWorkersデプロイで配信します。
cd cloudflare
npm install
npm run check
npm run deployルームごとにSQLite-backed Durable Objectを割り当て、Hibernation WebSocket APIで リアルタイム通信を維持します。無人30分・作成後12時間の削除、resume token、 commandId重複排除、位置共有停止時の座標削除もNode版と同じです。徒歩ルートと場所検索は 別々のDurable Objectでキャッシュと公開サービスへの全体間隔を調整します。
WebSocketをUpgrade前に対象ルームへ割り当てるため、Cloudflare版では
/ws?room=<roomId> を使います。詳しい設定、環境変数、デプロイ前の注意点は
cloudflare/README.md を参照してください。
結論: ユーザー間の直接通信ではありません。各ブラウザが同一オリジンのルームサーバーへ WebSocket接続し、サーバーがルーム状態の正本を管理・検証して、参加者全員へ配信する サーバー権威型です。P2P / WebRTCは使用せず、ホストの端末も通信の中継やデータ保管をしません。
flowchart LR
subgraph clients["参加者の端末"]
A["ブラウザ A<br/>ホスト権限あり"]
B["ブラウザ B<br/>一般参加者"]
end
subgraph service["いまここの同一オリジンサーバー"]
WS["WebSocket<br/>/ws?room=..."]
Engine["ルームエンジン<br/>入力検証・レート制限<br/>commandId重複排除"]
State["ルーム状態の正本<br/>参加者・最新位置・待ち合わせ<br/>スタンプ・絵文字・ログ直近50件・revision"]
end
A <-->|"join / resume / コマンド<br/>welcome / ack / snapshot"| WS
B <-->|"join / resume / コマンド<br/>welcome / ack / snapshot"| WS
WS <--> Engine
Engine <--> State
ブラウザAとブラウザBの間に接続はありません。更新は必ず
ブラウザ → サーバーで検証・保存 → 各ブラウザへ完全snapshot配信 の順で反映されます。
本番はHTTPS/WSS、ローカル開発はHTTP/WSです。公開運用ではWSSで通信路を暗号化しますが、サーバーが
内容を検証・保持するため、参加者間のエンドツーエンド暗号化ではありません。
sequenceDiagram
autonumber
participant A as ブラウザA(ホスト)
participant S as ルームサーバー
participant B as ブラウザB
Note over A,B: AとBの間には直接接続を作らない
A->>S: join(ルームID・名前・アイコン)
S-->>A: room.welcome(自分専用resume token・完全snapshot)
B->>S: join または resume(再接続時はresume token付き)
S-->>B: room.welcome(自分専用resume token・完全snapshot)
S-->>A: room.snapshot(Bの参加を反映)
A->>S: location.update / meetup.set / chat.send
S->>S: 検証・レート制限・重複排除・正本更新
S-->>A: ack
S-->>A: room.snapshot
S-->>B: room.snapshot
room.welcome のresume tokenは接続した本人だけへ返します。通常の更新では、クライアントが
生成したcommandIdへackを返し、変更後のルーム全体を各参加者向けのroom.snapshotとして
配信します。画面はackだけで状態を確定せず、現在より新しいrevisionのsnapshotを正として
反映します。再送された同じcommandIdはサーバー側で重複適用しません。
| 経路 | 通信相手 | 主な送信内容 | サーバー・共有上の扱い |
|---|---|---|---|
WS/WSS /ws?room=<roomId> |
ブラウザ ↔ ルームサーバー | join / resume、最新位置、待ち合わせ、スタンプ・絵文字、退出・終了、heartbeat |
サーバーが検証して正本へ反映し、ack / errorと完全snapshotを配信 |
POST /api/place-search |
ブラウザ → 同一オリジンサーバー → Nominatim | 利用者が検索ボタンを押したときの検索語 | サーバーが中継・レート制限・一時キャッシュ。候補は要求元だけへ返し、ルーム状態には保存しない |
POST /api/route |
ブラウザ → 同一オリジンサーバー → FOSSGIS | 自分の最新位置と待ち合わせ地点 | サーバーが中継・レート制限・一時キャッシュ。経路は要求元だけへ返し、メッセージ履歴には保存しない |
| 地図タイルのHTTPS | ブラウザ → OpenStreetMap | 表示範囲と縮尺に対応するタイル番号、通常のリクエスト情報 | いまここのサーバーは中継しない。各ブラウザが直接取得 |
| 外部徒歩ナビ | ブラウザ → Google マップ | 待ち合わせ地点。現在地は本サービスが作るURLに含めない | 利用者がリンクを選んだときだけ遷移 |
| 主体 | 役割 | データの持ち方 |
|---|---|---|
| 一般参加者のブラウザ | GPSまたは地図指定の位置、スタンプ・絵文字などをサーバーへ送信し、snapshotを描画 | ルームの正本は持たない。表示名・アイコン・同意記録はlocalStorage、ルーム別resume tokenはタブごとのsessionStorageへ保存 |
| ホストのブラウザ | 一般参加者の操作に加え、待ち合わせの設定・解除とルーム終了を要求できる | 権限を持つ参加者にすぎず、他参加者の通信中継やルーム状態の正本保持はしない |
| Node版サーバー | WebSocket接続、入力検証、レート制限、ホスト判定、状態更新、snapshot配信 | 単一Nodeプロセスのメモリが正本。再起動すると消失し、無人30分・作成後12時間でも削除 |
| Cloudflare版サーバー | Workerがルーム番号から1ルーム1 Durable Objectへ振り分け、同じルーム処理を実行 | Durable ObjectのSQLite-backed storageへ一時保存。Hibernation WebSocketとアラームを使い、無人30分・作成後12時間で削除 |
最初の参加者をサーバーがホストにします。ホストが通信切断すると、サーバーが接続中の参加者へ ホスト権限を引き継ぎ、15秒以内に同じresume tokenから再接続した場合は元ホストへ戻します。 したがって、ホストの画面を閉じても他の参加者同士が接続済みならルーム状態はサーバーに残ります。 一方、サーバーが停止すると参加者間だけで同期を継続することはできません。
binngo の、heartbeat・指数バックオフ再接続・ルームTTL・resume token・完全snapshot・
commandId重複排除という仕組みを位置共有向けに移植しています。
- 入室と位置共有を分離し、許可拒否でもルームには参加可能
- ルーム作成・参加前に、TOPで規約・プライバシー・外部送信情報への同意を確認
- 位置共有は、共有項目・相手・削除時期を確認して同意した後だけ開始
- 共有停止・切断・GPS失敗・権限取り消し時に公開座標を削除
- 連続した位置履歴を保存せず、参加者ごとの最新位置だけを保持
- IPや端末特性によるフィンガープリントを使わず、ルーム別の匿名再接続キーだけで同じ参加者へ復帰
- 6文字IDはルーム内の見分け用であり、本人確認を保証しない
- 徒歩ルートは自分の位置だけを取得し、他参加者の経路を外部へ問い合わせない
- 場所検索はボタン操作時だけ行い、検索語をルーム状態へ保存しない
- ホスト終了、無人30分、作成後12時間でルームデータを削除
- ROOMの「データ管理」で、ルーム全体の削除とこの端末だけの保存データ消去を分けて表示
- 「ルームのデータを削除して終了」はホスト専用で、共有データを削除して全員を退出
- 「この端末の保存データを消去」は、このブラウザの保存値だけを消して本人だけ退出
- ルーム秘密はURLの
#より後ろに置き、最初のページ取得や地図タイルのRefererへ送らない - 1ルーム20人まで、スタンプID・絵文字1つ・座標・名前・アイコンをサーバー側で検証
- Zen Maru Gothicをセルフホストし、フォント表示のためにGoogleへ接続しない
- Google マップへの遷移は利用者が外部ナビを選んだ場合だけ
ブラウザはルーム参加時に、URL断片から取り出した秘密のルームIDを同一Originの
/ws?room=<roomId> へ送ります。初期ページのURL断片はHTTPへ送られませんが、
WebSocket Upgradeのパスはホスティングやリバースプロキシのログ対象になり得ます。
本番では /ws のクエリ文字列を記録しないか、room 値をマスキングしてください。
OpenStreetMapの地図タイルはブラウザから直接取得するため、表示範囲・縮尺に対応するタイル番号と 通常のリクエスト情報が提供元へ送られます。徒歩ルート検索ではサーバーからルート提供者へ 始点・終点を、場所検索では検索語を検索提供者へ送ります。これらと本番ホスティングの情報は、 画面の「外部送信情報」に表示します。
Node版の状態は単一プロセスのメモリ上にあります。複数インスタンスや再起動後の復元が 必要な公開運用では、同梱のCloudflare Durable Objects版、またはTTL付きRedis状態と Pub/Subを利用してください。正確な現在地を扱うため、アクセス解析やエラー収集へ 生座標を送らない運用を推奨します。
npm run test
npm run build
npm run verify













