Ver Fonte

Remove some references to `window` from headless

Move some functions out of headless, since they're only used by the UI code.
JC Brand há 1 ano atrás
pai
commit
022ce1aecd
43 ficheiros alterados com 223 adições e 242 exclusões
  1. 3 3
      package-lock.json
  2. 6 7
      src/headless/plugins/chat/message.js
  3. 15 9
      src/headless/plugins/chat/model.js
  4. 1 1
      src/headless/plugins/muc/muc.js
  5. 4 7
      src/headless/plugins/roster/contacts.js
  6. 2 2
      src/headless/plugins/status/utils.js
  7. 1 1
      src/headless/shared/connection/index.js
  8. 32 30
      src/headless/tests/eventemitter.js
  9. 4 3
      src/headless/tests/persistence.js
  10. 2 2
      src/headless/types/plugins/chat/message.d.ts
  11. 1 1
      src/headless/types/plugins/chat/model.d.ts
  12. 0 5
      src/headless/types/utils/html.d.ts
  13. 0 6
      src/headless/types/utils/index.d.ts
  14. 0 18
      src/headless/types/utils/url.d.ts
  15. 3 3
      src/headless/utils/arraybuffer.js
  16. 0 15
      src/headless/utils/html.js
  17. 4 12
      src/headless/utils/index.js
  18. 0 68
      src/headless/utils/url.js
  19. 1 1
      src/plugins/chatview/chat.js
  20. 1 1
      src/plugins/chatview/message-form.js
  21. 3 2
      src/plugins/chatview/tests/http-file-upload.js
  22. 4 5
      src/plugins/minimize/utils.js
  23. 1 1
      src/plugins/omemo/utils.js
  24. 1 1
      src/plugins/roomslist/view.js
  25. 1 1
      src/shared/chat/message-actions.js
  26. 1 1
      src/shared/chat/message.js
  27. 1 1
      src/shared/chat/templates/message.js
  28. 5 1
      src/shared/chat/templates/unfurl.js
  29. 2 1
      src/shared/components/image.js
  30. 1 1
      src/shared/rich-text.js
  31. 1 1
      src/types/headless/plugins/chat/message.d.ts
  32. 1 1
      src/types/headless/plugins/chat/model.d.ts
  33. 0 5
      src/types/headless/utils/html.d.ts
  34. 0 6
      src/types/headless/utils/index.d.ts
  35. 0 8
      src/types/headless/utils/url.d.ts
  36. 1 1
      src/types/plugins/controlbox/model.d.ts
  37. 1 2
      src/types/plugins/muc-views/sidebar.d.ts
  38. 4 4
      src/types/plugins/omemo/utils.d.ts
  39. 1 1
      src/types/plugins/roomslist/model.d.ts
  40. 6 1
      src/types/utils/html.d.ts
  41. 16 0
      src/types/utils/url.d.ts
  42. 17 2
      src/utils/html.js
  43. 75 0
      src/utils/url.js

+ 3 - 3
package-lock.json

@@ -9288,8 +9288,8 @@
     },
     "node_modules/strophe.js": {
       "version": "2.0.0",
-      "resolved": "git+ssh://git@github.com/strophe/strophejs.git#62cd93989269e86fb13e9eade1aa9a7470c4aab6",
-      "integrity": "sha512-oOj9oZ74/XzANeOaZspleA6bHXMGISqoFlLjf1upSNCnShZT2faRPsdqBHPeVgMGf1C8htJdUwuRvp/UrmLJeA==",
+      "resolved": "git+ssh://git@github.com/strophe/strophejs.git#a02d8932d6d6175a72833f7493b5ca9562438509",
+      "integrity": "sha512-CoezG0Hubhrmp7fKhXdPJ8puhalzjzGcnbo/qfiG/yGsy0uXKlQJd0giS22AchKbqrevgGcGrTZmMXVESXFIwA==",
       "license": "MIT",
       "dependencies": {
         "abab": "^2.0.3"
@@ -10383,7 +10383,7 @@
         "pluggable.js": "3.0.1",
         "sizzle": "^2.3.5",
         "sprintf-js": "^1.1.2",
-        "strophe.js": "strophe/strophejs#62cd93989269e86fb13e9eade1aa9a7470c4aab6",
+        "strophe.js": "strophe/strophejs#a02d8932d6d6175a72833f7493b5ca9562438509",
         "urijs": "^1.19.10"
       },
       "devDependencies": {}

+ 6 - 7
src/headless/plugins/chat/message.js

@@ -1,16 +1,16 @@
 /**
  * @typedef {import('@converse/skeletor').Model} Model
  */
+import sizzle from 'sizzle';
 import ModelWithContact from './model-with-contact.js';
 import _converse from '../../shared/_converse.js';
 import api from '../../shared/api/index.js';
-import converse from '../../shared/api/public.js';
 import dayjs from 'dayjs';
 import log from '../../log.js';
 import { getOpenPromise } from '@converse/openpromise';
 import { SUCCESS, FAILURE } from '../../shared/constants.js';
-
-const { Strophe, sizzle, u } = converse.env;
+import { Strophe, $iq } from 'strophe.js';
+import { getUniqueId } from '../../utils/index.js';
 
 /**
  * Represents a (non-MUC) message.
@@ -23,7 +23,7 @@ class Message extends ModelWithContact {
 
     defaults () {
         return {
-            'msgid': u.getUniqueId(),
+            'msgid': getUniqueId(),
             'time': new Date().toISOString(),
             'is_ephemeral': false
         };
@@ -80,7 +80,7 @@ class Message extends ModelWithContact {
         const is_ephemeral = this.isEphemeral();
         if (is_ephemeral) {
             const timeout = typeof is_ephemeral === "number" ? is_ephemeral : 10000;
-            this.ephemeral_timer = window.setTimeout(() => this.safeDestroy(), timeout);
+            this.ephemeral_timer = setTimeout(() => this.safeDestroy(), timeout);
         }
     }
 
@@ -192,8 +192,7 @@ class Message extends ModelWithContact {
     sendSlotRequestStanza () {
         if (!this.file) return Promise.reject(new Error('file is undefined'));
 
-        const iq = converse.env
-            .$iq({
+        const iq = $iq({
                 'from': _converse.session.get('jid'),
                 'to': this.get('slot_request_url'),
                 'type': 'get'

+ 15 - 9
src/headless/plugins/chat/model.js

@@ -485,17 +485,17 @@ class ChatBox extends ModelWithContact {
      */
     setChatState (state, options) {
         if (this.chat_state_timeout !== undefined) {
-            window.clearTimeout(this.chat_state_timeout);
+            clearTimeout(this.chat_state_timeout);
             delete this.chat_state_timeout;
         }
         if (state === COMPOSING) {
-            this.chat_state_timeout = window.setTimeout(
+            this.chat_state_timeout = setTimeout(
                 this.setChatState.bind(this),
                 _converse.TIMEOUTS.PAUSED,
                 PAUSED
             );
         } else if (state === PAUSED) {
-            this.chat_state_timeout = window.setTimeout(
+            this.chat_state_timeout = setTimeout(
                 this.setChatState.bind(this),
                 _converse.TIMEOUTS.INACTIVE,
                 INACTIVE
@@ -1015,7 +1015,7 @@ class ChatBox extends ModelWithContact {
             return;
         }
         const data = item.dataforms.where({'FORM_TYPE': {'value': Strophe.NS.HTTPUPLOAD, 'type': "hidden"}}).pop();
-        const max_file_size = window.parseInt((data?.attributes || {})['max-file-size']?.value);
+        const max_file_size = parseInt((data?.attributes || {})['max-file-size']?.value, 10);
         const slot_request_url = item?.id;
 
         if (!slot_request_url) {
@@ -1036,12 +1036,18 @@ class ChatBox extends ModelWithContact {
              */
             file = await api.hook('beforeFileUpload', this, file);
 
-            if (!window.isNaN(max_file_size) && file.size > max_file_size) {
+            if (!isNaN(max_file_size) && file.size > max_file_size) {
+                const size = filesize(max_file_size);
+                const message = Array.isArray(size)
+                    ? __('The size of your file, %1$s, exceeds the maximum allowed by your server.', file.name)
+                    : __(
+                        'The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.',
+                        file.name, size
+                    );
                 return this.createMessage({
-                    'message': __('The size of your file, %1$s, exceeds the maximum allowed by your server, which is %2$s.',
-                        file.name, filesize(max_file_size)),
-                    'type': 'error',
-                    'is_ephemeral': true
+                    message,
+                    type: 'error',
+                    is_ephemeral: true
                 });
             } else {
                 const initial_attrs = await this.getOutgoingMessageAttributes();

+ 1 - 1
src/headless/plugins/muc/muc.js

@@ -2256,7 +2256,7 @@ class MUC extends ChatBox {
         const actors_per_traffic_state = converse.MUC_TRAFFIC_STATES_LIST.reduce(reducer, {});
         const actors_per_role_change = converse.MUC_ROLE_CHANGES_LIST.reduce(reducer, {});
         this.notifications.set(Object.assign(actors_per_chat_state, actors_per_traffic_state, actors_per_role_change));
-        window.setTimeout(() => this.removeNotification(actor, state), 10000);
+        setTimeout(() => this.removeNotification(actor, state), 10000);
     }
 
     handleMetadataFastening (attrs) {

+ 4 - 7
src/headless/plugins/roster/contacts.js

@@ -36,11 +36,10 @@ class RosterContacts extends Collection {
      * Register a handler for roster IQ "set" stanzas, which update
      * roster contacts.
      */
-    // eslint-disable-next-line class-methods-use-this
     registerRosterHandler () {
         // Register a handler for roster IQ "set" stanzas, which update
         // roster contacts.
-        api.connection.get().addHandler(iq => {
+        api.connection.get().addHandler((iq) => {
             _converse.state.roster.onRosterPush(iq);
             return true;
         }, Strophe.NS.ROSTER, 'iq', "set");
@@ -50,14 +49,12 @@ class RosterContacts extends Collection {
      * Register a handler for RosterX message stanzas, which are
      * used to suggest roster contacts to a user.
      */
-    // eslint-disable-next-line class-methods-use-this
     registerRosterXHandler () {
         let t = 0;
         const connection = api.connection.get();
-        connection.addHandler(
-            function (msg) {
-                const { roster } = _converse.state;
-                window.setTimeout(function () {
+        connection.addHandler((msg) => {
+                setTimeout(() => {
+                    const { roster } = _converse.state;
                     api.connection.get().flush();
                     roster.subscribeToSuggestedItems(msg);
                 }, t);

+ 2 - 2
src/headless/plugins/status/utils.js

@@ -147,7 +147,7 @@ export function registerIntervalHandler () {
     window.addEventListener('keypress', onUserActivity);
     window.addEventListener('mousemove', onUserActivity);
     window.addEventListener(getUnloadEvent(), onUserActivity, {'once': true, 'passive': true});
-    everySecondTrigger = window.setInterval(onEverySecond, 1000);
+    everySecondTrigger = setInterval(onEverySecond, 1000);
 }
 
 export function tearDown () {
@@ -158,7 +158,7 @@ export function tearDown () {
     window.removeEventListener('mousemove', onUserActivity);
     window.removeEventListener(getUnloadEvent(), onUserActivity);
     if (everySecondTrigger) {
-        window.clearInterval(everySecondTrigger);
+        clearInterval(everySecondTrigger);
         everySecondTrigger = null;
     }
 }

+ 1 - 1
src/headless/shared/connection/index.js

@@ -45,7 +45,7 @@ export class Connection extends Strophe.Connection {
     async onDomainDiscovered (response) {
         const { api } = _converse;
         const text = await response.text();
-        const xrd = (new window.DOMParser()).parseFromString(text, "text/xml").firstElementChild;
+        const xrd = (new DOMParser()).parseFromString(text, "text/xml").firstElementChild;
         if (xrd.nodeName != "XRD" || xrd.namespaceURI != "http://docs.oasis-open.org/ns/xri/xrd-1.0") {
             return log.warn("Could not discover XEP-0156 connection methods");
         }

+ 32 - 30
src/headless/tests/eventemitter.js

@@ -1,58 +1,60 @@
 /*global mock */
 
+const container = {};
+
 describe("The _converse Event Emitter", function() {
 
     it("allows you to subscribe to emitted events", mock.initConverse((_converse) => {
-        window.callback = function () {};
-        spyOn(window, 'callback');
-        _converse.on('connected', window.callback);
+        container.callback = function () {};
+        spyOn(container, 'callback');
+        _converse.on('connected', container.callback);
         _converse.api.trigger('connected');
-        expect(window.callback).toHaveBeenCalled();
+        expect(container.callback).toHaveBeenCalled();
         _converse.api.trigger('connected');
-        expect(window.callback.calls.count(), 2);
+        expect(container.callback.calls.count(), 2);
         _converse.api.trigger('connected');
-        expect(window.callback.calls.count(), 3);
+        expect(container.callback.calls.count(), 3);
     }));
 
     it("allows you to listen once for an emitted event", mock.initConverse((_converse) => {
-        window.callback = function () {};
-        spyOn(window, 'callback');
-        _converse.once('connected', window.callback);
+        container.callback = function () {};
+        spyOn(container, 'callback');
+        _converse.once('connected', container.callback);
         _converse.api.trigger('connected');
-        expect(window.callback).toHaveBeenCalled();
+        expect(container.callback).toHaveBeenCalled();
         _converse.api.trigger('connected');
-        expect(window.callback.calls.count(), 1);
+        expect(container.callback.calls.count(), 1);
         _converse.api.trigger('connected');
-        expect(window.callback.calls.count(), 1);
+        expect(container.callback.calls.count(), 1);
     }));
 
     it("allows you to stop listening or subscribing to an event", mock.initConverse((_converse) => {
-        window.callback = function () {};
-        window.anotherCallback = function () {};
-        window.neverCalled = function () {};
+        container.callback = function () {};
+        container.anotherCallback = function () {};
+        container.neverCalled = function () {};
 
-        spyOn(window, 'callback');
-        spyOn(window, 'anotherCallback');
-        spyOn(window, 'neverCalled');
-        _converse.on('connected', window.callback);
-        _converse.on('connected', window.anotherCallback);
+        spyOn(container, 'callback');
+        spyOn(container, 'anotherCallback');
+        spyOn(container, 'neverCalled');
+        _converse.on('connected', container.callback);
+        _converse.on('connected', container.anotherCallback);
 
         _converse.api.trigger('connected');
-        expect(window.callback).toHaveBeenCalled();
-        expect(window.anotherCallback).toHaveBeenCalled();
+        expect(container.callback).toHaveBeenCalled();
+        expect(container.anotherCallback).toHaveBeenCalled();
 
-        _converse.off('connected', window.callback);
+        _converse.off('connected', container.callback);
 
         _converse.api.trigger('connected');
-        expect(window.callback.calls.count(), 1);
-        expect(window.anotherCallback.calls.count(), 2);
+        expect(container.callback.calls.count(), 1);
+        expect(container.anotherCallback.calls.count(), 2);
 
-        _converse.once('connected', window.neverCalled);
-        _converse.off('connected', window.neverCalled);
+        _converse.once('connected', container.neverCalled);
+        _converse.off('connected', container.neverCalled);
 
         _converse.api.trigger('connected');
-        expect(window.callback.calls.count(), 1);
-        expect(window.anotherCallback.calls.count(), 3);
-        expect(window.neverCalled).not.toHaveBeenCalled();
+        expect(container.callback.calls.count(), 1);
+        expect(container.anotherCallback.calls.count(), 3);
+        expect(container.neverCalled).not.toHaveBeenCalled();
     }));
 });

+ 4 - 3
src/headless/tests/persistence.js

@@ -4,8 +4,9 @@ describe("The persistent store", function() {
 
     it("is unique to the user based on their JID",
             mock.initConverse([], {'persistent_store': 'IndexedDB'}, (_converse) => {
-
-        expect(_converse.storage.persistent.config().storeName).toBe(_converse.bare_jid);
-        expect(_converse.storage.persistent.config().description).toBe('indexedDB instance');
+        const { session, storage } = _converse;
+        const bare_jid = session.get('bare_jid');
+        expect(storage.persistent.config().storeName).toBe(bare_jid);
+        expect(storage.persistent.config().description).toBe('indexedDB instance');
     }));
 });

+ 2 - 2
src/headless/types/plugins/chat/message.d.ts

@@ -14,7 +14,7 @@ declare class Message extends ModelWithContact {
      */
     constructor(models?: Model[], options?: object);
     defaults(): {
-        msgid: any;
+        msgid: string;
         time: string;
         is_ephemeral: boolean;
     };
@@ -27,7 +27,7 @@ declare class Message extends ModelWithContact {
      * @method _converse.Message#setTimerForEphemeralMessage
      */
     setTimerForEphemeralMessage(): void;
-    ephemeral_timer: number;
+    ephemeral_timer: NodeJS.Timeout;
     checkValidity(): boolean;
     /**
      * Determines whether this messsage may be retracted by the current user.

+ 1 - 1
src/headless/types/plugins/chat/model.d.ts

@@ -81,7 +81,7 @@ declare class ChatBox extends ModelWithContact {
      * @param { string } state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
      */
     setChatState(state: string, options: any): ChatBox;
-    chat_state_timeout: number;
+    chat_state_timeout: NodeJS.Timeout;
     /**
      * Given an error `<message>` stanza's attributes, find the saved message model which is
      * referenced by that error.

+ 0 - 5
src/headless/types/utils/html.d.ts

@@ -9,11 +9,6 @@ export function isElement(el: any): boolean;
  * @returns { boolean }
  */
 export function isTagEqual(stanza: Element | typeof Strophe.Builder, name: string): boolean;
-/**
- * @param {HTMLElement} el
- * @param {boolean} include_margin
- */
-export function getOuterWidth(el: HTMLElement, include_margin?: boolean): number;
 /**
  * Converts an HTML string into a DOM element.
  * Expects that the HTML string has only one top-level element,

+ 0 - 6
src/headless/types/utils/index.d.ts

@@ -27,12 +27,10 @@ declare const _default: {
     getDefaultStore: typeof getDefaultStore;
     getLongestSubstring: typeof getLongestSubstring;
     getOpenPromise: any;
-    getOuterWidth: typeof getOuterWidth;
     getRandomInt: typeof getRandomInt;
     getSelectValues: typeof getSelectValues;
     getURI: typeof getURI;
     getUniqueId: typeof getUniqueId;
-    isAllowedProtocolForMedia: typeof isAllowedProtocolForMedia;
     isAudioURL: typeof isAudioURL;
     isElement: typeof isElement;
     isEmptyMessage: typeof isEmptyMessage;
@@ -60,7 +58,6 @@ declare const _default: {
     safeSave: typeof safeSave;
     shouldClearCache: typeof shouldClearCache;
     shouldCreateMessage: typeof shouldCreateMessage;
-    shouldRenderMediaFromURL: typeof shouldRenderMediaFromURL;
     stringToArrayBuffer: typeof stringToArrayBuffer;
     stringToElement: typeof stringToElement;
     toStanza: typeof toStanza;
@@ -78,10 +75,8 @@ import { createStore } from "./storage.js";
 import { getCurrentWord } from "./form.js";
 import { getDefaultStore } from "./storage.js";
 declare function getLongestSubstring(string: any, candidates: any): any;
-import { getOuterWidth } from "./html.js";
 import { getSelectValues } from "./form.js";
 import { getURI } from "./url.js";
-import { isAllowedProtocolForMedia } from "./url.js";
 import { isAudioURL } from "./url.js";
 import { isElement } from "./html.js";
 import { isError } from "./object.js";
@@ -112,7 +107,6 @@ import { queryChildren } from "./html.js";
 import { replaceCurrentWord } from "./form.js";
 import { shouldClearCache } from "./session.js";
 declare function shouldCreateMessage(attrs: any): any;
-import { shouldRenderMediaFromURL } from "./url.js";
 import { stringToArrayBuffer } from "./arraybuffer.js";
 import { stringToElement } from "./html.js";
 import { toStanza } from "strophe.js";

+ 0 - 18
src/headless/types/utils/url.d.ts

@@ -4,13 +4,6 @@
  * @returns {boolean}
  */
 export function isValidURL(text: string): boolean;
-/**
- * Given a url, check whether the protocol being used is allowed for rendering
- * the media in the chat (as opposed to just rendering a URL hyperlink).
- * @param {string} url
- * @returns {boolean}
- */
-export function isAllowedProtocolForMedia(url: string): boolean;
 /**
  * @param {string|URI} url
  */
@@ -25,22 +18,11 @@ export function getURI(url: string | URI): any;
  *  checkFileTypes(['.gif'], 'https://conversejs.org/cat.gif?foo=bar');
  */
 export function checkFileTypes(types: string[], url: string): boolean;
-export function isDomainWhitelisted(whitelist: any, url: any): any;
-export function shouldRenderMediaFromURL(url_text: any, type: any): any;
 export function filterQueryParamsFromURL(url: any): any;
-export function isDomainAllowed(url: any, setting: any): any;
-/**
- * Accepts a {@link MediaURLData} object and then checks whether its domain is
- * allowed for rendering in the chat.
- * @param {MediaURLData} o
- * @returns {boolean}
- */
-export function isMediaURLDomainAllowed(o: any): boolean;
 export function isURLWithImageExtension(url: any): boolean;
 export function isGIFURL(url: any): boolean;
 export function isAudioURL(url: any): boolean;
 export function isVideoURL(url: any): boolean;
 export function isImageURL(url: any): any;
 export function isEncryptedFileURL(url: any): any;
-export type MediaURLData = any;
 //# sourceMappingURL=url.d.ts.map

+ 3 - 3
src/headless/utils/arraybuffer.js

@@ -24,9 +24,9 @@ export function arrayBufferToBase64 (ab) {
 }
 
 export function base64ToArrayBuffer (b64) {
-    const binary_string =  window.atob(b64),
-          len = binary_string.length,
-          bytes = new Uint8Array(len);
+    const binary_string = atob(b64);
+    const len = binary_string.length;
+    const bytes = new Uint8Array(len);
 
     for (let i = 0; i < len; i++) {
         bytes[i] = binary_string.charCodeAt(i)

+ 0 - 15
src/headless/utils/html.js

@@ -26,21 +26,6 @@ export function isTagEqual (stanza, name) {
     }
 }
 
-/**
- * @param {HTMLElement} el
- * @param {boolean} include_margin
- */
-export function getOuterWidth (el, include_margin=false) {
-    let width = el.offsetWidth;
-    if (!include_margin) {
-        return width;
-    }
-    const style = window.getComputedStyle(el);
-    width += parseInt(style.marginLeft ? style.marginLeft : '0', 10) +
-             parseInt(style.marginRight ? style.marginRight : '0', 10);
-    return width;
-}
-
 /**
  * Converts an HTML string into a DOM element.
  * Expects that the HTML string has only one top-level element,

+ 4 - 12
src/headless/utils/index.js

@@ -7,7 +7,7 @@ import log, { LEVELS } from '../log.js';
 import { Model } from '@converse/skeletor';
 import { toStanza } from 'strophe.js';
 import { getOpenPromise } from '@converse/openpromise';
-import { shouldClearCache } from './session.js';
+import { shouldClearCache, isTestEnv, isUniView } from './session.js';
 import { merge, isError, isFunction } from './object.js';
 import { createStore, getDefaultStore } from './storage.js';
 import { waitUntil } from './promise.js';
@@ -22,7 +22,6 @@ import {
     webForm2xForm
 } from './form.js';
 import {
-    getOuterWidth,
     isElement,
     isTagEqual,
     queryChildren,
@@ -36,19 +35,15 @@ import {
     base64ToArrayBuffer,
 } from './arraybuffer.js';
 import {
+    checkFileTypes,
+    getURI,
     isAudioURL,
     isGIFURL,
-    isVideoURL,
     isImageURL,
     isURLWithImageExtension,
-    checkFileTypes,
-    getURI,
-    shouldRenderMediaFromURL,
-    isAllowedProtocolForMedia,
+    isVideoURL,
 } from './url.js';
 
-import { isTestEnv, isUniView } from './session.js';
-
 
 /**
  * The utils object
@@ -200,12 +195,10 @@ export default Object.assign({
     getDefaultStore,
     getLongestSubstring,
     getOpenPromise,
-    getOuterWidth,
     getRandomInt,
     getSelectValues,
     getURI,
     getUniqueId,
-    isAllowedProtocolForMedia,
     isAudioURL,
     isElement,
     isEmptyMessage,
@@ -233,7 +226,6 @@ export default Object.assign({
     safeSave,
     shouldClearCache,
     shouldCreateMessage,
-    shouldRenderMediaFromURL,
     stringToArrayBuffer,
     stringToElement,
     toStanza,

+ 0 - 68
src/headless/utils/url.js

@@ -1,6 +1,3 @@
-/**
- * @typedef {module:headless-shared-chat-utils.MediaURLData} MediaURLData
- */
 import URI from 'urijs';
 import log from '../log.js';
 import { settings_api } from '../shared/settings/api.js';
@@ -21,24 +18,6 @@ export function isValidURL (text) {
     }
 }
 
-/**
- * Given a url, check whether the protocol being used is allowed for rendering
- * the media in the chat (as opposed to just rendering a URL hyperlink).
- * @param {string} url
- * @returns {boolean}
- */
-export function isAllowedProtocolForMedia (url) {
-    const uri = getURI(url);
-    const { protocol } = window.location;
-    if (['chrome-extension:','file:'].includes(protocol)) {
-        return true;
-    }
-    return (
-        protocol === 'http:' ||
-        (protocol === 'https:' && ['https', 'aesgcm'].includes(uri.protocol().toLowerCase()))
-    );
-}
-
 /**
  * @param {string|URI} url
  */
@@ -69,28 +48,6 @@ export function checkFileTypes (types, url) {
     return !!types.filter(ext => filename.endsWith(ext)).length;
 }
 
-export function isDomainWhitelisted (whitelist, url) {
-    const uri = getURI(url);
-    const subdomain = uri.subdomain();
-    const domain = uri.domain();
-    const fulldomain = `${subdomain ? `${subdomain}.` : ''}${domain}`;
-    return whitelist.includes(domain) || whitelist.includes(fulldomain);
-}
-
-export function shouldRenderMediaFromURL (url_text, type) {
-    if (!isAllowedProtocolForMedia(url_text)) {
-        return false;
-    }
-    const may_render = settings.get('render_media');
-    const is_domain_allowed = isDomainAllowed(url_text, `allowed_${type}_domains`);
-
-    if (Array.isArray(may_render)) {
-        return is_domain_allowed && isDomainWhitelisted (may_render, url_text);
-    } else {
-        return is_domain_allowed && may_render;
-    }
-}
-
 export function filterQueryParamsFromURL (url) {
     const paramsArray = settings.get('filter_url_query_params');
     if (!paramsArray) return url;
@@ -98,31 +55,6 @@ export function filterQueryParamsFromURL (url) {
     return parsed_uri.removeQuery(paramsArray).toString();
 }
 
-export function isDomainAllowed (url, setting) {
-    const allowed_domains = settings.get(setting);
-    if (!Array.isArray(allowed_domains)) {
-        return true;
-    }
-    try {
-        return isDomainWhitelisted(allowed_domains, url);
-    } catch (error) {
-        log.debug(error);
-        return false;
-    }
-}
-
-/**
- * Accepts a {@link MediaURLData} object and then checks whether its domain is
- * allowed for rendering in the chat.
- * @param {MediaURLData} o
- * @returns {boolean}
- */
-export function isMediaURLDomainAllowed (o) {
-    return o.is_audio && isDomainAllowed(o.url, 'allowed_audio_domains') ||
-        o.is_video && isDomainAllowed(o.url, 'allowed_video_domains') ||
-        o.is_image && isDomainAllowed(o.url, 'allowed_image_domains');
-}
-
 export function isURLWithImageExtension (url) {
     return checkFileTypes(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.svg'], url);
 }

+ 1 - 1
src/plugins/chatview/chat.js

@@ -4,7 +4,7 @@ import BaseChatView from 'shared/chat/baseview.js';
 import tplChat from './templates/chat.js';
 import { __ } from 'i18n';
 import { _converse, api } from '@converse/headless';
-import { ACTIVE } from 'headless/shared/constants.js';
+import { ACTIVE } from '@converse/headless/shared/constants.js';
 
 /**
  * The view of an open/ongoing chat conversation.

+ 1 - 1
src/plugins/chatview/message-form.js

@@ -2,7 +2,7 @@
  * @typedef {import('shared/chat/emoji-dropdown.js').default} EmojiDropdown
  */
 import tplMessageForm from './templates/message-form.js';
-import { ACTIVE, COMPOSING } from 'headless/shared/constants.js';
+import { ACTIVE, COMPOSING } from '@converse/headless/shared/constants.js';
 import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless";

+ 3 - 2
src/plugins/chatview/tests/http-file-upload.js

@@ -187,16 +187,17 @@ describe("XEP-0363: HTTP File Upload", function () {
 
                 it("is uploaded and sent out", mock.initConverse(['chatBoxesFetched'], {} ,async (_converse) => {
                     const { api } = _converse;
+                    const domain = _converse.session.get('domain');
                     const base_url = 'https://conversejs.org';
                     await mock.waitUntilDiscoConfirmed(
-                        _converse, _converse.domain,
+                        _converse, domain,
                         [{'category': 'server', 'type':'IM'}],
                         ['http://jabber.org/protocol/disco#items'], [], 'info');
 
                     const send_backup = XMLHttpRequest.prototype.send;
                     const IQ_stanzas = api.connection.get().IQ_stanzas;
 
-                    await mock.waitUntilDiscoConfirmed(_converse, _converse.domain, [], [], ['upload.montague.tld'], 'items');
+                    await mock.waitUntilDiscoConfirmed(_converse, domain, [], [], ['upload.montague.tld'], 'items');
                     await mock.waitUntilDiscoConfirmed(_converse, 'upload.montague.tld', [], [Strophe.NS.HTTPUPLOAD], []);
                     await mock.waitForRoster(_converse, 'current');
                     const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';

+ 4 - 5
src/plugins/minimize/utils.js

@@ -6,12 +6,11 @@
  * @typedef {import('plugins/controlbox/controlbox').default} ControlBoxView
  * @typedef {import('plugins/headlines-view/view').default} HeadlinesFeedView
  */
-import { _converse, api, converse } from '@converse/headless';
+import { _converse, api, converse, u } from '@converse/headless';
 import { __ } from 'i18n';
-import { isTestEnv } from '@converse/headless/utils/session.js';
-import {ACTIVE, INACTIVE} from 'headless/shared/constants';
+import { ACTIVE, INACTIVE } from '@converse/headless/shared/constants';
 
-const { dayjs, u } = converse.env;
+const { dayjs } = converse.env;
 
 /**
  * @param { ChatBox|MUC } chat
@@ -73,7 +72,7 @@ function getBoxesWidth (newchat) {
  * @param { ChatView|MUCView|ControlBoxView|HeadlinesFeedView } [newchat]
  */
 export function trimChats (newchat) {
-    if (isTestEnv() || api.settings.get('no_trimming') || api.settings.get("view_mode") !== 'overlayed') {
+    if (u.isTestEnv() || api.settings.get('no_trimming') || api.settings.get("view_mode") !== 'overlayed') {
         return;
     }
     const shown_chats = getShownChats();

+ 1 - 1
src/plugins/omemo/utils.js

@@ -27,7 +27,7 @@ import {
     hexToArrayBuffer,
     stringToArrayBuffer
 } from '@converse/headless/utils/arraybuffer.js';
-import MUC from 'headless/plugins/muc/muc.js';
+import MUC from '@converse/headless/plugins/muc/muc.js';
 import {IQError, UserFacingError} from 'shared/errors.js';
 import DeviceLists from './devicelists.js';
 

+ 1 - 1
src/plugins/roomslist/view.js

@@ -9,7 +9,7 @@ import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless";
 import { initStorage } from '@converse/headless/utils/storage.js';
 import { isChatRoom } from '@converse/headless/plugins/muc/utils.js';
-import { CLOSED, OPENED } from 'headless/shared/constants.js';
+import { CLOSED, OPENED } from '@converse/headless/shared/constants.js';
 
 const { Strophe, u } = converse.env;
 

+ 1 - 1
src/shared/chat/message-actions.js

@@ -4,7 +4,7 @@ import { api, converse, log, _converse } from '@converse/headless';
 import { getMediaURLs } from '@converse/headless/shared/chat/utils.js';
 import { CHATROOMS_TYPE } from '@converse/headless/shared/constants';
 import { html } from 'lit';
-import { isMediaURLDomainAllowed, isDomainWhitelisted } from '@converse/headless/utils/url.js';
+import { isMediaURLDomainAllowed, isDomainWhitelisted } from 'utils/url.js';
 import { until } from 'lit/directives/until.js';
 
 import './styles/message-actions.scss';

+ 1 - 1
src/shared/chat/message.js

@@ -13,7 +13,7 @@ import tplMessageText from './templates/message-text.js';
 import tplRetraction from './templates/retraction.js';
 import tplSpinner from 'templates/spinner.js';
 import { CustomElement } from 'shared/components/element.js';
-import { SUCCESS } from 'headless/shared/constants.js';
+import { SUCCESS } from '@converse/headless/shared/constants.js';
 import { __ } from 'i18n';
 import { api, converse, log } from  '@converse/headless';
 import { getHats } from './utils.js';

+ 1 - 1
src/shared/chat/templates/message.js

@@ -2,7 +2,7 @@ import 'shared/avatar/avatar.js';
 import 'shared/chat/unfurl.js';
 import { __ } from 'i18n';
 import { html } from "lit";
-import { shouldRenderMediaFromURL } from '@converse/headless/utils/url.js';
+import { shouldRenderMediaFromURL } from 'utils/url';
 
 
 export default (el, o) => {

+ 5 - 1
src/shared/chat/templates/unfurl.js

@@ -1,7 +1,11 @@
 import 'shared/components/image.js';
-import { getURI, isGIFURL, isDomainAllowed } from '@converse/headless/utils/url.js';
+import { getURI, isGIFURL } from '@converse/headless/utils/url.js';
+import { isDomainAllowed } from 'utils/url.js';
 import { html } from 'lit';
 
+/**
+ * @param {string} url
+ */
 function isValidURL (url) {
     // We don't consider relative URLs as valid
     return !!getURI(url).host();

+ 2 - 1
src/shared/components/image.js

@@ -2,7 +2,8 @@ import tplGif from 'templates/gif.js';
 import tplImage from 'templates/image.js';
 import { CustomElement } from './element.js';
 import { api } from "@converse/headless";
-import { filterQueryParamsFromURL, isGIFURL, shouldRenderMediaFromURL } from '@converse/headless/utils/url.js';
+import { filterQueryParamsFromURL, isGIFURL } from '@converse/headless/utils/url.js';
+import { shouldRenderMediaFromURL } from 'utils/url.js';
 
 
 export default class Image extends CustomElement {

+ 1 - 1
src/shared/rich-text.js

@@ -21,8 +21,8 @@ import {
     isGIFURL,
     isImageURL,
     isVideoURL,
-    shouldRenderMediaFromURL,
 } from '@converse/headless/utils/url.js';
+import { shouldRenderMediaFromURL } from 'utils/url.js';
 
 
 /**

+ 1 - 1
src/types/headless/plugins/chat/message.d.ts

@@ -27,7 +27,7 @@ declare class Message extends ModelWithContact {
      * @method _converse.Message#setTimerForEphemeralMessage
      */
     setTimerForEphemeralMessage(): void;
-    ephemeral_timer: number;
+    ephemeral_timer: NodeJS.Timeout;
     checkValidity(): boolean;
     /**
      * Determines whether this messsage may be retracted by the current user.

+ 1 - 1
src/types/headless/plugins/chat/model.d.ts

@@ -81,7 +81,7 @@ declare class ChatBox extends ModelWithContact {
      * @param { string } state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE)
      */
     setChatState(state: string, options: any): ChatBox;
-    chat_state_timeout: number;
+    chat_state_timeout: NodeJS.Timeout;
     /**
      * Given an error `<message>` stanza's attributes, find the saved message model which is
      * referenced by that error.

+ 0 - 5
src/types/headless/utils/html.d.ts

@@ -9,11 +9,6 @@ export function isElement(el: any): boolean;
  * @returns { boolean }
  */
 export function isTagEqual(stanza: Element | typeof Strophe.Builder, name: string): boolean;
-/**
- * @param {HTMLElement} el
- * @param {boolean} include_margin
- */
-export function getOuterWidth(el: HTMLElement, include_margin?: boolean): number;
 /**
  * Converts an HTML string into a DOM element.
  * Expects that the HTML string has only one top-level element,

+ 0 - 6
src/types/headless/utils/index.d.ts

@@ -27,12 +27,10 @@ declare const _default: {
     getDefaultStore: typeof getDefaultStore;
     getLongestSubstring: typeof getLongestSubstring;
     getOpenPromise: any;
-    getOuterWidth: typeof getOuterWidth;
     getRandomInt: typeof getRandomInt;
     getSelectValues: typeof getSelectValues;
     getURI: typeof getURI;
     getUniqueId: typeof getUniqueId;
-    isAllowedProtocolForMedia: typeof isAllowedProtocolForMedia;
     isAudioURL: typeof isAudioURL;
     isElement: typeof isElement;
     isEmptyMessage: typeof isEmptyMessage;
@@ -60,7 +58,6 @@ declare const _default: {
     safeSave: typeof safeSave;
     shouldClearCache: typeof shouldClearCache;
     shouldCreateMessage: typeof shouldCreateMessage;
-    shouldRenderMediaFromURL: typeof shouldRenderMediaFromURL;
     stringToArrayBuffer: typeof stringToArrayBuffer;
     stringToElement: typeof stringToElement;
     toStanza: typeof toStanza;
@@ -78,10 +75,8 @@ import { createStore } from "./storage.js";
 import { getCurrentWord } from "./form.js";
 import { getDefaultStore } from "./storage.js";
 declare function getLongestSubstring(string: any, candidates: any): any;
-import { getOuterWidth } from "./html.js";
 import { getSelectValues } from "./form.js";
 import { getURI } from "./url.js";
-import { isAllowedProtocolForMedia } from "./url.js";
 import { isAudioURL } from "./url.js";
 import { isElement } from "./html.js";
 import { isError } from "./object.js";
@@ -112,7 +107,6 @@ import { queryChildren } from "./html.js";
 import { replaceCurrentWord } from "./form.js";
 import { shouldClearCache } from "./session.js";
 declare function shouldCreateMessage(attrs: any): any;
-import { shouldRenderMediaFromURL } from "./url.js";
 import { stringToArrayBuffer } from "./arraybuffer.js";
 import { stringToElement } from "./html.js";
 import { toStanza } from "strophe.js";

+ 0 - 8
src/types/headless/utils/url.d.ts

@@ -4,13 +4,6 @@
  * @returns {boolean}
  */
 export function isValidURL(text: string): boolean;
-/**
- * Given a url, check whether the protocol being used is allowed for rendering
- * the media in the chat (as opposed to just rendering a URL hyperlink).
- * @param {string} url
- * @returns {boolean}
- */
-export function isAllowedProtocolForMedia(url: string): boolean;
 /**
  * @param {string|URI} url
  */
@@ -26,7 +19,6 @@ export function getURI(url: string | URI): any;
  */
 export function checkFileTypes(types: string[], url: string): boolean;
 export function isDomainWhitelisted(whitelist: any, url: any): any;
-export function shouldRenderMediaFromURL(url_text: any, type: any): any;
 export function filterQueryParamsFromURL(url: any): any;
 export function isDomainAllowed(url: any, setting: any): any;
 /**

+ 1 - 1
src/types/plugins/controlbox/model.d.ts

@@ -14,7 +14,7 @@ declare class ControlBox extends Model {
         closed: boolean;
         num_unread: number;
         time_opened: any;
-        type: string;
+        type: any;
         url: string;
     };
     validate(attrs: any): any;

+ 1 - 2
src/types/plugins/muc-views/sidebar.d.ts

@@ -6,7 +6,7 @@ export default class MUCSidebar extends CustomElement {
     };
     jid: any;
     initialize(): void;
-    filter: RosterFilter;
+    filter: any;
     model: any;
     render(): import("lit-html").TemplateResult<1>;
     /** @param {MouseEvent} ev */
@@ -17,5 +17,4 @@ export default class MUCSidebar extends CustomElement {
     onOccupantClicked(ev: MouseEvent): void;
 }
 import { CustomElement } from "shared/components/element.js";
-import { RosterFilter } from "@converse/headless/plugins/roster/filter.js";
 //# sourceMappingURL=sidebar.d.ts.map

+ 4 - 4
src/types/plugins/omemo/utils.d.ts

@@ -61,16 +61,16 @@ export type MUCMessageAttributes = any;
 export type ChatBox = import('@converse/headless').ChatBox;
 import { IQError } from "shared/errors.js";
 import { UserFacingError } from "shared/errors.js";
-declare function decryptMessage(obj: any): Promise<string>;
+declare function decryptMessage(obj: any): Promise<any>;
 /**
  * @param {string} plaintext
  */
 declare function encryptMessage(plaintext: string): Promise<{
     key: ArrayBuffer;
     tag: ArrayBuffer;
-    key_and_tag: ArrayBufferLike;
-    payload: string;
-    iv: string;
+    key_and_tag: any;
+    payload: any;
+    iv: any;
 }>;
 export {};
 //# sourceMappingURL=utils.d.ts.map

+ 1 - 1
src/types/plugins/roomslist/model.d.ts

@@ -3,7 +3,7 @@ declare class RoomsListModel extends Model {
     defaults(): {
         muc_domain: any;
         nick: any;
-        toggle_state: string;
+        toggle_state: any;
         collapsed_domains: any[];
     };
     /**

+ 6 - 1
src/types/utils/html.d.ts

@@ -70,10 +70,15 @@ export function slideIn(el: HTMLElement, duration?: number): Promise<any>;
  * @returns {TemplateResult}
  */
 export function xForm2TemplateResult(field: HTMLElement, stanza: Element, options?: any): TemplateResult;
+/**
+ * @param {HTMLElement} el
+ * @param {boolean} include_margin
+ */
+export function getOuterWidth(el: HTMLElement, include_margin?: boolean): number;
 export default u;
 export type TemplateResult = import('lit').TemplateResult;
 export namespace Strophe {
     type Builder = any;
 }
-import u from "../headless/utils/index.js";
+import { u } from "@converse/headless";
 //# sourceMappingURL=html.d.ts.map

+ 16 - 0
src/types/utils/url.d.ts

@@ -0,0 +1,16 @@
+export function isDomainWhitelisted(whitelist: any, url: any): any;
+export function isDomainAllowed(url: any, setting: any): any;
+/**
+ * Accepts a {@link MediaURLData} object and then checks whether its domain is
+ * allowed for rendering in the chat.
+ * @param {MediaURLData} o
+ * @returns {boolean}
+ */
+export function isMediaURLDomainAllowed(o: any): boolean;
+/**
+ * @param {string} url_text
+ * @param {"audio"|"image"|"video"} type
+ */
+export function shouldRenderMediaFromURL(url_text: string, type: "audio" | "image" | "video"): any;
+export type MediaURLData = any;
+//# sourceMappingURL=url.d.ts.map

+ 17 - 2
src/utils/html.js

@@ -17,8 +17,7 @@ import tplFormUrl from '../templates/form_url.js';
 import tplFormUsername from '../templates/form_username.js';
 import tplHyperlink from 'templates/hyperlink.js';
 import tplVideo from 'templates/video.js';
-import u from '../headless/utils/index.js';
-import { api, converse, log } from '@converse/headless';
+import { api, converse, log, u } from '@converse/headless';
 import { getURI, isAudioURL, isImageURL, isVideoURL, isValidURL } from '@converse/headless/utils/url.js';
 import { render } from 'lit';
 import { queryChildren } from '@converse/headless/utils/html.js';
@@ -575,6 +574,21 @@ export function xForm2TemplateResult (field, stanza, options={}) {
     }
 }
 
+/**
+ * @param {HTMLElement} el
+ * @param {boolean} include_margin
+ */
+export function getOuterWidth (el, include_margin=false) {
+    let width = el.offsetWidth;
+    if (!include_margin) {
+        return width;
+    }
+    const style = window.getComputedStyle(el);
+    width += parseInt(style.marginLeft ? style.marginLeft : '0', 10) +
+             parseInt(style.marginRight ? style.marginRight : '0', 10);
+    return width;
+}
+
 Object.assign(u, {
     addClass,
     ancestor,
@@ -583,6 +597,7 @@ Object.assign(u, {
     getElementFromTemplateResult,
     getNextElement,
     getOOBURLMarkup,
+    getOuterWidth,
     hasClass,
     hideElement,
     isEqualNode,

+ 75 - 0
src/utils/url.js

@@ -0,0 +1,75 @@
+/**
+ * @typedef {module:headless-shared-chat-utils.MediaURLData} MediaURLData
+ */
+import { api, log, u } from '@converse/headless';
+
+const { getURI } = u;
+
+export function isDomainWhitelisted (whitelist, url) {
+    const uri = getURI(url);
+    const subdomain = uri.subdomain();
+    const domain = uri.domain();
+    const fulldomain = `${subdomain ? `${subdomain}.` : ''}${domain}`;
+    return whitelist.includes(domain) || whitelist.includes(fulldomain);
+}
+
+export function isDomainAllowed (url, setting) {
+    const allowed_domains = api.settings.get(setting);
+    if (!Array.isArray(allowed_domains)) {
+        return true;
+    }
+    try {
+        return isDomainWhitelisted(allowed_domains, url);
+    } catch (error) {
+        log.debug(error);
+        return false;
+    }
+}
+
+/**
+ * Accepts a {@link MediaURLData} object and then checks whether its domain is
+ * allowed for rendering in the chat.
+ * @param {MediaURLData} o
+ * @returns {boolean}
+ */
+export function isMediaURLDomainAllowed (o) {
+    return o.is_audio && isDomainAllowed(o.url, 'allowed_audio_domains') ||
+        o.is_video && isDomainAllowed(o.url, 'allowed_video_domains') ||
+        o.is_image && isDomainAllowed(o.url, 'allowed_image_domains');
+}
+
+/**
+ * Given a url, check whether the protocol being used is allowed for rendering
+ * the media in the chat (as opposed to just rendering a URL hyperlink).
+ * @param {string} url
+ * @returns {boolean}
+ */
+function isAllowedProtocolForMedia (url) {
+    const uri = getURI(url);
+    const { protocol } = window.location;
+    if (['chrome-extension:','file:'].includes(protocol)) {
+        return true;
+    }
+    return (
+        protocol === 'http:' ||
+        (protocol === 'https:' && ['https', 'aesgcm'].includes(uri.protocol().toLowerCase()))
+    );
+}
+
+/**
+ * @param {string} url_text
+ * @param {"audio"|"image"|"video"} type
+ */
+export function shouldRenderMediaFromURL (url_text, type) {
+    if (!isAllowedProtocolForMedia(url_text)) {
+        return false;
+    }
+    const may_render = api.settings.get('render_media');
+    const is_domain_allowed = isDomainAllowed(url_text, `allowed_${type}_domains`);
+
+    if (Array.isArray(may_render)) {
+        return is_domain_allowed && isDomainWhitelisted (may_render, url_text);
+    } else {
+        return is_domain_allowed && may_render;
+    }
+}