Skip to content

Commit

Permalink
feat: ws and rpc server handlers (#8)
Browse files Browse the repository at this point in the history
* new packages

* ws & rpc connections

* default expiration

* local proto
  • Loading branch information
lauti7 authored Apr 11, 2024
1 parent 4ad6cad commit e9233dc
Show file tree
Hide file tree
Showing 15 changed files with 3,317 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ dist/
node_modules/
**/.DS_Store
coverage
.env
.env
protoc3
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
PROTOBUF_VERSION = 3.19.1
PROTOC ?= protoc
UNAME := $(shell uname)
PROTO_FILES := $(wildcard src/*.proto)
export PATH := ts-server/node_modules/.bin:/usr/local/include/:protoc3/bin:$(PATH)

ifeq ($(UNAME),Darwin)
PROTOBUF_ZIP = protoc-$(PROTOBUF_VERSION)-osx-x86_64.zip
else
PROTOBUF_ZIP = protoc-$(PROTOBUF_VERSION)-linux-x86_64.zip
endif

# for ts
install_compiler:
@# remove local folder
rm -rf protoc3 || true

@# Make sure you grab the latest version
curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOBUF_VERSION)/$(PROTOBUF_ZIP)

@# Unzip
unzip $(PROTOBUF_ZIP) -d protoc3
@# delete the files
rm $(PROTOBUF_ZIP)

@# move protoc to /usr/local/bin/
chmod +x protoc3/bin/protoc

build: install_compiler
@./build-local-proto.sh
5 changes: 5 additions & 0 deletions build-local-proto.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
./protoc3/bin/protoc \
--plugin=./node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_opt=esModuleInterop=true,returnObservable=false,outputServices=generic-definitions \
--ts_proto_out="$(pwd)/src" -I="$(pwd)" \
"$(pwd)/friendships_ea.proto"
158 changes: 158 additions & 0 deletions friendships_ea.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// THIS LIVES HERE FOR NOW, BUT IT SHOULD BE MOVED TO @dcl/protcol when release

syntax = "proto3";
package decentraland.social.friendships_ea;

import "google/protobuf/empty.proto";


// This message is a response that is sent from the server to the client
message FriendshipEventResponse {
oneof body {
RequestResponse request = 1;
AcceptResponse accept = 2;
RejectResponse reject = 4;
DeleteResponse delete = 5;
CancelResponse cancel = 6;
}
}

message FriendshipEventResponses {
repeated FriendshipEventResponse responses = 1;
}

message FriendshipEventPayload {
oneof body {
RequestPayload request = 1;
AcceptPayload accept = 2;
RejectPayload reject = 4;
DeletePayload delete = 5;
CancelPayload cancel = 6;
}
}

message User { string address = 1; }

message Users { repeated User users = 1; }

message RequestResponse {
User user = 1;
int64 created_at = 2;
optional string message = 3;
}

message RequestPayload {
User user = 1;
optional string message = 3;
}

message Requests {
int64 total = 1; // Total amount of friendship requests
repeated RequestResponse items = 2;
}

message RequestEvents {
Requests outgoing = 1; // Requests the authed user have sent to users
Requests incoming = 2; // Requests the authed user have received from users
}

message AcceptResponse { User user = 1; }

message AcceptPayload { User user = 1; }

message RejectResponse { User user = 1; }

message RejectPayload { User user = 1; }

message DeleteResponse { User user = 1; }

message DeletePayload { User user = 1; }

message CancelResponse { User user = 1; }

message CancelPayload { User user = 1; }

message UpdateFriendshipPayload {
FriendshipEventPayload event = 1;
}

message MutualFriendsPayload {
User user = 1;
}

message BadRequestError {
string message = 1;
}
message UnauthorizedError {
string message = 1;
}
message ForbiddenError {
string message = 1;
}
message TooManyRequestsError {
string message = 1;
}
message InternalServerError {
string message = 1;
}

message UsersResponse {
oneof response {
Users users = 1;
InternalServerError internal_server_error = 2;
UnauthorizedError unauthorized_error = 3;
ForbiddenError forbidden_error = 4;
TooManyRequestsError too_many_requests_error = 5;
BadRequestError bad_request_error = 6;
}
}

message RequestEventsResponse {
oneof response {
RequestEvents events = 1;
InternalServerError internal_server_error = 2;
UnauthorizedError unauthorized_error = 3;
ForbiddenError forbidden_error = 4;
TooManyRequestsError too_many_requests_error = 5;
}
}

message UpdateFriendshipResponse {
oneof response {
FriendshipEventResponse event = 1;
InternalServerError internal_server_error = 2;
UnauthorizedError unauthorized_error = 3;
ForbiddenError forbidden_error = 4;
TooManyRequestsError too_many_requests_error = 5;
BadRequestError bad_request_error = 6;
}
}

message SubscribeFriendshipEventsUpdatesResponse {
oneof response {
FriendshipEventResponses events = 1;
InternalServerError internal_server_error = 2;
UnauthorizedError unauthorized_error = 3;
ForbiddenError forbidden_error = 4;
TooManyRequestsError too_many_requests_error = 5;
}
}

service FriendshipsService {
// Get the list of friends for the authenticated user
rpc GetFriends(google.protobuf.Empty) returns (stream UsersResponse) {}

// Get the list of mutual friends between the authenticated user and the one in the parameter
rpc GetMutualFriends(MutualFriendsPayload) returns (stream UsersResponse) {}

// Get the list of request events for the authenticated user
rpc GetRequestEvents(google.protobuf.Empty) returns (RequestEventsResponse) {}

// Update friendship status: REQUEST, ACCEPT, REJECT, CANCEL, DELETE
rpc UpdateFriendshipEvent(UpdateFriendshipPayload)
returns (UpdateFriendshipResponse) {}

// Subscribe to updates of friendship status: REQUEST, ACCEPT, REJECT, CANCEL, DELETE
rpc SubscribeFriendshipEventsUpdates(google.protobuf.Empty)
returns (stream SubscribeFriendshipEventsUpdatesResponse) {}
}
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node --trace-warnings --abort-on-uncaught-exception --unhandled-rejections=strict dist/index.js",
"dev": "nodemon --watch 'src/**' --ext 'ts,json' --ignore 'src/**/*.spec.ts' --ignore 'src/migrations' --exec 'ts-node src/index.ts'",
"test": "jest --forceExit --detectOpenHandles --coverage --verbose",
"migrate": "node-pg-migrate --database-url-var PG_COMPONENT_PSQL_CONNECTION_STRING --envPath .env -j ts --tsconfig tsconfig.json -m ./src/migrations",
"lint:check": "eslint '**/*.{js,ts}'",
"lint:fix": "eslint '**/*.{js,ts}' --fix"
},
"devDependencies": {
"@dcl/eslint-config": "^2.0.0",
"@protobuf-ts/protoc": "^2.9.4",
"@types/node": "^20.11.28",
"@types/ws": "^8.5.10",
"@well-known-components/test-helpers": "^1.5.6",
"nodemon": "^3.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.2"
},
Expand All @@ -24,12 +28,18 @@
"tabWidth": 2
},
"dependencies": {
"@dcl/platform-crypto-middleware": "^1.0.2",
"@dcl/protocol": "^1.0.0-2569677750.commit-6ce832a",
"@dcl/rpc": "^1.1.2",
"@well-known-components/env-config-provider": "^1.2.0",
"@well-known-components/fetch-component": "^2.0.2",
"@well-known-components/http-server": "^2.1.0",
"@well-known-components/interfaces": "^1.4.3",
"@well-known-components/logger": "^3.1.3",
"@well-known-components/metrics": "^2.1.0",
"@well-known-components/pg-component": "^0.2.2",
"@well-known-components/uws-http-server": "^0.0.1-20240314125425.commit-711dd8f"
"@well-known-components/uws-http-server": "^0.0.1-20240314125425.commit-711dd8f",
"fp-future": "^1.0.1",
"ws": "^8.16.0"
}
}
80 changes: 80 additions & 0 deletions src/adapters/rpcServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { createRpcServer } from '@dcl/rpc'
import { registerService } from '@dcl/rpc/dist/codegen'
import {
FriendshipsServiceDefinition,
UsersResponse,
SubscribeFriendshipEventsUpdatesResponse,
RequestEventsResponse,
UpdateFriendshipResponse
} from '../friendships_ea'
import { AppComponents, RpcServerContext } from '../types'

export default function createRpcServerComponent(components: Pick<AppComponents, 'logs'>) {
const { logs } = components

const server = createRpcServer<RpcServerContext>({
logger: logs.getLogger('rpc-server')
})

const _logger = logs.getLogger('rpc-server-handler')
// Mocked server until we get the new service definition & db queries done
server.setHandler(async function handler(port) {
registerService(port, FriendshipsServiceDefinition, async () => ({
getFriends(_request, _context) {
const generator = async function* () {
const response: UsersResponse = {
users: { users: [] }
}
yield response
}

return generator()
},
getMutualFriends(_request, _context) {
const generator = async function* () {
const response: UsersResponse = {
users: { users: [] }
}
yield response
}

return generator()
},
async getRequestEvents(_request, _context) {
const res: RequestEventsResponse = {
events: {
outgoing: { items: [], total: 0 },
incoming: { items: [], total: 0 }
}
}
return res
},
async updateFriendshipEvent(_request, _context) {
const res: UpdateFriendshipResponse = {
event: {
accept: {
user: {
address: '0xa'
}
}
}
}
return res
},
subscribeFriendshipEventsUpdates(_request, _context) {
const generator = async function* () {
const response: SubscribeFriendshipEventsUpdatesResponse = {
events: {
responses: []
}
}
yield response
}

return generator()
}
}))
})

return server
}
25 changes: 25 additions & 0 deletions src/adapters/ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { WebSocketServer } from 'ws'
import { IWebSocketComponent } from '../types'

export async function createWsComponent(): Promise<IWebSocketComponent> {
let wss: WebSocketServer | undefined

async function start() {
if (wss) return

wss = new WebSocketServer({ noServer: true })
}

async function stop() {
wss?.close()
wss = undefined
}

await start()

return {
start,
stop,
ws: wss!
}
}
15 changes: 13 additions & 2 deletions src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ import { AppComponents, GlobalContext } from './types'
import { metricDeclarations } from './metrics'
import { createPgComponent } from '@well-known-components/pg-component'
import { createDBComponent } from './adapters/db'
import { createWsComponent } from './adapters/ws'
import createRpcServerComponent from './adapters/rpcServer'
import { createFetchComponent } from '@well-known-components/fetch-component'

// Initialize all the components of the app
export async function initComponents(): Promise<AppComponents> {
const config = await createDotEnvConfigComponent({ path: ['.env.default', '.env'] })
const metrics = await createMetricsComponent(metricDeclarations, { config })
const logs = await createLogComponent({ metrics })
const server = await createServerComponent<GlobalContext>({ config, logs }, {})

const ws = await createWsComponent()
const server = await createServerComponent<GlobalContext>({ config, logs, ws: ws.ws }, {})
const statusChecks = await createStatusCheckComponent({ server, config })
const rpcServer = createRpcServerComponent({ logs })

const fetcher = createFetchComponent()

let databaseUrl: string | undefined = await config.getString('PG_COMPONENT_PSQL_CONNECTION_STRING')
if (!databaseUrl) {
Expand Down Expand Up @@ -54,6 +62,9 @@ export async function initComponents(): Promise<AppComponents> {
statusChecks,
metrics,
pg,
db
db,
ws,
rpcServer,
fetcher
}
}
Loading

0 comments on commit e9233dc

Please sign in to comment.