<div class="nH" style role="main"><div class="nH" role="list" style> (or "nH bh")sdk.Conversations.registerThreadViewHandler(threadView => { const mutationObserver = new MutationObserver(...); mutationObserver.observe(...); threadView.on('destroy', () => { mutationObserver.disconnect(); });});function isMessageNode(msgEle:HTMLElement): boolean { if (! msgEle.innerText) return false;
if (isPrimaryMessage(msgEle) || msgEle.className === "gmail_quote" // quoted message ) { return true; } return false;}
// Clapsed message: className="adf ads" or div class "iA g6"// Expanded message: "adn ads", its sibling is reply divfunction isPrimaryMessage(msgEle:HTMLElement) { return msgEle.parentElement // "a3s aXjCH" && msgEle.parentElement.className.startsWith("ii gt") && msgEle.parentElement.style.display != "none" || msgEle.className === "h7 ie nH oy8Mbf" // Display after reply || msgEle.firstChild && (msgEle.firstChild as HTMLElement).className === "adn ads" // Expanded message display div || msgEle.className === "ii gt" && msgEle.style.display != "none" // Message view body element ;}
class MessageObserver { observer: MutationObserver; observingStarted: boolean; observedThreadView: any; previousObservedThreadView: any; myEmailAddress: string; observerConfig: {childList: boolean; subtree: boolean};
constructor(myEmail: string) { this.observer = new MutationObserver(this.watchMessage()); this.observingStarted = false; this.myEmailAddress = myEmail; this.observerConfig = {childList: true, subtree: true}; }
watchMessage() { let that = this; return (mutationList) => { mutationList.forEach(mutation => { if (mutation.type === 'childList') { [].slice.call(mutation.addedNodes) .filter(isMessageNode).map(ele => decryptMessageNode(that.myEmailAddress, ele)); } }); } }
startObserving() { if (!this.observedThreadView || this.observedThreadView === this.previousObservedThreadView) return; // Observe the thread view element let $threadElement = $('div.nH[role="main"][style]');
let threadEle = $threadElement.get(0); if (!threadEle) return;
this.previousObservedThreadView = this.observedThreadView; if (this.observingStarted) { this.observer.disconnect(); } this.observingStarted = true; this.observer.observe(threadEle, this.observerConfig); }
stopObserving() { if (this.observingStarted) { this.observer.disconnect(); this.observedThreadView = this.previousObservedThreadView = undefined; this.observingStarted = false; } }
setObservedThreadView(tv) { this.observedThreadView = tv; }}
sdk.Conversations.registerThreadViewHandler(threadView => { messageObserver.setObservedThreadView(threadView); messageObserver.startObserving();
threadView.on("destroy", event => { messageObserver.stopObserving(); });
});
if (mutation.type === 'childList') { [].slice.call(mutation.addedNodes) .filter(isMessageNode).map(ele => decryptMessageNode(that.myEmailAddress, ele)); } if (mutation.type === 'childList') { [].slice.call(mutation.addedNodes).map(processNode); }
where,
function processNode(node) {
if (!isMessageNode(node)) return;
decryptMessageNode(myEmailAddress, node);}
stopObserving() { if (this.observingStarted) { this.observer.disconnect(); this.observedThreadView = this.previousObservedThreadView = undefined; this.observingStarted = false; } }
setObservedThreadView(tv) { this.observedThreadView = tv; }} if (mutation.type === 'childList') { [].slice.call(mutation.addedNodes) .filter(isMessageNode).map(ele => decryptMessageNode(that.myEmailAddress, ele)); } if (mutation.type === 'childList') { [].slice.call(mutation.addedNodes).map(processNode); }
where
function processNode(node) {
if (!isMessageNode(node)) return;
decryptMessageNode(that.myEmailAddress, node);
}if<span
class MessageObserver { observer: MutationObserver; observingStarted: boolean; myEmailAddress: string; observerConfig: {childList: boolean; subtree: boolean};
constructor(myEmail: string) { this.observer = new MutationObserver(this.watchMessage()); this.observingStarted = false; this.myEmailAddress = myEmail; this.observerConfig = {childList: true, subtree: true}; }
watchMessage() { let myEmail = this.myEmailAddress; return (mutationList) => { mutationList.forEach(mutation => { if (mutation.type === 'childList') { [].slice.call(mutation.addedNodes) .filter(isRawMessageNode).map(ele => decryptMessageNode(myEmail, ele)); } }); } }
startObserving() { // Observe the thread view element // let $threadElement = $('div.nH[role="list"]') || $('div.nH.bh[role="list"]'); let threadEle = $('div.nH[role="main"][style]').get(0); if (!threadEle) return; this.observer.observe(threadEle, this.observerConfig); }
stopObserving() { this.observer.disconnect(); }}
sdk.Conversations.registerThreadViewHandler(threadView => {
// Decryption and verifying
let messageObserver = new MessageObserver(sdk.User.getEmailAddress());
messageObserver.startObserving();
threadView.on("destroy", event => {
messageObserver.stopObserving();
});
});
function isRawMessageNode(msgEle:HTMLElement): boolean { if (! msgEle.innerText // || msgEle.getAttribute(decryptedAttribute) == decryptedAttributeValue ) { return false; }
if (isPrimaryMessage(msgEle) || msgEle.classList.contains("gmail_quote") // quoted message ) { return true; } return false;}
// Clapsed message: className="adf ads" or div class "iA g6"// Expanded message: "adn ads", its sibling is reply divfunction isPrimaryMessage(msgEle:HTMLElement) { return msgEle.parentElement // "a3s aXjCH" && hasAllClasses(msgEle.parentElement, ["ii", "gt"]) && msgEle.parentElement.style.display != "none" || hasAllClasses(msgEle, ["h7", "ie", "nH", "oy8Mbf"]) // Display after reply || msgEle.firstChild && hasAllClasses(<HTMLElement>msgEle.firstChild, ["adn", "ads"]) // Expanded message display div || hasAllClasses(msgEle, ["ii", "gt"]) && msgEle.style.display != "none" // Message view body element ;}
function hasAllClasses(el: HTMLElement, classes: string[]): boolean { if (! el.classList) return false;
for(let i = 0; i < classes.length; ++i) { if (! el.classList.contains(classes[i])) { return false; } } return true;}