WIP Introduce Channel class

This is a breaking change, introducing a new Channel class to hold the
channel meta information, and functions to open either the last
DailyArchive, or one for a specified date.
This commit is contained in:
Basti 2022-01-04 13:13:21 -06:00
parent 189cd1f86d
commit a0bec9a12c
Signed by untrusted user: basti
GPG Key ID: 9F88009D31D99C72

View File

@ -199,55 +199,45 @@ const ChatMessages = function (privateClient, publicClient) {
publicClient.declareType("daily-archive-meta", "https://kosmos.org/ns/v2/chat-channel-meta", archiveMetaSchema); publicClient.declareType("daily-archive-meta", "https://kosmos.org/ns/v2/chat-channel-meta", archiveMetaSchema);
/** /**
* A daily archive stores chat messages by calendar day. * A chat channel
* *
* @class * @class
*/ */
class DailyArchive { class Channel {
/** /**
* @param {object} options * @param {object} options
* @param {object} options.service * @param {object} options.service
* @param {string} options.service.protocol - Type of chat service/protocol (e.g. "IRC", "XMPP", "Campfire", "Slack") * @param {string} options.service.protocol - Type of chat service/protocol (e.g. "IRC", "XMPP", "Campfire", "Slack")
* @param {string} options.service.domain - Domain of the chat service (e.g. "irc.libera.chat", "kosmos.chat") * @param {string} options.service.domain - Domain of the chat service (e.g. "irc.libera.chat", "kosmos.chat")
* @param {string} options.channelName - Name of room/channel (e.g. "#kosmos") * @param {string} options.name - Name of room/channel (e.g. "#kosmos")
* @param {string} [options.channelType] - Type of channel ("room" or "person") * @param {string} [options.type] - Type of channel ("room" or "person")
* @param {date} options.date - Date of archive day
* @param {boolean} [options.isPublic] - Store logs in public folder (defaults to false)
* @param {string} [options.previous] - Date of previous log file as `YYYY/MM/DD`. Looked up automatically when not given
* @param {string} [options.next] - Date of next log file as `YYYY/MM/DD`. looked up automatically when not given
* *
* @example * @example
* // IRC archive: * // IRC channel:
* const archive = new chatMessages.DailyArchive({ * const channel = new remoteStorage.chatMessages.Channel({
* service: { * service: {
* protocol: 'IRC', * protocol: 'IRC',
* domain: 'irc.libera.chat', * domain: 'irc.libera.chat',
* }, * },
* channelName: '#kosmos-dev', * name: '#kosmos-dev',
* channelType: 'room', * type: 'room'
* date: new Date(),
* isPublic: true
* }); * });
* *
* // XMPP archive: * // XMPP channel:
* const archive = new chatMessages.DailyArchive({ * const channel = new remoteStorage.chatMessages.Channel({
* service: { * service: {
* protocol: 'XMPP', * protocol: 'XMPP',
* domain: 'kosmos.chat', * domain: 'kosmos.chat',
* }, * },
* channelName: 'kosmos-dev', * name: 'kosmos-dev',
* channelType: 'room', * type: 'room'
* date: new Date(),
* isPublic: false
* }); * });
*
*/ */
constructor (options) { constructor (options) {
// //
// Defaults // Defaults
// //
options.isPublic = options.isPublic || false; options.type = options.type || "room";
options.channelType = options.channelType || "room";
// //
// Validate options // Validate options
@ -258,16 +248,10 @@ const ChatMessages = function (privateClient, publicClient) {
if (typeof options.service !== "object" || if (typeof options.service !== "object" ||
typeof options.service.protocol !== "string" || typeof options.service.protocol !== "string" ||
typeof options.service.domain !== "string") { typeof options.service.domain !== "string") {
throw "service must be an object containing at least service \"protocol\" and \"domain\""; throw "options.service must be an object containing at least service \"protocol\" and \"domain\"";
} }
if (typeof options.channelName !== "string") { if (typeof options.name !== "string") {
throw "channelName must be a string"; throw "options.name must be a string";
}
if (!(options.date instanceof Date)) {
throw "date must be a date object";
}
if (typeof options.isPublic !== "boolean") {
throw "isPublic must be a boolean value";
} }
/** /**
@ -278,14 +262,130 @@ const ChatMessages = function (privateClient, publicClient) {
this.service = options.service; this.service = options.service;
/** /**
* @property {string} channelName - Name of channel (e.g. "#kosmos") * @property {string} name - Name of channel (e.g. "#kosmos")
*/ */
this.channelName = options.channelName; this.name = options.name;
/** /**
* @property {string} channelType - Type of channel ("room" or "person") * @property {string} type - Type of channel ("room" or "person")
*/ */
this.channelType = options.channelType; this.type = options.type;
/**
* @property {string} path - Base directory path of the channel archives
*/
if (this.type === "room") {
// Normal chatroom
const name = this.name.replace(/#/,'');
this.path = `${this.service.domain}/channels/${name}`;
} else {
// User direct messages
this.path = `${this.service.domain}/users/${this.name}`;
}
/**
* @property {string} metaPath - Path of the channel's metadata document
*/
this.metaPath = `${this.path}/meta`;
}
/*
* Returns the last stored DailyArchive, or a new one for the current day.
*
* @param {object} options
* @param {boolean} [options.public] - Public or private archive. Defaults to 'false'.
*
* @returns {Promise}
*/
async openLastDailyArchive (options={}) {
const client = options.public ? publicClient : privateClient;
const meta = await client.getObject(this.metaPath);
if (meta && meta.last) {
return new DailyArchive({
channel: this,
date: new Date(meta.last.replace(/\//g,'-')),
isPublic: options.public
});
} else {
return new DailyArchive({
channel: this,
date: new Date(),
isPublic: options.public
});
}
}
/*
* Returns a DailyArchive for the given date
*
* @param {object} options
* @param {date} options.date - Date of archive day
* @param {boolean} [options.public] - Public or private archive. Defaults to 'false'.
*
* @returns {Promise}
*/
openDailyArchive (options={}) {
if (!(options.date instanceof Date)) {
throw 'options.date must be a date object';
}
return new DailyArchive({
channel: this,
date: options.date,
isPublic: options.public
});
}
}
/**
* A daily archive stores chat messages by calendar day.
*
* @class
*/
class DailyArchive {
/**
* @param {object} options
* @param {string} options.channel - A Channel instance
* @param {date} options.date - Date of archive day
* @param {boolean} [options.isPublic] - Store logs in public folder (defaults to false)
* @param {string} [options.previous] - Date of previous log file as `YYYY/MM/DD`. Looked up automatically when not given
* @param {string} [options.next] - Date of next log file as `YYYY/MM/DD`. Looked up automatically when not given
*
* @example
* const channel = new remoteStorage.chatMessages.Channel({ ...options })
* const archive = new chatMessages.DailyArchive({
* channel: channel,
* date: new Date(),
* isPublic: true
* });
*/
constructor (options) {
//
// Defaults
//
options.isPublic = options.isPublic || false;
//
// Validate options
//
if (typeof options !== "object") {
throw "options must be an object";
}
if (!(options.channel instanceof Channel)) {
throw "options.channel must be a Channel object";
}
if (!(options.date instanceof Date)) {
throw "options.date must be a date object";
}
if (typeof options.isPublic !== "boolean") {
throw "options.isPublic must be a boolean value";
}
/**
* @property {Channel} channel - A Channel instance
*/
this.channel = options.channel;
/** /**
* @property {string} date - Gregorian calendar date of the archive's content * @property {string} date - Gregorian calendar date of the archive's content
@ -310,27 +410,15 @@ const ChatMessages = function (privateClient, publicClient) {
*/ */
this.dateId = this.parsedDate.year+'/'+this.parsedDate.month+'/'+this.parsedDate.day; this.dateId = this.parsedDate.year+'/'+this.parsedDate.month+'/'+this.parsedDate.day;
/**
* @property {string} channelPath - Base directory path of the channel archives
*/
if (this.channelType === "room") {
// Normal chatroom
const channelName = this.channelName.replace(/#/,'');
this.channelPath = `${this.service.domain}/channels/${channelName}`;
} else {
// User direct messages
this.channelPath = `${this.service.domain}/users/${this.channelName}`;
}
/** /**
* @property {string} path - Path of the archive document * @property {string} path - Path of the archive document
*/ */
this.path = `${this.channelPath}/${this.dateId}`; this.path = `${this.channel.path}/${this.dateId}`;
/** /**
* @property {string} metaPath - Path of the channel's metadata document * @property {string} metaPath - Path of the channel's metadata document
*/ */
this.metaPath = `${this.channelPath}/meta`; this.metaPath = `${this.channel.path}/meta`;
/** /**
* @property {object} client - Public or private remoteStorgage.js BaseClient * @property {object} client - Public or private remoteStorgage.js BaseClient
@ -359,7 +447,7 @@ const ChatMessages = function (privateClient, publicClient) {
* @returns {Promise} * @returns {Promise}
*/ */
addMessage (message) { addMessage (message) {
if (this.isPublic && !this.channelName.match(/^#/)) { if (this.isPublic && !this.channel.name.match(/^#/)) {
return Promise.resolve(false); return Promise.resolve(false);
} }
@ -384,7 +472,7 @@ const ChatMessages = function (privateClient, publicClient) {
* @returns {Promise} * @returns {Promise}
*/ */
addMessages (messages, overwrite) { addMessages (messages, overwrite) {
if (this.isPublic && !this.channelName.match(/^#/)) { if (this.isPublic && !this.channel.name.match(/^#/)) {
return Promise.resolve(false); return Promise.resolve(false);
} }
@ -489,14 +577,14 @@ const ChatMessages = function (privateClient, publicClient) {
* @private * @private
*/ */
_buildArchiveObject () { _buildArchiveObject () {
const roomName = this.channelName.replace(/#/,''); const roomName = this.channel.name.replace(/#/,'');
const archive = { const archive = {
"@id": "chat-messages/"+this.service.domain+"/channels/"+roomName+"/", "@id": "chat-messages/"+this.channel.service.domain+"/channels/"+roomName+"/",
"@type": "ChatChannel", "@type": "ChatChannel",
"service": this.service, "service": this.channel.service,
"name": this.channelName, "name": this.channel.name,
"type": this.channelType, "type": this.channel.type,
"today": { "today": {
"@id": this.dateId, "@id": this.dateId,
"@type": "ChatLog", "@type": "ChatLog",
@ -505,10 +593,10 @@ const ChatMessages = function (privateClient, publicClient) {
} }
}; };
switch (this.service.protocol) { switch (this.channel.service.protocol) {
case 'IRC': case 'IRC':
if (!this.channelName.match(/^#/)) { if (!this.channel.name.match(/^#/)) {
archive["@id"] = "chat-messages/"+this.service.domain+"/users/"+this.channelName+"/"; archive["@id"] = "chat-messages/"+this.channel.service.domain+"/users/"+this.channel.name+"/";
} }
break; break;
case 'XMPP': case 'XMPP':
@ -630,10 +718,10 @@ const ChatMessages = function (privateClient, publicClient) {
// When creating a new meta doc, we need to find the oldest archive, // When creating a new meta doc, we need to find the oldest archive,
// because older versions of the module did not write a meta doc. // because older versions of the module did not write a meta doc.
const first = await this._findFirstArchive(); const first = await this._findFirstArchive();
const roomName = this.channelName.replace(/#/,''); const roomName = this.channel.name.replace(/#/,'');
const meta = { const meta = {
'@id': `chat-messages/${this.service.domain}/channels/${roomName}/meta`, '@id': `chat-messages/${this.channel.service.domain}/channels/${roomName}/meta`,
'@type': 'ChatChannelMeta', '@type': 'ChatChannelMeta',
first: first, first: first,
last: this.dateId // TODO might have to search for last? last: this.dateId // TODO might have to search for last?
@ -677,6 +765,8 @@ const ChatMessages = function (privateClient, publicClient) {
console.warn('[chat-messages] Error trying to store object', error); console.warn('[chat-messages] Error trying to store object', error);
return error; return error;
}); });
// TODO await this.client.startSync();
} }
}; };