ズブの素人がCursorを使ってリアルタイム入力に対応した掲示板を作ってみた

こんにちは。
最近個人開発をはじめたHirotoと申します。

このたび、Cursorというものを知って色々と遊んでおり、なんとたった1日でWebアプリ「NEAR」をリリースすることになりました！

内容は、リアルタイム同期できる掲示板です。

https://near-production.up.railway.app/

## プロジェクト概要

「NEAR」は、リアルタイムコミュニケーションを可能にする現代的な掲示板アプリケーションです。従来の掲示板との違いは、ユーザーの入力状況をリアルタイムで共有する機能や、多彩なカテゴリ分け、テキスト装飾機能など、よりインタラクティブな体験を提供することにあります。


## 開発の経緯

このプロジェクトは、既存の掲示板サービスの不満点を解消するために始まりました。特に以下の点に焦点を当てて開発を進めました：

- リアルタイム性の欠如
- モバイル対応の不足
- 視覚的な表現力の制限

## 主要な技術スタック

- バックエンド: **Node.js + Express**
- リアルタイム通信: **WebSocket (ws)**
- 認証: **Google OAuth 2.0 + Passport.js**
- フロントエンド: **HTML5, CSS3, JavaScript (バニラ)**
- データ保存: **ファイルベースのJSON保存**

## 主な機能

### 1. リアルタイムメッセージング

WebSocketを使用したリアルタイム通信により、メッセージは投稿と同時にLINEのように全ユーザーの画面に反映されます。


```javascript
// WebSocket接続設定
function connectWebSocket(sid) {
  if (ws) {
    ws.close();
  }

  try {
    ws = new WebSocket((location.protocol === 'https:' ? 'wss:' : 'ws:') + 
      '//' + location.host + `?sessionId=${sid}`);

    ws.onopen = () => {
      console.log('WebSocket connected');
    };

    ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        if (data.type === 'categories') {
          categories = data.categories;
          showCategories(data.categories);
          selectCategory('general');
        } else if (data.type === 'message') {
          if (data.categoryId === currentCategory) {
            appendMessage(data);
            const messagesContainer = document.getElementById("messages");
            messagesContainer.scrollTop = 0;
          }
        } else if (data.type === 'typing') {
          if (data.categoryId === currentCategory) {
            updateTypingIndicator(data.users);
          }
        }
      } catch (error) {
        console.error('Error processing WebSocket message:', error);
      }
    };
    // エラー処理も実装
  } catch (error) {
    console.error('WebSocket connection error:', error);
    handleWebSocketError();
  }
}
```

### 2. 入力中表示機能

ユーザーが入力している内容をリアルタイムで他のユーザーに表示する機能を実装しました。これにより、チャットのような即時性を掲示板に持たせることができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4015962/e1a48ae3-14a9-44c9-b9fd-d183e9d8d6dd.png)

```javascript
// 入力中の状態を更新
async function updateTypingStatus(isTyping) {
  if (!sessionId || !currentCategory) return;

  const input = document.getElementById('messageInput');
  let content = input.value.trim();

  // 装飾ボタンの状態に応じてMarkdown形式に変換
  content = applyDecorations(content);

  try {
    await fetch('/api/typing', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        sessionId,
        isTyping,
        content,
        categoryId: currentCategory
      }),
    });
  } catch (error) {
    console.error('Typing status update error:', error);
  }
}
```

### 3. Google OAuth認証

Passport.jsとGoogle OAuth 2.0を使用した安全な認証システムを実装しました。特にモバイルデバイスでの認証体験を最適化するために多くの調整を行いました。

```javascript
passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: process.env.GOOGLE_CALLBACK_URL || "/auth/google/callback",
      // モバイルデバイスのサポートを改善
      userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo",
      // セッションの改善
      passReqToCallback: true,
    },
    (request, accessToken, refreshToken, profile, done) => {
      return done(null, profile);
    }
  )
);
```

### 4. カテゴリ管理

複数のカテゴリでメッセージを整理し、ユーザーが興味のある話題に簡単にアクセスできる仕組みを構築しました。

```javascript
// カテゴリの定義
const categories = [
  { id: "general", name: "一般", description: "一般的な話題" },
  { id: "tech", name: "テクノロジー", description: "技術関連の話題" },
  { id: "sports", name: "スポーツ", description: "スポーツ関連の話題" },
  // 他多数のカテゴリ...
];

// カテゴリの選択関数
async function selectCategory(categoryId) {
  currentCategory = categoryId;

  // カテゴリの選択状態を更新
  document.querySelectorAll('.category-item').forEach(item => {
    item.classList.remove('active');
  });
  const selectedItem = document.querySelector(`.category-item[data-id="${categoryId}"]`);
  if (selectedItem) {
    selectedItem.classList.add('active');
  }

  // カテゴリヘッダーと内容を更新する処理...
}
```

### 5. テキスト装飾機能

メッセージに表現力を持たせるために、太字、下線、色、サイズなどを変更できる装飾機能を実装しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4015962/ad55f9db-c8fc-40c6-b821-d68bedfd25bb.png)


```javascript
// 装飾を適用する関数
function applyDecorations(content) {
  if (!content) return '';

  // 装飾ボタンの状態に応じてMarkdown形式に変換
  if (activeDecorations.bold) content = `**${content}**`;
  if (activeDecorations.italic) content = `*${content}*`;
  if (activeDecorations.underline) content = `__${content}__`;
  if (activeDecorations.code) content = `\`${content}\``;

  // 色とサイズはHTML形式で対応
  if (selectedColor || activeDecorations.size) {
    let style = '';
    if (selectedColor) style += `color: ${selectedColor};`;
    if (activeDecorations.size) style += `font-size: ${activeDecorations.size};`;
    content = `<span style="${style}">${content}</span>`;
  }

  return content;
}
```

### 6. レスポンシブデザイン

PCからスマートフォンまで様々なデバイスで快適に利用できるようレスポンシブデザインを採用しました。特にモバイル向けのハンバーガーメニューの実装は、使いやすさを向上させています。

```javascript
// モバイルメニュー制御
function setupMobileMenu() {
  const menuButton = document.getElementById('menuButton');
  const closeButton = document.getElementById('closeMenu');
  const overlay = document.getElementById('overlay');
  const categoriesMenu = document.getElementById('categories');

  if (menuButton && closeButton && overlay && categoriesMenu) {
    // ハンバーガーメニューボタンのクリックイベント
    menuButton.addEventListener('click', () => {
      categoriesMenu.classList.add('open');
      overlay.style.display = 'block';
      document.body.style.overflow = 'hidden'; // スクロール防止
    });

    // 閉じるボタンと画面外タップでメニューを閉じる処理...
  }
}
```

## 開発で直面した課題

### 1. LINE/埋め込みブラウザでのGoogle認証問題

Googleは2022年以降、LINEやFacebookなどの埋め込みブラウザでのOAuth認証を制限しています。この問題に対応するため、ブラウザ検出とユーザーガイダンスの実装が必要でした。

```javascript
// LINE ブラウザ検出関数
function isLineOrEmbeddedBrowser(userAgent) {
  return (
    userAgent &&
    (userAgent.includes("Line") ||
      userAgent.includes("FBAV") ||
      userAgent.includes("Instagram") ||
      userAgent.includes("FBAN"))
  );
}

// Google認証ルートの修正
app.get("/auth/google", (req, res, next) => {
  const userAgent = req.headers["user-agent"] || "";
  
  // LINE または他の埋め込みブラウザかチェック
  if (isLineOrEmbeddedBrowser(userAgent)) {
    // 外部ブラウザを使用するよう指示するページを表示
    return res.send(`
      <!DOCTYPE html>
      <html>
      <head>
        <!-- LINEブラウザ検出時のHTML... -->
      </head>
      <body>
        <div class="container">
          <h1>外部ブラウザでログインしてください</h1>
          <!-- 外部ブラウザ使用の案内 -->
        </div>
      </body>
      </html>
    `);
  }
  
  // 通常のブラウザの場合は、元の認証フローを続行
  passport.authenticate("google", authOptions)(req, res, next);
});
```

### 2. WebSocketコネクション管理

WebSocketを使った安定したリアルタイム通信の実装は特に難しい部分でした。接続の維持、再接続処理、認証状態のセキュアな管理などなど。

```javascript
// WebSocket接続時の処理
wss.on("connection", (ws, req) => {
  // セッションIDを取得
  const url = new URL(req.url, "http://localhost");
  const sessionId = url.searchParams.get("sessionId");

  if (!sessionId) {
    ws.close(1008, "Session ID is required");
    return;
  }

  const user = userSessions.get(sessionId);
  if (!user) {
    ws.close(1008, "Invalid session");
    return;
  }

  clients.add(ws);
  console.log(`新しいクライアントが接続しました: ${user.name}`);

  // 初期データの送信と各種イベントハンドラの設定...
});
```

### 3. モバイル対応のUI設計

画面サイズが限られたモバイルデバイスで、多くの機能を使いやすく配置するように意識しました。特に装飾ボタンの配置調整とか。

```css
/* モバイル表示時のヘッダー調整 */
@media (max-width: 768px) {
  .container {
    flex-direction: column;
    padding: 10px;
  }

  .header {
    position: relative;
    margin-bottom: 1rem;
  }

  .hamburger-button {
    display: block;
  }

  .categories {
    position: fixed;
    top: 0;
    left: -300px;
    width: 250px;
    height: 100vh;
    background: white;
    z-index: 99;
    transition: left 0.3s ease;
    overflow-y: auto;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    padding-top: 60px;
  }

  /* その他のモバイル調整スタイル... */
}
```

## 今後の展望

NEARの開発は継続中で、以下の機能追加を予定しています：

1. **画像・メディア共有機能**: ユーザーが画像や他のメディアを共有できる機能
2. **通知システム**: 新しい投稿やメンション時の通知
3. **検索機能の強化**: カテゴリ内のメッセージをキーワードで検索
4. **ユーザープロフィール**: より詳細なプロフィール情報の表示

## まとめ

NEARの開発を通じて、フロントエンドとバックエンドの統合、認証システム、リアルタイム通信など多くの技術的課題に取り組みました。特にユーザー体験を最優先に考え、デザインの細部にこだわったことで、初期の目標以上の成果を達成できたと感じています。

今後もユーザーフィードバックを取り入れながら、より使いやすく、魅力的なプラットフォームへと進化させていく予定です。

---

*この記事は「NEAR」の開発者が実際の開発過程をもとに執筆しました。お問い合わせやフィードバックは[こちら](marksman.gk@gmail.com)からお寄せください。*