Skip to content

Instantly share code, notes, and snippets.

@shinsaka
Created February 24, 2026 06:45
Show Gist options
  • Select an option

  • Save shinsaka/7c1f4b697120baca2b65e79c5e992dfd to your computer and use it in GitHub Desktop.

Select an option

Save shinsaka/7c1f4b697120baca2b65e79c5e992dfd to your computer and use it in GitHub Desktop.
Simple Client for AWS AppSync Events
<!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