From 639ee50fc9a8b4e0db24632b3a3ac23c42f16c07 Mon Sep 17 00:00:00 2001 From: Taruni Paleru Date: Thu, 3 Aug 2023 18:13:25 -0400 Subject: [PATCH] removing all fake news --- docs/README.md | 8 +- docs/_sidebar.md | 9 +- docs/builder/README.md | 2 +- docs/builder/deploy.md | 11 +- docs/buildkit/README.md | 206 +++++++++++++++++++++++-------- docs/buildkit/reference.md | 136 -------------------- docs/index.html | 2 +- docs/statics/appId-appSecret.png | Bin 0 -> 7129 bytes docs/statics/logo.svg | 82 ++++-------- docs/statics/reg-logo.svg | 58 +++++++++ package-lock.json | 6 +- package.json | 13 +- 12 files changed, 259 insertions(+), 274 deletions(-) delete mode 100644 docs/buildkit/reference.md create mode 100644 docs/statics/appId-appSecret.png create mode 100644 docs/statics/reg-logo.svg diff --git a/docs/README.md b/docs/README.md index 29ea1e78..ceddefaf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,12 @@ -drawing +drawing -> A multiplayer game development framework & cloud hosting platform +> Democratizing multiplayer game development Today, Hathora consists of three major components: - [Hathora Builder](/builder/README.md): opinionated NodeJS framework for multiplayer games -- [Hathora BuildKit](/buildkit/README.md): lightweight SDKs for building networked apps that conform to the Hathora Protocol -- [Hathora Cloud](/cloud/README.md): managed hosting platform to run + scale applications confirming to the Hathora Protocol +- [Hathora BuildKit](/buildkit/README.md): lightweight networking libraries that make it easy to write real-time client-server applications in JavaScript +- [Hathora Cloud](https://hathora.dev/docs): managed hosting platform for multiplayer game servers This is how the three parts fit in: diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 720c6fbd..466569f2 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,18 +1,13 @@ - [Overview](README.md) -- [Architecture](architecture.md) -- [Roadmap](roadmap.md) - Hathora BuildKit - [Getting Started](buildkit/README.md) - - [Tutorial: Top Down Shooter](buildkit/tutorial_top_down_shooter.md) + - [Tutorial: Top Down Shooter](https://bullet-mania.vercel.app/) - [Tutorial: 3D Platformer](buildkit/tutorial_3d_platformer.md) - - [Reference](buildkit/reference.md) - Hathora Cloud - - - [Overview](cloud/README.md) - - [CLI](cloud/reference.md) + - [Get started](https://hathora.dev/docs) - Hathora Builder diff --git a/docs/builder/README.md b/docs/builder/README.md index c5a1ec05..574d0199 100644 --- a/docs/builder/README.md +++ b/docs/builder/README.md @@ -1,5 +1,5 @@
- drawing + drawing Builder
diff --git a/docs/builder/deploy.md b/docs/builder/deploy.md index df562bae..d0d508b0 100644 --- a/docs/builder/deploy.md +++ b/docs/builder/deploy.md @@ -39,13 +39,6 @@ For Typescript backends, the only file inside the `server/dist` directory is `in - Node.js must be installed - The `DATA_DIR` environment variable must be set to the path where Hathora should write its data files -- The `COORDINATOR_HOST` environment variable must be set to the URL of the Coordinator being used (if you are using a custom Coordinator other than the Hathora managed one) - -Example command to start the backend: - -```sh -COORDINATOR_HOST=coordinator.example.com DATA_DIR=./data node index.mjs -``` ### Hathora Cloud @@ -60,6 +53,4 @@ Navigate to the project root for your game and confirm there is a Dockerfile (th └─ server ``` -Run `hathora cloud deploy --appName ${MY_GAME_NAME}` to begin the global deployment process. If you run into any issues, please refer to the [Hathora Cloud](../cloud/README.md) documentation. - -> You maybe interested in setting up Continuous Delivery. You can find instructions [here](../cloud/README.md#continuous-delivery). +Run `hathora cloud deploy --appName ${MY_GAME_NAME}` to begin the global deployment process. If you run into any issues, please refer to the [Hathora Cloud](https://hathora.dev/docs) documentation. diff --git a/docs/buildkit/README.md b/docs/buildkit/README.md index cb79a5f2..49ba77c3 100644 --- a/docs/buildkit/README.md +++ b/docs/buildkit/README.md @@ -1,9 +1,9 @@
- drawing + drawing BuildKit
- + GitHub logo Hathora BuildKit @@ -13,11 +13,11 @@ -Hathora BuildKit makes it easy to write applications that conform to the Hathora Protocol, and therefore can be deployed on [Hathora Cloud](../cloud/README.md). +Hathora BuildKits are lightweight networking libraries. They make it easier to write real-time client-server applications in JavaScript. ## Benefits -- Simple SDKs provide powerful and scalable messaging infrastructure +- Import a simple library to provide powerful and scalable messaging infrastructure - Easy deployment onto Hathora Cloud, which provides global compute scheduling and an optimized edge network - Flexible with regards to language, persistence, serialization techniques, etc (currently only Typescript is supported, but more languages coming soon) @@ -42,79 +42,183 @@ Hathora BuildKit makes it easy to write applications that conform to the Hathora ``` 2. Inside this typescript project, install the server SDKs: `npm i @hathora/server-sdk`. Also install the required dev dependencies: `npm i -D typescript ts-node @types/node`. -3. Grab an `appId` + `appSecret` pair by running `curl -X POST https://coordinator.hathora.dev/registerApp`. -4. Set the `APP_SECRET` environment variable and create the following `server.mts` file: +3. Log onto [Hathora Console](https://console.hathora.dev/login) and [create an application](https://hathora.dev/docs/guides/deploy-hathora#create-an-application). +4. Grab the `appId` + `appSecret` from your application's page as seen below. + +drawing + +5. Set the `APP_SECRET` environment variable and create the following `server.mts` file: ```ts // server.mts -import { register } from "@hathora/server-sdk"; - -const coordinator = await register({ - appSecret: process.env.APP_SECRET!, - authInfo: { anonymous: { separator: "-" } }, - store: { - newState(roomId, userId, data) { - console.log("newState", roomId.toString(36), userId); - }, - subscribeUser(roomId, userId) { - console.log("subscribeUser", roomId.toString(36), userId); - }, - unsubscribeUser(roomId, userId) { - console.log("unsubscribeUser", roomId.toString(36), userId); - }, - onMessage(roomId, userId, data) { - const dataBuf = Buffer.from(data.buffer, data.byteOffset, data.byteLength); - console.log("onMessage", roomId.toString(36), userId, dataBuf.toString("utf8")); - coordinator.sendMessage(roomId, userId, dataBuf); - }, +// ... + +const store: Application = { + // A function called by Hathora to verify a connecting user's token + verifyToken(token: string): UserId | undefined { + const userId = verifyJwt(token, "YOUR_HATHORA_APP_SECRET"); + + if (userId === undefined) { + console.error("Failed to verify token", token); + } + + return userId; }, -}); -console.log(`Connected to ${coordinator.host} with storeId ${coordinator.storeId}`); -``` + // Called when a new user connects to your server, this is a good place to init rooms and spawn players + subscribeUser(roomId: RoomId, userId: string): void { + // Make sure the room exists (or create one if not) + if (!rooms.has(roomId)) { + console.log("Creating new room..."); + + rooms.set(roomId, { + players: [] + }); + } + + const game = rooms.get(roomId)!; + + // Make sure the player hasn't already spawned, then spawn them + if (!game.players.some((player) => player.id === userId)) { + game.players.push({ + id: userId, + x: 0, + y: 0 + }); + } + }, + + // Called when a user disconnects from your server, this is a good place to cleanup data for that player + unsubscribeUser(roomId: RoomId, userId: string): void { + // Make sure the room exists + if (!rooms.has(roomId)) { + return; + } + + const game = rooms.get(roomId)!; + const idx = game.players.findIndex((player) => player.id === userId); + + // Remove the player from the room's state + if (idx >= 0) { + game.players.splice(idx, 1); + } + }, -5. Run your server via `npx ts-node-esm server.mts`. You should see a message like this: - > Connected to coordinator.hathora.dev with storeId 81e5804a-5ffe-496c-8a68-da071945b558 + // Called when a message is sent to the server for handling, much of your core logic will live here + async onMessage(roomId: RoomId, userId: string, data: ArrayBuffer): Promise { + // Make sure the room exists + if (!rooms.has(roomId)) { + return; + } + + // Get the player, or return out of the function if they don't exist + const game = rooms.get(roomId)!; + const player = game.players.find((player) => player.id === userId); + if (player === undefined) { + return; + } + + // Parse out the data string being sent from the client + const message = JSON.parse(Buffer.from(data).toString("utf8")); + + if (message.type === 'test-message') { + if (message.value === 'Hello Hathora server!') { + // Define a response message... + const msg = { + type: 'test-response', + value: 'Hello Hathora clients!' + }; + + // Then broadcast it to all connected clients in this room + server.broadcastMessage(roomId, Buffer.from(JSON.stringify(msg), "utf8")); + } + } + // else if (message.type === 'some-other-action') { + // // (handle other message types) + // } + } +}; + +// Boot server +const port = 4000; +const server = await startServer(store, port); +console.log(`Server listening on port ${port}`); +``` #### Client Once your server is connected to the Coordinator, you can start passing messages back and forth. Let's build a client to do that. 1. In your typescript project, install the client SDKs: `npm i @hathora/client-sdk` -2. Fill in the `APP_ID` from above and implement `onMessage` and `onError` methods: +2. Fill in the `APP_ID` from above. Establish a connection using the `onMessage` and `onError` methods: ```ts // client.mts - import { HathoraClient } from "@hathora/client-sdk"; -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); +async function establishConnection() { + // Instantiate an object which represents our local connection info... + const connectionInfo = { + host: "localhost", + port: 4000, + transportType: "tcp" as const + }; -const client = new HathoraClient(APP_ID); -const token = await client.loginAnonymous(); -const roomId = await client.create(token, new Uint8Array()); -const connection = await client.connect(token, roomId, onMessage, onError); + // Or pass undefined if working in a production Hathora environment + // const connectionInfo = undefined; -connection.write(encoder.encode("Hello world!")); + // Instantiate our client object (this is where you provide a valid Hathora APP_ID, which here is being passed via an environment variable) + const client = new HathoraClient("YOUR_HATHORA_APP_ID", connectionInfo); -function onMessage(msg: ArrayBuffer) { - console.log(decoder.decode(msg)); -} + // Use the client to get a token for the user + const token = await client.loginAnonymous(); + + // You can now create a new public room + const newPublicRoomId = await client.createPublicLobby(token); + + // Or a new private room + const newPrivateRoomId = await client.createPrivateLobby(token); + + // And query for existing public rooms + const existingPublicRoomIds = await client.getPublicLobbies(token); + + // Create a HathoraConnection instance + const connection = client.newConnection(newPublicRoomId); -function onError(error: any) { - console.error(error); + // Handle connection closing how you like + connection.onClose((error) => { + console.error("Connection closed", error); + }); + + // Initiate the connection + connection.connect(token); + + // Return our connection to be used later... + return connection; } ``` -3. Run your client via `npx ts-node-esm client.mts`. You should see "Hello world!" echoed back like so: - > Hello world! +3. Use the connection by running the code below: -On the server you should see output similar to the following: -> newState 305z91zyocpd4 k8vkwl7692 -subscribeUser 305z91zyocpd4 k8vkwl7692 -onMessage 305z91zyocpd4 k8vkwl7692 Hello world! +```ts +async function example() { + // Establish a Hathora connection (see above) + const connection = await establishConnection(); + + // Write JSON messages to the server + connection.writeJson({ + type: "test-message", + value: "Hello Hathora server!" + }); + + // Listen for JSON messages from the server + connection.onMessageJson((json) => { + // Handle the message in your app... + console.log(json); + }); +} +``` ### Next Steps diff --git a/docs/buildkit/reference.md b/docs/buildkit/reference.md deleted file mode 100644 index 47926560..00000000 --- a/docs/buildkit/reference.md +++ /dev/null @@ -1,136 +0,0 @@ -## Hathora BuildKit - Reference - -# Hathora Typescript Client SDK - -## Usage - -```ts -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); - -// Hathora appId from the coordinator -const APP_ID = "..."; -// create client -const client = new HathoraClient(APP_ID); - -// login -const token = await client.loginAnonymous(); -// create new room -const roomId = await client.create(token, new Uint8Array()); -// connect to room and subscribe to messages -const connection = client.connect(token, roomId, onMessage, onError); - -// send message to backend -connection.write(encoder.encode(JSON.stringify({ message: "Hello world" }))); - -// process message from backend -function onMessage(msg) { - console.log(JSON.parse(decoder.decode(data))); -} - -// process error from backend -function onError(error) { - console.error(error); -} -``` - -# Hathora Typescript Server SDK - -## Usage - -```ts -register(config: RegisterConfig): Promise; -``` - -Registers backend with the Hathora Coordinator. - -## Register Config - -```ts -export type RegisterConfig = { - coordinatorHost?: string; - appSecret: string; - storeId?: string; - authInfo: AuthInfo; - store: Store; -}; -``` - -### coordinatorHost - -The url of the coordinator instance to connect to - -Defaults to coordinator.hathora.dev - -### appSecret - -A secret string value to securely identify the backend - -### storeId - -A string to identify the backend instance - -Defaults to a random uuid - -### authInfo - -Configures the authentication providers for the application - -```ts -type AuthInfo = { - anonymous?: { separator: string }; - nickname?: {}; - google?: { clientId: string }; - email?: { secretApiKey: string }; -}; -``` - -### store - -A class or object conforming to the `Store` interface - -```ts -interface Store { - newState(roomId: StateId, userId: UserId, data: ArrayBufferView): void; - subscribeUser(roomId: StateId, userId: UserId): void; - unsubscribeUser(roomId: StateId, userId: UserId): void; - unsubscribeAll(): void; - onMessage(roomId: StateId, userId: UserId, data: ArrayBufferView): void; -} -``` - -### CoordinatorClient - -```ts -interface CoordinatorClient { - stateUpdate(roomId: StateId, userId: UserId, data: Buffer); - stateNotFound(roomId: StateId, userId: UserId); - ping(); -} -``` - -## Example - -```ts -const coordinator = await register({ - appSecret: process.env.APP_SECRET!, - authInfo: { anonymous: { separator: "-" } }, - store: { - newState(roomId, userId, data) { - console.log("newState", roomId.toString(36), userId, data); - }, - subscribeUser(roomId, userId) { - console.log("subscribeUser", roomId.toString(36), userId); - }, - unsubscribeUser(roomId, userId) { - console.log("unsubscribeUser", roomId.toString(36), userId); - }, - onMessage(roomId, userId, data) { - const dataBuf = Buffer.from(data.buffer, data.byteOffset, data.byteLength); - console.log("handleUpdate", roomId.toString(36), userId, dataBuf.toString("utf8")); - // echo data back to client - coordinator.sendMessage(roomId, userId, dataBuf); - }, - }, -}); -``` diff --git a/docs/index.html b/docs/index.html index af0fd427..0ee2dddd 100644 --- a/docs/index.html +++ b/docs/index.html @@ -25,7 +25,7 @@