Created
February 24, 2026 06:45
-
-
Save shinsaka/7c1f4b697120baca2b65e79c5e992dfd to your computer and use it in GitHub Desktop.
Simple Client for AWS AppSync Events
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AppSync Events Demo - リアルタイム配信</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| </head> | |
| <body> | |
| <div class="container mt-4" style="max-width: 800px;"> | |
| <h1 class="text-center mb-4">🚀 AppSync Events Demo</h1> | |
| <div class="card mb-3"> | |
| <div class="card-body"> | |
| <h5 class="card-title">接続設定</h5> | |
| <div class="mb-3"> | |
| <label for="realtimeDomain" class="form-label">Realtime Domain (WebSocket)</label> | |
| <input type="text" class="form-control" id="realtimeDomain" placeholder="xxxxx.appsync-realtime-api.ap-northeast-1.amazonaws.com"> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="apiKey" class="form-label">API Key</label> | |
| <input type="text" class="form-control" id="apiKey" placeholder="da2-xxxxxxxxxxxxxxxxxxxx"> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="channel" class="form-label">チャンネル</label> | |
| <select class="form-select" id="channel"> | |
| <option value="/default/*">/default/*</option> | |
| <option value="/default/channel">/default/channel</option> | |
| </select> | |
| </div> | |
| <div class="d-flex gap-2"> | |
| <button id="connectBtn" class="btn btn-success" onclick="connect()">接続</button> | |
| <button id="disconnectBtn" class="btn btn-danger" onclick="disconnect()" disabled>切断</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="status" class="alert alert-danger text-center fw-bold" role="alert">未接続</div> | |
| <div class="card"> | |
| <div class="card-body"> | |
| <h5 class="card-title">受信メッセージ</h5> | |
| <div id="messagesList" class="messages-list"> | |
| <div class="no-messages" id="noMessages">メッセージを待機中...</div> | |
| </div> | |
| <button class="btn btn-secondary mt-3" onclick="clearMessages()">メッセージをクリア</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let socket = null; | |
| let isConnected = false; | |
| function updateStatus(status, text) { | |
| const statusEl = document.getElementById('status'); | |
| const statusClasses = { | |
| 'connected': 'alert alert-success', | |
| 'disconnected': 'alert alert-danger', | |
| 'connecting': 'alert alert-warning' | |
| }; | |
| statusEl.className = `${statusClasses[status] || 'alert alert-secondary'} text-center fw-bold`; | |
| statusEl.textContent = text; | |
| } | |
| function connect() { | |
| const realtimeDomain = document.getElementById('realtimeDomain').value; | |
| const apiKey = document.getElementById('apiKey').value; | |
| const channel = document.getElementById('channel').value; | |
| if (!realtimeDomain || !apiKey) { | |
| alert('Realtime Domain と API Key を入力してください'); | |
| return; | |
| } | |
| updateStatus('connecting', '接続中...'); | |
| const httpDomain = realtimeDomain.replace('-realtime', ''); | |
| const header = btoa(JSON.stringify({ | |
| host: httpDomain, | |
| 'x-api-key': apiKey | |
| })).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); | |
| // WebSocket 接続 | |
| const wsUrl = `wss://${realtimeDomain}/event/realtime`; | |
| try { | |
| socket = new WebSocket(wsUrl, ['aws-appsync-event-ws', `header-${header}`]); | |
| socket.onopen = () => { | |
| subscribe(channel); // 接続後にサブスクライブ | |
| }; | |
| socket.onmessage = (event) => { | |
| handleMessage(JSON.parse(event.data)); | |
| }; | |
| socket.onclose = (event) => { | |
| setDisconnected(); | |
| }; | |
| socket.onerror = (error) => { | |
| updateStatus('disconnected', 'エラー: 接続に失敗しました'); | |
| }; | |
| } catch (error) { | |
| updateStatus('disconnected', `エラー: ${error.message}`); | |
| } | |
| } | |
| function subscribe(channel) { | |
| if (!socket || socket.readyState !== WebSocket.OPEN) { | |
| console.error('WebSocket not connected'); | |
| return; | |
| } | |
| const subscribeMessage = { | |
| type: 'subscribe', | |
| id: crypto.randomUUID(), | |
| channel: channel, | |
| authorization: { | |
| 'x-api-key': document.getElementById('apiKey').value | |
| } | |
| }; | |
| socket.send(JSON.stringify(subscribeMessage)); | |
| console.log('Sent subscribe:', subscribeMessage); | |
| } | |
| function handleMessage(data) { | |
| switch (data.type) { | |
| case 'connection_ack': | |
| break; | |
| case 'subscribe_success': | |
| setConnected(document.getElementById('channel').value); | |
| break; | |
| case 'data': | |
| // イベントデータを受信 | |
| if (data.event) { | |
| displayMessage(data); | |
| } | |
| break; | |
| case 'subscribe_error': | |
| updateStatus('disconnected', `サブスクライブエラー: ${data.errors?.[0]?.message || 'Unknown error'}`); | |
| break; | |
| case 'error': | |
| updateStatus('disconnected', `エラー: ${data.errors?.[0]?.message || 'Unknown error'}`); | |
| break; | |
| case 'ka': | |
| break; | |
| default: | |
| console.log('Unknown message type:', data.type); | |
| } | |
| } | |
| function displayMessage(data) { | |
| const messagesList = document.getElementById('messagesList'); | |
| const noMessages = document.getElementById('noMessages'); | |
| if (noMessages) { | |
| noMessages.style.display = 'none'; | |
| } | |
| const eventData = JSON.parse(data.event); | |
| const messageEl = document.createElement('div'); | |
| messageEl.className = 'message'; | |
| messageEl.innerHTML = ` | |
| <div class="message-header"> | |
| <span class="message-time">${eventData.timestamp || new Date().toISOString()}</span> | |
| </div> | |
| <div class="message-content">${JSON.stringify(eventData.payload || eventData, null, 2)}</div> | |
| `; | |
| messagesList.insertBefore(messageEl, messagesList.firstChild); | |
| } | |
| function setConnected(channel) { | |
| isConnected = true; | |
| updateStatus('connected', `接続中: ${channel}`); | |
| document.getElementById('connectBtn').disabled = true; | |
| document.getElementById('disconnectBtn').disabled = false; | |
| } | |
| function setDisconnected() { | |
| isConnected = false; | |
| updateStatus('disconnected', '未接続'); | |
| document.getElementById('connectBtn').disabled = false; | |
| document.getElementById('disconnectBtn').disabled = true; | |
| } | |
| function disconnect() { | |
| if (socket) { | |
| socket.close(); | |
| socket = null; | |
| } | |
| setDisconnected(); | |
| } | |
| function clearMessages() { | |
| const messagesList = document.getElementById('messagesList'); | |
| messagesList.innerHTML = '<div class="no-messages" id="noMessages">メッセージを待機中...</div>'; | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment