@pureweb/platform-streaming-agent
Version:
The PureWeb platform streaming agent enables your game to communicate and stream through the PureWeb Platform
221 lines (220 loc) • 9.46 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SteamVRLogMonitorExtension = void 0;
const Log_1 = __importDefault(require("../../Log"));
const ws_1 = __importDefault(require("ws"));
const IExtension_1 = require("../../IExtension");
const ConnectionStates_1 = require("../../ConnectionStates");
const node_fetch_1 = __importDefault(require("node-fetch"));
const platform_sdk_1 = require("@pureweb/platform-sdk");
const IPIFY_ENDPOINT_IPV4 = 'https://checkip.amazonaws.com';
const initialReconnectDelay = 1000;
//[SteamVR Directory]\bin\win64\vrstartup.exe
var HMDState;
(function (HMDState) {
HMDState[HMDState["NOT_PRESENT"] = 0] = "NOT_PRESENT";
HMDState[HMDState["INACTIVE"] = 1] = "INACTIVE";
HMDState[HMDState["ACTIVE"] = 2] = "ACTIVE";
})(HMDState || (HMDState = {}));
var PresenceState;
(function (PresenceState) {
PresenceState[PresenceState["NOT_PRESENT"] = 0] = "NOT_PRESENT";
PresenceState[PresenceState["PRESENT"] = 1] = "PRESENT";
})(PresenceState || (PresenceState = {}));
var GameConnectionState;
(function (GameConnectionState) {
GameConnectionState[GameConnectionState["CONNECTED"] = 0] = "CONNECTED";
GameConnectionState[GameConnectionState["DISCONNECTED"] = 1] = "DISCONNECTED";
})(GameConnectionState || (GameConnectionState = {}));
var EventStreamState;
(function (EventStreamState) {
EventStreamState[EventStreamState["OPEN"] = 0] = "OPEN";
EventStreamState[EventStreamState["CLOSED"] = 1] = "CLOSED";
EventStreamState[EventStreamState["ERROR"] = 2] = "ERROR";
})(EventStreamState || (EventStreamState = {}));
const maxRetries = 5;
class SteamVRLogMonitorExtension extends IExtension_1.AbstractExtension {
constructor() {
super();
this.ws = null;
this.currentReconnectDelay = initialReconnectDelay;
this.steamVRState = HMDState.NOT_PRESENT;
this.presenceState = PresenceState.NOT_PRESENT;
this.gameState = GameConnectionState.DISCONNECTED;
this.eventStreamState = EventStreamState.CLOSED;
this.fetchPublicIP = async () => {
Log_1.default.info(`Obtaining Public IP`);
return (await (0, node_fetch_1.default)(IPIFY_ENDPOINT_IPV4)).text();
};
this.makeIPContribution = async () => {
let IP = await this.IP;
IP = IP.replace(/\n/g, '');
Log_1.default.info(`Making IP Contribution ${IP}`);
const contribution = await this.agent
.makeContribution({
type: 'CXR_SERVICE',
data: JSON.stringify({ IP })
})
.catch((err) => {
Log_1.default.error('Failed to make contribution: ' + err);
this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.INTERNAL_ERROR);
});
this.contribution = contribution;
};
this.revokeIPContribution = async () => {
const IP = await this.IP;
if (this.contribution) {
Log_1.default.info(`Revoking IP Contribution (${IP})`);
await this.agent.removeContribution(this.contribution).catch((err) => {
Log_1.default.error('Failed to remove contribution', err);
});
this.contribution = undefined;
}
else {
Log_1.default.info(`No IP contribution to revoke`);
}
};
this.start = async () => {
this.IP = this.fetchPublicIP();
this.startRendezvousTimer();
this.monitor();
return true;
};
this.stop = () => {
return Promise.resolve(true);
};
this.id = Date.now();
}
async monitor(retryCount = 0) {
try {
Log_1.default.info(`Attempting to Establish Log Event Connection with SteamVR....`);
this.ws = new ws_1.default('ws://localhost:27062', {
headers: {
origin: 'http://localhost:27062'
}
});
this.ws.addEventListener('open', () => {
this.ws.send('console_open');
this.onWebsocketOpen();
this.ws.close();
});
this.ws.addEventListener('error', async (error) => {
if (retryCount < maxRetries) {
const delay = Math.pow(2, retryCount) * 500; // Exponential backoff
Log_1.default.info(`Retrying connection in ${delay / 1000} seconds...`);
await new Promise((resolve) => setTimeout(resolve, delay));
this.monitor(retryCount + 1);
}
else {
// logger.info('Maximum retry attempts reached. Did not connect.');
Log_1.default.error(error);
this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.STREAMER_RENDEZVOUS_MISSED);
}
});
}
catch (problem) {
// Do nothing. If there is problem with the initial connection our beckoff retry will take over
}
}
onWebsocketOpen() {
this.onRuntimeEventStreamConnection();
}
onStateChanged(handler) {
this.connectionStateChange = handler;
}
startRendezvousTimer() {
// start rendezvous timer
try {
if (this.config.rendezvousTimeoutSeconds >= 0) {
setTimeout(() => {
if (this.eventStreamState != EventStreamState.OPEN) {
Log_1.default.info(`Failed to connect to SteamVR`);
this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.STREAMER_RENDEZVOUS_MISSED);
}
else if (this.presenceState != PresenceState.PRESENT) {
Log_1.default.info(`Agent Rendezvous missed.`);
this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.AGENT_RENDEZVOUS_MISSED);
}
}, this.config.rendezvousTimeoutSeconds * 1000);
}
}
catch (err) {
Log_1.default.error(`${err}`);
this.stop();
}
}
onRuntimeEventStreamConnection() {
this.eventStreamState = EventStreamState.OPEN;
this.startPresenceTimer();
this.monitorPresence();
this.makeIPContribution();
}
monitorPresence() {
this.agent.subscribeToPresence(platform_sdk_1.PresenceTypes.ALL, {
arrivals: (data) => {
this.onAgentArrival(data);
},
departures: (data) => {
this.onAgentDeparture(data);
}
});
}
async onAgentArrival(data) {
if (this.agent.id != data.agentId) {
Log_1.default.info(`CXR Agent arrived: ${data.agentId}`);
this.presenceState = PresenceState.PRESENT;
try {
if (this.platform?.credentials?.agent?.launchRequestId?.length > 0) {
await this.platform.launchRequestAcknowledgements(platform_sdk_1.LaunchRequestAcknowledgementType.StreamingAgentReady, 'OpenXR stream is ready to be consumed');
}
else {
Log_1.default.info('No associated launch request. Assuming local development workflow');
}
}
catch (err) {
Log_1.default.error('Unable to acknowledge CXR streaming agent ready state: ' + err);
}
}
}
async onAgentDeparture(data) {
Log_1.default.info(`CXR Agent departed: ${data.agentId}`);
this.presenceState = PresenceState.NOT_PRESENT;
await this.revokeIPContribution();
try {
if (this.platform?.credentials?.agent?.launchRequestId?.length > 0) {
await this.platform.launchRequestAcknowledgements(platform_sdk_1.LaunchRequestAcknowledgementType.StreamingAgentFinished, 'CXR Agent has finished consuming stream');
}
else {
Log_1.default.info('No associated launch request. Assuming local development workflow');
}
}
catch (err) {
Log_1.default.error('Unable to acknowledge CXR streaming agent finish state: ' + err);
}
this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.PEERS_EXITED);
}
startPresenceTimer() {
// start rendezvous timer
try {
if (this.config.rendezvousTimeoutSeconds >= 0) {
setTimeout(() => {
if (this.presenceState === PresenceState.PRESENT) {
Log_1.default.info(`CXR Agent Rendezvous successful.`);
}
else {
Log_1.default.info(`CXR Agent Rendezvous missed.`);
this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.AGENT_RENDEZVOUS_MISSED);
}
}, this.config.rendezvousTimeoutSeconds * 1000);
}
}
catch (err) {
Log_1.default.error(`${err}`);
this.stop();
}
}
}
exports.SteamVRLogMonitorExtension = SteamVRLogMonitorExtension;