Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
474 changes: 474 additions & 0 deletions client/thunderbird-filelink-dl/.eslintrc

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion client/thunderbird-filelink-dl/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
*.xpi
*.xpt
23 changes: 23 additions & 0 deletions client/thunderbird-filelink-dl/_locales/en/messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"extensionDescription": {
"message": "Convert large attachments to links automatically, directly within the Composer window, using your own “DL” server/service instead of relying on 3rd-party providers"
},
"extensionName": {
"message": "DL FileLink Provider for Thunderbird"
},
"serviceName": {
"message": "DL"
},
"insertUploadGrant": {
"message": "Insert Upload Grant"
},
"serviceURL": {
"message": "Service URL"
},
"username": {
"message": "Username"
},
"password": {
"message": "Password"
}
}
49 changes: 49 additions & 0 deletions client/thunderbird-filelink-dl/api/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Portions Copyright (C) Philipp Kewisch, 2019 */

function ensureComposeWindow(window) {
if (window.document.documentElement.getAttribute("windowtype") != "msgcompose") {
throw new ExtensionError("Passed window is not a compose window");
}
}

this.dl = class extends ExtensionAPI {
getAPI(context) {
let { tabManager } = context.extension;
let windowTracker = Cu.getGlobalForObject(tabManager).windowTracker;

return {
dl: {
async composeCurrentIdentity(windowId) {
// Something like this will likely exist with bug 1590121 fixed.
let composeWindow = windowTracker.getWindow(windowId, context);
ensureComposeWindow(composeWindow);

let identity = composeWindow.gCurrentIdentity;

return {
name: identity.fullName,
email: identity.email,
replyTo: identity.replyTo,
};
},

async composeInsertHTML(windowId, html) {
// This API won't survive well. When this stops working, check out bug 1590121.
// Note it is also super unsafe, you need to make sure things are properly escaped in the
// caller.

let composeWindow = windowTracker.getWindow(windowId, context);
ensureComposeWindow(composeWindow);

let editor = composeWindow.GetCurrentEditor();
editor.beginTransaction();
editor.insertHTML(html);
editor.endTransaction();
}
}
};
}
};
26 changes: 26 additions & 0 deletions client/thunderbird-filelink-dl/api/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"namespace": "dl",
"functions": [
{
"name": "composeInsertHTML",
"type": "function",
"async": true,
"description": "Insert HTML into a compose window.",
"parameters": [
{ "name": "windowId", "type": "integer", "minimum": 0 },
{ "name": "html", "type": "string" }
]
},
{
"name": "composeCurrentIdentity",
"type": "function",
"async": true,
"description": "Get currently selected compose window identity",
"parameters": [
{ "name": "windowId", "type": "integer", "minimum": 0 }
]
}
]
}
]
150 changes: 150 additions & 0 deletions client/thunderbird-filelink-dl/background/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Portions Copyright (C) Philipp Kewisch, 2019 */

var abortControllers = new Map();
var tickets = new Map();

async function accountPrefs(accountId) {
let {
[`accounts.${accountId}.restURL`]: restURL,
[`accounts.${accountId}.username`]: username,
[`accounts.${accountId}.password`]: password
} = await messenger.storage.local.get([
`accounts.${accountId}.restURL`,
`accounts.${accountId}.username`,
`accounts.${accountId}.password`,
]);

return { restURL, username, password };
}

async function updateAccount(account) {
let resp = await request({
account: account,
method: "GET",
url: "info",
});

let prefs = await accountPrefs(account.id);

let { maxsize } = await resp.json();

await messenger.cloudFile.updateAccount(account.id, {
uploadSizeLimit: maxsize,
configured: !!(prefs.restURL && prefs.username && prefs.password)
});
}

async function request({ account, url, formData, signal, method="POST" }) {
let prefs = await accountPrefs(account.id);
let auth = "Basic " + btoa(prefs.username + ":" + prefs.password);

if (!formData && method == "POST") {
formData = new FormData();
}

if (formData && !formData.has("msg")) {
formData.append("msg", "{}");
}

let headers = {
"Authorization": auth,
"X-Authorization": auth,
};

let resp = await fetch(new URL(url, prefs.restURL).href, {
method: method,
headers: headers,
body: formData,
signal: signal
});

if (!resp.ok) {
let json = await resp.json();
throw new Error(json.error);
}

return resp;
}


messenger.cloudFile.onFileUpload.addListener(
async (account, { id, name, data }) => {
let controller = new AbortController();
abortControllers.set(id, controller);

let formData = new FormData();
console.log(data);
formData.append("file", new Blob([data]), name);

try {
let resp = await request({
account: account,
url: "newticket",
formData: formData,
signal: controller.signal
});
let json = await resp.json();

tickets.set(id, json.id);

return { url: json.url };
} finally {
abortControllers.delete(id);
}
}
);

messenger.cloudFile.onFileUploadAbort.addListener((account, id) => {
let controller = abortControllers.get(id);
if (controller) {
controller.abort();
abortControllers.delete(id);
}
});

messenger.cloudFile.onFileDeleted.addListener(async (account, id) => {
let ticketId = tickets.get(id);
if (ticketId) {
await request({
account: account,
url: "purgeticket/" + ticketId
});
tickets.delete(id);
}
});


messenger.cloudFile.getAllAccounts().then(async accounts => {
await Promise.all(accounts.map(updateAccount));
});

messenger.cloudFile.onAccountAdded.addListener(account => updateAccount(account));

messenger.composeAction.onClicked.addListener(async (...args) => {
// getLastFocused doesn't return compose windows for some reson
let windows = await messenger.windows.getAll({ windowTypes: ["messageCompose"] });
let focusedWindow = windows.find(window => window.focused);

// TODO you might want to add a popup to select which account to create the grant for. Right now
// Thunderbird just supports one account, so this is fine (tm).
let accounts = await messenger.cloudFile.getAllAccounts();

let identity = await messenger.dl.composeCurrentIdentity(focusedWindow.id);

let formData = new FormData();
formData.append("msg", JSON.stringify({ notify: identity.email }));

let response = await request({
account: accounts[0],
url: "newgrant",
formData: formData
});

let json = await response.json();

let url = encodeURI(json.url);
messenger.dl.composeInsertHTML(focusedWindow.id, `<a href="${url}">${url}</a>`);
});
128 changes: 0 additions & 128 deletions client/thunderbird-filelink-dl/build.sh

This file was deleted.

12 changes: 0 additions & 12 deletions client/thunderbird-filelink-dl/chrome.manifest

This file was deleted.

Loading