-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
send history in MUCs #227
Open
uhoreg
wants to merge
15
commits into
develop
Choose a base branch
from
uhoreg/history
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
send history in MUCs #227
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
8518cf2
initial work on sending history in MUCs
uhoreg a01c24f
remove console.logs
uhoreg 9fce998
more improvements, and actually use the client's history management
uhoreg 58e8bd1
apply all history limits
uhoreg 850e313
fix lint
uhoreg eeb8cad
add changelog
uhoreg ee90363
set a default history limit if none provided by the client
uhoreg cefb56e
Apply suggestions from code review
uhoreg 75d0ba3
more fixes
uhoreg 4f1223e
add unit test
uhoreg 57cef0b
fix build error
uhoreg 1eff791
Only emit history for gateways
Half-Shot d82f216
Fix history bug
Half-Shot 703a72d
Add history visibility settings
Half-Shot 4573ba2
Merge remote-tracking branch 'origin/develop' into uhoreg/history
Half-Shot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Send room history to XMPP clients when they join. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { Element } from "@xmpp/xml"; | ||
import { JID } from "@xmpp/jid"; | ||
|
||
export interface IHistoryLimits { | ||
maxchars?: number, | ||
maxstanzas?: number, | ||
seconds?: number, | ||
since?: Date, | ||
} | ||
|
||
/** | ||
* Abstraction of a history storage backend. | ||
*/ | ||
interface IHistoryStorage { | ||
// add a message to the history of a given room | ||
addMessage: (chatName: string, message: Element, jid: JID) => unknown; | ||
// get the history of a room. The storage should apply the limits that it | ||
// wishes too, and remove the limits that it applied from the `limits` | ||
// parameter. The returned list of Elements must include the Delayed | ||
// Delivery information. | ||
getHistory: (chatName: string, limits: IHistoryLimits) => Promise<Element[]>; | ||
} | ||
|
||
/** | ||
* Store room history in memory. | ||
*/ | ||
export class MemoryStorage implements IHistoryStorage { | ||
private history: Map<string, Element[]>; | ||
constructor(public maxHistory: number) { | ||
this.history = new Map(); | ||
} | ||
|
||
addMessage(chatName: string, message: Element, jid: JID): void { | ||
if (!this.history.has(chatName)) { | ||
this.history.set(chatName, []); | ||
} | ||
const currRoomHistory = this.history.get(chatName); | ||
|
||
// shallow-copy the message, and add the timestamp | ||
const copiedMessage = new Element(message.name, message.attrs); | ||
copiedMessage.append(message.children as Element[]); | ||
copiedMessage.attr("from", jid.toString()); | ||
copiedMessage.append(new Element("delay", { | ||
xmlns: "urn:xmpp:delay", | ||
from: chatName, | ||
stamp: (new Date()).toISOString(), | ||
})); | ||
|
||
currRoomHistory.push(copiedMessage); | ||
|
||
while (currRoomHistory.length > this.maxHistory) { | ||
currRoomHistory.shift(); | ||
} | ||
} | ||
|
||
async getHistory(chatName: string, limits: IHistoryLimits): Promise<Element[]> { | ||
return this.history.get(chatName) || []; | ||
} | ||
} | ||
|
||
// TODO: make a class that stores in PostgreSQL so that we don't lose history | ||
// when we restart | ||
Comment on lines
+61
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to file that as an issue or address this before merging this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably just file as an issue. |
||
|
||
/** | ||
* Manage room history for a MUC | ||
*/ | ||
export class HistoryManager { | ||
constructor( | ||
private storage: IHistoryStorage, | ||
) {} | ||
|
||
addMessage(chatName: string, message: Element, jid: JID): unknown { | ||
return this.storage.addMessage(chatName, message, jid); | ||
} | ||
|
||
async getHistory(chatName: string, limits: IHistoryLimits): Promise<Element[]> { | ||
if (limits.seconds) { | ||
const since = new Date(Date.now() - limits.seconds * 1000); | ||
if (limits.since === undefined || limits.since < since) { | ||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
limits.since = since; | ||
} | ||
delete limits.seconds; | ||
} | ||
let history: Element[] = await this.storage.getHistory(chatName, limits); | ||
|
||
// index of the first history element that we will keep after applying | ||
// the limits | ||
let idx = 0; | ||
|
||
if ("maxstanzas" in limits && history.length > limits.maxstanzas) { | ||
idx = history.length - limits.maxstanzas; | ||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if ("since" in limits) { | ||
// FIXME: binary search would be better than linear search | ||
for (; idx < history.length; idx++) { | ||
try { | ||
const ts = history[idx].getChild("delay", "urn:xmpp:delay")?.attr("stamp"); | ||
if (new Date(ts) >= limits.since) { | ||
break; | ||
} | ||
} catch {} | ||
} | ||
} | ||
|
||
if ("maxchars" in limits) { | ||
let numChars = 0; | ||
let i = history.length; | ||
for (; i > idx && numChars < limits.maxchars; i--) { | ||
jaller94 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
numChars += history[i - 1].toString().length; | ||
} | ||
idx = i; | ||
} | ||
|
||
if (idx > 0) { | ||
history = history.slice(idx); | ||
Half-Shot marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return history; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { expect } from "chai"; | ||
import { MemoryStorage, HistoryManager } from "../../src/xmppjs/HistoryManager"; | ||
import { Element } from "@xmpp/xml"; | ||
import { JID } from "@xmpp/jid"; | ||
|
||
describe("HistoryManager", () => { | ||
describe("MemoryStorage", () => { | ||
it("should return filtered history", async () => { | ||
const historyManager = new HistoryManager(new MemoryStorage(20)); | ||
historyManager.addMessage( | ||
"[email protected]", new Element("stanza1"), | ||
new JID("room1", "example.org", "user1"), | ||
); | ||
historyManager.addMessage( | ||
"[email protected]", new Element("stanza2"), | ||
new JID("room1", "example.org", "user1"), | ||
); | ||
historyManager.addMessage( | ||
"[email protected]", new Element("stanza3"), | ||
new JID("room1", "example.org", "user1"), | ||
); | ||
historyManager.addMessage( | ||
"[email protected]", new Element("stanza1"), | ||
new JID("room1", "example.org", "user1"), | ||
); | ||
|
||
const unfilteredRoom1 = await historyManager.getHistory("[email protected]", {}); | ||
expect(unfilteredRoom1.length).to.equal(3); | ||
const unfilteredRoom2 = await historyManager.getHistory("[email protected]", {}); | ||
expect(unfilteredRoom2.length).to.equal(1); | ||
|
||
const maxStanzasRoom1 = await historyManager.getHistory("[email protected]", { | ||
maxstanzas: 2, | ||
}); | ||
expect(maxStanzasRoom1.length).to.equal(2); | ||
const maxStanzasRoom2 = await historyManager.getHistory("[email protected]", { | ||
maxstanzas: 2, | ||
}); | ||
expect(maxStanzasRoom2.length).to.equal(1); | ||
|
||
// each stanza will be about 40 characters, so maxchars 50 should | ||
// only give us one stanza | ||
const maxCharsRoom1 = await historyManager.getHistory("[email protected]", { | ||
maxchars: 50, | ||
}); | ||
expect(maxCharsRoom1.length).to.equal(1); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why allow
unknown
to be returned and not justvoid
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it could be async and return
Promise<void>