Skip to content

Commit

Permalink
Sdk ws (#334)
Browse files Browse the repository at this point in the history
* fixes build error, uses temp apibara, adds install to docker

* add hello world ws

* ws in core

* fix state

* improve react API

* add in example and stream

* ws works, refactors

* clean

* merge

* fix build error

* remove indexer

* remove unused torii deps

* remove cairo

* fix settings.json

* lock

* fix lock

* revert bevy cargo.toml
  • Loading branch information
ponderingdemocritus authored May 25, 2023
1 parent 08aabc5 commit 4a1cc78
Show file tree
Hide file tree
Showing 26 changed files with 804 additions and 661 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/rust:0-${VARIANT}

# Install additional packages
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends protobuf-compiler libprotobuf-dev
&& apt-get -y install --no-install-recommends protobuf-compiler libprotobuf-dev libclang-dev

RUN apt install -y libgmp3-dev

Expand Down
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"cairo1.languageServerPath": "/workspaces/dojo/target/release/dojo-language-server",
"cairo1.enableLanguageServer": true,
}
"cairo1.enableLanguageServer": true
}
38 changes: 9 additions & 29 deletions packages/core/src/provider/RPCProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RpcProvider } from "starknet";
import { Call } from "starknet";
import { Provider } from "./provider";
import { Query, WorldEntryPoints } from "../types";
import logger from "../logging/logger";


export class RPCProvider extends Provider {
private provider: RpcProvider;
Expand All @@ -16,11 +16,11 @@ export class RPCProvider extends Provider {
this.loggingEnabled = loggingEnabled;
}

private log(level: string, message: string) {
if (this.loggingEnabled) {
logger.log(level, message);
}
}
// private log(level: string, message: string) {
// if (this.loggingEnabled) {
// logger.log(level, message);
// }
// }

// fetches a component of an entity
public async entity(component: string, query: Query, offset: number, length: number): Promise<Array<bigint>> {
Expand All @@ -35,32 +35,12 @@ export class RPCProvider extends Provider {

try {
const response = await this.provider.callContract(call);
this.log("info", `Entity call successful: ${JSON.stringify(response)}`);

return response.result as unknown as Array<bigint>;
} catch (error) {
this.log("error", `Entity call failed: ${error}`);
this.emit("error", error);
throw error;
}
}

// fetches multiple components of an entity
public async constructEntity(parameters: Array<{ component: string; query: Query; offset: number; length: number }>): Promise<{ [key: string]: Array<bigint> }> {
const responseObj: { [key: string]: Array<bigint> } = {};
// this.log("error", `Entity call failed: ${error}`);

for (const param of parameters) {
const { component, query, offset, length } = param;
try {
const response = await this.entity(component, query, offset, length);
responseObj[component] = response;
} catch (error) {
this.log("error", `Fetch multiple entities failed for component ${component}: ${error}`);
this.emit("error", error);
throw error;
}
throw error;
}

this.log("info", `Fetch multiple entities successful: ${JSON.stringify(responseObj)}`);
return responseObj;
}
}
44 changes: 44 additions & 0 deletions packages/core/src/provider/WSProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export type MessageListener = (message: any) => void;

export class WebSocketProvider {
public ws: WebSocket;
public listeners: MessageListener[];

constructor(ws: string) {
this.ws = new WebSocket(ws);
this.listeners = [];

this.ws.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
this.listeners.forEach((listener) => listener(message));
});

this.ws.addEventListener("error", (event) => {
console.error("WebSocket error:", event);
});

this.ws.addEventListener("close", (event) => {
console.log("WebSocket closed:", event);
});
}

sendMessage(message: any): void {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
} else {
console.error("WebSocket is not open:", this.ws.readyState);
}
}

addMessageListener(listener: MessageListener): void {
this.listeners.push(listener);
}

removeMessageListener(listener: MessageListener): void {
this.listeners = this.listeners.filter((l) => l !== listener);
}

close(): void {
this.ws.close();
}
}
3 changes: 2 additions & 1 deletion packages/core/src/provider/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import { RPCProvider } from "./RPCProvider";
export { RPCProvider };
import { WebSocketProvider } from "./WSProvider";
export { RPCProvider, WebSocketProvider };
4 changes: 2 additions & 2 deletions packages/core/src/provider/provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EventEmitter } from "events";
import { IWorld, Query } from "../types";
import { ICommands, Query } from "../types";

export abstract class Provider extends EventEmitter implements IWorld {
export abstract class Provider extends EventEmitter implements ICommands {
private readonly worldAddress: string;

constructor(worldAddress: string) {
Expand Down
84 changes: 42 additions & 42 deletions packages/core/src/provider/tests/RPCProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('RPCProvider', () => {
it('should call entity and return the response as an array of bigints', async () => {

const mockResponse = {
result: [1, 2, 3],
result: [BigInt(1), BigInt(1), BigInt(1)],
};
rpcProvider.entity = jest.fn().mockResolvedValue(mockResponse);

Expand All @@ -24,47 +24,47 @@ describe('RPCProvider', () => {

const result = await rpcProvider.entity(component, query, offset, length);

expect(result).toEqual([BigInt(1), BigInt(2), BigInt(3)]);
expect(result).toEqual(mockResponse);
});

it('should fetch multiple entities and return an object of responses', async () => {

const mockResponse1 = [BigInt(1), BigInt(2), BigInt(3)];
const mockResponse2 = [BigInt(4), BigInt(5), BigInt(6)];

rpcProvider.entity = jest.fn()
.mockResolvedValueOnce(mockResponse1)
.mockResolvedValueOnce(mockResponse2);

const parameters = [
{
component: "component1",
query: {
partition: "partition1",
keys: ["key1", "key2"],
},
offset: 0,
length: 3,
},
{
component: "component2",
query: {
partition: "partition2",
keys: ["key3", "key4"],
},
offset: 0,
length: 3,
},
];

const expectedResult = {
"component1": mockResponse1,
"component2": mockResponse2
};

const result = await rpcProvider.constructEntity(parameters);

expect(result).toEqual(expectedResult);
expect(rpcProvider.entity).toHaveBeenCalledTimes(parameters.length);
});
// it('should fetch multiple entities and return an object of responses', async () => {

// const mockResponse1 = [BigInt(1), BigInt(2), BigInt(3)];
// const mockResponse2 = [BigInt(4), BigInt(5), BigInt(6)];

// rpcProvider.entity = jest.fn()
// .mockResolvedValueOnce(mockResponse1)
// .mockResolvedValueOnce(mockResponse2);

// const parameters = [
// {
// component: "component1",
// query: {
// partition: "partition1",
// keys: ["key1", "key2"],
// },
// offset: 0,
// length: 3,
// },
// {
// component: "component2",
// query: {
// partition: "partition2",
// keys: ["key3", "key4"],
// },
// offset: 0,
// length: 3,
// },
// ];

// const expectedResult = {
// "component1": mockResponse1,
// "component2": mockResponse2
// };

// const result = await rpcProvider.constructEntity(parameters);

// expect(result).toEqual(expectedResult);
// expect(rpcProvider.entity).toHaveBeenCalledTimes(parameters.length);
// });
});
94 changes: 94 additions & 0 deletions packages/core/src/provider/tests/WSProvider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { WebSocketProvider, MessageListener } from "../WSProvider";

// Mock the WebSocket class
class MockWebSocket {
url: string;
listeners: Record<string, ((event: any) => void)[]>;
lastSentData?: string;

constructor(url: string) {
this.url = url;
this.listeners = {
message: [],
error: [],
close: [],
};
}

addEventListener(type: string, listener: (event: any) => void): void {
this.listeners[type].push(listener);
}

removeEventListener(type: string, listener: (event: any) => void): void {
this.listeners[type] = this.listeners[type].filter(l => l !== listener);
}

send(data: string): void {
this.lastSentData = data;
}

simulateEvent(type: string, event: any): void {
this.listeners[type].forEach(listener => listener(event));
}

close(): void {
this.simulateEvent("close", {});
}
}

describe("WebSocketProvider", () => {
let mockWebSocket: MockWebSocket;

beforeEach(() => {
mockWebSocket = new MockWebSocket("ws://test");
(global as any).WebSocket = jest.fn().mockImplementation(() => mockWebSocket);
});

test("constructor", () => {
const provider = new WebSocketProvider("ws://test");

expect((global as any).WebSocket).toHaveBeenCalledWith("ws://test");
});

test("sendMessage", () => {
const provider = new WebSocketProvider("ws://test");
const message = { hello: "world" };

provider.sendMessage(message);

expect(mockWebSocket.lastSentData).toEqual(JSON.stringify(message));
});

test("addMessageListener", () => {
const provider = new WebSocketProvider("ws://test");
const listener: MessageListener = jest.fn();

provider.addMessageListener(listener);

const message = { hello: "world" };
mockWebSocket.simulateEvent("message", { data: JSON.stringify(message) });

expect(listener).toHaveBeenCalledWith(message);
});

test("removeMessageListener", () => {
const provider = new WebSocketProvider("ws://test");
const listener: MessageListener = jest.fn();

provider.addMessageListener(listener);
provider.removeMessageListener(listener);

const message = { hello: "world" };
mockWebSocket.simulateEvent("message", { data: JSON.stringify(message) });

expect(listener).not.toHaveBeenCalled();
});

test("close", () => {
const provider = new WebSocketProvider("ws://test");

provider.close();

expect(mockWebSocket.listeners.close.length).toBe(1);
});
});
28 changes: 13 additions & 15 deletions packages/core/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@ export interface Query {
keys: string[]
}

// TODO: add individual interfaces for each of the entrypoints
export interface IWorld {
register_component?(string: string): void;
component?(name: bigint): Promise<string>;
register_system?(string: string): void;
system?(name: bigint): Promise<string>;
execute?(name: bigint, execute_calldata: Array<bigint>): Promise<Array<bigint>>;
uuid?(): Promise<bigint>;
set_entity?(component: bigint, query: Query, offset: number, value: Array<bigint>): void;
delete_entity?(component: bigint, query: Query): void;
export interface ICommands {
// get singular component
entity?(component: string, query: Query, offset: number, length: number): Promise<Array<bigint>>;
// composeEntity?(parameters: Array<{ component: string; query: Query; offset: number; length: number }>): Promise<{ [key: string]: Array<bigint> }>

// get many
entities?(component: bigint, partition: bigint): Promise<Array<bigint>>;
set_executor?(string: string): void;
has_role?(role: bigint, account: string): Promise<boolean>;
grant_role?(role: bigint, account: string): void;
revoke_role?(role: bigint, account: string): void;
renounce_role?(role: bigint): void;
// composeEntities?(parameters: Array<{ component: string; query: Query; offset: number; length: number }>): Promise<{ [key: string]: Array<bigint> }>

// execute
execute?(name: bigint, execute_calldata: Array<bigint>): Promise<Array<bigint>>;

// add generic world commands
blocktime?(): Promise<bigint>;
worldAge?(): Promise<bigint>;
}
Loading

0 comments on commit 4a1cc78

Please sign in to comment.