File size: 3,418 Bytes
73d36eb
 
 
dc1562a
73d36eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const { Server } = require("socket.io");

// Node.js 18+ならfetchは標準搭載
const port = 7860;
const io = new Server(port, {
  cors: {
    origin: "*",
  }
});

let activeUsers = 0;
const userSessions = {}; // 現在のセッション
const userSessionHistory = []; // 過去のセッション履歴も含む

// プライベートIPアドレスかどうかを判定する関数
function isPrivateIP(ip) {
  const parts = ip.split('.').map(part => parseInt(part, 10));
  if (parts.length !== 4) return false;
  const [a, b] = parts;

  return (
    a === 10 || // 10.0.0.0/8
    (a === 172 && b >= 16 && b <= 31) || // 172.16.0.0/12
    (a === 192 && b === 168) || // 192.168.0.0/16
    a === 127 || // 127.0.0.1 (loopback)
    a === 0 // 0.0.0.0 (unspecified)
  );
}

async function fetchIpInfo(ip) {
  try {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 5000); // 5s timeout

    const res = await fetch(`https://ipinfo.io/${ip}/json`, {
      signal: controller.signal,
    });

    clearTimeout(timeout);

    if (!res.ok) throw new Error(`HTTP error ${res.status}`);
    return await res.json();
  } catch (e) {
    if (e.name === 'AbortError') {
      console.error("IP info fetch timed out for IP:", ip);
    } else {
      console.error("IP info fetch failed:", e);
    }
    return null;
  }
}

io.on("connection", async (socket) => {
  if (socket.handshake.query.isAdmin === "true") {
    // 管理画面接続時に過去履歴を送信
    socket.emit("userSessionHistory", userSessionHistory);

    // 現在の接続数と現在のセッションも送信
    socket.emit("updateUserCount", activeUsers);
    socket.emit("userSessionsUpdate", userSessions);

    return;
  }

  console.log(`New connection from ${socket.id}, isAdmin: ${socket.handshake.query.isAdmin}`);

  activeUsers++;

  // IPアドレスを取得
  let ips = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address;
  if (typeof ips === 'string') {
    ips = ips.split(',').map(ip => ip.trim());
  } else {
    ips = [ips];
  }

  const ipString = ips.join(', '); // 全IPをカンマ区切りで保存
  const connectTime = new Date();

  userSessions[socket.id] = {
    socketId: socket.id,
    ip: ipString, // 全部のIPアドレス
    connectTime,
    disconnectTime: null,
    loc: null,
    city: null,
    region: null,
    country: null,
  };

  // 最初の公開IPだけをAPIに渡す
  const firstPublicIp = ips.find(ip => ip && !isPrivateIP(ip));
  if (firstPublicIp) {
    const ipInfo = await fetchIpInfo(firstPublicIp);
    if (ipInfo) {
      userSessions[socket.id].loc = ipInfo.loc || null;
      userSessions[socket.id].city = ipInfo.city || null;
      userSessions[socket.id].region = ipInfo.region || null;
      userSessions[socket.id].country = ipInfo.country || null;
    }
  }

  io.emit("updateUserCount", activeUsers);
  io.emit("userSessionsUpdate", userSessions);

  socket.on("disconnect", () => {
    activeUsers--;
    const disconnectTime = new Date();

    if (userSessions[socket.id]) {
      userSessions[socket.id].disconnectTime = disconnectTime;

      // 履歴にも保存
      userSessionHistory.push({ ...userSessions[socket.id] });

      // ※ 現在のセッションは保持しておく
    }

    io.emit("updateUserCount", activeUsers);
    io.emit("userSessionsUpdate", userSessions);
  });
});