Monkeypatch XMLHttpRequest so http uploads work without CORS headers

This commit is contained in:
Travis Burtrum 2022-10-06 00:58:29 -04:00
parent 799cd242f8
commit 2070696639
2 changed files with 223 additions and 92 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
.idea .idea
target target
*.map
*.tgz

224
dist/index.html vendored
View File

@ -20,106 +20,234 @@
<noscript>You need to enable JavaScript to run the Converse.js chat app.</noscript> <noscript>You need to enable JavaScript to run the Converse.js chat app.</noscript>
<div id="conversejs-bg"></div> <div id="conversejs-bg"></div>
<script> <script>
// access the pre-bundled global API functions // access the pre-bundled global API functions
const { invoke } = window.__TAURI__.tauri; const { invoke } = window.__TAURI__.tauri;
const { getClient, ResponseType } = window.__TAURI__.http; const { getClient, ResponseType, Body } = window.__TAURI__.http;
const tauriFetch = window.__TAURI__.http.fetch; const tauriFetch = window.__TAURI__.http.fetch;
const realFetch = window.fetch; const realFetch = window.fetch;
// this is clearly not a complete fetch implementation // this is clearly not a complete fetch implementation
// but it's enough for the omemo downloadFile() function... // but it's enough for the omemo downloadFile() function...
window.fetch = async function(url) { window.fetch = async function (url) {
console.log("fetching url: " + url); console.log("fetching url: " + url);
const client = await getClient(); const client = await getClient();
let response = await await client.get( let response = await await client.get(url, {
url, {
timeout: 30, timeout: 30,
responseType: ResponseType.Binary responseType: ResponseType.Binary,
}); });
let data = new Uint8Array(response.data).buffer; let data = new Uint8Array(response.data).buffer;
response.arrayBuffer = async function() { response.arrayBuffer = async function () {
return data; return data;
}; };
return response; return response;
}; };
function addCredentials (login, password) { // this is clearly not a complete XMLHttpRequest implementation
localStorage.setItem('login', login); // but it's enough for http upload and gif download
localStorage.setItem('password', password); const realXMLHttpRequest = window.XMLHttpRequest;
function fXMLHttpRequest() {
//this._xhr = new realXMLHttpRequest();
this.upload = {};
this.upload.addEventListener = function (type, listener, useCapture) {
console.log("fake XMLHttpRequest.upload.addEventListener()");
};
}
// Constructor
function cXMLHttpRequest() {
return new fXMLHttpRequest();
}
cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
cXMLHttpRequest.DONE = 4;
cXMLHttpRequest.prototype.open = function (sMethod, sUrl, bAsync, sUser, sPassword) {
this._method = sMethod;
this._url = sUrl;
this._async = bAsync;
// Delete headers, required when object is reused
delete this._headers;
};
cXMLHttpRequest.prototype.setRequestHeader = function (sName, sValue) {
if (!this._headers) {
this._headers = {};
} }
function getCredentials () { this._headers[sName] = sValue;
const credentials = {} };
credentials.login = localStorage.getItem('login') || '';
cXMLHttpRequest.prototype.overrideMimeType = function (mimeType) {
this._mimeType = mimeType;
};
cXMLHttpRequest.prototype.send = function (vData) {
const realThis = this;
/*
const xhr = new realXMLHttpRequest();
//xhr.onreadystatechange = this.onreadystatechange;
xhr.onreadystatechange = async () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
realThis.status = xhr.status;
realThis.readyState = xhr.readyState;
realThis.responseText = xhr.responseText;
realThis.response = xhr.response;
//console.log("xhr status: " + realThis.status}`);
console.log(`xhr status: ${realThis.status}`);
console.log(`xhr readyState: ${realThis.readyState}`);
console.log(`xhr responseText: ${realThis.responseText}`);
console.log(`xhr response: ${realThis.response}`);
if (realThis.onreadystatechange !== undefined) {
realThis.onreadystatechange();
}
if (realThis.onload !== undefined) {
realThis.onload();
}
}
};
console.log(`open(${this._method}, ${this._url}, ${this._async})`);
xhr.open(this._method, this._url, this._async);
if (this._mimeType !== undefined) {
console.log(`overrideMimeType(${this._mimeType})`);
xhr.overrideMimeType(this._mimeType);
}
for (const key in realThis._headers) {
console.log(`setRequestHeader(${key}: ${realThis._headers[key]})`);
xhr.setRequestHeader(key, realThis._headers[key]);
}
console.log(`send(${vData})`);
xhr.send(vData);
*/
(async function () {
let response = null;
if (realThis._method == "PUT") {
const filePromise = new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e) {
resolve(reader.result);
};
reader.readAsArrayBuffer(vData);
});
let buffer = await filePromise;
const client = await getClient();
response = await await client.put(realThis._url, Body.bytes(buffer), {
timeout: 30,
responseType: ResponseType.Text,
headers: realThis._headers,
});
} else if (realThis._method == "GET") {
const client = await getClient();
response = await await client.get(realThis._url, {
timeout: 30,
responseType: ResponseType.Text,
headers: realThis._headers,
});
}
realThis.status = response.status;
realThis.readyState = XMLHttpRequest.DONE;
realThis.responseText = response.data;
realThis.response = response.data;
if (realThis.onreadystatechange !== undefined) {
realThis.onreadystatechange();
}
if (realThis.onload !== undefined) {
realThis.onload();
}
})();
};
window.XMLHttpRequest = cXMLHttpRequest;
function addCredentials(login, password) {
localStorage.setItem("login", login);
localStorage.setItem("password", password);
}
function getCredentials() {
const credentials = {};
credentials.login = localStorage.getItem("login") || "";
if (credentials.login) { if (credentials.login) {
credentials.password = localStorage.getItem('password'); credentials.password = localStorage.getItem("password");
} }
return credentials; return credentials;
} }
function removeCredentials () { function removeCredentials() {
localStorage.removeItem('login'); localStorage.removeItem("login");
localStorage.removeItem('password'); localStorage.removeItem("password");
} }
converse.plugins.add('converse-desktop-credentials', { converse.plugins.add("converse-desktop-credentials", {
initialize () { initialize() {
console.log("converse-desktop-credentials - initialize"); console.log("converse-desktop-credentials - initialize");
const { _converse } = this; const { _converse } = this;
const { api } = _converse; const { api } = _converse;
api.listen.on('afterResourceBinding', () => { api.listen.on("afterResourceBinding", () => {
console.log("converse-desktop-credentials - afterResourceBinding"); console.log("converse-desktop-credentials - afterResourceBinding");
if (_converse.connection.pass) { if (_converse.connection.pass) {
addCredentials( addCredentials(_converse.bare_jid, _converse.connection.pass);
_converse.bare_jid,
_converse.connection.pass
);
} }
}); });
api.listen.on('logout', () => { api.listen.on("logout", () => {
console.log("converse-desktop-credentials - logout"); console.log("converse-desktop-credentials - logout");
removeCredentials(); removeCredentials();
}); });
} },
}); });
const { login, password } = getCredentials(); const { login, password } = getCredentials();
console.log('login: ' + login); console.log("login: " + login);
invoke('proxy_port').then((port) => { invoke("proxy_port")
console.log('proxy_port: ' + port); .then((port) => {
console.log("proxy_port: " + port);
converse.initialize({ converse
.initialize({
auto_login: login && password, auto_login: login && password,
jid: login, jid: login,
password: password, password: password,
authentication: 'login', authentication: "login",
auto_away: 300, auto_away: 300,
auto_reconnect: true, auto_reconnect: true,
websocket_url: 'ws://127.0.0.1:' + port + '/xmpp-websocket/', //websocket_url: 'ws://127.0.0.1:' + port + '/xmpp-websocket/',
assets_path: './dist/', websocket_url: "wss://burtrum.org/xmpp-websocket/",
assets_path: "./dist/",
discover_connection_methods: false, discover_connection_methods: false,
message_archiving: 'always', message_archiving: "always",
play_sounds: false, play_sounds: false,
view_mode: 'fullscreen', view_mode: "fullscreen",
//loglevel: 'debug', //loglevel: 'debug',
muc_respect_autojoin: true, muc_respect_autojoin: true,
muc_show_logs_before_join: true, muc_show_logs_before_join: true,
whitelisted_plugins: ['converse-desktop-credentials'] whitelisted_plugins: ["converse-desktop-credentials"],
}).catch((reason) => { })
.catch((reason) => {
document.body.innerText = "error starting converse: " + reason; document.body.innerText = "error starting converse: " + reason;
console.log(document.body.innerText); console.log(document.body.innerText);
}); });
}).catch((reason) => { })
.catch((reason) => {
document.body.innerText = "error starting proxy: " + reason; document.body.innerText = "error starting proxy: " + reason;
console.log(document.body.innerText); console.log(document.body.innerText);
}); });