Skip to content

Commit

Permalink
Add platform module
Browse files Browse the repository at this point in the history
  • Loading branch information
unknownskl committed Sep 25, 2024
1 parent 4d1b886 commit fb12a84
Show file tree
Hide file tree
Showing 15 changed files with 562 additions and 5 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"storeapi": "yarn workspace @greenlight/storeapi",
"xcloudapi": "yarn workspace @greenlight/xcloudapi",
"webapi": "yarn workspace @greenlight/webapi",
"platform": "yarn workspace @greenlight/platform",
"test": "yarn logger test && yarn authentication test && yarn storeapi test && yarn xcloudapi test"
},
"workspaces": [
Expand Down
11 changes: 11 additions & 0 deletions packages/platform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @greenlight/platform

This package acts like a bridge to the packages consolidated which should make it easier to interact with the API's. All the code is loaded in a seperate worker thread which makes it faster for use in electron.

## Class: Platform

### constructor():void

### loadWorker():Promise<boolean>

Returns true if the user is currently authenticated. false if not.
30 changes: 30 additions & 0 deletions packages/platform/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@greenlight/platform",
"version": "1.0.0",
"dependencies": {
"@greenlight/authentication": "workspace:^",
"@greenlight/logger": "workspace:^",
"@greenlight/storeapi": "workspace:^",
"@greenlight/webapi": "workspace:^",
"@greenlight/xcloudapi": "workspace:^"
},
"license": "MIT",
"main": "dist/index",
"scripts": {
"start": "yarn build:deps && yarn build && DEBUG='*' node --inspect dist/bin/example.js",
"build": "tsc --build",
"build:deps": "yarn workspace @greenlight/logger build && yarn workspace @greenlight/authentication build && yarn workspace @greenlight/storeapi build && yarn workspace @greenlight/webapi build && yarn workspace @greenlight/xcloudapi build",
"clean": "rm -rf dist/ && rm -rf *.tsbuildinfo",
"test": "yarn build && mocha -r ../../node_modules/ts-node/register tests/**.ts tests/**/*.ts"
},
"devDependencies": {
"@types/chai": "^4",
"@types/debug": "^4.1.7",
"@types/mocha": "^10",
"@types/node": "^20.9.0",
"chai": "^4.3.6",
"mocha": "^10.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
27 changes: 27 additions & 0 deletions packages/platform/src/bin/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

import GreenlightPlatform from '../index'
import Logger from '@greenlight/logger'

class Cli {
private _platform = new GreenlightPlatform()
public logger = new Logger('GreenlightPlatform:Cli')

constructor(){
this.logger.log('Greenlight Platform Cli')

this._platform.loadWorker().then((authStatus) => {
this.logger.log('Worker loaded, Authentication status:', authStatus === true ? 'Authenticated' : 'Unauthenticated')

this._platform.api.Gamepass.getList('coming').then((titles) => {
this.logger.log('Titles:', titles)

this._platform.close()

}).catch((error) => {
this.logger.error('Error getting gamertag:', error)
})
})
}
}

new Cli()
26 changes: 26 additions & 0 deletions packages/platform/src/controllers/authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import GreenlightPlatform from '..'

export default class Authentication {

private _platform:GreenlightPlatform

constructor(platform:GreenlightPlatform){
this._platform = platform
}

getGamertag() {
return this._platform.sendMessage({
controller: 'Authentication',
action: 'user.getGamertag'
})
}


getUserhash() {
return this._platform.sendMessage({
controller: 'Authentication',
action: 'user.getUserhash'
})
}

}
98 changes: 98 additions & 0 deletions packages/platform/src/controllers/gamepass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import GreenlightPlatform from '..'

export interface SiglResponse {
properties: {
siglId: string,
title: string,
description: string,
requiresShuffling: boolean,
imageUrl: string
},
productIds: string[]
}

export default class Gamepass {

private _platform:GreenlightPlatform

constructor(platform:GreenlightPlatform){
this._platform = platform
}

async getTitles() {
return await this._platform.sendMessage({
controller: 'xCloudApi',
action: 'getTitles'
})
}

async getList(type:string) {
const titles = await (this._platform.sendMessage({
controller: 'xCloudApi',
action: 'getList',
params: [
type
]
}) as Promise<SiglResponse>)

return await this.getProductIds(titles.productIds)
}

async getSigl(siglId:string) {
return await this._platform.sendMessage({
controller: 'xCloudApi',
action: 'getSigl',
params: [
siglId
]
})
}

async getProductIds(productIds:string[]) {
const products = await this._platform.sendMessage({
controller: 'storeApi',
action: 'getProductIds',
params: [
productIds
]
})

if(products.length !== productIds.length){
await this._platform.sendMessage({
controller: 'storeApi',
action: 'loadProductIds',
params: [
productIds
]
})

const retProducts = await this._platform.sendMessage({
controller: 'storeApi',
action: 'getProductIds',
params: [
productIds
]
})

return await this.parseItems(retProducts)
} else {
return await this.parseItems(products)
}
}

async parseItems(items:any[]){
const parsedItems = []
for(const item in items){
const parsedItem = await this._platform.sendMessage({
controller: 'storeApi',
action: 'parseTitle',
params: [
items[item]
]
})
parsedItems.push(parsedItem)
}
return parsedItems
}

}
7 changes: 7 additions & 0 deletions packages/platform/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Authentication from './authentication'
import Gamepass from './gamepass'

export default {
Authentication: Authentication,
Gamepass: Gamepass
}
114 changes: 114 additions & 0 deletions packages/platform/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import worker from 'node:worker_threads'
import Logger from '@greenlight/logger'

import PlatformWorker, { WorkerMessage, WorkerMessageResponse } from './worker'

import Authentication from './controllers/authentication'
import Gamepass from './controllers/gamepass'

export default class GreenlightPlatform {

public logger:Logger = new Logger('GreenlightPlatform')
private _mainChannel = new worker.MessageChannel()
private _worker?:any
private _platformWorker?:PlatformWorker

public api = {
Authentication: new Authentication(this),
Gamepass: new Gamepass(this),
}

constructor() {
if(! worker.isMainThread)
this.logger = this.logger.extend('Worker')

this.logger.log('constructor() Creating new GreenlightPlatform instance')
}

loadWorker() {
return new Promise((resolve, reject) => {
if(! worker.isMainThread)
throw new Error('Cannot load worker in worker thread, need to be loaded in the main thread.')

this._worker = new worker.Worker(__filename);
this._worker.once('message', (msg:'ok'|'unauthenticated'|'error') => {
(msg !== 'error') ? resolve(msg === 'ok' ? true : false) : reject(false)

this._worker.on('message', (msg:string) => {
this.logger.error('Worker message:', msg)
})
})
this._worker.on('error', (error:any) => {
console.log('Worker error:', error)
reject(error)
})
this._worker.on('exit', (exit:any) => {
if(exit > 0){
this.logger.error('Worker exited with exit code:', exit)
} else {
this.logger.log('Worker exited normally with exit coce:', exit)
}
})

this._worker.postMessage({ port: this._mainChannel.port1 }, [this._mainChannel.port1]);
this._mainChannel.port2.on('message', (value) => this.message(value));
})
}

startWorker() {
this.logger.log('startWorker() Starting worker thread')

worker.parentPort?.once('message', (handler) => {
try {
this._platformWorker = new PlatformWorker(this, handler.port)
this._platformWorker.once('ready', (result) => {
if(result === true)
worker.parentPort?.postMessage('ok');
else
worker.parentPort?.postMessage('unauthenticated');
})
} catch (error) {
worker.parentPort?.postMessage('error');
}
})
}

message(value:WorkerMessageResponse) {
this.logger.log('message() Received message:', JSON.stringify(value));

if(value.requestId !== undefined){
const promise = this._messagePromiseQueue.get(value.requestId)
if(promise){
this._messagePromiseQueue.delete(value.requestId)
promise.resolve(value.data)
} else {
this.logger.error('message() Promise not found for requestId:', value.requestId)
}
}
}

_messagePromiseQueue = new Map<number, { resolve:Function, reject:Function }>()

sendMessage(value:WorkerMessage):Promise<any> {
return new Promise((resolve, reject) => {
if(this._worker){
value.requestId = Math.floor(Math.random() * 1000)
this.logger.log('sendMessage() Sending message to worker:', JSON.stringify(value));
this._mainChannel.port2.postMessage(value)

this._messagePromiseQueue.set(value.requestId, { resolve, reject })
}
})
}

close() {
this.logger.log('close() Open promises:', this._messagePromiseQueue.size, this._messagePromiseQueue);
return this.sendMessage({ action: 'close' })
}
}

if(! worker.isMainThread){
// Bootstrap worker
const Platform = new GreenlightPlatform()
Platform.startWorker()
}
Loading

0 comments on commit fb12a84

Please sign in to comment.