-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
483 additions
and
48 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
dist/ | ||
docs/ | ||
node_modules/ | ||
public/static |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Topology Protocol Example | ||
|
||
This is an example of Topology Protocol usage in a chat system where a user can create or connect to a chat room, send and read the messages sent in the group chat. | ||
|
||
## Specifics | ||
|
||
Messages are represented as strings in the format (timestamp, content, senderId). Chat is a class which extends TopologyObject and has Gset\<string> as an attribute to store the list of messages. | ||
|
||
## How to run locally | ||
|
||
After cloning the repository, run the following commands: | ||
|
||
```bash | ||
cd ts-topology/examples/chat | ||
yarn | ||
yarn build | ||
yarn dev | ||
``` | ||
|
||
Debugging is made easier by setting the mode in `webpack.config.js` to "development": | ||
|
||
```js | ||
module.exports = { | ||
mode: "development", | ||
... | ||
} | ||
``` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
{ | ||
"name": "topology-example-chat", | ||
"version": "1.0.0", | ||
"description": "Topology Protocol Chat Exmaple", | ||
"main": "src/index.ts", | ||
"repository": "https://github.com/topology-foundation/ts-topology.git", | ||
"license": "MIT", | ||
"dependencies": { | ||
"@topology-foundation/crdt": "file:../../packages/crdt", | ||
"@topology-foundation/network": "file:../../packages/network", | ||
"@topology-foundation/node": "file:../../packages/node", | ||
"@topology-foundation/object": "file:../../packages/object", | ||
"crypto-browserify": "^3.12.0", | ||
"process": "^0.11.10", | ||
"stream-browserify": "^3.0.0", | ||
"ts-node": "^10.9.2", | ||
"vm-browserify": "^1.1.2" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.11.16", | ||
"ts-loader": "^9.3.1", | ||
"typescript": "^4.7.4", | ||
"webpack": "^5.74.0", | ||
"webpack-cli": "^5.1.4", | ||
"webpack-dev-server": "^5.0.4" | ||
}, | ||
"scripts": { | ||
"build": "webpack", | ||
"clean": "rm -rf dist/ node_modules/", | ||
"dev": "webpack serve", | ||
"start": "ts-node ./src/index.ts" | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Topology - Chat</title> | ||
</head> | ||
<body> | ||
<div> | ||
<h1>Topology Protocol - Chat</h1> | ||
<p>Current peer ID <span id="peerId"></span></p> | ||
<p>Connected to <span id="chatId"></span></p> | ||
<p>peers: <span id="peers"></span></p> | ||
<p>discovery_peers: <span id="discoveryPeers"></span></p> | ||
<p>object_peers: <span id="objectPeers"></span></p> | ||
|
||
<input id="roomInput" type="text" placeholder="Room ID" /> | ||
<button id="joinRoom">Connect</button> | ||
<button id="fetchMessages">Fetch Messages</button> | ||
<button id="createRoom">Create Room</button> | ||
</div> | ||
|
||
<div | ||
id="chat" | ||
style=" | ||
overflow-y: scroll; | ||
min-height: 200px; | ||
max-height: 60vh; | ||
width: 100%; | ||
" | ||
> | ||
<!-- Messages will appear here --> | ||
</div> | ||
|
||
<div style="margin-bottom: 10px"> | ||
<input id="messageInput" type="text" placeholder="Message" /> | ||
<button id="sendMessage">Send</button> | ||
</div> | ||
|
||
<script> | ||
var input = document.getElementById("messageInput"); | ||
input.addEventListener("keypress", function (event) { | ||
if (event.key === "Enter") { | ||
document.getElementById("sendMessage").click(); | ||
} | ||
}); | ||
</script> | ||
|
||
<script src="./static/bundle/script.js"></script> | ||
</body> | ||
</html> |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { toString as uint8ArrayToString } from "uint8arrays/to-string"; | ||
import { IChat } from "./objects/chat"; | ||
|
||
export const handleChatMessages = (chat: IChat, e: any) => { | ||
if (e.detail.msg.topic === "topology::discovery") return; | ||
const input = uint8ArrayToString(e.detail.msg.data); | ||
const message = JSON.parse(input); | ||
console.log("Received message!: ", message); | ||
switch (message["type"]) { | ||
case "object_update": { | ||
const fn = uint8ArrayToString(new Uint8Array(message["data"])); | ||
handleObjectUpdate(chat, fn); | ||
break; | ||
} | ||
default: { | ||
break; | ||
} | ||
} | ||
}; | ||
|
||
function handleObjectUpdate(chat: IChat, fn: string) { | ||
// In this case we only have addMessage | ||
// `addMessage(${timestamp}, ${message}, ${node.getPeerId()})` | ||
let args = fn.replace("addMessage(", "").replace(")", "").split(", "); | ||
console.log("Received message: ", args); | ||
try { | ||
chat.addMessage(args[0], args[1], args[2]); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { TopologyNode } from "@topology-foundation/node"; | ||
import { Chat, IChat } from "./objects/chat"; | ||
import { handleChatMessages } from "./handlers"; | ||
import { GSet } from "@topology-foundation/crdt"; | ||
|
||
const node = new TopologyNode(); | ||
// CRO = Conflict-free Replicated Object | ||
let chatCRO: IChat; | ||
let peers: string[] = []; | ||
let discoveryPeers: string[] = []; | ||
let objectPeers: string[] = []; | ||
|
||
const render = () => { | ||
const element_peerId = <HTMLDivElement>document.getElementById("peerId"); | ||
element_peerId.innerHTML = node.networkNode.peerId; | ||
|
||
const element_peers = <HTMLDivElement>document.getElementById("peers"); | ||
element_peers.innerHTML = "[" + peers.join(", ") + "]"; | ||
|
||
const element_discoveryPeers = <HTMLDivElement>document.getElementById("discoveryPeers"); | ||
element_discoveryPeers.innerHTML = "[" + discoveryPeers.join(", ") + "]"; | ||
|
||
const element_objectPeers = <HTMLDivElement>document.getElementById("objectPeers"); | ||
element_objectPeers.innerHTML = "[" + objectPeers.join(", ") + "]"; | ||
|
||
if(!chatCRO) return; | ||
const chat = chatCRO.getMessages(); | ||
const element_chat = <HTMLDivElement>document.getElementById("chat"); | ||
element_chat.innerHTML = ""; | ||
|
||
if(chat.set().size == 0){ | ||
const div = document.createElement("div"); | ||
div.innerHTML = "No messages yet"; | ||
div.style.padding = "10px"; | ||
element_chat.appendChild(div); | ||
return; | ||
} | ||
Array.from(chat.set()).sort().forEach((message: string) => { | ||
const div = document.createElement("div"); | ||
div.innerHTML = message; | ||
div.style.padding = "10px"; | ||
element_chat.appendChild(div); | ||
}); | ||
|
||
} | ||
|
||
async function sendMessage(message: string) { | ||
let timestamp: string = Date.now().toString(); | ||
if(!chatCRO) { | ||
console.error("Chat CRO not initialized"); | ||
alert("Please create or join a chat room first"); | ||
return; | ||
} | ||
console.log("Sending message: ", `(${timestamp}, ${message}, ${node.networkNode.peerId})`); | ||
chatCRO.addMessage(timestamp, message, node.networkNode.peerId); | ||
|
||
node.updateObject(chatCRO, `addMessage(${timestamp}, ${message}, ${node.networkNode.peerId})`); | ||
render(); | ||
} | ||
|
||
async function main() { | ||
await node.start(); | ||
render(); | ||
|
||
node.addCustomGroupMessageHandler((e) => { | ||
handleChatMessages(chatCRO, e); | ||
peers = node.networkNode.getAllPeers(); | ||
discoveryPeers = node.networkNode.getGroupPeers("topology::discovery"); | ||
if(chatCRO) objectPeers = node.networkNode.getGroupPeers(chatCRO.getObjectId()); | ||
render(); | ||
}); | ||
|
||
let button_create = <HTMLButtonElement>document.getElementById("createRoom"); | ||
button_create.addEventListener("click", () => { | ||
chatCRO = new Chat(node.networkNode.peerId); | ||
node.createObject(chatCRO); | ||
(<HTMLButtonElement>document.getElementById("chatId")).innerHTML = chatCRO.getObjectId(); | ||
render(); | ||
}); | ||
|
||
let button_connect = <HTMLButtonElement>document.getElementById("joinRoom"); | ||
button_connect.addEventListener("click", async () => { | ||
let input: HTMLInputElement = <HTMLInputElement>document.getElementById("roomInput"); | ||
let objectId = input.value; | ||
if(!objectId){ | ||
alert("Please enter a room id"); | ||
return; | ||
} | ||
await node.subscribeObject(objectId, true); | ||
}); | ||
|
||
let button_fetch = <HTMLButtonElement>document.getElementById("fetchMessages"); | ||
button_fetch.addEventListener("click", async () => { | ||
let input: HTMLInputElement = <HTMLInputElement>document.getElementById("roomInput"); | ||
let objectId = input.value; | ||
try { | ||
|
||
let object: any = node.getObject(objectId); | ||
console.log("Object received: ", object); | ||
|
||
let arr: string[] = Array.from(object["chat"]["_set"]); | ||
object["chat"]["_set"] = new Set<string>(arr); | ||
object["chat"] = Object.assign(new GSet<string>(new Set<string>()), object["chat"]); | ||
chatCRO = Object.assign(new Chat(node.networkNode.peerId), object); | ||
|
||
(<HTMLButtonElement>document.getElementById("chatId")).innerHTML = objectId; | ||
render(); | ||
} catch (e) { | ||
console.error("Error while connecting to the CRO ", objectId, e); | ||
} | ||
}); | ||
|
||
let button_send = <HTMLButtonElement>document.getElementById("sendMessage"); | ||
button_send.addEventListener("click", async () => { | ||
let input: HTMLInputElement = <HTMLInputElement>document.getElementById("messageInput"); | ||
let message: string = input.value; | ||
input.value = ""; | ||
if(!message){ | ||
console.error("Tried sending an empty message"); | ||
alert("Please enter a message"); | ||
return; | ||
} | ||
await sendMessage(message); | ||
const element_chat = <HTMLDivElement>document.getElementById("chat"); | ||
element_chat.scrollTop = element_chat.scrollHeight; | ||
}); | ||
} | ||
|
||
main(); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { TopologyObject } from "@topology-foundation/object"; | ||
import { GSet } from "@topology-foundation/crdt"; | ||
|
||
export interface IChat extends TopologyObject { | ||
chat: GSet<string>; | ||
addMessage(timestamp: string, message: string, node_id: string): void; | ||
getMessages(): GSet<string>; | ||
merge(other: Chat): void; | ||
} | ||
|
||
export class Chat extends TopologyObject implements IChat { | ||
// store messages as strings in the format (timestamp, message, peerId) | ||
chat: GSet<string>; | ||
|
||
constructor(peerId: string) { | ||
super(peerId); | ||
this.chat = new GSet<string>(new Set<string>()); | ||
} | ||
|
||
addMessage(timestamp: string, message: string, node_id: string): void { | ||
this.chat.add(`(${timestamp}, ${message}, ${node_id})`); | ||
} | ||
|
||
getMessages(): GSet<string> { | ||
return this.chat; | ||
} | ||
|
||
merge(other: Chat): void { | ||
this.chat.merge(other.chat); | ||
} | ||
|
||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es6", | ||
"module": "ESNEXT", | ||
"rootDir": ".", | ||
"strict": true, | ||
"moduleResolution": "node", | ||
"allowSyntheticDefaultImports": true, | ||
"allowJs": true, | ||
"esModuleInterop": true, | ||
"skipLibCheck": true, | ||
"forceConsistentCasingInFileNames": true | ||
} | ||
} |
Oops, something went wrong.