Skip to content

Ad Hoc Group Communication Based on Multicast in Wireless LAN developed via React Native

License

Notifications You must be signed in to change notification settings

x3388638/LANChat

Repository files navigation

LANChat

LANChat

基於無線區網群播之 Ad Hoc 群組通訊
Ad Hoc Group Communication Based on Multicast in Wireless LAN

即運作於區網間不需要對外網路的分散式通訊軟體,
使用者將裝置連至同一 WiFi 即可通訊。

系統內以群組作為通訊單位,
其中包含 LOBBY (公開群組)可與網路內所有使用者形成 Ad Hoc 群組並通訊,
以及使用者自行創建的私人群組。

私人群組內之訊息傳遞皆經過加密處理,
使用者於群組內可取得由群組資訊與加密金鑰組成之 QR Code,
其他使用者透過掃描 QR Code 方可加入群組。

Special thanks

Logo designed by ihtiht.

目錄

特色

  • 支援 Android、iOS 雙系統
  • 離線聊天,不須對外網路
  • 分散式,無中央伺服器
  • Ad Hoc 隨意群組
  • 訊息加密
  • 訊息形式支援純文字、圖片、檔案 (UTF8 文字檔)、票選活動、緊急訊息
  • 一鍵發送緊急訊息

安裝方式

1. 依照官方文件架設環境

https://facebook.github.io/react-native/docs/getting-started.html Building Projects with Native Code

  • Android
    • react-native-cli
    • Java JDK
    • Android Studio
  • iOS
    • react-native-cli
    • watchman
    • Xcode

2. clone project

git clone https://github.com/x3388638/LANChat.git

3. 安裝 node modules

cd LANChat
npm install

4. 在實機上執行

https://facebook.github.io/react-native/docs/running-on-device.html

Android:

adb devices
react-native run-android

iOS: 使用 Xcode 開啟

測試環境

MacOS:

MacOS: 10.13.4
Xcode: 9.3
Java JDK: 1.8.0
nodejs: 6.11.3
npm: 6.0.1
React: 16.3.1
React Native: 0.55.3
react-native-cli: 2.0.1
iOS device(s): MGAA2TA/A (11.3.1)
Android device(s): SM-T700 (5.0.2)、ASUS_Z00AD (5.0)、SGP312 (5.0.2)、C5502 (5.0.2)

Ubuntu:

Ubuntu: 16.04
Java JDK: 1.8.0
nodejs: 6.11.3
npm: 3.10.10
React: 16.3.1
React Native: 0.55.3
react-native-cli: 2.0.1
Android device(s): ASUS_Z00AD (5.0)

系統畫面

登入

群組列表 (主頁面)

個人資料

掃描 QR Code (加入群組)

創建群組

群組頁面

發送緊急訊息

資料格式

封包

alive (udp)

{
    type: 'alive',
    paylaod: {
        ip: 'string'
    }
}

userData (tcp)

{
    type: 'userData',
    payload: {
        uid: 'string',
        data: {
            username: 'string',
            selfIntro: 'string',
            uid: 'string'
        },
        joinedGroups: {
            [group ID]: 'encrypted groupID string'
        }
    }
}

msg (tcp)

encrypted

{
    type: 'msg',
    payload: {
        groupID: 'string',
        encryptedID: 'encrypted groupID string',
        data: Encrypt('group key string', JSON.stringify({
            key: 'msg id string',
            sender: 'user id string',
            timestamp: 'ISO 8601 string',
            type: 'text" || 'poll' || 'vote' || 'img' || 'file',
            [type]: 'string' || 'img base64 string' || 'file string'
        }))
    }
}

plain text

{
    type: 'msg',
    payload: {
        groupID: 'LOBBY',
        data: {
            key: 'msg id string',
            sender: 'user id string',
            timestamp: 'ISO 8601 string',
            type: 'text' || 'emergency' || 'poll' || 'vote' || 'img' || 'file',
            [type]: 'string' || 'img base64 string' || 'file string'
        }
    }
}

type: text

text: 'string'

type: emergency

emergency: 'string'

type: img

img: 'base64 string'

type: poll

poll: {
    pollID: 'uuid',
    title: 'string',
    desc: 'string',
    options: [
        {
            id: 'option id string',
            text: 'string'
        }
    ]
}

type: vote

vote: {
    voteID: 'vote id string',
    pollID: 'poll id string',
    optionID: 'option id string'
}

type: file

file: {
    fileID: 'string',
    fileName: 'string',
}

msgSync (tcp)

{
    type: 'msgSync',
    payload: {
        [groupID]: {
            encryptID: 'encrypted groupID string',
            messages: Encrypt('group key string', JSON.stringify([
                {
                    key: 'msg id string',
                    sender: 'user id string',
                    timestamp: 'ISO 8601 string',
                    type: 'text' || 'poll' || 'vote' || 'img' || 'file',
                    [type]: 'string' || 'img base64 string' || 'file string'
                }
            ]))
        },
        'LOBBY': {
            messages: JSON.stringify([
                {
                    key: 'msg id string',
                    sender: 'user id string',
                    timestamp: 'ISO 8601 string',
                    type: 'text' || 'poll' || 'vote' || 'img' || 'file',
                    [type]: 'string' || 'img base64 string' || 'file string'
                }
            ])
        }
    }
}

tcp fileReq

encrypted

{
    type: 'fileReq',
    payload: {
        bssid: {{ group's bssid }}
        groupID: 'group id',
        data: Encrypt('group key string', JSON.stringify({
            fileID: 'file id string',
            reqID: 'file request id string'
        })
    }
}

plain text

{
    type: 'fileReq',
    payload: {
        groupID: 'LOBBY',
        data: JSON.stringify({
            fileID: 'file id string',
            reqID: 'file request id string'
        })
    }
}

tcp fileRes

encrypted

{
    type: 'fileRes',
    payload: {
        groupID: 'group id',
        data: Encrypted('group key string', JSON.stringify({
            reqID: 'file request id plain text',
            error: null || 'error msg',
            fileName: 'string',
            file: 'utf8 string'
        })
    }
}

plain text

{
    type: 'fileRes',
    payload: {
        groupID: 'group id',
        data: JSON.stringify({
            reqID: 'file request id plain text',
            error: null || 'error msg',
            fileName: 'string',
            file: 'utf8 string'
        })
    }
}

儲存 (AsyncStorage & global)

@LANChat:personalInfo

{
    normal: {
        username: 'string',
        selfIntro: '+string',
        uid: 'hash(deviceID)'
    },
    emergency: {
        name: '+string',
        birth: '+string',
        phone: '+string',
        gender: '+string ("F" || "M")',
        bloodType: '+string ("A" || "B" || "AB" || "O")',
        address: '+string',
        memo: '+string'
    }
}

@LANChat:joinedGroups

{
    [bssid]: {
        [groupID]: {
            groupID: 'string',
            groupName: 'string',
            groupDesc: 'string',
            key: 'string',
            net: {
                ssid: 'string',
                bssid: 'string'
            },
            createdTime: 'ISO 8601 string'
        }
    }
}

@LANChat:messages

{
    [bssid]: {
        [groipID]: {
            [msgID]: {
                key: 'msg id string',
                read: 'boolean',
                sender: 'user id string',
                timestamp: 'ISO 8601 string',
                type: 'text' || 'img' || 'file',
                [type]: 'string' || 'img base64 string' || file Buffer
            }
        }
    }
}

@LANChat:users

{
    [uid]: {
        uid: 'string',
        username: 'string',
        selfIntro: 'string',
        lastSeen: 'ISO 8601 string',
        joinedGroups: ['group ID string']
    }
}

@LANChat:usersByNet

{
    [bssid]: {
        [uid]: 1
    }
}

@LANChat:poll

{
    [pollID]: {
    	bssid: 'string',
        groupID: 'group id string',
        creater: 'user id string',
        title: 'string',
        desc: 'string',
        timestamp: 'ISO 8601 string',
        options: [
            {
                id: 'option id string'
                text: 'string'
            }
        ]
    }
}

@LANChat:vote

{
    [voteID]: {
        bssid: 'bssid',
        groupID: 'group id string',
        pollID: 'poll id string',
        optionID: 'option id string',
        voter: 'user id string',
        timestamp: 'ISO 8601 string'
    }
}

@LANChat:file

{
    [bssid]: {
        [groupID]: {
            [fileID]: {
                fileID: 'string',
                fileName: 'string',
                filePath: 'string'
            }
        }
    }
}

global.netUsers

{
    [ip]: {
        uid: 'user id string',
        username: 'string',
        selfIntro: 'string',
        lastSeen: 'ISO 8601 string',
        ip: 'string',
        tcpSocket: 'net socket'
    }
}