Bug 1504508 - Add WeTransfer FileLink Provider; r=Fallen
authorGeoff Lankow <geoff@darktrojan.net>
Sun, 09 Dec 2018 20:02:52 +1300
changeset 33866 ffb6350a5d0cf0a5f2c813368c06473a54cd44e2
parent 33865 463d4f409e9a2d0e67befd639afce76dc3cef5e0
child 33867 091ba7e6fd2c48932e3ab65b63b1b20f340c7b2e
push id388
push userclokep@gmail.com
push dateMon, 28 Jan 2019 20:54:56 +0000
reviewersFallen
bugs1504508
Bug 1504508 - Add WeTransfer FileLink Provider; r=Fallen
mail/components/cloudfile/moz.build
mail/components/cloudfile/wetransfer/_locales/en/messages.json
mail/components/cloudfile/wetransfer/background/background.js
mail/components/cloudfile/wetransfer/content/moments.css
mail/components/cloudfile/wetransfer/content/moments.html
mail/components/cloudfile/wetransfer/content/moments.js
mail/components/cloudfile/wetransfer/images/logo.svg
mail/components/cloudfile/wetransfer/install.rdf
mail/components/cloudfile/wetransfer/manifest.json
mail/components/cloudfile/wetransfer/moz.build
mail/extensions/built_in_addons.json
mail/installer/package-manifest.in
mail/test/mozmill/cloudfile/test-cloudfile-add-account-dialog.js
--- a/mail/components/cloudfile/moz.build
+++ b/mail/components/cloudfile/moz.build
@@ -15,8 +15,12 @@ EXTRA_COMPONENTS += [
     'nsHightail.js',
 ]
 
 EXTRA_JS_MODULES += [
     'cloudFileAccounts.js',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
+
+DIRS += [
+    'wetransfer',
+]
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/_locales/en/messages.json
@@ -0,0 +1,11 @@
+{
+  "extensionDescription": {
+    "message": "Share your attachments via WeTransfer"
+  },
+  "extensionName": {
+    "message": "FileLink for WeTransfer"
+  },
+  "serviceName": {
+    "message": "WeTransfer"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/background/background.js
@@ -0,0 +1,156 @@
+/* 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/. */
+
+/* globals browser */
+
+const FIVE_MB = 5242880;
+const TWO_GB = 2147483648;
+
+class WeTransferSession {
+  constructor() {
+    this.token = null;
+  }
+
+  async _request(endpoint, fetchinfo, withToken = true) {
+    let url = new URL(endpoint, "https://dev.wetransfer.com/");
+    let headers = { "content-type": "application/json" };
+
+    // Before you spend time trying to find out what this means, please note
+    // that doing so and using the information WILL cause WeTransfer to revoke
+    // this extension's privileges, which means not one Thunderbird user will
+    // be able to upload to WeTransfer using Thunderbird. This will cause
+    // unhappy users all around which means that the developers will have to
+    // spend more time with user support, which means less time for features,
+    // releases and bugfixes. For a paid developer this would actually mean
+    // financial harm.
+    //
+    // Do you really want all of this to be your fault? Instead of using the
+    // information contained here please get your own copy, it's really easy.
+    /* eslint-disable */
+    ((y,z)=>{z["\x78\x2d\x61\x70\x69\x2d\x6b\x65\x79"]=y["\x41\x72\x72\x61"+
+    "\x79"]["\x66\x72\x6f\x6d"](("\x78\x4f\x57\x49\x33")+"\x51\x3a\x78\x39"+
+    "\x42\x6d\x6f\x78\x76\x74"+("\x67\x32\x58\x48\x67\x36\x45\x53\x70\x32")+
+    "\x73\x6e\x70\x4a\x7b\x75\x58\x79\x6f\x58"+"\x53\x44\x51\x64\x31"+"",c=>
+    y[("\x53\x74\x72\x69\x6e\x67")+("")]["\x66\x72\x6f\x6d\x43\x68\x61\x72"+
+    "\x43\x6f\x64\x65"](c["\x63\x68\x61\x72\x43\x6f\x64\x65\x41\x74"](0)-!0)
+    )["\x6a\x6f\x69\x6e"]("");})(window,headers)
+    /* eslint-enable */
+
+    if (this.token && withToken) {
+      headers.Authorization = "Bearer " + this.token;
+    }
+
+    fetchinfo.mode = "cors";
+    fetchinfo.headers = headers;
+    if (!fetchinfo.method) {
+      fetchinfo.method = "POST";
+    }
+
+    let response = await fetch(url, fetchinfo);
+
+    let responseData;
+    if (response.headers.get("content-type") == "application/json") {
+      try {
+        responseData = await response.json();
+      } catch (e) {
+        if (!response.ok) {
+          throw new Error(response.statusText);
+        }
+        throw e;
+      }
+
+      if (responseData.success === false || responseData.error) {
+        throw new Error(responseData.error);
+      }
+    } else {
+      responseData = await response.text();
+    }
+    return responseData;
+  }
+
+  async authorize(signal = null) {
+    let data = await this._request("/v2/authorize", { signal }, false);
+    this.token = data.token;
+  }
+
+  async createTransfer(name, data, description = "", signal = null) {
+    if (!this.token) {
+      await this.authorize();
+    }
+
+    let transfer = await this._request("/v2/transfers", {
+      body: JSON.stringify({
+        message: name,
+        files: [{ name, size: data.byteLength }],
+      }),
+      signal,
+    });
+
+    let file = transfer.files[0];
+    for (let partNumber = 1; partNumber <= file.multipart.part_numbers; partNumber++) {
+      let uploadURL = await this._request(
+        `/v2/transfers/${transfer.id}/files/${file.id}/upload-url/${partNumber}`,
+        {
+          method: "GET",
+          signal,
+        },
+      );
+
+      await fetch(uploadURL.url, {
+        method: "PUT",
+        body: data.slice((partNumber - 1) * FIVE_MB, partNumber * FIVE_MB),
+        signal,
+      });
+    }
+
+    await this._request(
+      `/v2/transfers/${transfer.id}/files/${file.id}/upload-complete`,
+      {
+        method: "PUT",
+        body: JSON.stringify({ part_numbers: file.multipart.part_numbers }),
+        signal,
+      },
+    );
+
+    return this._request(
+      `/v2/transfers/${transfer.id}/finalize`,
+      {
+        method: "PUT",
+        signal,
+      }
+    );
+  }
+}
+
+var abortControllers = new Map();
+
+browser.cloudFile.onFileUpload.addListener(async (account, { id, name, data }) => {
+  let session = new WeTransferSession();
+  let controller = new AbortController();
+  abortControllers.set(id, controller);
+
+  try {
+    let transfer = await session.createTransfer(name, data, "", controller.signal);
+    return { url: transfer.url };
+  } finally {
+    abortControllers.delete(id);
+  }
+});
+
+browser.cloudFile.onFileUploadAbort.addListener((account, id) => {
+  let controller = abortControllers.get(id);
+  if (controller) {
+    controller.abort();
+  }
+});
+
+browser.cloudFile.getAllAccounts().then(async (accounts) => {
+  for (let account of accounts) {
+    await browser.cloudFile.updateAccount(account.id, { uploadSizeLimit: TWO_GB });
+  }
+});
+
+browser.cloudFile.onAccountAdded.addListener(async (account) => {
+  await browser.cloudFile.updateAccount(account.id, { uploadSizeLimit: TWO_GB });
+});
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/content/moments.css
@@ -0,0 +1,44 @@
+/* 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/. */
+
+html, body {
+  margin: 0;
+  padding: 0;
+  overflow: hidden;
+}
+
+body {
+  min-height: 300px;
+  font: message-box;
+}
+
+.text-link {
+  color: -moz-nativehyperlinktext;
+}
+
+a {
+  display: block;
+}
+
+h1 {
+  font-size: 18px;
+  margin: 0;
+}
+
+.text-link:hover {
+  text-decoration: underline;
+}
+
+#background {
+  max-height: 100%;
+  max-width: 100%;
+}
+
+#title {
+  text-align: end;
+}
+
+#story {
+  margin-top: 2ex;
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/content/moments.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!-- 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/. -->
+
+<html>
+  <head>
+    <script src="moments.js"></script>
+    <meta charset="UTF-8">
+    <link rel="stylesheet" type="text/css" href="moments.css"/>
+  </head>
+  <body>
+    <h1>WeTransfer is the simplest way to send your files around the world.</h1>
+    <a class="text-link" href="https://wetransfer.com/legal/terms" target="_blank">Privacy Policy</a>
+    <a id="story" href="#" target="_blank">
+      <img id="background"></img>
+      <div id="title"></div>
+    </a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/content/moments.js
@@ -0,0 +1,14 @@
+/* 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/. */
+
+(async () => {
+  let resp = await fetch("https://backgrounds.wetransfer.net/moment/list.json");
+  let items = await resp.json();
+  let item = items[Math.floor(Math.random() * items.length)];
+  if (item.url.startsWith("https:") && item.file.startsWith("https:")) {
+    document.getElementById("background").src = item.file;
+    document.getElementById("story").href = item.url;
+    document.getElementById("title").textContent = item.title;
+  }
+})();
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/images/logo.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="52" height="29" class="spinner__logo" viewBox="-4 -2 52 29">
+	<defs>
+		<path id="b" d="M25.4 10.6c0-6.2 4.4-9.9 10.1-9.9C40.6.7 44 3.3 44 6.9c0 3.4-2.9 5.6-6.1 5.6-1.8 0-3.1-.3-4-1-.3-.3-.5-.2-.5.1 0 1.3.5 2.3 1.3 3.2.7.7 2 1.2 3.2 1.2 1.3 0 2.4-.3 3.4-.8s1.8-.3 2.3.5c.6.9-.2 2.1-.9 2.9-1.3 1.4-3.8 2.4-7 2.4-6.5-.2-10.3-4.6-10.3-10.4zm-13.3 4.1c.6 0 1 .3 1.4 1l1.8 2.9c.7 1.1 1.3 1.9 2.6 1.9s2-.5 2.6-2c.8-1.8 1.7-4.1 2.4-7.1.9-3.4 1.3-5.4 1.3-7.1s-.5-2.7-2.4-3c-2.5-.5-6-.7-9.7-.7s-7.2.2-9.7.6C.5 1.6 0 2.6 0 4.3S.4 8 1.2 11.4c.8 3 1.6 5.2 2.4 7.1.7 1.5 1.3 2 2.6 2s1.9-.8 2.6-1.9l1.8-2.9c.5-.6.9-1 1.5-1z"></path>
+		<filter id="a" width="200%" height="200%" x="-50%" y="-50%" filterUnits="objectBoundingBox">
+			<feOffset dx="0" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+			<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+		</filter>
+	</defs>
+	<g fill="none">
+		<use fill="#17181A" fill-opacity="0.11" filter="url(#a)" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#b"></use>
+		<use fill="rgb(0, 0, 0)" fill-rule="evenodd" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#b"></use>
+	</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/install.rdf
@@ -0,0 +1,2 @@
+This file exists as a placeholder until https://bugzilla.mozilla.org/show_bug.cgi?id=1472123 is fixed.
+This sentence exists so this file is different from the one in Lightning.
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/manifest.json
@@ -0,0 +1,32 @@
+{
+  "manifest_version": 2,
+  "name": "__MSG_extensionName__",
+  "description": "__MSG_extensionDescription__",
+  "version": "2.0.0",
+  "applications": {
+    "gecko": {
+      "id": "wetransfer@extensions.thunderbird.net",
+      "strict_min_version": "60.4"
+    }
+  },
+  "default_locale": "en",
+
+  "icons": {
+    "16": "images/logo.svg",
+    "32": "images/logo.svg",
+    "48": "images/logo.svg",
+    "128": "images/logo.svg"
+  },
+
+  "cloud_file": {
+    "name": "__MSG_serviceName__",
+    "settings_url": "/content/moments.html",
+    "management_url": "/content/moments.html"
+  },
+
+  "background": {
+    "scripts": [
+      "background/background.js"
+    ]
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mail/components/cloudfile/wetransfer/moz.build
@@ -0,0 +1,27 @@
+# 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/.
+
+DIST_SUBDIR = 'features/wetransfer@extensions.thunderbird.net'
+
+FINAL_TARGET_FILES += [
+    'install.rdf',
+    'manifest.json',
+]
+FINAL_TARGET_FILES.background += [
+    'background/background.js',
+]
+FINAL_TARGET_FILES.content += [
+    'content/moments.css',
+    'content/moments.html',
+    'content/moments.js',
+]
+FINAL_TARGET_FILES.images += [
+    'images/logo.svg',
+]
+FINAL_TARGET_FILES._locales.en += [
+    '_locales/en/messages.json',
+]
+
+USE_EXTENSION_MANIFEST = True
+export('USE_EXTENSION_MANIFEST')
--- a/mail/extensions/built_in_addons.json
+++ b/mail/extensions/built_in_addons.json
@@ -1,1 +1,8 @@
-{ "dictionaries": {"en-US": "dictionaries/en-US.dic"}, "system": [] }
+{
+  "dictionaries": {
+    "en-US": "dictionaries/en-US.dic"
+  },
+  "system": [
+    "wetransfer@extensions.thunderbird.net"
+  ]
+}
--- a/mail/installer/package-manifest.in
+++ b/mail/installer/package-manifest.in
@@ -460,16 +460,18 @@
 #ifdef MOZ_CALENDAR
 [calendar]
 #ifdef NIGHTLY_BUILD
 @RESPATH@/extensions/{e2fda1a4-762b-4020-b5ad-a41df1933103}/
 #else
 @RESPATH@/distribution/extensions/{e2fda1a4-762b-4020-b5ad-a41df1933103}/
 #endif
 #endif
+@RESPATH@/features/wetransfer@extensions.thunderbird.net/
+
 @RESPATH@/components/nsPrompter.manifest
 @RESPATH@/components/nsPrompter.js
 @RESPATH@/components/CaptivePortalDetectComponents.manifest
 @RESPATH@/components/captivedetect.js
 @RESPATH@/components/servicesComponents.manifest
 @RESPATH@/components/servicesSettings.manifest
 @RESPATH@/components/RemoteSettingsComponents.js
 @RESPATH@/components/cryptoComponents.manifest
--- a/mail/test/mozmill/cloudfile/test-cloudfile-add-account-dialog.js
+++ b/mail/test/mozmill/cloudfile/test-cloudfile-add-account-dialog.js
@@ -23,40 +23,46 @@ ChromeUtils.import('resource:///modules/
 ChromeUtils.import('resource:///modules/iteratorUtils.jsm');
 
 var kCategory = 'cloud-files';
 var kDialogId = 'addCloudFileAccount';
 var kRootURL = collector.addHttpResource('../cloudfile/html', '');
 var kSettingsWithForm = kRootURL + 'settings-with-form.xhtml';
 
 var gOldProviders = {};
+var gOldWeTransferProvider = null;
 
 function setupModule(module) {
   for (let lib of MODULE_REQUIRES) {
     collector.getModule(lib).installInto(module);
   }
 
   // Save the old providers...
   for (let entry of fixIterator(Services.catMan.enumerateCategory(kCategory),
                                 Ci.nsISupportsCString)) {
     let value = Services.catMan.getCategoryEntry(kCategory, entry.data);
     gOldProviders[entry] = value;
   }
 
   // Clear out the old entries
   Services.catMan.deleteCategory(kCategory);
+  gOldWeTransferProvider = cloudFileAccounts.getProviderForType(
+    "ext-wetransfer@extensions.thunderbird.net"
+  );
+  cloudFileAccounts.unregisterProvider(gOldWeTransferProvider.type);
 }
 
 function teardownModule(module) {
   // Clear out any leftover entries.
   Services.catMan.deleteCategory(kCategory);
 
   // Put the old entries back
   for (let [key, value] of Object.entries(gOldProviders))
     Services.catMan.addCategoryEntry(kCategory, key, value, false, true);
+  cloudFileAccounts.registerProvider(gOldWeTransferProvider);
 }
 
 /**
  * Helper function that ensures that we know the number of registered
  * Filelink service providers.
  *
  * @param aNum the number of expected registered Filelink service providers.
  */