@fnlb-project/fnbr
Version:
A library to interact with Epic Games' Fortnite HTTP and XMPP services
179 lines • 7.04 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const Endpoints_1 = tslib_1.__importDefault(require("../../resources/Endpoints"));
const enums_1 = require("../../resources/enums");
const Base_1 = tslib_1.__importDefault(require("../Base"));
const Util_1 = require("../util/Util");
// private scope
const generateCustomCorrelationId = () => `EOS-${Date.now()}-${(0, Util_1.getUUID)()}`;
/**
* Represent's the client's chat manager (dm, party chat) via eos.
*/
class ChatManager extends Base_1.default {
constructor() {
super(...arguments);
/**
* DM conversations cache map (account id -> conversation id)
*/
this.dmConversations = new Map();
}
/**
* Whether the keypair for message signing exists
*/
get keypairExists() {
return !!this.privateKey && !!this.publicKey;
}
/**
* Whether the keypair has been registered on epic's servers
*/
get keypairRegistered() {
return !!this.publicKeyData;
}
/**
* Returns the chat namespace, this is the eos deployment id
*/
get namespace() {
return this.client.config.eosDeploymentId;
}
/**
* Sends a private message to the specified user
* @param user the account id or displayname
* @param message the message object
* @returns the message id
* @throws {UserNotFoundError} When the specified user was not found
* @throws {EpicgamesAPIError} When the api request failed
*/
async whisperUser(user, message) {
const accountId = await this.client.user.resolveId(user);
const conversationId = await this.getDMConversationId(accountId);
return this.sendMessageInConversation(conversationId, message, [accountId, this.client.user.self.id], enums_1.ConversationType.DirectMessage);
}
/**
* Sends a message in the specified conversation (e.g. party chat)
* @param conversationId the conversation id, usually `p-[PARTYID]`
* @param message the message object
* @param allowedRecipients the account ids, that should receive the message
* @returns the message id
* @throws {EpicgamesAPIError}
*/
async sendMessageInConversation(conversationId, message, allowedRecipients, conversationType) {
const correlationId = generateCustomCorrelationId();
const { body, signature } = await this.createSignedMessage(conversationId, message.body, conversationType === enums_1.ConversationType.DirectMessage ? enums_1.SignedMessageType.Persistent : enums_1.SignedMessageType.Party);
await this.client.http.epicgamesRequest({
method: 'POST',
url: `${Endpoints_1.default.EOS_CHAT}/v1/public/${conversationType === enums_1.ConversationType.DirectMessage ? '_' : this.namespace}`
+ `/conversations/${conversationId}/messages?fromAccountId=${this.client.user.self.id}`,
headers: {
'Content-Type': 'application/json',
'X-Epic-Correlation-ID': correlationId,
},
data: {
allowedRecipients,
message: {
body,
},
isReportable: false,
metadata: {
TmV: '2',
Pub: this.publicKeyData.jwt,
Sig: signature,
NPM: conversationType === enums_1.ConversationType.Party ? '1' : undefined,
PlfNm: this.client.config.platform,
PlfId: this.client.user.self.id,
},
},
}, enums_1.AuthSessionStoreKey.FortniteEOS);
return correlationId;
}
async createDMConversation(recepientId, createIfExists = false) {
return this.client.http.epicgamesRequest({
method: 'POST',
url: `${Endpoints_1.default.EOS_CHAT}/v1/public/_/conversations?createIfExists=${createIfExists}`,
headers: {
'Content-Type': 'application/json',
},
data: {
title: '',
type: 'dm',
members: [this.client.user.self.id, recepientId],
},
}, enums_1.AuthSessionStoreKey.FortniteEOS);
}
/**
* Ensures that message signing is possible
*/
async ensureMessageSigning() {
if (!this.keypairExists) {
await this.generateKeypair();
}
if (!this.keypairRegistered) {
await this.registerKeypair();
}
}
/**
* Resolves the conversation id for a dm with the specified user
* @param recepientId The account id of the recepient
* @returns The conversation id
*/
async getDMConversationId(recepientId) {
if (this.dmConversations.has(recepientId)) {
return this.dmConversations.get(recepientId);
}
const conversationData = await this.createDMConversation(recepientId);
this.dmConversations.set(recepientId, conversationData.conversationId);
return conversationData.conversationId;
}
/**
* Signs a message for the specified conversation
* @param conversationId The conversation id
* @param content The message content
* @param type The signed message type
*/
async createSignedMessage(conversationId, content, type) {
await this.ensureMessageSigning();
const timestamp = Date.now();
const messageInfo = {
mid: (0, Util_1.getUUID)(),
sid: this.client.user.self.id,
rid: conversationId,
msg: content,
tst: timestamp,
seq: 1,
rec: false,
mts: [],
cty: type,
};
const body = Buffer.from(JSON.stringify(messageInfo), 'utf-8').toString('base64');
const messageToSign = Buffer.concat([Buffer.from(body, 'utf-8'), Buffer.from([0])]);
const signature = await (0, Util_1.signCrossPlatformMessage)(this.privateKey, messageToSign);
return { body, signature };
}
/**
* Generates a ed25519 keypair for message signing
*/
async generateKeypair() {
const { privateKey, publicKeyB64 } = await (0, Util_1.generateCrossPlatformKeyPair)();
this.privateKey = privateKey;
this.publicKey = publicKeyB64;
}
/**
* Registers the public key on epic's servers
*/
async registerKeypair() {
const publicKeyData = await this.client.http.epicgamesRequest({
method: 'POST',
url: `${Endpoints_1.default.PUBLICKEY}/v2/publickey/`,
headers: {
'Content-Type': 'application/json',
},
data: {
key: this.publicKey,
algorithm: 'ed25519',
},
}, enums_1.AuthSessionStoreKey.Fortnite);
this.publicKeyData = publicKeyData;
}
}
exports.default = ChatManager;
//# sourceMappingURL=ChatManager.js.map