Skip to content

Commit c66c56f

Browse files
committed
Use optional npm dependencies to shim DOMParser and WebSocket in node
1 parent ea6126b commit c66c56f

File tree

6 files changed

+153
-60
lines changed

6 files changed

+153
-60
lines changed

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,9 @@
7373
"sinon-qunit": "~2.0.0",
7474
"yarpm": "^0.2.1"
7575
},
76-
"dependencies": {}
76+
"dependencies": {},
77+
"optionalDependencies": {
78+
"ws": "^7.0.0",
79+
"xmldom": "^0.1.27"
80+
}
7781
}

src/bosh.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
/* global window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject */
99

1010
import core from './core';
11+
import { DOMParser } from './shims'
1112

1213
const Strophe = core.Strophe;
1314
const $build = core.$build;

src/core.js

Lines changed: 5 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import MD5 from './md5';
1010
import SHA1 from './sha1';
1111
import utils from './utils';
12+
import * as shims from './shims';
1213

1314
/** Function: $build
1415
* Create a Strophe.Builder.
@@ -330,26 +331,6 @@ const Strophe = {
330331
*/
331332
_xmlGenerator: null,
332333

333-
/** PrivateFunction: _makeGenerator
334-
* _Private_ function that creates a dummy XML DOM document to serve as
335-
* an element and text node generator.
336-
*/
337-
_makeGenerator: function () {
338-
let doc;
339-
// IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload.
340-
// Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be
341-
// less than 10 in the case of IE9 and below.
342-
if (document.implementation.createDocument === undefined ||
343-
document.implementation.createDocument && document.documentMode && document.documentMode < 10) {
344-
doc = this._getIEXmlDom();
345-
doc.appendChild(doc.createElement('strophe'));
346-
} else {
347-
doc = document.implementation
348-
.createDocument('jabber:client', 'strophe', null);
349-
}
350-
return doc;
351-
},
352-
353334
/** Function: xmlGenerator
354335
* Get the DOM document to generate elements.
355336
*
@@ -358,45 +339,11 @@ const Strophe = {
358339
*/
359340
xmlGenerator: function () {
360341
if (!Strophe._xmlGenerator) {
361-
Strophe._xmlGenerator = Strophe._makeGenerator();
342+
Strophe._xmlGenerator = shims.getDummyXMLDOMDocument()
362343
}
363344
return Strophe._xmlGenerator;
364345
},
365346

366-
/** PrivateFunction: _getIEXmlDom
367-
* Gets IE xml doc object
368-
*
369-
* Returns:
370-
* A Microsoft XML DOM Object
371-
* See Also:
372-
* http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx
373-
*/
374-
_getIEXmlDom : function() {
375-
let doc = null;
376-
const docStrings = [
377-
"Msxml2.DOMDocument.6.0",
378-
"Msxml2.DOMDocument.5.0",
379-
"Msxml2.DOMDocument.4.0",
380-
"MSXML2.DOMDocument.3.0",
381-
"MSXML2.DOMDocument",
382-
"MSXML.DOMDocument",
383-
"Microsoft.XMLDOM"
384-
];
385-
386-
for (let d=0; d<docStrings.length; d++) {
387-
if (doc === null) {
388-
try {
389-
doc = new ActiveXObject(docStrings[d]);
390-
} catch (e) {
391-
doc = null;
392-
}
393-
} else {
394-
break;
395-
}
396-
}
397-
return doc;
398-
},
399-
400347
/** Function: xmlElement
401348
* Create an XML DOM element.
402349
*
@@ -512,8 +459,8 @@ const Strophe = {
512459
xmlHtmlNode: function (html) {
513460
let node;
514461
//ensure text is escaped
515-
if (DOMParser) {
516-
const parser = new DOMParser();
462+
if (shims.DOMParser) {
463+
const parser = new shims.DOMParser();
517464
node = parser.parseFromString(html, "text/xml");
518465
} else {
519466
node = new ActiveXObject("Microsoft.XMLDOM");
@@ -1160,7 +1107,7 @@ Strophe.Builder.prototype = {
11601107
* The Strophe.Builder object.
11611108
*/
11621109
h: function (html) {
1163-
const fragment = document.createElement('body');
1110+
const fragment = Strophe.xmlGenerator().createElement('body');
11641111
// force the browser to try and fix any invalid HTML tags
11651112
fragment.innerHTML = html;
11661113
// copy cleaned html into an xml dom
@@ -3505,7 +3452,6 @@ Strophe.SASLOAuthBearer.prototype = new Strophe.SASLMechanism("OAUTHBEARER", tru
35053452
Strophe.SASLOAuthBearer.prototype.test = function(connection) {
35063453
return connection.pass !== null;
35073454
};
3508-
35093455
Strophe.SASLOAuthBearer.prototype.onChallenge = function(connection) {
35103456
let auth_str = 'n,';
35113457
if (connection.authcid !== null) {

src/shims.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* This module provides uniform
3+
* Shims APIs and globals that are not present in all JS environments,
4+
* the most common example for Strophe being browser APIs like WebSocket
5+
* and DOM that don't exist under nodejs.
6+
*
7+
* Usually these will be supplied in nodejs by conditionally requiring a
8+
* NPM module that provides a compatible implementation.
9+
*/
10+
11+
/**
12+
* WHATWG WebSockets API
13+
* https://www.w3.org/TR/websockets/
14+
*
15+
* Interface to use the web socket protocol
16+
*
17+
* Used implementations:
18+
* - supported browsers: built-in in WebSocket global
19+
* https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Browser_compatibility
20+
* - nodejs: use standard-compliant 'ws' module
21+
* https://www.npmjs.com/package/ws
22+
*/
23+
function getWebSocketImplementation () {
24+
let WebSocketImplementation = WebSocket
25+
if (typeof WebSocketImplementation === 'undefined') {
26+
try {
27+
WebSocketImplementation = require('ws');
28+
} catch (err) {
29+
throw new Error('You must install the "ws" package to use Strophe in nodejs.');
30+
}
31+
}
32+
return WebSocketImplementation
33+
}
34+
export const WebSocket = getWebSocketImplementation()
35+
36+
/**
37+
* DOMParser
38+
* https://w3c.github.io/DOM-Parsing/#the-domparser-interface
39+
*
40+
* Interface to parse XML strings into Document objects
41+
*
42+
* Used implementations:
43+
* - supported browsers: built-in in DOMParser global
44+
* https://developer.mozilla.org/en-US/docs/Web/API/DOMParser#Browser_compatibility
45+
* - nodejs: use 'xmldom' module
46+
* https://www.npmjs.com/package/xmldom
47+
*/
48+
function getDOMParserImplementation () {
49+
let DOMParserImplementation = DOMParser
50+
if (typeof DOMParserImplementation === 'undefined') {
51+
try {
52+
DOMParserImplementation = require('xmldom').DOMParser;
53+
} catch (err) {
54+
throw new Error('You must install the "xmldom" package to use Strophe in nodejs.');
55+
}
56+
}
57+
return DOMParserImplementation
58+
}
59+
export const DOMParser = getDOMParserImplementation()
60+
61+
/**
62+
* Gets IE xml doc object. Used by getDummyXMLDocument shim.
63+
*
64+
* Returns:
65+
* A Microsoft XML DOM Object
66+
* See Also:
67+
* http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx
68+
*/
69+
function _getIEXmlDom () {
70+
const docStrings = [
71+
"Msxml2.DOMDocument.6.0",
72+
"Msxml2.DOMDocument.5.0",
73+
"Msxml2.DOMDocument.4.0",
74+
"MSXML2.DOMDocument.3.0",
75+
"MSXML2.DOMDocument",
76+
"MSXML.DOMDocument",
77+
"Microsoft.XMLDOM"
78+
];
79+
for (let d = 0; d < docStrings.length; d++) {
80+
try {
81+
const doc = new ActiveXObject(docStrings[d]);
82+
return doc
83+
} catch (e) {
84+
// Try next one
85+
}
86+
}
87+
}
88+
89+
/**
90+
* Creates a dummy XML DOM document to serve as an element and text node generator.
91+
*
92+
* Used implementations:
93+
* - IE < 10: avoid using createDocument() due to a memory leak, use ie-specific
94+
* workaround
95+
* - other supported browsers: use document's createDocument
96+
* - nodejs: use 'xmldom'
97+
*/
98+
export function getDummyXMLDOMDocument () {
99+
// nodejs
100+
if (typeof document === 'undefined') {
101+
try {
102+
const DOMImplementation = require('xmldom').DOMImplementation;
103+
return new DOMImplementation().createDocument('jabber:client', 'strophe', null);
104+
} catch (err) {
105+
throw new Error('You must install the "xmldom" package to use Strophe in nodejs.');
106+
}
107+
}
108+
// IE < 10
109+
if (
110+
document.implementation.createDocument === undefined ||
111+
document.implementation.createDocument && document.documentMode && document.documentMode < 10
112+
) {
113+
const doc = _getIEXmlDom();
114+
doc.appendChild(doc.createElement('strophe'));
115+
return doc
116+
}
117+
// All other supported browsers
118+
return document.implementation.createDocument('jabber:client', 'strophe', null)
119+
}
120+

src/websocket.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
/* global window, clearTimeout, WebSocket, DOMParser */
99

1010
import core from './core';
11+
import { WebSocket, DOMParser } from './shims';
1112

1213
const Strophe = core.Strophe;
1314
const $build = core.$build;

0 commit comments

Comments
 (0)