= 1073741824) {
return Math.round(10 * bytes / 1073741824) / 10 + ' GB';
- } else if (bytes >= 1048576) {
+ }
+ else if (bytes >= 1048576) {
return Math.round(bytes / 1048576) + ' MB';
- } else if (bytes > 1024) {
+ }
+ else if (bytes > 1024) {
return Math.round(bytes / 1024) + ' KB';
- } else {
+ }
+ else {
return bytes + ' Bytes';
}
}
@@ -762,7 +673,8 @@ class ReceiveDialog extends Dialog {
fileOther = imagesOnly
? Localization.getTranslation("dialogs.file-other-description-image")
: Localization.getTranslation("dialogs.file-other-description-file");
- } else if (files.length >= 2) {
+ }
+ else if (files.length >= 2) {
fileOther = imagesOnly
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
@@ -828,7 +740,7 @@ class ReceiveFileDialog extends ReceiveDialog {
return;
}
// dequeue next file
- setTimeout(_ => {
+ setTimeout(() => {
this._busy = false;
this._nextFiles();
}, 300);
@@ -846,7 +758,8 @@ class ReceiveFileDialog extends ReceiveDialog {
if (Object.keys(previewElement).indexOf(mime) === -1) {
resolve(false);
- } else {
+ }
+ else {
let element = document.createElement(previewElement[mime]);
element.controls = true;
element.onload = _ => {
@@ -876,7 +789,8 @@ class ReceiveFileDialog extends ReceiveDialog {
descriptor = imagesOnly
? Localization.getTranslation("dialogs.title-image")
: Localization.getTranslation("dialogs.title-file");
- } else {
+ }
+ else {
descriptor = imagesOnly
? Localization.getTranslation("dialogs.title-image-plural")
: Localization.getTranslation("dialogs.title-file-plural");
@@ -938,7 +852,8 @@ class ReceiveFileDialog extends ReceiveDialog {
tmpZipBtn.download = filenameDownload;
tmpZipBtn.href = url;
tmpZipBtn.click();
- } else {
+ }
+ else {
this._downloadFilesIndividually(files);
}
@@ -947,7 +862,7 @@ class ReceiveFileDialog extends ReceiveDialog {
}
Events.fire('notify-user', Localization.getTranslation("notifications.download-successful", null, {descriptor: descriptor}));
this.$downloadBtn.style.pointerEvents = "none";
- setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
+ setTimeout(() => this.$downloadBtn.style.pointerEvents = "unset", 2000);
};
document.title = files.length === 1
@@ -958,10 +873,11 @@ class ReceiveFileDialog extends ReceiveDialog {
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
this.show();
- setTimeout(_ => {
+ setTimeout(() => {
if (canShare) {
this.$shareBtn.click();
- } else {
+ }
+ else {
this.$downloadBtn.click();
}
}, 500);
@@ -970,7 +886,8 @@ class ReceiveFileDialog extends ReceiveDialog {
.then(canPreview => {
if (canPreview) {
console.log('the file is able to preview');
- } else {
+ }
+ else {
console.log('the file is not able to preview');
}
})
@@ -987,7 +904,7 @@ class ReceiveFileDialog extends ReceiveDialog {
}
hide() {
- this.$shareBtn.setAttribute('hidden', '');
+ this.$shareBtn.setAttribute('hidden', true);
this.$previewBox.innerHTML = '';
super.hide();
this._dequeueFile();
@@ -1068,12 +985,12 @@ class ReceiveRequestDialog extends ReceiveDialog {
hide() {
// clear previewBox after dialog is closed
- setTimeout(_ => this.$previewBox.innerHTML = '', 300);
+ setTimeout(() => this.$previewBox.innerHTML = '', 300);
super.hide();
// show next request
- setTimeout(_ => this._dequeueRequests(), 500);
+ setTimeout(() => this._dequeueRequests(), 500);
}
}
@@ -1097,11 +1014,11 @@ class InputKeyContainer {
}
_enableChars() {
- this.$inputKeyChars.forEach(char => char.removeAttribute("disabled"));
+ this.$inputKeyChars.forEach(char => char.removeAttribute('disabled'));
}
_disableChars() {
- this.$inputKeyChars.forEach(char => char.setAttribute("disabled", ""));
+ this.$inputKeyChars.forEach(char => char.setAttribute('disabled', true));
}
_clearChars() {
@@ -1133,10 +1050,12 @@ class InputKeyContainer {
if (e.key === "Backspace" && previousSibling && !e.target.value) {
previousSibling.value = '';
previousSibling.focus();
- } else if (e.key === "ArrowRight" && nextSibling) {
+ }
+ else if (e.key === "ArrowRight" && nextSibling) {
e.preventDefault();
nextSibling.focus();
- } else if (e.key === "ArrowLeft" && previousSibling) {
+ }
+ else if (e.key === "ArrowLeft" && previousSibling) {
e.preventDefault();
previousSibling.focus();
}
@@ -1172,7 +1091,8 @@ class InputKeyContainer {
_evaluateKeyChars() {
if (this.$inputKeyContainer.querySelectorAll('input:placeholder-shown').length > 0) {
this._onNotAllCharsFilled();
- } else {
+ }
+ else {
this._onAllCharsFilled();
const lastCharFocused = document.activeElement === this.$inputKeyChars[this.$inputKeyChars.length - 1];
@@ -1204,8 +1124,8 @@ class PairDeviceDialog extends Dialog {
this.inputKeyContainer = new InputKeyContainer(
this.$el.querySelector('.input-key-container'),
/\d/,
- () => this.$pairSubmitBtn.removeAttribute("disabled"),
- () => this.$pairSubmitBtn.setAttribute("disabled", ""),
+ () => this.$pairSubmitBtn.removeAttribute('disabled'),
+ () => this.$pairSubmitBtn.setAttribute('disabled', true),
() => this._submit()
);
@@ -1229,20 +1149,21 @@ class PairDeviceDialog extends Dialog {
this.evaluateUrlAttributes();
this.pairPeer = {};
-
- this._evaluateNumberRoomSecrets();
}
_onKeyDown(e) {
if (this.isShown() && e.code === "Escape") {
// Timeout to prevent paste mode from getting cancelled simultaneously
- setTimeout(_ => this._close(), 50);
+ setTimeout(() => this._close(), 50);
}
}
_onPaste(e) {
e.preventDefault();
- let pastedKey = e.clipboardData.getData("Text").replace(/\D/g,'').substring(0, 6);
+ let pastedKey = e.clipboardData
+ .getData("Text")
+ .replace(/\D/g,'')
+ .substring(0, 6);
this.inputKeyContainer._onPaste(pastedKey);
}
@@ -1365,12 +1286,13 @@ class PairDeviceDialog extends Dialog {
deviceName = $peer.ui._peer.name.deviceName;
}
- PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
+ PersistentStorage
+ .addRoomSecret(roomSecret, displayName, deviceName)
.then(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success"));
this._evaluateNumberRoomSecrets();
})
- .finally(_ => {
+ .finally(() => {
this._cleanUp();
this.hide();
})
@@ -1406,23 +1328,26 @@ class PairDeviceDialog extends Dialog {
}
_onSecretRoomDeleted(roomSecret) {
- PersistentStorage.deleteRoomSecret(roomSecret).then(_ => {
- this._evaluateNumberRoomSecrets();
- });
+ PersistentStorage
+ .deleteRoomSecret(roomSecret)
+ .then(_ => {
+ this._evaluateNumberRoomSecrets();
+ });
}
_evaluateNumberRoomSecrets() {
- PersistentStorage.getAllRoomSecrets()
+ PersistentStorage
+ .getAllRoomSecrets()
.then(roomSecrets => {
if (roomSecrets.length > 0) {
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
- } else {
- this.$editPairedDevicesHeaderBtn.setAttribute('hidden', '');
- this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
+ }
+ else {
+ this.$editPairedDevicesHeaderBtn.setAttribute('hidden', true);
+ this.$footerInstructionsPairedDevices.setAttribute('hidden', true);
}
Events.fire('evaluate-footer-badges');
- Events.fire('header-evaluated', 'edit-paired-devices');
});
}
}
@@ -1451,50 +1376,58 @@ class EditPairedDevicesDialog extends Dialog {
const autoAcceptString = Localization.getTranslation("dialogs.auto-accept").toLowerCase();
const roomSecretsEntries = await PersistentStorage.getAllRoomSecretEntries();
- roomSecretsEntries.forEach(roomSecretsEntry => {
- let $pairedDevice = document.createElement('div');
- $pairedDevice.classList = ["paired-device"];
+ roomSecretsEntries
+ .forEach(roomSecretsEntry => {
+ let $pairedDevice = document.createElement('div');
+ $pairedDevice.classList = ["paired-device"];
- $pairedDevice.innerHTML = `
-
- ${roomSecretsEntry.display_name}
-
-
- ${roomSecretsEntry.device_name}
-
-
- ${autoAcceptString}
-
-
- ${unpairString}
-
`
+ $pairedDevice.innerHTML = `
+
+ ${roomSecretsEntry.display_name}
+
+
+ ${roomSecretsEntry.device_name}
+
+
+ ${autoAcceptString}
+
+
+ ${unpairString}
+
`
- $pairedDevice.querySelector('input[type="checkbox"]').addEventListener('click', e => {
- PersistentStorage.updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked).then(roomSecretsEntry => {
- Events.fire('auto-accept-updated', {
- 'roomSecret': roomSecretsEntry.entry.secret,
- 'autoAccept': e.target.checked
+ $pairedDevice
+ .querySelector('input[type="checkbox"]')
+ .addEventListener('click', e => {
+ PersistentStorage
+ .updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked)
+ .then(roomSecretsEntry => {
+ Events.fire('auto-accept-updated', {
+ 'roomSecret': roomSecretsEntry.entry.secret,
+ 'autoAccept': e.target.checked
+ });
+ });
});
- });
- });
- $pairedDevice.querySelector('button').addEventListener('click', e => {
- PersistentStorage.deleteRoomSecret(roomSecretsEntry.secret).then(roomSecret => {
- Events.fire('room-secrets-deleted', [roomSecret]);
- Events.fire('evaluate-number-room-secrets');
- e.target.parentNode.parentNode.remove();
- });
+ $pairedDevice
+ .querySelector('button')
+ .addEventListener('click', e => {
+ PersistentStorage
+ .deleteRoomSecret(roomSecretsEntry.secret)
+ .then(roomSecret => {
+ Events.fire('room-secrets-deleted', [roomSecret]);
+ Events.fire('evaluate-number-room-secrets');
+ e.target.parentNode.parentNode.remove();
+ });
+ })
+
+ this.$pairedDevicesWrapper.html = "";
+ this.$pairedDevicesWrapper.appendChild($pairedDevice)
})
-
- this.$pairedDevicesWrapper.html = "";
- this.$pairedDevicesWrapper.appendChild($pairedDevice)
- })
-
}
hide() {
super.hide();
- setTimeout(_ => {
+ setTimeout(() => {
this.$pairedDevicesWrapper.innerHTML = ""
}, 300);
}
@@ -1504,14 +1437,17 @@ class EditPairedDevicesDialog extends Dialog {
}
_clearRoomSecrets() {
- PersistentStorage.getAllRoomSecrets()
+ PersistentStorage
+ .getAllRoomSecrets()
.then(roomSecrets => {
- PersistentStorage.clearRoomSecrets().finally(_ => {
- Events.fire('room-secrets-deleted', roomSecrets);
- Events.fire('evaluate-number-room-secrets');
- Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared"));
- this.hide();
- })
+ PersistentStorage
+ .clearRoomSecrets()
+ .finally(() => {
+ Events.fire('room-secrets-deleted', roomSecrets);
+ Events.fire('evaluate-number-room-secrets');
+ Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared"));
+ this.hide();
+ })
});
}
@@ -1525,9 +1461,11 @@ class EditPairedDevicesDialog extends Dialog {
if (!peer || !peer._roomIds["secret"]) return;
- PersistentStorage.updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName).then(roomSecretEntry => {
- console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`);
- })
+ PersistentStorage
+ .updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName)
+ .then(roomSecretEntry => {
+ console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`);
+ })
}
}
@@ -1555,8 +1493,8 @@ class PublicRoomDialog extends Dialog {
this.inputKeyContainer = new InputKeyContainer(
this.$el.querySelector('.input-key-container'),
/[a-z|A-Z]/,
- () => this.$joinSubmitBtn.removeAttribute("disabled"),
- () => this.$joinSubmitBtn.setAttribute("disabled", ""),
+ () => this.$joinSubmitBtn.removeAttribute('disabled'),
+ () => this.$joinSubmitBtn.setAttribute('disabled', true),
() => this._submit()
);
@@ -1590,7 +1528,8 @@ class PublicRoomDialog extends Dialog {
_onHeaderBtnClick() {
if (this.roomId) {
this.show();
- } else {
+ }
+ else {
this._createPublicRoom();
}
}
@@ -1760,7 +1699,7 @@ class PublicRoomDialog extends Dialog {
this.roomId = null;
this.inputKeyContainer._cleanUp();
sessionStorage.removeItem('public_room_id');
- this.$footerBadgePublicRoomDevices.setAttribute('hidden', '');
+ this.$footerBadgePublicRoomDevices.setAttribute('hidden', true);
Events.fire('evaluate-footer-badges');
}
}
@@ -1774,7 +1713,7 @@ class SendTextDialog extends Dialog {
this.$form = this.$el.querySelector('form');
this.$submit = this.$el.querySelector('button[type="submit"]');
this.$form.addEventListener('submit', e => this._onSubmit(e));
- this.$text.addEventListener('input', e => this._onChange(e));
+ this.$text.addEventListener('input', _ => this._onChange());
Events.on('keydown', e => this._onKeyDown(e));
}
@@ -1783,7 +1722,8 @@ class SendTextDialog extends Dialog {
if (e.code === "Escape") {
this.hide();
- } else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
+ }
+ else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
if (this._textInputEmpty()) return;
this._send();
}
@@ -1793,10 +1733,11 @@ class SendTextDialog extends Dialog {
return !this.$text.innerText || this.$text.innerText === "\n";
}
- _onChange(e) {
+ _onChange() {
if (this._textInputEmpty()) {
- this.$submit.setAttribute('disabled', '');
- } else {
+ this.$submit.setAttribute('disabled', true);
+ }
+ else {
this.$submit.removeAttribute('disabled');
}
}
@@ -1854,7 +1795,8 @@ class ReceiveTextDialog extends Dialog {
if (e.code === "KeyC" && (e.ctrlKey || e.metaKey)) {
await this._onCopy()
this.hide();
- } else if (e.code === "Escape") {
+ }
+ else if (e.code === "Escape") {
this.hide();
}
}
@@ -1904,7 +1846,8 @@ class ReceiveTextDialog extends Dialog {
async _onCopy() {
const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
- navigator.clipboard.writeText(sanitizedText)
+ navigator.clipboard
+ .writeText(sanitizedText)
.then(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard"));
this.hide();
@@ -1916,7 +1859,7 @@ class ReceiveTextDialog extends Dialog {
hide() {
super.hide();
- setTimeout(_ => this._dequeueRequests(), 500);
+ setTimeout(() => this._dequeueRequests(), 500);
}
}
@@ -1938,28 +1881,31 @@ class Base64ZipDialog extends Dialog {
// ?base64text=paste
// base64 encoded string is ready to be pasted from clipboard
this.preparePasting('text');
- } else if (base64Text === 'hash') {
+ }
+ else if (base64Text === 'hash') {
// ?base64text=hash#BASE64ENCODED
// base64 encoded string is url hash which is never sent to server and faster (recommended)
this.processBase64Text(base64Hash)
.catch(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
console.log("Text content incorrect.");
- }).finally(_ => {
+ }).finally(() => {
this.hide();
});
- } else {
+ }
+ else {
// ?base64text=BASE64ENCODED
// base64 encoded string was part of url param (not recommended)
this.processBase64Text(base64Text)
.catch(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
console.log("Text content incorrect.");
- }).finally(_ => {
+ }).finally(() => {
this.hide();
});
}
- } else if (base64Zip) {
+ }
+ else if (base64Zip) {
this.show();
if (base64Zip === "hash") {
// ?base64zip=hash#BASE64ENCODED
@@ -1968,10 +1914,11 @@ class Base64ZipDialog extends Dialog {
.catch(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect"));
console.log("File content incorrect.");
- }).finally(_ => {
+ }).finally(() => {
this.hide();
});
- } else {
+ }
+ else {
// ?base64zip=paste || ?base64zip=true
this.preparePasting('files');
}
@@ -1992,9 +1939,10 @@ class Base64ZipDialog extends Dialog {
this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", null, {type: translateType});
this._clickCallback = _ => this.processClipboard(type);
this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
- } else {
+ }
+ else {
console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.")
- this.$pasteBtn.setAttribute('hidden', '');
+ this.$pasteBtn.setAttribute('hidden', true);
this.$fallbackTextarea.setAttribute('placeholder', Localization.getTranslation("dialogs.base64-paste-to-send", null, {type: translateType}));
this.$fallbackTextarea.removeAttribute('hidden');
this._inputCallback = _ => this.processInput(type);
@@ -2031,7 +1979,8 @@ class Base64ZipDialog extends Dialog {
try {
if (type === 'text') {
await this.processBase64Text(base64);
- } else {
+ }
+ else {
await this.processBase64Zip(base64);
}
} catch(_) {
@@ -2104,29 +2053,26 @@ class Notifications {
// Check if the browser supports notifications
if (!('Notification' in window)) return;
- // Check whether notification permissions have already been granted
- if (Notification.permission !== 'granted') {
- this.$headerNotificationButton = $('notification');
- this.$headerNotificationButton.removeAttribute('hidden');
- this.$headerNotificationButton.addEventListener('click', _ => this._requestPermission());
- }
+ this.$headerNotificationButton = $('notification');
+
+ this.$headerNotificationButton.addEventListener('click', _ => this._requestPermission());
- Events.fire('header-evaluated', 'notification');
Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
Events.on('files-received', e => this._downloadNotification(e.detail.files));
Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId));
}
- _requestPermission() {
- Notification.requestPermission(permission => {
- if (permission !== 'granted') {
- Events.fire('notify-user', Localization.getTranslation("notifications.notifications-permissions-error"));
- return;
- }
- Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
- this.$headerNotificationButton.setAttribute('hidden', "");
- });
+ async _requestPermission() {
+ await Notification.
+ requestPermission(permission => {
+ if (permission !== 'granted') {
+ Events.fire('notify-user', Localization.getTranslation("notifications.notifications-permissions-error"));
+ return;
+ }
+ Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
+ this.$headerNotificationButton.setAttribute('hidden', true);
+ });
}
_notify(title, body) {
@@ -2160,8 +2106,9 @@ class Notifications {
const peerDisplayName = $(peerId).ui._displayName();
if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) {
const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message);
- this._bind(notification, _ => window.open(message, '_blank', null, true));
- } else {
+ this._bind(notification, _ => window.open(message, '_blank', "noreferrer"));
+ }
+ else {
const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message);
this._bind(notification, _ => this._copyText(message, notification));
}
@@ -2180,13 +2127,15 @@ class Notifications {
let title;
if (files.length === 1) {
title = `${files[0].name}`;
- } else {
+ }
+ else {
let fileOther;
if (files.length === 2) {
fileOther = imagesOnly
? Localization.getTranslation("dialogs.file-other-description-image")
: Localization.getTranslation("dialogs.file-other-description-file");
- } else {
+ }
+ else {
fileOther = imagesOnly
? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
: Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
@@ -2215,17 +2164,19 @@ class Notifications {
descriptor = imagesOnly
? Localization.getTranslation("dialogs.title-image")
: Localization.getTranslation("dialogs.title-file");
- } else {
+ }
+ else {
descriptor = imagesOnly
? Localization.getTranslation("dialogs.title-image-plural")
: Localization.getTranslation("dialogs.title-file-plural");
}
- let title = Localization.getTranslation("notifications.request-title", null, {
- name: displayName,
- count: request.header.length,
- descriptor: descriptor.toLowerCase()
- });
+ let title = Localization
+ .getTranslation("notifications.request-title", null, {
+ name: displayName,
+ count: request.header.length,
+ descriptor: descriptor.toLowerCase()
+ });
const notification = this._notify(title, Localization.getTranslation("notifications.click-to-show"));
}
@@ -2236,21 +2187,27 @@ class Notifications {
notification.close();
}
- _copyText(message, notification) {
- if (navigator.clipboard.writeText(message)) {
+ async _copyText(message, notification) {
+ if (await navigator.clipboard.writeText(message)) {
notification.close();
this._notify(Localization.getTranslation("notifications.copied-text"));
- } else {
+ }
+ else {
this._notify(Localization.getTranslation("notifications.copied-text-error"));
}
}
_bind(notification, handler) {
if (notification.then) {
- notification.then(_ => serviceWorker.getNotifications().then(_ => {
- serviceWorker.addEventListener('notificationclick', handler);
- }));
- } else {
+ notification.then(_ => {
+ serviceWorker
+ .getNotifications()
+ .then(_ => {
+ serviceWorker.addEventListener('notificationclick', handler);
+ })
+ });
+ }
+ else {
notification.onclick = handler;
}
}
@@ -2289,14 +2246,17 @@ class WebShareTargetUI {
if (url) {
shareTargetText = url; // we share only the link - no text.
- } else if (title && text) {
+ }
+ else if (title && text) {
shareTargetText = title + '\r\n' + text;
- } else {
+ }
+ else {
shareTargetText = title + text;
}
Events.fire('activate-paste-mode', {files: [], text: shareTargetText})
- } else if (share_target_type === "files") {
+ }
+ else if (share_target_type === "files") {
let openRequest = window.indexedDB.open('pairdrop_store')
openRequest.onsuccess = e => {
const db = e.target.result;
@@ -2355,7 +2315,7 @@ class NoSleepUI {
static enable() {
if (!this._interval) {
NoSleepUI._nosleep.enable();
- NoSleepUI._interval = setInterval(_ => NoSleepUI.disable(), 10000);
+ NoSleepUI._interval = setInterval(() => NoSleepUI.disable(), 10000);
}
}
@@ -2367,306 +2327,6 @@ class NoSleepUI {
}
}
-class PersistentStorage {
- constructor() {
- if (!('indexedDB' in window)) {
- PersistentStorage.logBrowserNotCapable();
- return;
- }
- const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4);
- DBOpenRequest.onerror = (e) => {
- PersistentStorage.logBrowserNotCapable();
- console.log('Error initializing database: ');
- console.log(e)
- };
- DBOpenRequest.onsuccess = () => {
- console.log('Database initialised.');
- };
- DBOpenRequest.onupgradeneeded = (e) => {
- const db = e.target.result;
- const txn = e.target.transaction;
-
- db.onerror = e => console.log('Error loading database: ' + e);
-
- console.log(`Upgrading IndexedDB database from version ${e.oldVersion} to version ${e.newVersion}`);
-
- if (e.oldVersion === 0) {
- // initiate v1
- db.createObjectStore('keyval');
- let roomSecretsObjectStore1 = db.createObjectStore('room_secrets', {autoIncrement: true});
- roomSecretsObjectStore1.createIndex('secret', 'secret', { unique: true });
- }
- if (e.oldVersion <= 1) {
- // migrate to v2
- db.createObjectStore('share_target_files');
- }
- if (e.oldVersion <= 2) {
- // migrate to v3
- db.deleteObjectStore('share_target_files');
- db.createObjectStore('share_target_files', {autoIncrement: true});
- }
- if (e.oldVersion <= 3) {
- // migrate to v4
- let roomSecretsObjectStore4 = txn.objectStore('room_secrets');
- roomSecretsObjectStore4.createIndex('display_name', 'display_name');
- roomSecretsObjectStore4.createIndex('auto_accept', 'auto_accept');
- }
- }
- }
-
- static logBrowserNotCapable() {
- console.log("This browser does not support IndexedDB. Paired devices will be gone after the browser is closed.");
- }
-
- static set(key, value) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('keyval', 'readwrite');
- const objectStore = transaction.objectStore('keyval');
- const objectStoreRequest = objectStore.put(value, key);
- objectStoreRequest.onsuccess = _ => {
- console.log(`Request successful. Added key-pair: ${key} - ${value}`);
- resolve(value);
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static get(key) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('keyval', 'readonly');
- const objectStore = transaction.objectStore('keyval');
- const objectStoreRequest = objectStore.get(key);
- objectStoreRequest.onsuccess = _ => {
- console.log(`Request successful. Retrieved key-pair: ${key} - ${objectStoreRequest.result}`);
- resolve(objectStoreRequest.result);
- }
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- });
- }
-
- static delete(key) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('keyval', 'readwrite');
- const objectStore = transaction.objectStore('keyval');
- const objectStoreRequest = objectStore.delete(key);
- objectStoreRequest.onsuccess = _ => {
- console.log(`Request successful. Deleted key: ${key}`);
- resolve();
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static addRoomSecret(roomSecret, displayName, deviceName) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequest = objectStore.add({
- 'secret': roomSecret,
- 'display_name': displayName,
- 'device_name': deviceName,
- 'auto_accept': false
- });
- objectStoreRequest.onsuccess = e => {
- console.log(`Request successful. RoomSecret added: ${e.target.result}`);
- resolve();
- }
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static async getAllRoomSecrets() {
- try {
- const roomSecrets = await this.getAllRoomSecretEntries();
- let secrets = [];
- for (let i = 0; i < roomSecrets.length; i++) {
- secrets.push(roomSecrets[i].secret);
- }
- console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
- return(secrets);
- } catch (e) {
- this.logBrowserNotCapable();
- return 0;
- }
- }
-
- static getAllRoomSecretEntries() {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readonly');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequest = objectStore.getAll();
- objectStoreRequest.onsuccess = e => {
- resolve(e.target.result);
- }
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- });
- }
-
- static getRoomSecretEntry(roomSecret) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readonly');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequestKey = objectStore.index("secret").getKey(roomSecret);
- objectStoreRequestKey.onsuccess = e => {
- const key = e.target.result;
- if (!key) {
- console.log(`Nothing to retrieve. Entry for room_secret not existing: ${roomSecret}`);
- resolve();
- return;
- }
- const objectStoreRequestRetrieval = objectStore.get(key);
- objectStoreRequestRetrieval.onsuccess = e => {
- console.log(`Request successful. Retrieved entry for room_secret: ${key}`);
- resolve({
- "entry": e.target.result,
- "key": key
- });
- }
- objectStoreRequestRetrieval.onerror = (e) => {
- reject(e);
- }
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- });
- }
-
- static deleteRoomSecret(roomSecret) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequestKey = objectStore.index("secret").getKey(roomSecret);
- objectStoreRequestKey.onsuccess = e => {
- if (!e.target.result) {
- console.log(`Nothing to delete. room_secret not existing: ${roomSecret}`);
- resolve();
- return;
- }
- const key = e.target.result;
- const objectStoreRequestDeletion = objectStore.delete(key);
- objectStoreRequestDeletion.onsuccess = _ => {
- console.log(`Request successful. Deleted room_secret: ${key}`);
- resolve(roomSecret);
- }
- objectStoreRequestDeletion.onerror = (e) => {
- reject(e);
- }
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static clearRoomSecrets() {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequest = objectStore.clear();
- objectStoreRequest.onsuccess = _ => {
- console.log('Request successful. All room_secrets cleared');
- resolve();
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static updateRoomSecretNames(roomSecret, displayName, deviceName) {
- return this.updateRoomSecret(roomSecret, undefined, displayName, deviceName);
- }
-
- static updateRoomSecretAutoAccept(roomSecret, autoAccept) {
- return this.updateRoomSecret(roomSecret, undefined, undefined, undefined, autoAccept);
- }
-
- static updateRoomSecret(roomSecret, updatedRoomSecret = undefined, updatedDisplayName = undefined, updatedDeviceName = undefined, updatedAutoAccept = undefined) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- this.getRoomSecretEntry(roomSecret)
- .then(roomSecretEntry => {
- if (!roomSecretEntry) {
- resolve(false);
- return;
- }
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- // Do not use `updatedRoomSecret ?? roomSecretEntry.entry.secret` to ensure compatibility with older browsers
- const updatedRoomSecretEntry = {
- 'secret': updatedRoomSecret !== undefined ? updatedRoomSecret : roomSecretEntry.entry.secret,
- 'display_name': updatedDisplayName !== undefined ? updatedDisplayName : roomSecretEntry.entry.display_name,
- 'device_name': updatedDeviceName !== undefined ? updatedDeviceName : roomSecretEntry.entry.device_name,
- 'auto_accept': updatedAutoAccept !== undefined ? updatedAutoAccept : roomSecretEntry.entry.auto_accept
- };
-
- const objectStoreRequestUpdate = objectStore.put(updatedRoomSecretEntry, roomSecretEntry.key);
-
- objectStoreRequestUpdate.onsuccess = e => {
- console.log(`Request successful. Updated room_secret: ${roomSecretEntry.key}`);
- resolve({
- "entry": updatedRoomSecretEntry,
- "key": roomSecretEntry.key
- });
- }
-
- objectStoreRequestUpdate.onerror = (e) => {
- reject(e);
- }
- })
- .catch(e => reject(e));
- };
-
- DBOpenRequest.onerror = e => reject(e);
- })
- }
-}
-
class BrowserTabsConnector {
constructor() {
this.bc = new BroadcastChannel('pairdrop');
@@ -2726,114 +2386,4 @@ class BrowserTabsConnector {
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
return peerIdsBrowser;
}
-}
-
-class BackgroundCanvas {
- constructor() {
- this.c = $$('canvas');
- this.cCtx = this.c.getContext('2d');
- this.$footer = $$('footer');
-
- Events.on('bg-resize', _ => this.init());
- Events.on('redraw-canvas', _ => this.init());
- Events.on('translation-loaded', _ => this.init());
-
- //fade-in on load
- Events.on('ui-faded-in', _ => this._fadeIn());
-
- window.onresize = _ => Events.fire('bg-resize');
- }
-
- _fadeIn() {
- this.c.classList.remove('opacity-0');
- }
-
- init() {
- let oldW = this.w;
- let oldH = this.h;
- let oldOffset = this.offset
- this.w = document.documentElement.clientWidth;
- this.h = document.documentElement.clientHeight;
- this.offset = this.$footer.offsetHeight - 27;
- if (this.h >= 800) this.offset += 10;
-
- if (oldW === this.w && oldH === this.h && oldOffset === this.offset) return; // nothing has changed
-
- this.c.width = this.w;
- this.c.height = this.h;
- this.x0 = this.w / 2;
- this.y0 = this.h - this.offset;
- this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13);
-
- this.drawCircles(this.cCtx);
- }
-
-
- drawCircle(ctx, radius) {
- ctx.beginPath();
- ctx.lineWidth = 2;
- let opacity = Math.max(0, 0.3 * (1 - 1 * radius / Math.max(this.w, this.h)));
- ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`;
- ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI);
- ctx.stroke();
- }
-
- drawCircles(ctx) {
- ctx.clearRect(0, 0, this.w, this.h);
- for (let i = 0; i < 13; i++) {
- this.drawCircle(ctx, this.dw * i + 33 + 66);
- }
- }
-}
-
-class PairDrop {
- constructor() {
- Events.on('initial-translation-loaded', _ => {
- const server = new ServerConnection();
- const peers = new PeersManager(server);
- const peersUI = new PeersUI();
- const backgroundCanvas = new BackgroundCanvas();
- const languageSelectDialog = new LanguageSelectDialog();
- const receiveFileDialog = new ReceiveFileDialog();
- const receiveRequestDialog = new ReceiveRequestDialog();
- const sendTextDialog = new SendTextDialog();
- const receiveTextDialog = new ReceiveTextDialog();
- const pairDeviceDialog = new PairDeviceDialog();
- const clearDevicesDialog = new EditPairedDevicesDialog();
- const publicRoomDialog = new PublicRoomDialog();
- const base64ZipDialog = new Base64ZipDialog();
- const toast = new Toast();
- const notifications = new Notifications();
- const networkStatusUI = new NetworkStatusUI();
- const webShareTargetUI = new WebShareTargetUI();
- const webFileHandlersUI = new WebFileHandlersUI();
- const noSleepUI = new NoSleepUI();
- const broadCast = new BrowserTabsConnector();
- });
- }
-}
-
-const persistentStorage = new PersistentStorage();
-const pairDrop = new PairDrop();
-const localization = new Localization();
-
-if ('serviceWorker' in navigator) {
- navigator.serviceWorker.register('/service-worker.js')
- .then(serviceWorker => {
- console.log('Service Worker registered');
- window.serviceWorker = serviceWorker
- });
-}
-
-window.addEventListener('beforeinstallprompt', installEvent => {
- if (!window.matchMedia('(display-mode: minimal-ui)').matches) {
- // only display install btn when not installed
- const installBtn = document.querySelector('#install')
- installBtn.removeAttribute('hidden');
- installBtn.addEventListener('click', () => {
- installBtn.setAttribute('hidden', '');
- installEvent.prompt();
- });
- }
- return installEvent.preventDefault();
-});
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/public/scripts/util.js b/public/scripts/util.js
index eada792..6dba206 100644
--- a/public/scripts/util.js
+++ b/public/scripts/util.js
@@ -37,6 +37,31 @@ if (!navigator.clipboard) {
}
}
+// Polyfills
+window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
+
+window.hiddenProperty = 'hidden' in document
+ ? 'hidden'
+ : 'webkitHidden' in document
+ ? 'webkitHidden'
+ : 'mozHidden' in document
+ ? 'mozHidden'
+ : null;
+
+window.visibilityChangeEvent = 'visibilitychange' in document
+ ? 'visibilitychange'
+ : 'webkitvisibilitychange' in document
+ ? 'webkitvisibilitychange'
+ : 'mozvisibilitychange' in document
+ ? 'mozvisibilitychange'
+ : null;
+
+window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
+window.android = /android/i.test(navigator.userAgent);
+window.isMobile = window.iOS || window.android;
+
+
+// Helper functions
const zipper = (() => {
let zipWriter;
@@ -52,7 +77,8 @@ const zipper = (() => {
const blobURL = URL.createObjectURL(await zipWriter.close());
zipWriter = null;
return blobURL;
- } else {
+ }
+ else {
throw new Error("Zip file closed");
}
},
@@ -61,7 +87,8 @@ const zipper = (() => {
const file = new File([await zipWriter.close()], filename, {type: "application/zip"});
zipWriter = null;
return file;
- } else {
+ }
+ else {
throw new Error("Zip file closed");
}
},
@@ -411,3 +438,23 @@ function changeFavicon(src) {
document.querySelector('[rel="icon"]').href = src;
document.querySelector('[rel="shortcut icon"]').href = src;
}
+
+function arrayBufferToBase64(buffer) {
+ let binary = '';
+ let bytes = new Uint8Array(buffer);
+ let len = bytes.byteLength;
+ for (let i = 0; i < len; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ return window.btoa( binary );
+}
+
+function base64ToArrayBuffer(base64) {
+ let binary_string = window.atob(base64);
+ let len = binary_string.length;
+ let bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+ return bytes.buffer;
+}
\ No newline at end of file
diff --git a/public/service-worker.js b/public/service-worker.js
index f84d49d..10061b8 100644
--- a/public/service-worker.js
+++ b/public/service-worker.js
@@ -1,20 +1,24 @@
const cacheVersion = 'v1.9.4';
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
const forceFetch = false; // FOR DEVELOPMENT: Set to true to always update assets instead of using cached versions
-const urlsToCache = [
+const relativePathsToCache = [
'./',
'index.html',
'manifest.json',
- 'styles.css',
+ 'styles/styles-main.css',
+ 'styles/deferred-styles.css',
'scripts/localization.js',
+ 'scripts/main.js',
'scripts/network.js',
- 'scripts/NoSleep.min.js',
- 'scripts/QRCode.min.js',
- 'scripts/theme.js',
+ 'scripts/no-sleep.min.js',
+ 'scripts/persistent-storage.js',
+ 'scripts/qr-code.min.js',
'scripts/ui.js',
+ 'scripts/ui-main.js',
'scripts/util.js',
'scripts/zip.min.js',
'sounds/blop.mp3',
+ 'sounds/blop.ogg',
'images/favicon-96x96.png',
'images/favicon-96x96-notification.png',
'images/android-chrome-192x192.png',
@@ -32,32 +36,49 @@ const urlsToCache = [
'lang/ja.json',
'lang/nb.json',
'lang/nl.json',
+ 'lang/tr.json',
'lang/ro.json',
'lang/ru.json',
'lang/zh-CN.json'
];
+const relativePathsNotToCache = [
+ 'config'
+]
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(cacheTitle)
.then(function(cache) {
- return cache.addAll(urlsToCache).then(_ => {
- console.log('All files cached.');
- });
+ return cache
+ .addAll(relativePathsToCache)
+ .then(_ => {
+ console.log('All files cached.');
+ });
})
);
});
// fetch the resource from the network
const fromNetwork = (request, timeout) =>
- new Promise((fulfill, reject) => {
+ new Promise((resolve, reject) => {
const timeoutId = setTimeout(reject, timeout);
- fetch(request).then(response => {
- clearTimeout(timeoutId);
- fulfill(response);
- update(request).then(() => console.log("Cache successfully updated for", request.url));
- }, reject);
+ fetch(request)
+ .then(response => {
+ clearTimeout(timeoutId);
+ resolve(response);
+
+ if (doNotCacheRequest(request)) return;
+
+ update(request)
+ .then(() => console.log("Cache successfully updated for", request.url))
+ .catch(reason => console.log("Cache could not be updated for", request.url, "Reason:", reason));
+ })
+ .catch(error => {
+ // Handle any errors that occurred during the fetch
+ console.error(`Could not fetch ${request.url}. Are you online?`);
+ reject(error);
+ });
});
// fetch the resource from the browser cache
@@ -68,17 +89,32 @@ const fromCache = request =>
cache.match(request)
);
+const rootUrl = location.href.substring(0, location.href.length - "service-worker.js".length);
+const rootUrlLength = rootUrl.length;
+
+const doNotCacheRequest = request => {
+ const requestRelativePath = request.url.substring(rootUrlLength);
+ return relativePathsNotToCache.indexOf(requestRelativePath) !== -1
+};
+
// cache the current page to make it available for offline
-const update = request =>
+const update = request => new Promise((resolve, reject) => {
+ if (doNotCacheRequest(request)) {
+ reject("Url is specifically prevented from being cached in the serviceworker.");
+ return;
+ }
caches
.open(cacheTitle)
.then(cache =>
- fetch(request)
- .then(async response => {
- await cache.put(request, response);
+ fetch(request, {cache: "no-store"})
+ .then(response => {
+ cache
+ .put(request, response)
+ .then(() => resolve());
})
- .catch(() => console.log(`Cache could not be updated. ${request.url}`))
+ .catch(reason => reject(reason))
);
+});
// general strategy when making a request (eg if online try to fetch it
// from cache, if something fails fetch from network. Update cache everytime files are fetched.
@@ -90,16 +126,19 @@ self.addEventListener('fetch', function(event) {
const share_url = await evaluateRequestData(event.request);
return Response.redirect(encodeURI(share_url), 302);
})());
- } else {
+ }
+ else {
// Regular requests not related to Web Share Target.
if (forceFetch) {
event.respondWith(fromNetwork(event.request, 10000));
- } else {
+ }
+ else {
event.respondWith(
- fromCache(event.request).then(rsp => {
- // if fromCache resolves to undefined fetch from network instead
- return rsp || fromNetwork(event.request, 10000);
- })
+ fromCache(event.request)
+ .then(rsp => {
+ // if fromCache resolves to undefined fetch from network instead
+ return rsp || fromNetwork(event.request, 10000);
+ })
);
}
}
@@ -109,15 +148,16 @@ self.addEventListener('fetch', function(event) {
// on activation, we clean up the previously registered service workers
self.addEventListener('activate', evt => {
return evt.waitUntil(
- caches.keys().then(cacheNames => {
- return Promise.all(
- cacheNames.map(cacheName => {
- if (cacheName !== cacheTitle) {
- return caches.delete(cacheName);
- }
- })
- );
- })
+ caches.keys()
+ .then(cacheNames => {
+ return Promise.all(
+ cacheNames.map(cacheName => {
+ if (cacheName !== cacheTitle) {
+ return caches.delete(cacheName);
+ }
+ })
+ );
+ })
)
}
);
@@ -157,7 +197,8 @@ const evaluateRequestData = function (request) {
DBOpenRequest.onerror = _ => {
resolve(pairDropUrl);
}
- } else {
+ }
+ else {
let urlArgument = '?share-target=text';
if (title) urlArgument += `&title=${title}`;
diff --git a/public/styles/deferred-styles.css b/public/styles/deferred-styles.css
new file mode 100644
index 0000000..8fe43eb
--- /dev/null
+++ b/public/styles/deferred-styles.css
@@ -0,0 +1,727 @@
+/* All styles in this sheet are not needed on page load and deferred */
+
+/* Peers */
+
+x-peers.overflowing {
+ background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
+ linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
+ /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .2), rgba(var(--text-color), 0)),
+ radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .2), rgba(var(--text-color), 0)) 0 100%;
+
+ background-repeat: no-repeat;
+ background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
+
+ /* Opera doesn't support this in the shorthand */
+ background-attachment: local, local, scroll, scroll;
+}
+
+x-peers:has(> x-peer) {
+ --peers-per-row: 10;
+}
+
+/* peers-per-row if height is too small for 2 rows */
+@media screen and (min-height: 538px) and (max-height: 683px) and (max-width: 402px),
+screen and (min-height: 517px) and (max-height: 664px) and (min-width: 426px) {
+ x-peers:has(> x-peer) {
+ --peers-per-row: 3;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(7)) {
+ --peers-per-row: 4;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(10)) {
+ --peers-per-row: 5;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(13)) {
+ --peers-per-row: 6;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(16)) {
+ --peers-per-row: 7;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(19)) {
+ --peers-per-row: 8;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(22)) {
+ --peers-per-row: 9;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(25)) {
+ --peers-per-row: 10;
+ }
+}
+
+/* peers-per-row if height is too small for 3 rows */
+@media screen and (min-height: 683px) and (max-width: 402px),
+screen and (min-height: 664px) and (min-width: 426px) {
+ x-peers:has(> x-peer) {
+ --peers-per-row: 3;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(10)) {
+ --peers-per-row: 4;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(13)) {
+ --peers-per-row: 5;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(16)) {
+ --peers-per-row: 6;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(19)) {
+ --peers-per-row: 7;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(22)) {
+ --peers-per-row: 8;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(25)) {
+ --peers-per-row: 9;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(28)) {
+ --peers-per-row: 10;
+ }
+}
+
+/* Peer */
+
+x-peer {
+ padding: 8px;
+ align-content: start;
+ flex-wrap: wrap;
+}
+
+x-peer label {
+ width: var(--peer-width);
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ position: relative;
+}
+
+x-peer x-icon {
+ --icon-size: 40px;
+ margin-bottom: 4px;
+ transition: transform 150ms;
+ will-change: transform;
+ display: flex;
+ flex-direction: column;
+}
+
+x-peer .icon-wrapper {
+ width: var(--icon-size);
+ padding: 12px;
+ border-radius: 50%;
+ background: var(--primary-color);
+ color: white;
+ display: flex;
+}
+
+x-peer.type-secret .icon-wrapper {
+ background: var(--paired-device-color);
+}
+
+x-peer:not(.type-ip):not(.type-secret).type-public-id .icon-wrapper {
+ background: var(--public-room-color);
+}
+
+x-peer x-icon > .highlight-wrapper {
+ align-self: center;
+ align-items: center;
+ margin: 7px auto 0;
+ height: 6px;
+}
+
+x-peer x-icon > .highlight-wrapper > .highlight {
+ width: 15px;
+ height: 6px;
+ border-radius: 4px;
+ margin-left: 1px;
+ margin-right: 1px;
+ display: none;
+}
+
+x-peer.type-ip x-icon > .highlight-wrapper > .highlight.highlight-room-ip {
+ background-color: var(--primary-color);
+ display: inline;
+}
+
+x-peer.type-secret x-icon > .highlight-wrapper > .highlight.highlight-room-secret {
+ background-color: var(--paired-device-color);
+ display: inline;
+}
+
+x-peer.type-public-id x-icon > .highlight-wrapper > .highlight.highlight-room-public-id {
+ background-color: var(--public-room-color);
+ display: inline;
+}
+
+x-peer:not([status]):hover x-icon,
+x-peer:not([status]):focus x-icon {
+ transform: scale(1.05);
+}
+
+x-peer[status] x-icon {
+ box-shadow: none;
+ opacity: 0.8;
+ transform: scale(1);
+}
+
+
+x-peer.ws-peer {
+ margin-top: -1.5px;
+}
+
+x-peer.ws-peer .progress {
+ margin-top: 3px;
+}
+
+x-peer.ws-peer .icon-wrapper{
+ border: solid 3px var(--ws-peer-color);
+}
+
+x-peer.ws-peer .highlight-wrapper {
+ margin-top: 3px;
+}
+
+#websocket-fallback {
+ opacity: 0.5;
+}
+
+#websocket-fallback > span:nth-of-type(2) {
+ border-bottom: solid 2px var(--ws-peer-color);
+}
+
+.device-descriptor {
+ width: 100%;
+ text-align: center;
+}
+
+.device-descriptor > div {
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: center;
+}
+
+.status,
+.device-name,
+.connection-hash {
+ opacity: 0.7;
+}
+
+.device-name {
+ font-size: 14px;
+ white-space: nowrap;
+}
+
+.connection-hash {
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+x-peer:not([status]) .status,
+x-peer[status] .device-name {
+ display: none;
+}
+
+x-peer[status] {
+ pointer-events: none;
+}
+
+x-peer x-icon {
+ animation: pop 600ms ease-out 1;
+}
+
+@keyframes pop {
+ 0% {
+ transform: scale(0.7);
+ }
+
+ 40% {
+ transform: scale(1.2);
+ }
+}
+
+x-peer[drop] x-icon {
+ transform: scale(1.1);
+}
+
+
+ Dialog
+
+x-dialog x-background {
+ background: rgba(0, 0, 0, 0.61);
+ z-index: 10;
+ transition: opacity 300ms;
+ will-change: opacity;
+ padding: 15px;
+ overflow: overlay;
+}
+
+x-dialog x-paper {
+ display: flex;
+ flex-direction: column;
+ width: calc(100vw - 10px);
+ z-index: 3;
+ background: white;
+ border-radius: 8px;
+ max-width: 400px;
+ overflow: hidden;
+ box-sizing: border-box;
+ transition: transform 300ms;
+ will-change: transform;
+}
+
+#pair-device-dialog x-paper,
+#edit-paired-devices-dialog x-paper,
+#public-room-dialog x-paper,
+#language-select-dialog x-paper {
+ position: absolute;
+ top: max(50%, 350px);
+ margin-top: -328.5px;
+}
+
+x-paper > .row:first-of-type {
+ background-color: var(--accent-color);
+ border-bottom: solid 4px var(--border-color);
+ margin-bottom: 10px;
+}
+
+x-paper > .row:first-of-type h2 {
+ color: white;
+}
+
+#pair-device-dialog,
+#edit-paired-devices-dialog {
+ --accent-color: var(--paired-device-color);
+}
+
+#public-room-dialog {
+ --accent-color: var(--public-room-color);
+}
+
+#pair-device-dialog ::-moz-selection,
+#pair-device-dialog ::selection {
+ color: black;
+ background: var(--paired-device-color);
+}
+
+#public-room-dialog ::-moz-selection,
+#public-room-dialog ::selection {
+ color: black;
+ background: var(--public-room-color);
+}
+
+x-dialog:not([show]) {
+ pointer-events: none;
+}
+
+x-dialog:not([show]) x-paper {
+ transform: scale(0.1);
+}
+
+x-dialog a {
+ color: var(--primary-color);
+}
+
+/* Pair Devices Dialog & Public Room Dialog */
+
+.input-key-container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ margin-top: 10px;
+}
+
+.input-key-container > input {
+ width: 45px;
+ height: 45px;
+ font-size: 30px;
+ padding: 0;
+ text-align: center;
+ text-transform: uppercase;
+ display: -webkit-box !important;
+ display: -webkit-flex !important;
+ display: -moz-flex !important;
+ display: -ms-flexbox !important;
+ display: flex !important;
+ -webkit-justify-content: center;
+ -ms-justify-content: center;
+ justify-content: center;
+}
+
+.input-key-container > input {
+ margin: 0 3px;
+}
+
+.input-key-container.six-chars > input:nth-of-type(4) {
+ margin-left: 5%;
+}
+
+.key {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ user-select: text;
+ display: inline-block;
+ font-size: 50px;
+ letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 20px);
+ text-indent: calc(0.5 * (11px + min(calc((100vw - 80px - 99px) / 100 * 6), 28px)));
+ margin: 25px 0;
+}
+
+.key-qr-code {
+ margin: 16px;
+ width: fit-content;
+ align-self: center;
+}
+
+.key-instructions {
+ flex-direction: column;
+}
+
+x-dialog h2 {
+ margin-top: 5px;
+ margin-bottom: 0;
+}
+
+x-dialog hr {
+ height: 3px;
+ border: none;
+ width: 100%;
+ background-color: var(--border-color);
+}
+
+.hr-note {
+ margin-top: 10px;
+ margin-bottom: 20px;
+}
+
+.hr-note hr {
+ margin-bottom: -2px;
+}
+
+.hr-note > div {
+ height: 0;
+ transform: translateY(-10px);
+}
+
+
+.hr-note > div > span {
+ padding: 3px 10px;
+ border-radius: 10px;
+ color: rgb(var(--text-color));
+ background-color: rgb(var(--bg-color));
+ border: var(--border-color) solid 3px;
+ text-transform: uppercase;
+}
+
+#pair-device-dialog x-background {
+ padding: 16px!important;
+}
+
+/* Edit Paired Devices Dialog */
+.paired-devices-wrapper:empty:before {
+ content: attr(data-empty);
+}
+
+.paired-devices-wrapper:empty {
+ padding: 10px;
+}
+
+.paired-devices-wrapper {
+ border-top: solid 4px var(--paired-device-color);
+ border-bottom: solid 4px var(--paired-device-color);
+ max-height: 65vh;
+ overflow: scroll;
+ background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
+ linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
+ /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .3), rgba(var(--text-color), 0)),
+ radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .3), rgba(var(--text-color), 0)) 0 100%;
+
+ background-repeat: no-repeat;
+ background-size: 100% 80px, 100% 80px, 100% 24px, 100% 24px;
+
+ /* Opera doesn't support this in the shorthand */
+ background-attachment: local, local, scroll, scroll;
+}
+
+.paired-device {
+ display: flex;
+ justify-content: space-between;
+ flex-direction: column;
+ align-items: center;
+}
+
+.paired-device:not(:last-child) {
+ border-bottom: solid 4px var(--paired-device-color);
+}
+
+.paired-device > .display-name,
+.paired-device > .device-name {
+ width: 100%;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ text-align: center;
+ align-self: center;
+ border-bottom: solid 2px rgba(128, 128, 128, 0.5);
+ opacity: 1;
+}
+.paired-device span {
+ width: 100%;
+}
+
+.paired-device > .button-wrapper {
+ display: flex;
+ height: 36px;
+ justify-content: space-between;
+ flex-direction: row;
+ align-items: center;
+ width: 100%;
+}
+
+.paired-device > .button-wrapper > label,
+.paired-device > .button-wrapper > button {
+ display: flex;
+ align-items: center;
+ text-align: center;
+ white-space: nowrap;
+ justify-content: center;
+ width: 50%;
+ padding-left: 6px;
+ padding-right: 6px;
+ height: 36px;
+}
+
+.paired-device > .button-wrapper > :not(:last-child) {
+ border-right: solid 1px rgba(128, 128, 128, 0.5);
+}
+
+.paired-device > .button-wrapper > :not(:first-child) {
+ border-left: solid 1px rgba(128, 128, 128, 0.5);
+}
+
+.paired-device * {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Receive Dialog */
+
+x-paper > .row {
+ padding: 10px;
+}
+
+/* button row*/
+x-paper > .button-row {
+ border-top: solid 3px var(--border-color);
+ height: 50px;
+ margin-top: 10px;
+}
+
+x-paper > .button-row > .button {
+ height: 100%;
+ width: 100%;
+}
+
+html:not([dir="rtl"]) x-paper > .button-row > .button:not(:first-child) {
+ border-right: solid 1.5px var(--border-color);
+}
+
+html:not([dir="rtl"]) x-paper > .button-row > .button:not(:last-child) {
+ border-left: solid 1.5px var(--border-color);
+}
+
+html[dir="rtl"] x-paper > .button-row > .button:not(:first-child) {
+ border-left: solid 1.5px var(--border-color);
+}
+
+html[dir="rtl"] x-paper > .button-row > .button:not(:last-child) {
+ border-right: solid 1.5px var(--border-color);
+}
+
+.language-buttons > button > span {
+ margin: 0 0.3em;
+}
+
+.file-description {
+ max-width: 100%;
+}
+
+.file-description span {
+ display: inline;
+ word-break: normal;
+}
+
+.file-name {
+ font-style: italic;
+ max-width: 100%;
+ margin-top: 5px;
+}
+
+.file-stem {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-right: 1px;
+}
+
+/* Send Text Dialog */
+x-dialog .dialog-subheader {
+ padding-top: 16px;
+ padding-bottom: 16px;
+}
+
+#send-text-dialog .display-name-wrapper {
+ padding-bottom: 0;
+}
+
+#text-input {
+ min-height: 200px;
+ width: 100%;
+}
+
+/* Receive Text Dialog */
+
+#receive-text-dialog #text {
+ width: 100%;
+ word-break: break-all;
+ max-height: calc(100vh - 393px);
+ overflow-x: hidden;
+ overflow-y: auto;
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ user-select: text;
+ white-space: pre-wrap;
+}
+
+#receive-text-dialog #text a {
+ cursor: pointer;
+}
+
+#receive-text-dialog #text a:hover {
+ text-decoration: underline;
+}
+
+#receive-text-dialog h3 {
+ /* Select the received text when double-clicking the dialog */
+ user-select: none;
+ pointer-events: none;
+}
+
+.row-separator {
+ border-bottom: solid 2.5px var(--border-color);
+ margin: auto -24px;
+}
+
+#base64-paste-btn,
+#base64-paste-dialog .textarea {
+ width: 100%;
+ height: 40vh;
+ border: solid 12px #438cff;
+ border-radius: 8px;
+}
+
+#base64-paste-dialog .textarea {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+
+#base64-paste-dialog .textarea::before {
+ font-size: 15px;
+ letter-spacing: 0.12em;
+ color: var(--primary-color);
+ font-weight: 700;
+ text-transform: uppercase;
+ white-space: pre-wrap;
+}
+
+
+/* Peer loading Indicator */
+
+.progress {
+ width: 80px;
+ height: 80px;
+ position: absolute;
+ top: -8px;
+ clip: rect(0px, 80px, 80px, 40px);
+ --progress: rotate(0deg);
+ transition: transform 200ms;
+}
+
+.circle {
+ width: 72px;
+ height: 72px;
+ border: 4px solid var(--primary-color);
+ border-radius: 40px;
+ position: absolute;
+ clip: rect(0px, 40px, 80px, 0px);
+ will-change: transform;
+ transform: var(--progress);
+}
+
+.over50 {
+ clip: rect(auto, auto, auto, auto);
+}
+
+.over50 .circle.right {
+ transform: rotate(180deg);
+}
+
+
+/*
+ Color Themes
+*/
+
+/* Colored Elements */
+
+x-dialog x-paper {
+ background-color: rgb(var(--bg-color));
+}
+
+.textarea {
+ color: rgb(var(--text-color)) !important;
+ background-color: var(--bg-color-secondary) !important;
+}
+
+.textarea * {
+ margin: 0 !important;
+ padding: 0 !important;
+ color: unset !important;
+ background: unset !important;
+ border: unset !important;
+ opacity: unset !important;
+ font-family: inherit !important;
+ font-size: inherit !important;
+ font-style: unset !important;
+ font-weight: unset !important;
+}
+
+/* Image/Video/Audio Preview */
+.file-preview {
+ margin-bottom: 15px;
+}
+
+.file-preview:empty {
+ display: none;
+}
+
+.file-preview > img,
+.file-preview > audio,
+.file-preview > video {
+ max-width: 100%;
+ max-height: 40vh;
+ margin: auto;
+ display: block;
+}
\ No newline at end of file
diff --git a/public/styles.css b/public/styles/styles-main.css
similarity index 52%
rename from public/styles.css
rename to public/styles/styles-main.css
index 4d5fadb..ac28b04 100644
--- a/public/styles.css
+++ b/public/styles/styles-main.css
@@ -1,3 +1,5 @@
+/* All styles in this sheet are needed on page load */
+
/* Constants */
:root {
@@ -7,6 +9,7 @@
--public-room-color: #db8500;
--accent-color: var(--primary-color);
--peer-width: 120px;
+ --ws-peer-color: #ff6b6b;
color-scheme: light dark;
}
@@ -30,17 +33,10 @@ body {
body {
height: 100%;
- /* mobile viewport bug fix */
- min-height: -moz-available; /* WebKit-based browsers will ignore this. */
- min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
- min-height: fill-available;
}
html {
height: 100%;
- min-height: -moz-available; /* WebKit-based browsers will ignore this. */
- min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
- min-height: fill-available;
}
.fw {
@@ -292,126 +288,13 @@ x-noscript {
}
-/* Peers List */
+/* Peers */
#x-peers-filler {
display: flex;
flex-grow: 1;
}
-x-peers {
- position: relative;
- display: flex;
- flex-flow: row wrap;
- flex-grow: 1;
- align-items: start !important;
- justify-content: center;
-
- z-index: 2;
- transition: --bg-color 0.5s ease;
- overflow-y: scroll;
- overflow-x: hidden;
- overscroll-behavior-x: none;
- scrollbar-width: none;
-
- --peers-per-row: 6; /* default if browser does not support :has selector */
- --x-peers-width: min(100vw, calc(var(--peers-per-row) * (var(--peer-width) + 25px) - 16px));
- width: var(--x-peers-width);
- margin-right: 20px;
- margin-left: 20px;
-}
-
-x-peers.overflowing {
- background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
- linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
- /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .2), rgba(var(--text-color), 0)),
- radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .2), rgba(var(--text-color), 0)) 0 100%;
-
- background-repeat: no-repeat;
- background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
-
- /* Opera doesn't support this in the shorthand */
- background-attachment: local, local, scroll, scroll;
-}
-
-x-peers:has(> x-peer) {
- --peers-per-row: 10;
-}
-
-@media screen and (min-height: 505px) and (max-height: 649px) and (max-width: 426px),
-screen and (min-height: 486px) and (max-height: 631px) and (min-width: 426px) {
- x-peers:has(> x-peer) {
- --peers-per-row: 3;
- }
-
- x-peers:has(> x-peer:nth-of-type(7)) {
- --peers-per-row: 4;
- }
-
- x-peers:has(> x-peer:nth-of-type(10)) {
- --peers-per-row: 5;
- }
-
- x-peers:has(> x-peer:nth-of-type(13)) {
- --peers-per-row: 6;
- }
-
- x-peers:has(> x-peer:nth-of-type(16)) {
- --peers-per-row: 7;
- }
-
- x-peers:has(> x-peer:nth-of-type(19)) {
- --peers-per-row: 8;
- }
-
- x-peers:has(> x-peer:nth-of-type(22)) {
- --peers-per-row: 9;
- }
-
- x-peers:has(> x-peer:nth-of-type(25)) {
- --peers-per-row: 10;
- }
-}
-
-@media screen and (min-height: 649px) and (max-width: 425px),
-screen and (min-height: 631px) and (min-width: 426px) {
- x-peers:has(> x-peer) {
- --peers-per-row: 3;
- }
-
- x-peers:has(> x-peer:nth-of-type(10)) {
- --peers-per-row: 4;
- }
-
- x-peers:has(> x-peer:nth-of-type(13)) {
- --peers-per-row: 5;
- }
-
- x-peers:has(> x-peer:nth-of-type(16)) {
- --peers-per-row: 6;
- }
-
- x-peers:has(> x-peer:nth-of-type(19)) {
- --peers-per-row: 7;
- }
-
- x-peers:has(> x-peer:nth-of-type(22)) {
- --peers-per-row: 8;
- }
-
- x-peers:has(> x-peer:nth-of-type(25)) {
- --peers-per-row: 9;
- }
-
- x-peers:has(> x-peer:nth-of-type(28)) {
- --peers-per-row: 10;
- }
-}
-
-::-webkit-scrollbar {
- display: none;
-}
-
/* Empty Peers List */
x-no-peers {
@@ -449,153 +332,33 @@ x-no-peers[drop-bg] * {
}
-
-/* Peer */
-
-x-peer {
- padding: 8px;
- align-content: start;
- flex-wrap: wrap;
-}
-
-x-peer label {
- width: var(--peer-width);
- touch-action: manipulation;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
- position: relative;
-}
-
input[type="file"] {
visibility: hidden;
position: absolute;
}
-x-peer x-icon {
- --icon-size: 40px;
- margin-bottom: 4px;
- transition: transform 150ms;
- will-change: transform;
+x-peers {
+ position: relative;
display: flex;
- flex-direction: column;
+ flex-flow: row wrap;
+ flex-grow: 1;
+ align-items: start !important;
+ justify-content: center;
+
+ z-index: 2;
+ transition: --bg-color 0.5s ease;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ overscroll-behavior-x: none;
+ scrollbar-width: none;
+
+ --peers-per-row: 6; /* default if browser does not support :has selector */
+ --x-peers-width: min(100vw, calc(var(--peers-per-row) * (var(--peer-width) + 25px) - 16px));
+ width: var(--x-peers-width);
+ margin-right: 20px;
+ margin-left: 20px;
}
-x-peer .icon-wrapper {
- width: var(--icon-size);
- padding: 12px;
- border-radius: 50%;
- background: var(--primary-color);
- color: white;
- display: flex;
-}
-
-x-peer.type-secret .icon-wrapper {
- background: var(--paired-device-color);
-}
-
-x-peer:not(.type-ip):not(.type-secret).type-public-id .icon-wrapper {
- background: var(--public-room-color);
-}
-
-x-peer x-icon > .highlight-wrapper {
- align-self: center;
- align-items: center;
- margin: 7px auto 0;
- height: 6px;
-}
-
-x-peer x-icon > .highlight-wrapper > .highlight {
- width: 15px;
- height: 6px;
- border-radius: 4px;
- margin-left: 1px;
- margin-right: 1px;
- display: none;
-}
-
-x-peer.type-ip x-icon > .highlight-wrapper > .highlight.highlight-room-ip {
- background-color: var(--primary-color);
- display: inline;
-}
-
-x-peer.type-secret x-icon > .highlight-wrapper > .highlight.highlight-room-secret {
- background-color: var(--paired-device-color);
- display: inline;
-}
-
-x-peer.type-public-id x-icon > .highlight-wrapper > .highlight.highlight-room-public-id {
- background-color: var(--public-room-color);
- display: inline;
-}
-
-x-peer:not([status]):hover x-icon,
-x-peer:not([status]):focus x-icon {
- transform: scale(1.05);
-}
-
-x-peer[status] x-icon {
- box-shadow: none;
- opacity: 0.8;
- transform: scale(1);
-}
-
-.device-descriptor {
- width: 100%;
- text-align: center;
-}
-
-.device-descriptor > div {
- width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: center;
-}
-
-.status,
-.device-name,
-.connection-hash {
- opacity: 0.7;
-}
-
-.device-name {
- font-size: 14px;
- white-space: nowrap;
-}
-
-.connection-hash {
- font-size: 12px;
- white-space: nowrap;
-}
-
-x-peer:not([status]) .status,
-x-peer[status] .device-name {
- display: none;
-}
-
-x-peer[status] {
- pointer-events: none;
-}
-
-x-peer x-icon {
- animation: pop 600ms ease-out 1;
-}
-
-@keyframes pop {
- 0% {
- transform: scale(0.7);
- }
-
- 40% {
- transform: scale(1.2);
- }
-}
-
-x-peer[drop] x-icon {
- transform: scale(1.1);
-}
-
-
-
/* Footer */
footer {
@@ -702,403 +465,12 @@ html[dir="rtl"] #edit-pen {
transform: rotateY(180deg);
}
-/* Dialog */
-
-x-dialog x-background {
- background: rgba(0, 0, 0, 0.61);
- z-index: 10;
- transition: opacity 300ms;
- will-change: opacity;
- padding: 15px;
- overflow: overlay;
-}
-
-x-dialog x-paper {
- display: flex;
- flex-direction: column;
- width: calc(100vw - 10px);
- z-index: 3;
- background: white;
- border-radius: 8px;
- max-width: 400px;
- overflow: hidden;
- box-sizing: border-box;
- transition: transform 300ms;
- will-change: transform;
-}
-
-#pair-device-dialog x-paper,
-#edit-paired-devices-dialog x-paper,
-#public-room-dialog x-paper,
-#language-select-dialog x-paper {
- position: absolute;
- top: max(50%, 350px);
- margin-top: -328.5px;
-}
-
-x-paper > .row:first-of-type {
- background-color: var(--accent-color);
- border-bottom: solid 4px var(--border-color);
- margin-bottom: 10px;
-}
-
-x-paper > .row:first-of-type h2 {
- color: white;
-}
-
-#pair-device-dialog,
-#edit-paired-devices-dialog {
- --accent-color: var(--paired-device-color);
-}
-
-#public-room-dialog {
- --accent-color: var(--public-room-color);
-}
-
-#pair-device-dialog ::-moz-selection,
-#pair-device-dialog ::selection {
- color: black;
- background: var(--paired-device-color);
-}
-
-#public-room-dialog ::-moz-selection,
-#public-room-dialog ::selection {
- color: black;
- background: var(--public-room-color);
-}
-
-x-dialog:not([show]) {
- pointer-events: none;
-}
-
-x-dialog:not([show]) x-paper {
- transform: scale(0.1);
-}
-
+/* Dialogs needed on page load */
x-dialog:not([show]) x-background {
opacity: 0;
}
-x-dialog a {
- color: var(--primary-color);
-}
-
-/* Pair Devices Dialog & Public Room Dialog */
-
-.input-key-container {
- width: 100%;
- display: flex;
- justify-content: center;
- margin-top: 10px;
-}
-
-.input-key-container > input {
- width: 45px;
- height: 45px;
- font-size: 30px;
- padding: 0;
- text-align: center;
- text-transform: uppercase;
- display: -webkit-box !important;
- display: -webkit-flex !important;
- display: -moz-flex !important;
- display: -ms-flexbox !important;
- display: flex !important;
- -webkit-justify-content: center;
- -ms-justify-content: center;
- justify-content: center;
-}
-
-.input-key-container > input {
- margin: 0 3px;
-}
-
-.input-key-container.six-chars > input:nth-of-type(4) {
- margin-left: 5%;
-}
-
-.key {
- -webkit-user-select: text;
- -moz-user-select: text;
- user-select: text;
- display: inline-block;
- font-size: 50px;
- letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 20px);
- text-indent: calc(0.5 * (11px + min(calc((100vw - 80px - 99px) / 100 * 6), 28px)));
- margin: 25px 0;
-}
-
-.key-qr-code {
- margin: 16px;
- width: fit-content;
- align-self: center;
-}
-
-.key-instructions {
- flex-direction: column;
-}
-
-x-dialog h2 {
- margin-top: 5px;
- margin-bottom: 0;
-}
-
-x-dialog hr {
- height: 3px;
- border: none;
- width: 100%;
- background-color: var(--border-color);
-}
-
-.hr-note {
- margin-top: 10px;
- margin-bottom: 20px;
-}
-
-.hr-note hr {
- margin-bottom: -2px;
-}
-
-.hr-note > div {
- height: 0;
- transform: translateY(-10px);
-}
-
-
-.hr-note > div > span {
- padding: 3px 10px;
- border-radius: 10px;
- color: rgb(var(--text-color));
- background-color: rgb(var(--bg-color));
- border: var(--border-color) solid 3px;
- text-transform: uppercase;
-}
-
-#pair-device-dialog x-background {
- padding: 16px!important;
-}
-
-/* Edit Paired Devices Dialog */
-.paired-devices-wrapper:empty:before {
- content: attr(data-empty);
-}
-
-.paired-devices-wrapper:empty {
- padding: 10px;
-}
-
-.paired-devices-wrapper {
- border-top: solid 4px var(--paired-device-color);
- border-bottom: solid 4px var(--paired-device-color);
- max-height: 65vh;
- overflow: scroll;
- background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
- linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
- /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .3), rgba(var(--text-color), 0)),
- radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .3), rgba(var(--text-color), 0)) 0 100%;
-
- background-repeat: no-repeat;
- background-size: 100% 80px, 100% 80px, 100% 24px, 100% 24px;
-
- /* Opera doesn't support this in the shorthand */
- background-attachment: local, local, scroll, scroll;
-}
-
-.paired-device {
- display: flex;
- justify-content: space-between;
- flex-direction: column;
- align-items: center;
-}
-
-.paired-device:not(:last-child) {
- border-bottom: solid 4px var(--paired-device-color);
-}
-
-.paired-device > .display-name,
-.paired-device > .device-name {
- width: 100%;
- height: 36px;
- display: flex;
- align-items: center;
- text-align: center;
- align-self: center;
- border-bottom: solid 2px rgba(128, 128, 128, 0.5);
- opacity: 1;
-}
-.paired-device span {
- width: 100%;
-}
-
-.paired-device > .button-wrapper {
- display: flex;
- height: 36px;
- justify-content: space-between;
- flex-direction: row;
- align-items: center;
- width: 100%;
-}
-
-.paired-device > .button-wrapper > label,
-.paired-device > .button-wrapper > button {
- display: flex;
- align-items: center;
- text-align: center;
- white-space: nowrap;
- justify-content: center;
- width: 50%;
- padding-left: 6px;
- padding-right: 6px;
- height: 36px;
-}
-
-.paired-device > .button-wrapper > :not(:last-child) {
- border-right: solid 1px rgba(128, 128, 128, 0.5);
-}
-
-.paired-device > .button-wrapper > :not(:first-child) {
- border-left: solid 1px rgba(128, 128, 128, 0.5);
-}
-
-.paired-device * {
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Receive Dialog */
-
-x-paper > .row {
- padding: 10px;
-}
-
-/* button row*/
-x-paper > .button-row {
- border-top: solid 3px var(--border-color);
- height: 50px;
- margin-top: 10px;
-}
-
-x-paper > .button-row > .button {
- height: 100%;
- width: 100%;
-}
-
-html:not([dir="rtl"]) x-paper > .button-row > .button:not(:first-child) {
- border-right: solid 1.5px var(--border-color);
-}
-
-html:not([dir="rtl"]) x-paper > .button-row > .button:not(:last-child) {
- border-left: solid 1.5px var(--border-color);
-}
-
-html[dir="rtl"] x-paper > .button-row > .button:not(:first-child) {
- border-left: solid 1.5px var(--border-color);
-}
-
-html[dir="rtl"] x-paper > .button-row > .button:not(:last-child) {
- border-right: solid 1.5px var(--border-color);
-}
-
-.language-buttons > button > span {
- margin: 0 0.3em;
-}
-
-.file-description {
- max-width: 100%;
-}
-
-.file-description span {
- display: inline;
- word-break: normal;
-}
-
-.file-name {
- font-style: italic;
- max-width: 100%;
- margin-top: 5px;
-}
-
-.file-stem {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- padding-right: 1px;
-}
-
-/* Send Text Dialog */
-x-dialog .dialog-subheader {
- padding-top: 16px;
- padding-bottom: 16px;
-}
-
-#send-text-dialog .display-name-wrapper {
- padding-bottom: 0;
-}
-
-#text-input {
- min-height: 200px;
- width: 100%;
-}
-
-/* Receive Text Dialog */
-
-#receive-text-dialog #text {
- width: 100%;
- word-break: break-all;
- max-height: calc(100vh - 393px);
- overflow-x: hidden;
- overflow-y: auto;
- -webkit-user-select: text;
- -moz-user-select: text;
- user-select: text;
- white-space: pre-wrap;
-}
-
-#receive-text-dialog #text a {
- cursor: pointer;
-}
-
-#receive-text-dialog #text a:hover {
- text-decoration: underline;
-}
-
-#receive-text-dialog h3 {
- /* Select the received text when double-clicking the dialog */
- user-select: none;
- pointer-events: none;
-}
-
-.row-separator {
- border-bottom: solid 2.5px var(--border-color);
- margin: auto -24px;
-}
-
-#base64-paste-btn,
-#base64-paste-dialog .textarea {
- width: 100%;
- height: 40vh;
- border: solid 12px #438cff;
- border-radius: 8px;
-}
-
-#base64-paste-dialog .textarea {
- display: flex;
- align-items: center;
- justify-content: center;
- text-align: center;
-}
-
-#base64-paste-dialog .textarea::before {
- font-size: 15px;
- letter-spacing: 0.12em;
- color: var(--primary-color);
- font-weight: 700;
- text-transform: uppercase;
- white-space: pre-wrap;
-}
-
-
/* Button */
.button {
@@ -1310,76 +682,11 @@ canvas.circles {
left: 0;
}
-/* Loading Indicator */
-
-.progress {
- width: 80px;
- height: 80px;
- position: absolute;
- top: -8px;
- clip: rect(0px, 80px, 80px, 40px);
- --progress: rotate(0deg);
- transition: transform 200ms;
-}
-
-.circle {
- width: 72px;
- height: 72px;
- border: 4px solid var(--primary-color);
- border-radius: 40px;
- position: absolute;
- clip: rect(0px, 40px, 80px, 0px);
- will-change: transform;
- transform: var(--progress);
-}
-
-.over50 {
- clip: rect(auto, auto, auto, auto);
-}
-
-.over50 .circle.right {
- transform: rotate(180deg);
-}
-
-
/* Generic placeholder */
[placeholder]:empty:before {
content: attr(placeholder);
}
-/* Toast */
-
-.toast-container {
- padding: 0 8px 24px;
- overflow: hidden;
- pointer-events: none;
-}
-
-x-toast {
- position: absolute;
- min-height: 48px;
- top: 50px;
- width: 100%;
- max-width: 344px;
- background-color: rgb(var(--text-color));
- color: rgb(var(--bg-color));
- align-items: center;
- box-sizing: border-box;
- padding: 8px 24px;
- z-index: 20;
- transition: opacity 200ms, transform 300ms ease-out;
- cursor: default;
- line-height: 24px;
- border-radius: 8px;
- pointer-events: all;
-}
-
-x-toast:not([show]):not(:hover) {
- opacity: 0;
- transform: translateY(-100px);
-}
-
-
/* Instructions */
x-instructions {
@@ -1450,6 +757,38 @@ x-peers:empty~x-instructions {
}
}
+/* Toast */
+
+.toast-container {
+ padding: 0 8px 24px;
+ overflow: hidden;
+ pointer-events: none;
+}
+
+x-toast {
+ position: absolute;
+ min-height: 48px;
+ top: 50px;
+ width: 100%;
+ max-width: 344px;
+ background-color: rgb(var(--text-color));
+ color: rgb(var(--bg-color));
+ align-items: center;
+ box-sizing: border-box;
+ padding: 8px 24px;
+ z-index: 20;
+ transition: opacity 200ms, transform 300ms ease-out;
+ cursor: default;
+ line-height: 24px;
+ border-radius: 8px;
+ pointer-events: all;
+}
+
+x-toast:not([show]):not(:hover) {
+ opacity: 0;
+ transform: translateY(-100px);
+}
+
/*
Color Themes
*/
@@ -1480,46 +819,6 @@ body {
transition: background-color 0.5s ease;
}
-x-dialog x-paper {
- background-color: rgb(var(--bg-color));
-}
-
-.textarea {
- color: rgb(var(--text-color)) !important;
- background-color: var(--bg-color-secondary) !important;
-}
-
-.textarea * {
- margin: 0 !important;
- padding: 0 !important;
- color: unset !important;
- background: unset !important;
- border: unset !important;
- opacity: unset !important;
- font-family: inherit !important;
- font-size: inherit !important;
- font-style: unset !important;
- font-weight: unset !important;
-}
-
-/* Image/Video/Audio Preview */
-.file-preview {
- margin-bottom: 15px;
-}
-
-.file-preview:empty {
- display: none;
-}
-
-.file-preview > img,
-.file-preview > audio,
-.file-preview > video {
- max-width: 100%;
- max-height: 40vh;
- margin: auto;
- display: block;
-}
-
/* Styles for users who prefer dark mode at the OS level */
@media (prefers-color-scheme: dark) {
@@ -1555,12 +854,20 @@ x-dialog x-paper {
}
/*
- iOS specific styles
+ Browser specific styles
*/
-@supports (-webkit-overflow-scrolling: touch) {
- html {
- min-height: -webkit-fill-available;
- }
+
+body {
+ /* mobile viewport bug fix */
+ min-height: -moz-available; /* WebKit-based browsers will ignore this. */
+ min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
+ min-height: fill-available;
+}
+
+html {
+ min-height: -moz-available; /* WebKit-based browsers will ignore this. */
+ min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
+ min-height: fill-available;
}
/* webkit scrollbar style*/
diff --git a/public_included_ws_fallback/images/android-chrome-192x192-maskable.png b/public_included_ws_fallback/images/android-chrome-192x192-maskable.png
deleted file mode 100644
index f0e9245..0000000
Binary files a/public_included_ws_fallback/images/android-chrome-192x192-maskable.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/android-chrome-192x192.png b/public_included_ws_fallback/images/android-chrome-192x192.png
deleted file mode 100644
index 0bdca51..0000000
Binary files a/public_included_ws_fallback/images/android-chrome-192x192.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/android-chrome-512x512-maskable.png b/public_included_ws_fallback/images/android-chrome-512x512-maskable.png
deleted file mode 100644
index cdda606..0000000
Binary files a/public_included_ws_fallback/images/android-chrome-512x512-maskable.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/android-chrome-512x512.png b/public_included_ws_fallback/images/android-chrome-512x512.png
deleted file mode 100644
index b01e679..0000000
Binary files a/public_included_ws_fallback/images/android-chrome-512x512.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/apple-touch-icon.png b/public_included_ws_fallback/images/apple-touch-icon.png
deleted file mode 100644
index 0a32878..0000000
Binary files a/public_included_ws_fallback/images/apple-touch-icon.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/favicon-96x96-notification.png b/public_included_ws_fallback/images/favicon-96x96-notification.png
deleted file mode 100644
index d3407c3..0000000
Binary files a/public_included_ws_fallback/images/favicon-96x96-notification.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/favicon-96x96.png b/public_included_ws_fallback/images/favicon-96x96.png
deleted file mode 100644
index b8a5746..0000000
Binary files a/public_included_ws_fallback/images/favicon-96x96.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/logo_blue_512x512.png b/public_included_ws_fallback/images/logo_blue_512x512.png
deleted file mode 100644
index 41d13fc..0000000
Binary files a/public_included_ws_fallback/images/logo_blue_512x512.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/logo_transparent_128x128.png b/public_included_ws_fallback/images/logo_transparent_128x128.png
deleted file mode 100644
index c276efe..0000000
Binary files a/public_included_ws_fallback/images/logo_transparent_128x128.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/logo_transparent_512x512.png b/public_included_ws_fallback/images/logo_transparent_512x512.png
deleted file mode 100644
index 367e24f..0000000
Binary files a/public_included_ws_fallback/images/logo_transparent_512x512.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/logo_transparent_white_512x512.png b/public_included_ws_fallback/images/logo_transparent_white_512x512.png
deleted file mode 100644
index 37589b6..0000000
Binary files a/public_included_ws_fallback/images/logo_transparent_white_512x512.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/logo_white_512x512.png b/public_included_ws_fallback/images/logo_white_512x512.png
deleted file mode 100644
index d7750d9..0000000
Binary files a/public_included_ws_fallback/images/logo_white_512x512.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/mstile-150x150.png b/public_included_ws_fallback/images/mstile-150x150.png
deleted file mode 100644
index 6380e32..0000000
Binary files a/public_included_ws_fallback/images/mstile-150x150.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_1.png b/public_included_ws_fallback/images/pairdrop_screenshot_mobile_1.png
deleted file mode 100644
index d93aafb..0000000
Binary files a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_1.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_2.png b/public_included_ws_fallback/images/pairdrop_screenshot_mobile_2.png
deleted file mode 100644
index 51ace10..0000000
Binary files a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_2.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_3.png b/public_included_ws_fallback/images/pairdrop_screenshot_mobile_3.png
deleted file mode 100644
index 57ad15a..0000000
Binary files a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_3.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_4.png b/public_included_ws_fallback/images/pairdrop_screenshot_mobile_4.png
deleted file mode 100644
index d5811ad..0000000
Binary files a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_4.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_5.png b/public_included_ws_fallback/images/pairdrop_screenshot_mobile_5.png
deleted file mode 100644
index d205fd9..0000000
Binary files a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_5.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_6.png b/public_included_ws_fallback/images/pairdrop_screenshot_mobile_6.png
deleted file mode 100644
index 23c06ae..0000000
Binary files a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_6.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_7.png b/public_included_ws_fallback/images/pairdrop_screenshot_mobile_7.png
deleted file mode 100644
index c32980a..0000000
Binary files a/public_included_ws_fallback/images/pairdrop_screenshot_mobile_7.png and /dev/null differ
diff --git a/public_included_ws_fallback/images/safari-pinned-tab.svg b/public_included_ws_fallback/images/safari-pinned-tab.svg
deleted file mode 100644
index 263ee4e..0000000
--- a/public_included_ws_fallback/images/safari-pinned-tab.svg
+++ /dev/null
@@ -1,251 +0,0 @@
-
-
-
-
-Created by potrace 1.11, written by Peter Selinger 2001-2013
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public_included_ws_fallback/images/snapdrop-graphics.sketch b/public_included_ws_fallback/images/snapdrop-graphics.sketch
deleted file mode 100644
index b8b756a..0000000
Binary files a/public_included_ws_fallback/images/snapdrop-graphics.sketch and /dev/null differ
diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html
deleted file mode 100644
index 8387ab7..0000000
--- a/public_included_ws_fallback/index.html
+++ /dev/null
@@ -1,618 +0,0 @@
-
-
-
-
-
-
-
- PairDrop
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- in room IAIAI
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- العربية
- -
- (Arabic)
-
-
- Deutsch
- -
- (German)
-
-
- English
-
-
- Español
- -
- (Spanish)
-
-
- Français
- -
- (French)
-
-
- Bahasa Indonesia
- -
- (Indonesian)
-
-
- Italiano
- -
- (Italian)
-
-
- Nederlands
- -
- (Dutch)
-
-
- Norsk
- -
- (Norwegian)
-
-
- Română
- -
- (Romanian)
-
-
- Русский язык
- -
- (Russian)
-
-
- Türkçe
- -
- (Turkish)
-
-
- 中文
- -
- (Chinese)
-
-
- 日本語
- -
- (Japanese)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Enable JavaScript
- PairDrop works only with JavaScript
-
-
-
-
\ No newline at end of file
diff --git a/public_included_ws_fallback/lang/ar.json b/public_included_ws_fallback/lang/ar.json
deleted file mode 100644
index 6a58459..0000000
--- a/public_included_ws_fallback/lang/ar.json
+++ /dev/null
@@ -1,159 +0,0 @@
-{
- "footer": {
- "webrtc": "إذا لم يكن WebRTC متاحًا.",
- "public-room-devices_title": "يمكن اكتشافك بواسطة الأجهزة الموجودة في هذه الغرفة العامة المستقلة عن الشبكة.",
- "display-name_data-placeholder": "تحميل …",
- "display-name_title": "قم بتحرير اسم جهازك بشكل دائم",
- "traffic": "حركة المرور هي",
- "paired-devices_title": "يمكن اكتشافك بواسطة الأجهزة المقترنة في جميع الأوقات بشكل مستقل عن الشبكة.",
- "public-room-devices": "في الغرفة {{roomId}}",
- "paired-devices": "بواسطة الأجهزة المقترنة",
- "on-this-network": "على هذه الشبكة",
- "routed": "توجيهّا من خلال الخادم",
- "discovery": "يمكنك اكتشاف:",
- "on-this-network_title": "يمكن للجميع اكتشافك على هذه الشبكة.",
- "known-as": "أنت معروف بأنك:"
- },
- "notifications": {
- "request-title": "يرغب {{name}} في نقل {{count}} {{descriptor}}",
- "unfinished-transfers-warning": "هناك تحويلات غير مكتملة. هل أنت متأكد أنك تريد إغلاق PairDrop؟",
- "message-received": "تم استلام الرابط بواسطة {{name}} - انقر للفتح",
- "rate-limit-join-key": "تم الوصول إلى الحد الأقصى. انتظر 10 ثوان وحاول مرة أخرى.",
- "connecting": "يتصل …",
- "pairing-key-invalidated": "المفتاح {{key}} خاطئ.",
- "pairing-key-invalid": "مُفتاح خاطئ",
- "connected": "متصل.",
- "pairing-not-persistent": "الأجهزة المقترنة ليست ثابتة.",
- "text-content-incorrect": "محتوى النص غير صحيح.",
- "message-transfer-completed": "اكتمل نقل الرسالة.",
- "file-transfer-completed": "اكتمل نقل الملف.",
- "file-content-incorrect": "محتوى الملف غير صحيح.",
- "files-incorrect": "الملفات غير صحيحة.",
- "selected-peer-left": "مُحَدد الاجهزة المقترنة.",
- "link-received": "تم استلام الرابط بواسطة {{name}} - انقر للفتح",
- "online": "لقد عدت متصلاً بالإنترنت",
- "public-room-left": "الخروج من الغرفة العامة {{publicRoomId}}",
- "copied-text": "نُسِخَ النص إلى الحافظة",
- "display-name-random-again": "يتم إنشاء اسم العرض بشكل عشوائي مرة أخرى.",
- "display-name-changed-permanently": "يتم تغيير اسم العرض بشكل دائم.",
- "copied-to-clipboard-error": "النسخ غير ممكن. انسخ يدويًا.",
- "pairing-success": "الأجهزة المقترنة.",
- "clipboard-content-incorrect": "محتوى الحافظة غير صحيح.",
- "display-name-changed-temporarily": "تم تغيير اسم العرض لهذه الجلسة فقط.",
- "copied-to-clipboard": "تم النسخ إلى الحافظة",
- "offline": "انت غير متصل",
- "pairing-tabs-error": "من المستحيل إقران علامتي تبويب متصفح الويب.",
- "public-room-id-invalid": "معرف الغرفة غير صالح",
- "click-to-download": "إضغط للتحميل",
- "pairing-cleared": "جميع الأجهزة غير مقترنة.",
- "notifications-enabled": "تم تمكين الإشعارات.",
- "online-requirement-pairing": "يجب أن تكون متصلاً بالإنترنت لإقران الأجهزة.",
- "ios-memory-limit": "لا يمكن إرسال ملفات إلى iOS إلا بحجم يصل إلى 200 ميجابايت مرة واحدة",
- "online-requirement-public-room": "يجب أن تكون متصلاً بالإنترنت لإنشاء غرفة عامة.",
- "copied-text-error": "فشلت الكتابة من الحافظة. انسخ يدويًا!",
- "download-successful": "تم تحميل {{descriptor}}",
- "click-to-show": "اضغط للعرض"
- },
- "header": {
- "cancel-paste-mode": "تمّ",
- "theme-auto_title": "تكيٌف المظهر مع النظام",
- "install_title": "تثبيت PairDrop",
- "theme-dark_title": "إستخدام دائما المظهر المظلم",
- "pair-device_title": "قم بإقران أجهزتك بشكل دائم",
- "join-public-room_title": "انضم إلى الغرفة العامة مؤقتًا",
- "notification_title": "تشغيل الإشعارات",
- "edit-paired-devices_title": "تعديل الأجهزة المقترنة",
- "language-selector_title": "إختر اللغةعربي",
- "about_title": "حول PairDrop",
- "about_aria-label": "افتح حول PairDrop",
- "theme-light_title": "إستخدم دائماً المظهر الفاتح"
- },
- "instructions": {
- "x-instructions_mobile": "انقر لإرسال الملفات أو انقر لفترة طويلة لإرسال رسالة",
- "click-to-send": "انقر للإرسال",
- "activate-paste-mode-and-other-files": "و{{count}} ملفات أخرى",
- "tap-to-send": "انقر للإرسال",
- "activate-paste-mode-base": "افتح PairDrop على الأجهزة الأخرى للإرسال",
- "no-peers-subtitle": "قم بإقران الأجهزة أو ادخل إلى غرفة عامة لتتمكن من إكتشافها على الشبكات الأخرى",
- "activate-paste-mode-shared-text": "النص المشترك",
- "x-instructions_desktop": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة",
- "no-peers-title": "افتح PairDrop على الأجهزة الأخرى لإرسال الملفات",
- "x-instructions_data-drop-bg": "حرر لتحديد المستلم",
- "no-peers_data-drop-bg": "حرر لتحديد المستلم",
- "x-instructions_data-drop-peer": "قم بالتحرير لإرسالها إلى النظير"
- },
- "peer-ui": {
- "processing": "مُعالجة …",
- "click-to-send-paste-mode": "انقر للإرسال {{descriptor}}",
- "click-to-send": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة",
- "waiting": "يُرجى الإنتظار…",
- "connection-hash": "للتحقق من أمان التشفير الشامل، قم بمقارنة رقم الأمان هذا على كلا الجهازين",
- "preparing": "يقترن…",
- "transferring": "جارٍ النقل…"
- },
- "dialogs": {
- "base64-paste-to-send": "الصق هنا لإرسال {{type}}",
- "auto-accept-instructions-2": "لقبول جميع الملفات المرسلة من هذا الجهاز تلقائيًا.",
- "receive-text-title": "تلقيت رسالة",
- "edit-paired-devices-title": "تحرير الأجهزة المقترنة",
- "cancel": "إلغاء",
- "auto-accept-instructions-1": "تفعيل",
- "pair-devices-title": "إقران الأجهزة بشكل دائم",
- "download": "تحميل",
- "title-file": "ملف",
- "base64-processing": "مُعالجة…",
- "decline": "رفض",
- "receive-title": "تم الاستلام {{descriptor}}",
- "leave": "مُغادرة",
- "join": "انضمام",
- "title-image-plural": "صور",
- "send": "ارسال",
- "base64-tap-to-paste": "انقر هنا للصق {{type}}",
- "base64-text": "نص",
- "copy": "نسخ",
- "file-other-description-image": "وصورة واحدة أخرى",
- "temporary-public-room-title": "غرفة عامة مؤقتة",
- "base64-files": "ملفات",
- "has-sent": "ارسلت:",
- "file-other-description-file": "وملف واحد آخر",
- "close": "إغلاق",
- "system-language": "لغة النظام",
- "unpair": "إلغاء الإقتران",
- "title-image": "صورة",
- "file-other-description-file-plural": "و{{count}} ملفات أخرى",
- "would-like-to-share": "ترغب في المشاركة",
- "send-message-to": "أرسال رسالة إلى",
- "language-selector-title": "إختر اللُغة",
- "pair": "إقتران",
- "hr-or": "او",
- "scan-qr-code": "أو مسح رمز الاستجابة السريعة.",
- "input-key-on-this-device": "أدخل هذا المفتاح على جهاز آخر",
- "download-again": "تحميل مرة أخرى",
- "accept": "قبول",
- "paired-devices-wrapper_data-empty": "لا توجد أجهزة مقترنة.",
- "enter-key-from-another-device": "أدخل المفتاح من جهاز آخر هنا.",
- "share": "مُشاركة",
- "auto-accept": "قبول تلقائي",
- "title-file-plural": "ملفات",
- "send-message-title": "إرسال رسالة",
- "input-room-id-on-another-device": "أدخل معرف الغرفة هذا على جهاز آخر ما ",
- "file-other-description-image-plural": "و{{count}} صور أخرى",
- "enter-room-id-from-another-device": "أدخل معرف الغرفة من جهاز آخر للانضمام إلى الغرفة."
- },
- "about": {
- "claim": "أسهل طريقة لنقل الملفات عبر الأجهزة",
- "tweet_title": "غرّد حول PairDrop",
- "close-about_aria-label": "إغلاق حول PairDrop",
- "buy-me-a-coffee_title": "اشتري لي القهوة!",
- "github_title": "PairDrop على جيت هاب",
- "faq_title": "أسئلة متكررة"
- },
- "document-titles": {
- "file-transfer-requested": "طلب نقل الملف",
- "message-received-plural": "{{count}} الرسائل المستلمة",
- "message-received": "تم إرسال الرسالة",
- "file-received": "تم استلام الملف",
- "file-received-plural": "{{count}} الملفات المستلمة",
- "image-transfer-requested": "طُلب نقل الصور المطلوبة"
- }
-}
diff --git a/public_included_ws_fallback/lang/de.json b/public_included_ws_fallback/lang/de.json
deleted file mode 100644
index ab2a95c..0000000
--- a/public_included_ws_fallback/lang/de.json
+++ /dev/null
@@ -1,167 +0,0 @@
-{
- "header": {
- "about_title": "Über PairDrop",
- "notification_title": "Benachrichtigungen aktivieren",
- "about_aria-label": "Über PairDrop öffnen",
- "install_title": "PairDrop installieren",
- "pair-device_title": "Deine Geräte dauerhaft koppeln",
- "edit-paired-devices_title": "Gekoppelte Geräte bearbeiten",
- "theme-auto_title": "Systemstil verwenden",
- "theme-dark_title": "Immer dunklen Stil verwenden",
- "theme-light_title": "Immer hellen Stil verwenden",
- "cancel-paste-mode": "Fertig",
- "language-selector_title": "Sprache Wählen",
- "join-public-room_title": "Öffentlichen Raum temporär betreten"
- },
- "dialogs": {
- "share": "Teilen",
- "download": "Herunterladen",
- "pair-devices-title": "Geräte Dauerhaft Koppeln",
- "input-key-on-this-device": "Gib diesen Schlüssel auf einem anderen Gerät ein",
- "enter-key-from-another-device": "Gib den Schlüssel von einem anderen Gerät hier ein.",
- "pair": "Koppeln",
- "cancel": "Abbrechen",
- "edit-paired-devices-title": "Gekoppelte Geräte Bearbeiten",
- "paired-devices-wrapper_data-empty": "Keine gekoppelten Geräte.",
- "close": "Schließen",
- "accept": "Akzeptieren",
- "decline": "Ablehnen",
- "title-image": "Bild",
- "title-file": "Datei",
- "title-image-plural": "Bilder",
- "title-file-plural": "Dateien",
- "scan-qr-code": "oder scanne den QR-Code.",
- "would-like-to-share": "möchte Folgendes teilen",
- "send": "Senden",
- "copy": "Kopieren",
- "receive-text-title": "Textnachricht Erhalten",
- "file-other-description-image-plural": "und {{count}} andere Bilder",
- "file-other-description-file-plural": "und {{count}} andere Dateien",
- "auto-accept-instructions-1": "Aktiviere",
- "auto-accept": "automatisch-akzeptieren",
- "auto-accept-instructions-2": "um automatisch alle Dateien von diesem Gerät zu akzeptieren.",
- "has-sent": "hat Folgendes gesendet:",
- "send-message-title": "Textnachricht Senden",
- "send-message-to": "Sende eine Textnachricht an",
- "base64-tap-to-paste": "Hier tippen, um {{type}} einzufügen",
- "base64-paste-to-send": "Hier einfügen, um {{type}} zu versenden",
- "base64-text": "Text",
- "base64-files": "Dateien",
- "base64-processing": "Bearbeitung läuft…",
- "file-other-description-image": "und ein anderes Bild",
- "file-other-description-file": "und eine andere Datei",
- "receive-title": "{{descriptor}} Erhalten",
- "download-again": "Erneut herunterladen",
- "system-language": "Systemsprache",
- "language-selector-title": "Sprache Einstellen",
- "hr-or": "ODER",
- "input-room-id-on-another-device": "Gib diese Raum-ID auf einem anderen Gerät ein",
- "unpair": "Entkoppeln",
- "leave": "Verlassen",
- "join": "Betreten",
- "enter-room-id-from-another-device": "Gib die Raum-ID von einem anderen Gerät hier ein.",
- "temporary-public-room-title": "Temporärer Öffentlicher Raum",
- "message_title": "Nachricht zum Senden hier einfügen",
- "pair-devices-qr-code_title": "Klicke, um Link zum Koppeln mit diesem Gerät zu kopieren",
- "public-room-qr-code_title": "Klicke, um Link zu diesem öffentlichen Raum zu kopieren"
- },
- "about": {
- "tweet_title": "Über PairDrop twittern",
- "faq_title": "Häufig gestellte Fragen",
- "close-about_aria-label": "Schließe Über PairDrop",
- "github_title": "PairDrop auf GitHub",
- "buy-me-a-coffee_title": "Kauf mir einen Kaffee!",
- "claim": "Der einfachste Weg, Dateien zwischen Geräten zu übertragen"
- },
- "footer": {
- "known-as": "Du wirst angezeigt als:",
- "display-name_title": "Setze einen permanenten Gerätenamen",
- "on-this-network": "in diesem Netzwerk",
- "paired-devices": "für gekoppelte Geräte",
- "traffic": "Datenverkehr wird",
- "display-name_placeholder": "Lade…",
- "routed": "durch den Server geleitet",
- "webrtc": "wenn WebRTC nicht verfügbar ist.",
- "display-name_data-placeholder": "Lade…",
- "public-room-devices_title": "Du kannst von Geräten in diesem öffentlichen Raum gefunden werden, unabhängig von deinem Netzwerk.",
- "paired-devices_title": "Du kannst immer von gekoppelten Geräten gefunden werden, egal in welchem Netzwerk.",
- "public-room-devices": "in Raum {{roomId}}",
- "discovery": "Du bist sichtbar:",
- "on-this-network_title": "Du kannst von jedem in diesem Netzwerk gefunden werden."
- },
- "notifications": {
- "link-received": "Link von {{name}} empfangen - Klicke um ihn zu öffnen",
- "message-received": "Nachricht von {{name}} empfangen - Klicke um sie zu kopieren",
- "click-to-download": "Klicken zum Download",
- "copied-text": "Text in die Zwischenablage kopiert",
- "connected": "Verbunden.",
- "pairing-success": "Geräte gekoppelt.",
- "display-name-random-again": "Anzeigename wird ab jetzt wieder zufällig generiert.",
- "pairing-tabs-error": "Es können keine zwei Webbrowser Tabs gekoppelt werden.",
- "pairing-not-persistent": "Gekoppelte Geräte sind nicht persistent.",
- "pairing-key-invalid": "Ungültiger Schlüssel",
- "pairing-key-invalidated": "Schlüssel {{key}} wurde ungültig gemacht.",
- "copied-to-clipboard": "In die Zwischenablage kopiert",
- "text-content-incorrect": "Textinhalt ist fehlerhaft.",
- "clipboard-content-incorrect": "Inhalt der Zwischenablage ist fehlerhaft.",
- "copied-text-error": "Konnte nicht in die Zwischenablage schreiben. Kopiere manuell!",
- "file-content-incorrect": "Dateiinhalt ist fehlerhaft.",
- "notifications-enabled": "Benachrichtigungen aktiviert.",
- "offline": "Du bist offline",
- "online": "Du bist wieder Online",
- "unfinished-transfers-warning": "Es wurden noch nicht alle Übertragungen fertiggestellt. Möchtest du PairDrop wirklich schließen?",
- "display-name-changed-permanently": "Anzeigename wurde dauerhaft geändert.",
- "download-successful": "{{descriptor}} heruntergeladen",
- "pairing-cleared": "Alle Geräte entkoppelt.",
- "click-to-show": "Klicken zum Anzeigen",
- "online-requirement": "Du musst online sein um Geräte zu koppeln.",
- "display-name-changed-temporarily": "Anzeigename wurde nur für diese Session geändert.",
- "request-title": "{{name}} möchte {{count}}{{descriptor}} übertragen",
- "connecting": "Verbindung wird hergestellt…",
- "files-incorrect": "Dateien sind fehlerhaft.",
- "file-transfer-completed": "Dateiübertragung abgeschlossen.",
- "message-transfer-completed": "Nachrichtenübertragung fertiggestellt.",
- "rate-limit-join-key": "Rate Limit erreicht. Warte 10 Sekunden und versuche es erneut.",
- "selected-peer-left": "Ausgewählter Peer ist gegangen.",
- "ios-memory-limit": "Für Übertragungen an iOS Geräte beträgt die maximale Dateigröße 200 MB",
- "public-room-left": "Öffentlichen Raum {{publicRoomId}} verlassen",
- "copied-to-clipboard-error": "Konnte nicht kopieren. Kopiere manuell.",
- "public-room-id-invalid": "Ungültige Raum-ID",
- "online-requirement-pairing": "Du musst online sein, um Geräte zu koppeln.",
- "online-requirement-public-room": "Du musst online sein, um öffentliche Räume erstellen zu können.",
- "notifications-permissions-error": "Benachrichtigungen wurden blockiert, weil der Nutzer die Berechtigungsanfrage mehrfach abgelehnt hat. Dies kann in den Einstellungen der Website zurückgesetzt werden, welche durch Klick auf das Schloss Symbol neben der URL Leiste erreicht werden können.",
- "pair-url-copied-to-clipboard": "Link zum Koppeln mit diesem Gerät in Zwischenablage kopiert",
- "room-url-copied-to-clipboard": "Link zu diesem öffentlichen Raum in Zwischenablage kopiert"
- },
- "instructions": {
- "x-instructions_desktop": "Klicke, um Dateien zu senden oder benutze einen Rechtsklick, um eine Textnachricht zu senden",
- "no-peers-title": "Öffne PairDrop auf anderen Geräten, um Dateien zu senden",
- "no-peers_data-drop-bg": "Hier ablegen, um Empfänger auszuwählen",
- "no-peers-subtitle": "Kopple Geräte oder besuche einen öffentlichen Raum, damit du in anderen Netzwerken sichtbar bist",
- "click-to-send": "Klicke zum Senden von",
- "tap-to-send": "Tippe zum Senden von",
- "x-instructions_data-drop-peer": "Hier ablegen, um an Peer zu senden",
- "x-instructions_data-drop-bg": "Loslassen um Empfänger auszuwählen",
- "x-instructions_mobile": "Tippe, um Dateien zu senden oder tippe lange, um Nachrichten zu senden",
- "activate-paste-mode-base": "Öffne PairDrop auf anderen Geräten zum Senden von",
- "activate-paste-mode-and-other-files": "und {{count}} anderen Dateien",
- "activate-paste-mode-shared-text": "freigegebenem Text"
- },
- "document-titles": {
- "file-transfer-requested": "Dateiübertragung angefordert",
- "file-received": "Datei erhalten",
- "file-received-plural": "{{count}} Dateien erhalten",
- "message-received": "Nachricht erhalten",
- "message-received-plural": "{{count}} Nachrichten erhalten",
- "image-transfer-requested": "Bilder Transfer beantragt"
- },
- "peer-ui": {
- "click-to-send": "Klicke, um Dateien zu senden oder benutze einen Rechtsklick, um eine Textnachricht zu senden",
- "connection-hash": "Um die Ende-zu-Ende Verschlüsselung zu verifizieren, vergleiche die Sicherheitsnummer auf beiden Geräten",
- "waiting": "Warte…",
- "click-to-send-paste-mode": "Klicken um {{descriptor}} zu senden",
- "transferring": "Übertragung läuft…",
- "processing": "Bearbeitung läuft…",
- "preparing": "Vorbereitung läuft…"
- }
-}
diff --git a/public_included_ws_fallback/lang/en.json b/public_included_ws_fallback/lang/en.json
deleted file mode 100644
index 1b19c2f..0000000
--- a/public_included_ws_fallback/lang/en.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "header": {
- "about_title": "About PairDrop",
- "language-selector_title": "Set Language",
- "about_aria-label": "Open About PairDrop",
- "theme-auto_title": "Adapt theme to system automatically",
- "theme-light_title": "Always use light theme",
- "theme-dark_title": "Always use dark theme",
- "notification_title": "Enable notifications",
- "install_title": "Install PairDrop",
- "pair-device_title": "Pair your devices permanently",
- "edit-paired-devices_title": "Edit paired devices",
- "join-public-room_title": "Join public room temporarily",
- "cancel-paste-mode": "Done"
- },
- "instructions": {
- "no-peers_data-drop-bg": "Release to select recipient",
- "no-peers-title": "Open PairDrop on other devices to send files",
- "no-peers-subtitle": "Pair devices or enter a public room to be discoverable on other networks",
- "x-instructions_desktop": "Click to send files or right click to send a message",
- "x-instructions_mobile": "Tap to send files or long tap to send a message",
- "x-instructions_data-drop-peer": "Release to send to peer",
- "x-instructions_data-drop-bg": "Release to select recipient",
- "click-to-send": "Click to send",
- "tap-to-send": "Tap to send",
- "activate-paste-mode-base": "Open PairDrop on other devices to send",
- "activate-paste-mode-and-other-files": "and {{count}} other files",
- "activate-paste-mode-shared-text": "shared text"
- },
- "footer": {
- "known-as": "You are known as:",
- "display-name_data-placeholder": "Loading…",
- "display-name_title": "Edit your device name permanently",
- "discovery": "You can be discovered:",
- "on-this-network": "on this network",
- "on-this-network_title": "You can be discovered by everyone on this network.",
- "paired-devices": "by paired devices",
- "paired-devices_title": "You can be discovered by paired devices at all times independent of the network.",
- "public-room-devices": "in room {{roomId}}",
- "public-room-devices_title": "You can be discovered by devices in this public room independent of the network.",
- "traffic": "Traffic is",
- "routed": "routed through the server",
- "webrtc": "if WebRTC is not available."
- },
- "dialogs": {
- "pair-devices-title": "Pair Devices Permanently",
- "input-key-on-this-device": "Input this key on another device",
- "scan-qr-code": "or scan the QR-code.",
- "enter-key-from-another-device": "Enter key from another device here.",
- "temporary-public-room-title": "Temporary Public Room",
- "input-room-id-on-another-device": "Input this room ID on another device",
- "enter-room-id-from-another-device": "Enter room ID from another device to join room.",
- "hr-or": "OR",
- "pair": "Pair",
- "cancel": "Cancel",
- "edit-paired-devices-title": "Edit Paired Devices",
- "unpair": "Unpair",
- "paired-devices-wrapper_data-empty": "No paired devices.",
- "auto-accept-instructions-1": "Activate",
- "auto-accept": "auto-accept",
- "auto-accept-instructions-2": "to automatically accept all files sent from that device.",
- "close": "Close",
- "join": "Join",
- "leave": "Leave",
- "would-like-to-share": "would like to share",
- "accept": "Accept",
- "decline": "Decline",
- "has-sent": "has sent:",
- "share": "Share",
- "download": "Download",
- "send-message-title": "Send Message",
- "send-message-to": "Send a Message to",
- "message_title": "Insert message to send",
- "send": "Send",
- "receive-text-title": "Message Received",
- "copy": "Copy",
- "base64-processing": "Processing…",
- "base64-tap-to-paste": "Tap here to paste {{type}}",
- "base64-paste-to-send": "Paste here to send {{type}}",
- "base64-text": "text",
- "base64-files": "files",
- "file-other-description-image": "and 1 other image",
- "file-other-description-file": "and 1 other file",
- "file-other-description-image-plural": "and {{count}} other images",
- "file-other-description-file-plural": "and {{count}} other files",
- "title-image": "Image",
- "title-file": "File",
- "title-image-plural": "Images",
- "title-file-plural": "Files",
- "receive-title": "{{descriptor}} Received",
- "download-again": "Download again",
- "language-selector-title": "Set Language",
- "system-language": "System Language",
- "public-room-qr-code_title": "Click to copy link to public room",
- "pair-devices-qr-code_title": "Click to copy link to pair this device"
- },
- "about": {
- "close-about_aria-label": "Close About PairDrop",
- "claim": "The easiest way to transfer files across devices",
- "github_title": "PairDrop on GitHub",
- "buy-me-a-coffee_title": "Buy me a coffee!",
- "tweet_title": "Tweet about PairDrop",
- "faq_title": "Frequently asked questions"
- },
- "notifications": {
- "display-name-changed-permanently": "Display name is changed permanently.",
- "display-name-changed-temporarily": "Display name is changed only for this session.",
- "display-name-random-again": "Display name is randomly generated again.",
- "download-successful": "{{descriptor}} downloaded",
- "pairing-tabs-error": "Pairing two web browser tabs is impossible.",
- "pairing-success": "Devices paired.",
- "pairing-not-persistent": "Paired devices are not persistent.",
- "pairing-key-invalid": "Invalid key",
- "pairing-key-invalidated": "Key {{key}} invalidated.",
- "pairing-cleared": "All Devices unpaired.",
- "public-room-id-invalid": "Invalid room ID",
- "public-room-left": "Left public room {{publicRoomId}}",
- "copied-to-clipboard": "Copied to clipboard",
- "pair-url-copied-to-clipboard": "Link to pair this device copied to clipboard",
- "room-url-copied-to-clipboard": "Link to public room copied to clipboard",
- "copied-to-clipboard-error": "Copying not possible. Copy manually.",
- "text-content-incorrect": "Text content is incorrect.",
- "file-content-incorrect": "File content is incorrect.",
- "clipboard-content-incorrect": "Clipboard content is incorrect.",
- "notifications-enabled": "Notifications enabled.",
- "notifications-permissions-error": "Notifications permission has been blocked as the user has dismissed the permission prompt several times. This can be reset in Page Info which can be accessed by clicking the lock icon next to the URL bar.",
- "link-received": "Link received by {{name}} - Click to open",
- "message-received": "Message received by {{name}} - Click to copy",
- "click-to-download": "Click to download",
- "request-title": "{{name}} would like to transfer {{count}} {{descriptor}}",
- "click-to-show": "Click to show",
- "copied-text": "Copied text to clipboard",
- "copied-text-error": "Writing to clipboard failed. Copy manually!",
- "offline": "You are offline",
- "online": "You are back online",
- "connected": "Connected.",
- "online-requirement-pairing": "You need to be online to pair devices.",
- "online-requirement-public-room": "You need to be online to create a public room.",
- "connecting": "Connecting…",
- "files-incorrect": "Files are incorrect.",
- "file-transfer-completed": "File transfer completed.",
- "ios-memory-limit": "Sending files to iOS is only possible up to 200 MB at once",
- "message-transfer-completed": "Message transfer completed.",
- "unfinished-transfers-warning": "There are unfinished transfers. Are you sure you want to close PairDrop?",
- "rate-limit-join-key": "Rate limit reached. Wait 10 seconds and try again.",
- "selected-peer-left": "Selected peer left."
- },
- "document-titles": {
- "file-received": "File Received",
- "file-received-plural": "{{count}} Files Received",
- "file-transfer-requested": "File Transfer Requested",
- "image-transfer-requested": "Image Transfer Requested",
- "message-received": "Message Received",
- "message-received-plural": "{{count}} Messages Received"
- },
- "peer-ui": {
- "click-to-send-paste-mode": "Click to send {{descriptor}}",
- "click-to-send": "Click to send files or right click to send a message",
- "connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices",
- "preparing": "Preparing…",
- "waiting": "Waiting…",
- "processing": "Processing…",
- "transferring": "Transferring…"
- }
-}
diff --git a/public_included_ws_fallback/lang/es.json b/public_included_ws_fallback/lang/es.json
deleted file mode 100644
index 5ba42c1..0000000
--- a/public_included_ws_fallback/lang/es.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "header": {
- "theme-auto_title": "Adaptar tema al sistema",
- "language-selector_title": "Configurar Idioma",
- "about_title": "Sobre PairDrop",
- "about_aria-label": "Abrir Sobre PairDrop",
- "cancel-paste-mode": "Listo",
- "install_title": "Instalar PairDrop",
- "theme-dark_title": "Siempre usar tema oscuro",
- "pair-device_title": "Empareja tus dispositivos permanentemente",
- "join-public-room_title": "Unirse a una sala pública temporalmente",
- "notification_title": "Activar notificaciones",
- "edit-paired-devices_title": "Editar dispositivos emparejados",
- "theme-light_title": "Siempre usar tema claro"
- },
- "footer": {
- "webrtc": "si WebRTC no está disponible.",
- "public-room-devices_title": "Puedes ser descubierto por dispositivos en esta sala pública independientemente de la red.",
- "display-name_data-placeholder": "Cargando…",
- "display-name_title": "Edita el nombre de tu dispositivo de forma permanente",
- "traffic": "El tráfico es",
- "paired-devices_title": "Puedes ser descubierto por los dispositivos emparejados todo el tiempo independientemente de la red.",
- "public-room-devices": "en la sala {{roomId}}",
- "paired-devices": "por dispositivos emparejados",
- "on-this-network": "en esta red",
- "routed": "enrutado a través del servidor",
- "discovery": "Puedes ser descubierto:",
- "on-this-network_title": "Puedes ser descubierto por todos en esta red.",
- "known-as": "Eres conocido como:"
- },
- "notifications": {
- "request-title": "{{name}} quiere transferir {{count}} {{descriptor}}",
- "unfinished-transfers-warning": "Hay transferencias no terminadas. ¿Estás seguro de que quieres cerrar PairDrop?",
- "message-received": "Mensaje recibido por {{name}} - Haga clic para copiar",
- "rate-limit-join-key": "Límite de intentos alcanzado. Espere 10 segundos y vuelva a intentarlo.",
- "connecting": "Conectando…",
- "pairing-key-invalidated": "Clave {{key}} invalidada.",
- "pairing-key-invalid": "Clave inválida",
- "connected": "Connectado.",
- "pairing-not-persistent": "Los dispositivos emparejados no son persistentes.",
- "text-content-incorrect": "El contenido del texto es incorrecto.",
- "message-transfer-completed": "Transferencia de mensaje completada.",
- "file-transfer-completed": "Transferencia de archivos completada.",
- "file-content-incorrect": "El contenido del archivo es incorrecto.",
- "files-incorrect": "Los archivos son incorrectos.",
- "selected-peer-left": "El dispositivo seleccionado se fue.",
- "link-received": "Link recibido por {{name}} - Haga clic para abrir",
- "online": "Estás de nuevo en línea",
- "public-room-left": "Salió de la sala pública {{publicRoomId}}",
- "copied-text": "Texto copiado al portapapeles",
- "display-name-random-again": "El nombre mostrado se genera aleatoriamente nuevamente.",
- "display-name-changed-permanently": "El nombre para mostrar se ha cambiado permanentemente.",
- "copied-to-clipboard-error": "No es posible copiarlo. Cópielo manualmente.",
- "pairing-success": "Dispositivos emparejados.",
- "clipboard-content-incorrect": "El contenido del portapapeles es incorrecto.",
- "display-name-changed-temporarily": "El nombre mostrado se cambia solo para esta sesión.",
- "copied-to-clipboard": "Copiado al portapapeles",
- "offline": "Estás desconectado",
- "pairing-tabs-error": "Emparejar dos pestañas del navegador es imposible.",
- "public-room-id-invalid": "ID de sala no válido",
- "click-to-download": "Haga clic para descargar",
- "pairing-cleared": "Todos los dispositivos han sido desemparejados.",
- "notifications-enabled": "Notificaciones habilitadas.",
- "online-requirement-pairing": "Debes estar en línea para emparejar dispositivos.",
- "ios-memory-limit": "Enviar archivos a iOS sólo admite hasta 200 MB a la vez",
- "online-requirement-public-room": "Debes estar en línea para crear una sala pública.",
- "copied-text-error": "Error al escribir en el portapapeles. ¡Cópielo manualmente!",
- "download-successful": "{{descriptor}} descargado",
- "click-to-show": "Click para mostrar",
- "notifications-permissions-error": "Las notificaciones se bloquearon porque el usuario rechazó la solicitud del permiso varias veces. Esto se puede restablecer en la configuración de la página web, a la que se quiere acceder haciendo clic en el icono del candado al lado de la barra con la URL.",
- "pair-url-copied-to-clipboard": "El enlace para emparejar este dispositivo se copió en el portapapeles",
- "room-url-copied-to-clipboard": "El enlace a la sala pública se copió en el portapapeles"
- },
- "instructions": {
- "x-instructions_mobile": "Toque para enviar archivos o toque prologádamente para enviar un mensaje",
- "click-to-send": "Haga clic para enviar",
- "activate-paste-mode-and-other-files": "y {{count}} archivos diferentes",
- "tap-to-send": "Toca para enviar",
- "activate-paste-mode-base": "Abra PairDrop en otros dispositivos para enviar",
- "no-peers-subtitle": "Empareje dispositivos o ingrese a una sala pública para que lo puedan encontrar en otras redes",
- "activate-paste-mode-shared-text": "texto compartido",
- "x-instructions_desktop": "Haga clic para enviar archivos o haga clic derecho para enviar un mensaje",
- "no-peers-title": "Abra PairDrop en otros dispositivos para enviar archivos",
- "x-instructions_data-drop-peer": "Liberar para enviar a un par",
- "x-instructions_data-drop-bg": "Liberar para seleccionar destinatario",
- "no-peers_data-drop-bg": "Liberar para seleccionar destinatario"
- },
- "peer-ui": {
- "processing": "Procesando…",
- "click-to-send-paste-mode": "Haga clic para enviar {{descriptor}}",
- "click-to-send": "Haga clic para enviar archivos o haga clic derecho para enviar un mensaje",
- "waiting": "Esperando…",
- "connection-hash": "Para verificar la seguridad del cifrado de extremo a extremo, compare este número de seguridad en ambos dispositivos",
- "preparing": "Preparando…",
- "transferring": "Transferiendo…"
- },
- "dialogs": {
- "base64-paste-to-send": "Pegar aquí para enviar {{type}}",
- "auto-accept-instructions-2": "para aceptar automáticamente todos los archivos enviados desde ese dispositivo.",
- "receive-text-title": "Mensaje Recibido",
- "edit-paired-devices-title": "Editar Dispositivos Emparejados",
- "cancel": "Cancelar",
- "auto-accept-instructions-1": "Activar",
- "pair-devices-title": "Emparejar dispositivos permanentemente",
- "download": "Descargar",
- "title-file": "Archivo",
- "base64-processing": "Procesando…",
- "decline": "Rechazar",
- "receive-title": "{{descriptor}} Recibido",
- "leave": "Salir",
- "join": "Unirse",
- "title-image-plural": "Imágenes",
- "send": "Enviar",
- "base64-tap-to-paste": "Toca aquí para pegar {{type}}",
- "base64-text": "texto",
- "copy": "Copiar",
- "file-other-description-image": "y una imagen mas",
- "temporary-public-room-title": "Sala pública temporal",
- "base64-files": "archivos",
- "has-sent": "ha enviado:",
- "file-other-description-file": "y otro archivo",
- "close": "Cerrar",
- "system-language": "Idioma del Sistema",
- "unpair": "Desemparejar",
- "title-image": "Imagen",
- "file-other-description-file-plural": "y {{count}} archivos más",
- "would-like-to-share": "quisiera compartir",
- "send-message-to": "Enviar un Mensaje a",
- "language-selector-title": "Configurar Idioma",
- "pair": "Emparejar",
- "hr-or": "O",
- "scan-qr-code": "o escanea el código QR.",
- "input-key-on-this-device": "Ingrese esta clave en otro dispositivo",
- "download-again": "Descargar de nuevo",
- "accept": "Aceptar",
- "paired-devices-wrapper_data-empty": "Sin dispositivos emparejados.",
- "enter-key-from-another-device": "Ingresa la clave de otro dispositivo aquí.",
- "share": "Compartir",
- "auto-accept": "aceptar automáticamente",
- "title-file-plural": "Archivos",
- "send-message-title": "Enviar Mensaje",
- "input-room-id-on-another-device": "Ingrese el ID de esta sala en otro dispositivo",
- "file-other-description-image-plural": "y {{count}} imágenes más",
- "enter-room-id-from-another-device": "Ingresa el ID de la sala desde otro dispositivo para unirte a la sala.",
- "message_title": "Insertar el mensaje a enviar",
- "pair-devices-qr-code_title": "Haz clic para copiar el enlace para emparejar este dispositivo",
- "public-room-qr-code_title": "Haz clic para copiar el enlace a la sala pública"
- },
- "about": {
- "claim": "La forma más sencilla de transferir archivos entre dispositivos",
- "tweet_title": "Tweetea sobre PairDrop",
- "close-about_aria-label": "Cerrar Sobre PairDrop",
- "buy-me-a-coffee_title": "¡Cómprame un café!",
- "github_title": "PairDrop en GitHub",
- "faq_title": "Preguntas frecuentes"
- },
- "document-titles": {
- "file-transfer-requested": "Transferencia de archivos solicitada",
- "image-transfer-requested": "Transferencia de imagen solicitada",
- "message-received-plural": "{{count}} Mensajes recibidos",
- "message-received": "Mensaje recibido",
- "file-received": "Archivo Recibido",
- "file-received-plural": "{{count}} Archivos Recibidos"
- }
-}
diff --git a/public_included_ws_fallback/lang/fr.json b/public_included_ws_fallback/lang/fr.json
deleted file mode 100644
index 133561e..0000000
--- a/public_included_ws_fallback/lang/fr.json
+++ /dev/null
@@ -1,160 +0,0 @@
-{
- "header": {
- "about_title": "À propos de PairDrop",
- "language-selector_title": "Choix de la langue",
- "about_aria-label": "Ouvrir à propos de PairDrop",
- "theme-auto_title": "Adapter le thème au système",
- "theme-light_title": "Toujours utiliser le thème clair",
- "theme-dark_title": "Toujours utiliser le thème sombre",
- "notification_title": "Activer les notifications",
- "install_title": "Installer PairDrop",
- "pair-device_title": "Associez vos appareils de manière permanente",
- "edit-paired-devices_title": "Gérer les appareils couplés",
- "join-public-room_title": "Rejoindre temporairement la salle publique",
- "cancel-paste-mode": "Terminé"
- },
- "instructions": {
- "no-peers_data-drop-bg": "Déposer pour choisir le destinataire",
- "no-peers-title": "Ouvrez PairDrop sur d'autres appareils pour envoyer des fichiers",
- "no-peers-subtitle": "Associez des appareils ou entrez dans une salle publique pour être visible sur d'autres réseaux",
- "x-instructions_desktop": "Cliquez pour envoyer des fichiers ou faites un clic droit pour envoyer un message",
- "x-instructions_mobile": "Appuyez pour envoyer des fichiers ou appuyez longuement pour envoyer un message",
- "x-instructions_data-drop-peer": "Déposer pour envoyer au destinataire",
- "x-instructions_data-drop-bg": "Lâcher pour choisir le destinataire",
- "click-to-send": "Cliquez pour envoyer",
- "tap-to-send": "Appuyez pour envoyer",
- "activate-paste-mode-base": "Ouvrez PairDrop sur d'autres appareils pour envoyer",
- "activate-paste-mode-and-other-files": "et {{count}} autres fichiers",
- "activate-paste-mode-shared-text": "texte partagé"
- },
- "footer": {
- "known-as": "Vous êtes connu comme :",
- "display-name_data-placeholder": "Chargement…",
- "display-name_title": "Modifiez le nom de votre appareil de manière permanente",
- "discovery": "Vous pouvez être découvert :",
- "on-this-network": "sur ce réseau",
- "on-this-network_title": "Vous pouvez être découvert par tout le monde sur ce réseau.",
- "paired-devices": "par les appareils couplés",
- "paired-devices_title": "Vous pouvez être découvert par les appareils couplés à tout moment, indépendamment du réseau.",
- "public-room-devices": "dans la salle {{roomId}}",
- "public-room-devices_title": "Vous pouvez être découvert par les appareils de cette salle publique indépendamment du réseau.",
- "traffic": "Le trafic est",
- "routed": "routé via le serveur",
- "webrtc": "si WebRTC n'est pas disponible.",
- "display-name_placeholder": "Chargement…"
- },
- "dialogs": {
- "pair-devices-title": "Associer les appareils de manière permanente",
- "input-key-on-this-device": "Saisissez cette clé sur un autre appareil",
- "scan-qr-code": "ou scannez le QR-code.",
- "enter-key-from-another-device": "Entrez ici la clé d'un autre appareil.",
- "temporary-public-room-title": "Salle publique temporaire",
- "input-room-id-on-another-device": "Saisissez cet ID de salle sur un autre appareil",
- "enter-room-id-from-another-device": "Entrez l'ID de la salle depuis un autre appareil pour rejoindre la salle.",
- "hr-or": "OU",
- "pair": "associer",
- "cancel": "Annuler",
- "edit-paired-devices-title": "Modifier les appareils couplés",
- "unpair": "Dissocier",
- "paired-devices-wrapper_data-empty": "Aucun appareil couplé.",
- "auto-accept-instructions-1": "Activer",
- "auto-accept": "auto-accepter",
- "auto-accept-instructions-2": "pour accepter automatiquement tous les fichiers envoyés depuis cet appareil.",
- "close": "Fermer",
- "join": "Rejoindre",
- "leave": "Partir",
- "would-like-to-share": "aimerait partager",
- "accept": "Accepter",
- "decline": "Refuser",
- "has-sent": "a envoyé :",
- "share": "Partage",
- "download": "Télécharger",
- "send-message-title": "Envoyer un message",
- "send-message-to": "Envoyer un message à",
- "send": "Envoyer",
- "receive-text-title": "Message reçu",
- "copy": "Copier",
- "base64-processing": "Traitement…",
- "base64-tap-to-paste": "Appuyez ici pour coller {{type}}",
- "base64-paste-to-send": "Coller ici pour envoyer {{type}}",
- "base64-text": "texte",
- "base64-files": "fichiers",
- "file-other-description-image": "et 1 autre image",
- "file-other-description-file": "et 1 autre fichier",
- "file-other-description-image-plural": "et {{count}} autres images",
- "file-other-description-file-plural": "et {{count}} autres fichiers",
- "title-image": "Image",
- "title-file": "Fichier",
- "title-image-plural": "Images",
- "title-file-plural": "Fichiers",
- "receive-title": "{{descriptor}} Reçu",
- "download-again": "Télécharger à nouveau",
- "language-selector-title": "Définir la langue",
- "system-language": "Langue du système"
- },
- "about": {
- "close-about_aria-label": "Fermer à propos de PairDrop",
- "claim": "Le moyen le plus simple de transférer des fichiers entre appareils",
- "github_title": "PairDrop sur GitHub",
- "buy-me-a-coffee_title": "Acheté-moi un café !",
- "tweet_title": "Tweet à propos de PairDrop",
- "faq_title": "Questions fréquemment posées"
- },
- "notifications": {
- "display-name-changed-permanently": "Le nom d'affichage est modifié de manière permanente.",
- "display-name-changed-temporarily": "Le nom d'affichage est modifié uniquement pour cette session.",
- "display-name-random-again": "Le nom d'affichage est à nouveau généré aléatoirement.",
- "download-successful": "{{descriptor}} téléchargé",
- "pairing-tabs-error": "Le couplage de deux onglets de navigateur Web est impossible.",
- "pairing-success": "Appareils couplés.",
- "pairing-not-persistent": "Les appareils couplés ne sont pas persistants.",
- "pairing-key-invalid": "Clé invalide",
- "pairing-key-invalidated": "Clé {{key}} invalidée.",
- "pairing-cleared": "Tous les appareils ne sont plus appairés.",
- "public-room-id-invalid": "ID de salle non valide",
- "public-room-left": "Salle publique {{publicRoomId}} quittée",
- "copied-to-clipboard": "Copié dans le presse-papier",
- "copied-to-clipboard-error": "Copie impossible. Copier manuellement.",
- "text-content-incorrect": "Le contenu du texte est incorrect.",
- "file-content-incorrect": "Le contenu du fichier est incorrect.",
- "clipboard-content-incorrect": "Le contenu du presse-papiers est incorrect.",
- "notifications-enabled": "Notifications activées.",
- "link-received": "Lien reçu par {{name}} - Cliquez pour ouvrir",
- "message-received": "Message reçu par {{name}} - Cliquez pour copier",
- "click-to-download": "Cliquez pour télécharger",
- "request-title": "{{name}} souhaite transférer {{count}} {{descriptor}}",
- "click-to-show": "Cliquez pour afficher",
- "copied-text": "Texte copié dans le presse-papiers",
- "copied-text-error": "L'écriture dans le presse-papiers a échoué. Copiez manuellement !",
- "offline": "Vous êtes hors ligne",
- "online": "Vous êtes de nouveau en ligne",
- "connected": "Connecté.",
- "online-requirement-pairing": "Vous devez être en ligne pour coupler des appareils.",
- "online-requirement-public-room": "Vous devez être en ligne pour créer une salle publique.",
- "connecting": "Connexion…",
- "files-incorrect": "Les fichiers sont incorrects.",
- "file-transfer-completed": "Transfert de fichier terminé.",
- "ios-memory-limit": "L'envoi de fichiers vers iOS n'est possible que jusqu'à 200 Mo à la fois",
- "message-transfer-completed": "Transfert de message terminé.",
- "unfinished-transfers-warning": "Il y a des transferts inachevés. Êtes-vous sûr de vouloir fermer PairDrop ?",
- "rate-limit-join-key": "Limite de débit atteinte. Attendez 10 secondes et réessayez.",
- "selected-peer-left": "Appareils sélectionnés restants."
- },
- "document-titles": {
- "file-received": "Fichier reçu",
- "file-received-plural": "{{count}} fichiers reçus",
- "file-transfer-requested": "Transfert de fichier demandé",
- "image-transfer-requested": "Transfert d'image demandé",
- "message-received": "Message reçu",
- "message-received-plural": "{{count}} Messages reçus"
- },
- "peer-ui": {
- "click-to-send-paste-mode": "Cliquez pour envoyer {{descriptor}}",
- "click-to-send": "Cliquez pour envoyer des fichiers ou faites un clic droit pour envoyer un message",
- "connection-hash": "Pour vérifier la sécurité du chiffrement de bout en bout, comparez ce numéro de sécurité sur les deux appareils",
- "preparing": "Préparation…",
- "waiting": "En attente…",
- "processing": "En cours…",
- "transferring": "Transfert en cours…"
- }
-}
diff --git a/public_included_ws_fallback/lang/id.json b/public_included_ws_fallback/lang/id.json
deleted file mode 100644
index 0e22dc5..0000000
--- a/public_included_ws_fallback/lang/id.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "footer": {
- "webrtc": "jika WebRTC tidak tersedia.",
- "public-room-devices_title": "Anda dapat ditemukan oleh perangkat di ruang publik ini terlepas dari jaringan.",
- "display-name_data-placeholder": "Memuat…",
- "display-name_title": "Edit nama perangkat Anda scr. permanen",
- "traffic": "Lalu lintas",
- "paired-devices_title": "Anda dapat ditemukan oleh perangkat yang dipasangkan setiap saat tergantung pada jaringan.",
- "public-room-devices": "dalam room {{roomId}}",
- "paired-devices": "pada prngkt. yg. dipasangkan",
- "on-this-network": "pada jaringan ini",
- "routed": "diarahkan melalui server",
- "discovery": "Anda dapat ditemukan:",
- "on-this-network_title": "Anda dapat ditemukan oleh semua orang di jaringan ini.",
- "known-as": "Anda dikenal sebagai:"
- },
- "notifications": {
- "request-title": "{{name}} ingin mentransfer {{count}} {{descriptor}}",
- "unfinished-transfers-warning": "Ada transfer yang belum selesai. Apakah Anda yakin ingin menutup PairDrop?",
- "message-received": "Pesan diterima dari {{name}} - Klik untuk menyalin",
- "rate-limit-join-key": "Batasan tercapai. Tunggu 10 detik dan coba lagi.",
- "connecting": "Menghubungkan…",
- "pairing-key-invalidated": "Kunci {{key}} tidak valid.",
- "pairing-key-invalid": "Kunci tidak valid",
- "connected": "Tersambung.",
- "pairing-not-persistent": "Perangkat dipasangkan tidak akan bertahan lama.",
- "text-content-incorrect": "Isi teks keliru.",
- "message-transfer-completed": "Transfer pesan selesai.",
- "file-transfer-completed": "Transfer file selesai.",
- "file-content-incorrect": "Isi file keliru.",
- "files-incorrect": "File tidak benar.",
- "selected-peer-left": "Rekan terpilih keluar.",
- "link-received": "Tautan diterima dari {{name}} - Klik untuk membuka",
- "online": "Anda kembali online",
- "public-room-left": "Keluar dari ruang publik {{publicRoomId}}",
- "copied-text": "Teks disalin ke papan klip",
- "display-name-random-again": "Nama tampilan dibuat secara acak lagi.",
- "display-name-changed-permanently": "Nama tampilan diubah secara permanen.",
- "copied-to-clipboard-error": "Penyalinan tak dapat dilakukan. Salinlah secara manual.",
- "pairing-success": "Perangkat dipasangkan.",
- "clipboard-content-incorrect": "Isi papan klip keliru.",
- "display-name-changed-temporarily": "Nama tampilan hanya diubah untuk sesi ini.",
- "copied-to-clipboard": "Disalin ke papan klip",
- "offline": "Anda sedang offline",
- "pairing-tabs-error": "Memasangkan dua tab browser web tidak mungkin dilakukan.",
- "public-room-id-invalid": "Room ID tidak valid",
- "click-to-download": "Klik untuk mengunduh",
- "pairing-cleared": "Semua Perangkat dilepaskan.",
- "notifications-enabled": "Notifikasi diaktifkan.",
- "online-requirement-pairing": "Anda harus online untuk memasangkan perangkat.",
- "ios-memory-limit": "Mengirim file ke iOS hanya dapat dilakukan hingga 200 MB sekaligus",
- "online-requirement-public-room": "Anda harus online untuk membuat ruang publik.",
- "copied-text-error": "Menyalin ke papan klip gagal. Salinlah secara manual!",
- "download-successful": "{{descriptor}} diunduh",
- "click-to-show": "Klik untuk menampilkan",
- "notifications-permissions-error": "Izin pemberitahuan telah diblokir karena pengguna telah mengabaikan permintaan izin beberapa kali. Hal ini dapat diatur ulang di Info Halaman yang dapat diakses dengan mengeklik ikon kunci di sebelah bar URL.",
- "pair-url-copied-to-clipboard": "Tautan untuk memasangkan perangkat ini disalin ke papan klip",
- "room-url-copied-to-clipboard": "Tautan ke ruang publik disalin ke papan klip"
- },
- "header": {
- "cancel-paste-mode": "Selesai",
- "theme-auto_title": "Sesuaikan tema dengan sistem",
- "install_title": "Instal PairDrop",
- "theme-dark_title": "Selalu gunakan tema gelap",
- "pair-device_title": "Pasangkan perangkat anda secara permanen",
- "join-public-room_title": "Bergabung dgn. ruang publik sementara",
- "notification_title": "Aktifkan notifikasi",
- "edit-paired-devices_title": "Edit perangkat yg. dipasangkan",
- "language-selector_title": "Atur Bahasa",
- "about_title": "Tentang PairDrop",
- "about_aria-label": "Buka Tentang PairDrop",
- "theme-light_title": "Selalu gunakan tema terang"
- },
- "instructions": {
- "x-instructions_mobile": "Ketuk untuk mengirim file atau ketuk lama untuk mengirim pesan",
- "click-to-send": "Klik untuk mengirim",
- "activate-paste-mode-and-other-files": "dan {{count}} file lainnya",
- "tap-to-send": "Ketuk untuk mengirim",
- "activate-paste-mode-base": "Buka PairDrop di perangkat lain untuk berkirim",
- "no-peers-subtitle": "Pasangkan perangkat atau masuk ke ruang publik agar dapat terdeteksi di jaringan lain",
- "activate-paste-mode-shared-text": "teks bersama",
- "x-instructions_desktop": "Klik untuk mengirim file atau klik kanan untuk mengirim pesan",
- "no-peers-title": "Buka PairDrop di perangkat lain untuk berkirim file",
- "x-instructions_data-drop-peer": "Lepaskan untuk mengirim ke rekan",
- "x-instructions_data-drop-bg": "Lepaskan untuk memilih penerima",
- "no-peers_data-drop-bg": "Lepaskan untuk memilih penerima"
- },
- "peer-ui": {
- "processing": "Memproses…",
- "click-to-send-paste-mode": "Klik untuk mengirim {{descriptor}}",
- "click-to-send": "Klik untuk mengirim file atau klik kanan untuk mengirim pesan",
- "waiting": "Menunggu…",
- "connection-hash": "Untuk memverifikasi keamanan enkripsi end-to-end, bandingkan nomor keamanan ini pada kedua perangkat",
- "preparing": "Menyiapkan…",
- "transferring": "Mentransfer…"
- },
- "dialogs": {
- "base64-paste-to-send": "Tempel di sini untuk mengirim {{type}}",
- "auto-accept-instructions-2": "untuk secara otomatis menerima semua file yang dikirim dari perangkat tersebut.",
- "receive-text-title": "Pesan Diterima",
- "edit-paired-devices-title": "Edit Perangkat yg. Dipasangkan",
- "cancel": "Batal",
- "auto-accept-instructions-1": "Aktifkan",
- "pair-devices-title": "Pasangkan Perangkat Scr. Permanen",
- "download": "Unduh",
- "title-file": "File",
- "base64-processing": "Memproses…",
- "decline": "Tolak",
- "receive-title": "{{descriptor}} Diterima",
- "leave": "Tinggalkan",
- "join": "Gabung",
- "title-image-plural": "Gambar",
- "send": "Kirim",
- "base64-tap-to-paste": "Ketuk di sini untuk menempelkan {{type}}",
- "base64-text": "teks",
- "copy": "Salin",
- "file-other-description-image": "dan 1 gambar lainnya",
- "temporary-public-room-title": "Ruang Publik Sementara",
- "base64-files": "file",
- "has-sent": "telah mengirim:",
- "file-other-description-file": "dan 1 file lainnya",
- "close": "Tutup",
- "system-language": "Bahasa Sistem",
- "unpair": "Lepas",
- "title-image": "Gambar",
- "file-other-description-file-plural": "dan {{count}} file lainnya",
- "would-like-to-share": "ingin berbagi",
- "send-message-to": "Kirim pesan ke",
- "language-selector-title": "Pilih Bahasa",
- "pair": "Pasangkan",
- "hr-or": "ATAU",
- "scan-qr-code": "atau pindai kode QR.",
- "input-key-on-this-device": "Masukkan kunci ini pada perangkat lain",
- "download-again": "Unduh lagi",
- "accept": "Terima",
- "paired-devices-wrapper_data-empty": "Tak ada perangkat yg. dipasangkan.",
- "enter-key-from-another-device": "Masukkan kunci dari perangkat lain di sini.",
- "share": "Bagikan",
- "auto-accept": "terima-otomatis",
- "title-file-plural": "File",
- "send-message-title": "Kirim Pesan",
- "input-room-id-on-another-device": "Masukkan room ID ini pada perangkat lain",
- "file-other-description-image-plural": "dan {{count}} gambar lainnya",
- "enter-room-id-from-another-device": "Masukkan room ID dari perangkat lain untuk bergabung dengan room.",
- "message_title": "Masukkan pesan untuk dikirim",
- "pair-devices-qr-code_title": "Klik untuk menyalin tautan untuk memasangkan perangkat ini",
- "public-room-qr-code_title": "Klik untuk menyalin tautan ke ruang publik"
- },
- "about": {
- "claim": "Cara termudah untuk mentransfer file lintas perangkat",
- "tweet_title": "Tweet tentang PairDrop",
- "close-about_aria-label": "Tutup Tentang PairDrop",
- "buy-me-a-coffee_title": "Traktir aku kopi!",
- "github_title": "PairDrop di GitHub",
- "faq_title": "Pertanyaan yang sering diajukan"
- },
- "document-titles": {
- "file-transfer-requested": "Permintaan Transfer File",
- "message-received-plural": "{{count}} Pesan Diterima",
- "message-received": "Pesan Diterima",
- "file-received": "File Diterima",
- "file-received-plural": "{{count}} File Diterima",
- "image-transfer-requested": "Permintaan Transfer Gambar"
- }
-}
diff --git a/public_included_ws_fallback/lang/it.json b/public_included_ws_fallback/lang/it.json
deleted file mode 100644
index d753438..0000000
--- a/public_included_ws_fallback/lang/it.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "footer": {
- "webrtc": "se WebRTC non è disponibile.",
- "public-room-devices_title": "Puoi essere rilevato dai dispositivi presenti in questa stanza pubblica indipendentemente dalla rete.",
- "display-name_data-placeholder": "Caricamento…",
- "display-name_title": "Modifica il nome del tuo dispositivo permanentemente",
- "traffic": "Il traffico è",
- "paired-devices_title": "Puoi essere rilevato dai dispositivi abbinati in ogni momento, indipendentemente dalla rete.",
- "public-room-devices": "nella stanza {{roomId}}",
- "paired-devices": "da dispositivi abbinati",
- "on-this-network": "su questa rete",
- "routed": "instradato attraverso il server",
- "discovery": "Puoi essere rilevato:",
- "on-this-network_title": "puoi essere rilevato da chiunque su questa rete.",
- "known-as": "Sei visibile come:"
- },
- "header": {
- "cancel-paste-mode": "Fatto",
- "theme-auto_title": "Adatta il tema al sistema automaticamente",
- "install_title": "Installa PairDrop",
- "theme-dark_title": "Usa sempre il tema scuro",
- "pair-device_title": "Abbina i tuoi dispositivi permanentemente",
- "join-public-room_title": "Unisciti ad una stanza pubblica temporaneamente",
- "notification_title": "Attiva notifiche",
- "edit-paired-devices_title": "Modifica i dispositivi abbinati",
- "language-selector_title": "Imposta Lingua",
- "about_title": "Informazioni su PairDrop",
- "about_aria-label": "Apri Informazioni su PairDrop",
- "theme-light_title": "Usa sempre il tema chiaro"
- },
- "instructions": {
- "x-instructions_mobile": "Tocca per inviare file o tocco prolungato per inviare un messaggio",
- "click-to-send": "Clicca per inviare",
- "activate-paste-mode-and-other-files": "e altri {{count}} files",
- "tap-to-send": "Tocca per inviare",
- "activate-paste-mode-base": "Apri PairDrop su altri dispositivi per inviare",
- "no-peers-subtitle": "Abbina dispositivi o entra in una stanza pubblica per essere rilevabile su altre reti",
- "activate-paste-mode-shared-text": "testo condiviso",
- "x-instructions_desktop": "Clicca per inviare files o usa il tasto destro per inviare un messaggio",
- "no-peers-title": "Apri PairDrop su altri dispositivi per inviare files",
- "x-instructions_data-drop-peer": "Rilascia per inviare al peer",
- "x-instructions_data-drop-bg": "Rilascia per selezionare il destinatario",
- "no-peers_data-drop-bg": "Rilascia per selezionare il destinatario"
- },
- "dialogs": {
- "auto-accept-instructions-2": "per accettare automaticamente tutti i files inviati da quel dispositivo.",
- "edit-paired-devices-title": "Modifica Dispositivi Abbinati",
- "cancel": "Annulla",
- "auto-accept-instructions-1": "Attiva",
- "pair-devices-title": "Abbina Dispositivi Permanentemente",
- "temporary-public-room-title": "Stanza Pubblica Temporanea",
- "close": "Chiudi",
- "unpair": "Dissocia",
- "pair": "Abbina",
- "scan-qr-code": "o scannerizza il codice QR.",
- "input-key-on-this-device": "Inserisci questo codice su un altro dispositivo",
- "paired-devices-wrapper_data-empty": "Nessun dispositivo abbinato.",
- "enter-key-from-another-device": "Inserisci il codice dell'altro dispositivo qui.",
- "auto-accept": "accetta-automaticamente",
- "input-room-id-on-another-device": "Inserisci l'ID di questa stanza su un altro dispositivo",
- "enter-room-id-from-another-device": "Inserisci l'ID stanza da un altro dispositivo per accedere alla stanza.",
- "base64-paste-to-send": "Incolla qui per inviare {{type}}",
- "receive-text-title": "Messaggio Ricevuto",
- "download": "Scarica",
- "title-file": "File",
- "base64-processing": "Elaborazione…",
- "decline": "Rifiuta",
- "receive-title": "{{descriptor}} Ricevuto",
- "leave": "Abbandona",
- "join": "Unisciti",
- "title-image-plural": "Immagini",
- "send": "Invia",
- "base64-tap-to-paste": "Tocca qui per incollare {{type}}",
- "base64-text": "testo",
- "copy": "Copia",
- "file-other-description-image": "e 1 altra immagine",
- "base64-files": "files",
- "has-sent": "ha inviato:",
- "file-other-description-file": "ed 1 altro file",
- "system-language": "Lingua di Sistema",
- "title-image": "Immagine",
- "file-other-description-file-plural": "e altri {{count}} files",
- "would-like-to-share": "vorrebbe condividere",
- "send-message-to": "Invia un messaggio a",
- "language-selector-title": "Imposta Lingua",
- "hr-or": "OPPURE",
- "download-again": "Scarica ancora",
- "accept": "Accetta",
- "share": "Condividi",
- "title-file-plural": "Files",
- "send-message-title": "Invia Messaggio",
- "file-other-description-image-plural": "e {{count}} altre immagini",
- "message_title": "Inserisci il messaggio da inviare",
- "pair-devices-qr-code_title": "Clicca per copiare il link di abbinamento di questo dispositivo",
- "public-room-qr-code_title": "Clicca per copirare il link della stanza pubblica"
- },
- "notifications": {
- "request-title": "{{name}} vorrebbe trasferire {{count}} {{descriptor}}",
- "unfinished-transfers-warning": "Ci sono dei trasferimenti in corso. Sei sicuro di voler chiudere PairDrop?",
- "message-received": "Messaggio ricevuto da {{name}} - Clicca per copiare",
- "rate-limit-join-key": "Limite raggiunto. Aspetta 10 secondi e riprova.",
- "connecting": "Connessione…",
- "pairing-key-invalidated": "Il codice {{key}} è stato invalidato.",
- "pairing-key-invalid": "Codice non valido",
- "connected": "Connesso.",
- "pairing-not-persistent": "I dispositivi abbinati non sono persistenti.",
- "text-content-incorrect": "Il contenuto testuale non è corretto.",
- "message-transfer-completed": "Trasferimento del messaggio completato.",
- "file-transfer-completed": "Trasferimento file completato.",
- "file-content-incorrect": "Il contenuto del file non è corretto.",
- "files-incorrect": "I file non sono corretti.",
- "selected-peer-left": "Peer selezionato ha abbandonato.",
- "link-received": "Link ricevuto da {{name}} - Clicca per aprire",
- "online": "Sei di nuovo online",
- "public-room-left": "Ha lasciato la stanza pubblica {{publicRoomId}}",
- "copied-text": "Testo copiato negli appunti",
- "display-name-random-again": "Il nome visualizzato è generato casualmente un'altra volta.",
- "display-name-changed-permanently": "Il nome visualizzato è cambiato permanentemente.",
- "copied-to-clipboard-error": "La copia non è possibile. Copia manualmente.",
- "pairing-success": "Dispositivi abbinati.",
- "clipboard-content-incorrect": "Il contenuto copiato non è corretto.",
- "display-name-changed-temporarily": "Il nome visualizzato è cambiato solo per questa sessione.",
- "copied-to-clipboard": "Copiato negli appunti",
- "offline": "Sei offline",
- "pairing-tabs-error": "Abbinare due schede del browser è impossibile.",
- "public-room-id-invalid": "ID stanza non valido",
- "click-to-download": "Clicca per scaricare",
- "pairing-cleared": "Tutti i dispositivi sono stati dissociati.",
- "notifications-enabled": "Notifiche attivate.",
- "online-requirement-pairing": "Devi essere online per abbinare dispositivi.",
- "ios-memory-limit": "L'invio di file a dispositivi iOS è possibile solo 200 MB alla volta",
- "online-requirement-public-room": "Devi essere online per creare una stanza pubblica.",
- "copied-text-error": "Scrittura negli appunti fallita. Copia manualmente!",
- "download-successful": "{{descriptor}} scaricato",
- "click-to-show": "Clicca per mostrare",
- "notifications-permissions-error": "Il permesso all'invio delle notifiche è stato negato poiché l'utente ha ignorato varie volte le richieste di permesso. Ciò può essere ripristinato nelle \"informazioni sito\" cliccando sull'icona a forma di lucchetto vicino alla barra degli indirizzi.",
- "pair-url-copied-to-clipboard": "Link di abbinamento copiato negli appunti",
- "room-url-copied-to-clipboard": "Link della stanza copiato negli appunti"
- },
- "peer-ui": {
- "processing": "Elaborazione…",
- "click-to-send-paste-mode": "Clicca per inviare {{descriptor}}",
- "click-to-send": "Clicca per inviare files o tasto destro per inviare un messaggio",
- "waiting": "In attesa…",
- "connection-hash": "Per verificare la sicurezza della crittografia end-to-end, confronta questo numero di sicurezza su entrambi i dispositivi",
- "preparing": "Preparazione…",
- "transferring": "Trasferimento…"
- },
- "about": {
- "claim": "Il modo più semplice per trasferire files tra dispositivi",
- "tweet_title": "Twitta riguardo PairDrop",
- "close-about_aria-label": "Chiudi Informazioni su PairDrop",
- "buy-me-a-coffee_title": "Comprami un caffè!",
- "github_title": "PairDrop su GitHub",
- "faq_title": "Domande Frequenti"
- },
- "document-titles": {
- "file-transfer-requested": "Trasferimento File Richiesto",
- "image-transfer-requested": "Trasferimento Immagine Richiesto",
- "message-received-plural": "{{count}} Messaggi ricevuti",
- "message-received": "Messaggio ricevuto",
- "file-received": "File Ricevuto",
- "file-received-plural": "{{count}} Files Ricevuti"
- }
-}
diff --git a/public_included_ws_fallback/lang/ja.json b/public_included_ws_fallback/lang/ja.json
deleted file mode 100644
index 8803fe6..0000000
--- a/public_included_ws_fallback/lang/ja.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "footer": {
- "webrtc": "WebRTCが利用できない場合。",
- "public-room-devices_title": "このデバイスはネットワークと関係なく、このパブリックルームのデバイスにより発見される可能性があります。",
- "display-name_data-placeholder": "読み込み中…",
- "display-name_title": "永続的なデバイス名を編集する",
- "traffic": "トラフィックは",
- "paired-devices_title": "このデバイスはネットワークと関係なく、常にペア設定したデバイスにより発見される可能性があります。",
- "public-room-devices": "ルーム{{roomId}}上",
- "paired-devices": "ペア設定したデバイス",
- "on-this-network": "このネットワーク上",
- "routed": "サーバーを経由します",
- "discovery": "このデバイスは以下で発見される可能性があります:",
- "on-this-network_title": "このデバイスはこのネットワーク上の誰にでも発見される可能性があります。",
- "known-as": "他のデバイスに表示される名前:"
- },
- "notifications": {
- "request-title": "{{name}}は{{count}}個の{{descriptor}}を共有しようとしています",
- "unfinished-transfers-warning": "未完了の転送があります。本当にPairDropを終了しますか?",
- "message-received": "{{name}}から受信したメッセージ(クリックしてコピー)",
- "rate-limit-join-key": "レート制限に到達しました。10秒待ってから再度お試しください。",
- "connecting": "接続中…",
- "pairing-key-invalidated": "コード{{key}}が失効しました。",
- "pairing-key-invalid": "無効なコード",
- "connected": "接続しました。",
- "pairing-not-persistent": "ペア設定されたデバイスは永続化されていません。",
- "text-content-incorrect": "テキストの内容が不正です。",
- "message-transfer-completed": "メッセージの送信が完了しました。",
- "file-transfer-completed": "ファイルの転送が完了しました。",
- "file-content-incorrect": "ファイルの内容が不正です。",
- "files-incorrect": "ファイルが間違っています。",
- "selected-peer-left": "選択した相手が退出しました。",
- "link-received": "{{name}}から受信したリンク(クリックして開く)",
- "online": "オンラインに復帰しました",
- "public-room-left": "パブリックルーム{{publicRoomId}}から退出しました",
- "copied-text": "テキストをクリップボードにコピーしました",
- "display-name-random-again": "表示名がもう一度ランダムに生成されました。",
- "display-name-changed-permanently": "永続的な表示名が変更されました。",
- "copied-to-clipboard-error": "コピーできませんでした。手動でコピーしてください。",
- "pairing-success": "デバイスがペア設定されました。",
- "clipboard-content-incorrect": "クリップボードの内容が不正です。",
- "display-name-changed-temporarily": "このセッションでの表示名が変更されました。",
- "copied-to-clipboard": "クリップボードにコピーしました",
- "offline": "オフラインです",
- "pairing-tabs-error": "同じWebブラウザーの2つのタブをペア設定することはできません。",
- "public-room-id-invalid": "無効なルームID",
- "click-to-download": "クリックしてダウンロード",
- "pairing-cleared": "全てのデバイスのペア設定を解除しました。",
- "notifications-enabled": "通知が有効です。",
- "online-requirement-pairing": "デバイスをペア設定するにはオンラインである必要があります。",
- "ios-memory-limit": "iOSへのファイル送信は一度に200MBまでしかできません",
- "online-requirement-public-room": "パブリックルームを作成するにはオンラインである必要があります。",
- "copied-text-error": "クリップボードにコピーできませんでした。手動でコピーしてください。",
- "download-successful": "{{descriptor}}をダウンロードしました",
- "click-to-show": "クリックして表示",
- "notifications-permissions-error": "ユーザーが権限のプロンプトを何度か閉じたため、通知の権限がブロックされました。これは、URL バーの横にある鍵アイコンをクリックしてアクセスできるページ情報からリセットできます。",
- "pair-url-copied-to-clipboard": "このデバイスをペア設定するリンクをクリップボードにコピーしました",
- "room-url-copied-to-clipboard": "パブリックルームへのリンクをクリップボードにコピーしました"
- },
- "header": {
- "cancel-paste-mode": "完了",
- "theme-auto_title": "テーマをシステムの設定に自動的に合わせる",
- "install_title": "PairDropをインストール",
- "theme-dark_title": "常にダークテーマを使用する",
- "pair-device_title": "あなたのデバイスを永続的にペア設定する",
- "join-public-room_title": "パブリックルームに一時的に参加する",
- "notification_title": "通知を有効にする",
- "edit-paired-devices_title": "ペア設定したデバイスを編集する",
- "language-selector_title": "言語を設定",
- "about_title": "PairDropについて",
- "about_aria-label": "PairDropについてを開く",
- "theme-light_title": "常にライトテーマを使用する"
- },
- "instructions": {
- "x-instructions_mobile": "タップしてファイルを送信または長押ししてメッセージを送信します",
- "click-to-send": "クリックして送信",
- "activate-paste-mode-and-other-files": "とその他{{count}}個のファイル",
- "tap-to-send": "タップして送信",
- "activate-paste-mode-base": "他のデバイスでPairDropを開いて送信します",
- "no-peers-subtitle": "デバイスをペア設定するかパブリックルームに参加すると、他のネットワーク上からも見つけられるようになります",
- "activate-paste-mode-shared-text": "共有されたテキスト",
- "x-instructions_desktop": "左クリックしてファイルを送信または右クリックしてメッセージを送信します",
- "no-peers-title": "他のデバイスでPairDropを開いてファイルを送信します",
- "x-instructions_data-drop-peer": "離すとこの相手に送信します",
- "x-instructions_data-drop-bg": "送信したい相手の上で離してください",
- "no-peers_data-drop-bg": "送信したい相手の上で離してください"
- },
- "peer-ui": {
- "processing": "処理中…",
- "click-to-send-paste-mode": "クリックして{{descriptor}}を送信",
- "click-to-send": "クリックしてファイルを送信または右クリックしてメッセージを送信します",
- "waiting": "待機中…",
- "connection-hash": "エンドツーエンド暗号化のセキュリティを確認するには、両方のデバイスのセキュリティナンバーを確認します",
- "preparing": "準備中…",
- "transferring": "転送中…"
- },
- "dialogs": {
- "base64-paste-to-send": "ここをタップして{{type}}を送信",
- "auto-accept-instructions-2": "そのデバイスから送信される全てのファイルを自動的に承諾します。",
- "receive-text-title": "メッセージを受信しました",
- "edit-paired-devices-title": "ペア設定したデバイスを編集",
- "cancel": "キャンセル",
- "auto-accept-instructions-1": "有効化",
- "pair-devices-title": "デバイスを永続的にペア設定",
- "download": "ダウンロード",
- "title-file": "ファイル",
- "base64-processing": "処理中…",
- "decline": "拒否",
- "receive-title": "{{descriptor}}を受信しました",
- "leave": "退出",
- "join": "参加",
- "title-image-plural": "複数の画像",
- "send": "送信",
- "base64-tap-to-paste": "ここをタップして{{type}}を貼り付け",
- "base64-text": "テキスト",
- "copy": "コピー",
- "file-other-description-image": "と1個の他の画像",
- "temporary-public-room-title": "一時的なパブリックルーム",
- "base64-files": "ファイル",
- "has-sent": "が送信しました:",
- "file-other-description-file": "と1個の他のファイル",
- "close": "閉じる",
- "system-language": "システムの言語",
- "unpair": "ペア解除",
- "title-image": "画像",
- "file-other-description-file-plural": "と{{count}}個の他のファイル",
- "would-like-to-share": "が以下のファイルを共有しようとしています",
- "send-message-to": "このデバイスにメッセージを送信:",
- "language-selector-title": "言語を設定",
- "pair": "ペア設定",
- "hr-or": "または",
- "scan-qr-code": "もしくはQRコードをスキャンします。",
- "input-key-on-this-device": "このキーを他のデバイスに入力する",
- "download-again": "もう一度ダウンロードする",
- "accept": "承諾",
- "paired-devices-wrapper_data-empty": "ペア設定したデバイスはありません。",
- "enter-key-from-another-device": "他のデバイスに表示されたキーをここに入力します。",
- "share": "共有",
- "auto-accept": "自動承諾",
- "title-file-plural": "複数のファイル",
- "send-message-title": "メッセージを送信",
- "input-room-id-on-another-device": "このルームIDを他のデバイスに入力",
- "file-other-description-image-plural": "と{{count}}個の他の画像",
- "enter-room-id-from-another-device": "他のデバイスに表示された参加したいルームのIDを入力します。",
- "message_title": "送信するメッセージを挿入",
- "pair-devices-qr-code_title": "クリックしてこのデバイスをペア設定するリンクをコピー",
- "public-room-qr-code_title": "クリックしてパブリックルームへのリンクをコピー"
- },
- "about": {
- "claim": "デバイス間でファイルを転送する最も簡単な方法",
- "tweet_title": "PairDropについてツイートする",
- "close-about_aria-label": "PairDropについてを閉じる",
- "buy-me-a-coffee_title": "コーヒーをおごってください!",
- "github_title": "GitHubでPairDropを見る",
- "faq_title": "FAQ"
- },
- "document-titles": {
- "file-transfer-requested": "ファイルの転送がリクエストされました",
- "image-transfer-requested": "画像の転送がリクエストされました",
- "message-received-plural": "{{count}}個のメッセージを受信しました",
- "message-received": "メッセージを受信しました",
- "file-received": "ファイルを受信しました",
- "file-received-plural": "{{count}}個のファイルを受信しました"
- }
-}
diff --git a/public_included_ws_fallback/lang/nb.json b/public_included_ws_fallback/lang/nb.json
deleted file mode 100644
index 394fb90..0000000
--- a/public_included_ws_fallback/lang/nb.json
+++ /dev/null
@@ -1,138 +0,0 @@
-{
- "header": {
- "edit-paired-devices_title": "Rediger sammenkoblede enheter",
- "about_title": "Om PairDrop",
- "about_aria-label": "Åpne «Om PairDrop»",
- "theme-auto_title": "Juster drakt til system",
- "theme-light_title": "Alltid bruk lys drakt",
- "theme-dark_title": "Alltid bruk mørk drakt",
- "notification_title": "Skru på merknader",
- "cancel-paste-mode": "Ferdig",
- "install_title": "Installer PairDrop",
- "pair-device_title": "Sammenkoble enhet"
- },
- "footer": {
- "webrtc": "hvis WebRTC ikke er tilgjengelig.",
- "display-name_data-placeholder": "Laster inn…",
- "display-name_title": "Rediger det vedvarende enhetsnavnet ditt",
- "traffic": "Trafikken",
- "on-this-network": "på dette nettverket",
- "known-as": "Du er kjent som:",
- "paired-devices": "sammenkoblede enheter",
- "routed": "Sendes gjennom tjeneren"
- },
- "instructions": {
- "x-instructions_desktop": "Klikk for å sende filer, eller høyreklikk for å sende en melding",
- "x-instructions_mobile": "Trykk for å sende filer, eller lang-trykk for å sende en melding",
- "x-instructions_data-drop-bg": "Slipp for å velge mottager",
- "click-to-send": "Klikk for å sende",
- "no-peers_data-drop-bg": "Slipp for å velge mottager",
- "no-peers-title": "Åpne PairDrop på andre enheter for å sende filer",
- "no-peers-subtitle": "Sammenkoble enheter for å kunne oppdages på andre nettverk",
- "x-instructions_data-drop-peer": "Slipp for å sende til likemann",
- "tap-to-send": "Trykk for å sende",
- "activate-paste-mode-base": "Åpne PairDrop på andre enheter for å sende",
- "activate-paste-mode-and-other-files": "og {{count}} andre filer",
- "activate-paste-mode-shared-text": "delt tekst"
- },
- "dialogs": {
- "input-key-on-this-device": "Skriv inn denne nøkkelen på en annen enhet",
- "pair-devices-title": "Sammenkoble enheter",
- "would-like-to-share": "ønsker å dele",
- "auto-accept-instructions-2": "for å godkjenne alle filer sendt fra den enheten automatisk.",
- "paired-devices-wrapper_data-empty": "Ingen sammenkoblede enheter.",
- "enter-key-from-another-device": "Skriv inn nøkkel fra en annen enhet for å fortsette.",
- "edit-paired-devices-title": "Rediger sammenkoblede enheter",
- "accept": "Godta",
- "has-sent": "har sendt:",
- "base64-paste-to-send": "Trykk her for å sende {{type}}",
- "base64-text": "tekst",
- "base64-files": "filer",
- "file-other-description-image-plural": "og {{count}} andre bilder",
- "receive-title": "{{descriptor}} mottatt",
- "send-message-title": "Send melding",
- "base64-processing": "Behandler…",
- "close": "Lukk",
- "decline": "Avslå",
- "download": "Last ned",
- "copy": "Kopier",
- "pair": "Sammenkoble",
- "cancel": "Avbryt",
- "scan-qr-code": "eller skann QR-koden.",
- "auto-accept-instructions-1": "Aktiver",
- "receive-text-title": "Melding mottatt",
- "auto-accept": "auto-godkjenn",
- "share": "Del",
- "send-message-to": "Send en melding til",
- "send": "Send",
- "base64-tap-to-paste": "Trykk her for å lime inn {{type}}",
- "file-other-description-image": "og ett annet bilde",
- "file-other-description-file-plural": "og {{count}} andre filer",
- "title-file-plural": "Filer",
- "download-again": "Last ned igjen",
- "file-other-description-file": "og én annen fil",
- "title-image": "Bilde",
- "title-file": "Fil",
- "title-image-plural": "Bilder"
- },
- "about": {
- "close-about_aria-label": "Lukk «Om PairDrop»",
- "faq_title": "Ofte stilte spørsmål",
- "claim": "Den enkleste måten å overføre filer mellom enheter",
- "buy-me-a-coffee_title": "Spander drikke.",
- "tweet_title": "Tvitre om PairDrop",
- "github_title": "PairDrop på GitHub"
- },
- "notifications": {
- "copied-to-clipboard": "Kopiert til utklippstavlen",
- "pairing-tabs-error": "Sammenkobling av to nettleserfaner er ikke mulig.",
- "notifications-enabled": "Merknader påskrudd.",
- "click-to-show": "Klikk for å vise",
- "copied-text": "Tekst kopiert til utklippstavlen",
- "connected": "Tilkoblet.",
- "online": "Du er tilbake på nett",
- "file-transfer-completed": "Filoverføring utført.",
- "selected-peer-left": "Valgt likemann dro.",
- "pairing-key-invalid": "Ugyldig nøkkel",
- "connecting": "Kobler til …",
- "pairing-not-persistent": "Sammenkoblede enheter er ikke vedvarende.",
- "offline": "Du er frakoblet",
- "online-requirement": "Du må være på nett for å koble sammen enheter.",
- "display-name-random-again": "Visningsnavnet er tilfeldig generert igjen.",
- "display-name-changed-permanently": "Visningsnavnet er endret for godt.",
- "display-name-changed-temporarily": "Visningsnavnet er endret kun for denne økten.",
- "text-content-incorrect": "Tekstinnholdet er uriktig.",
- "file-content-incorrect": "Filinnholdet er uriktig.",
- "click-to-download": "Klikk for å laste ned",
- "message-transfer-completed": "Meldingsoverføring utført.",
- "download-successful": "{{descriptor}} nedlastet",
- "pairing-success": "Enheter sammenkoblet.",
- "pairing-cleared": "Sammenkobling av alle enheter opphevet.",
- "pairing-key-invalidated": "Nøkkel {{key}} ugyldiggjort.",
- "copied-text-error": "Kunne ikke legge innhold i utklkippstavlen. Kopier manuelt!",
- "clipboard-content-incorrect": "Utklippstavleinnholdet er uriktig.",
- "link-received": "Lenke mottatt av {{name}} - Klikk for å åpne.",
- "request-title": "{{name}} ønsker å overføre {{count}} {{descriptor}}",
- "message-received": "Melding mottatt av {{name}} - Klikk for å åpne",
- "files-incorrect": "Filene er uriktige.",
- "ios-memory-limit": "Forsendelse av filer til iOS er kun mulig opptil 200 MB av gangen.",
- "unfinished-transfers-warning": "Lukk med ufullførte overføringer?",
- "rate-limit-join-key": "Forsøksgrense overskredet. Vent 10 sek. og prøv igjen."
- },
- "document-titles": {
- "file-received": "Fil mottatt",
- "file-received-plural": "{{count}} filer mottatt",
- "message-received": "Melding mottatt",
- "file-transfer-requested": "Filoverføring forespurt",
- "message-received-plural": "{{count}} meldinger mottatt"
- },
- "peer-ui": {
- "preparing": "Forbereder …",
- "waiting": "Venter…",
- "processing": "Behandler …",
- "transferring": "Overfører …",
- "click-to-send": "Klikk for å sende filer, eller høyreklikk for å sende en melding",
- "click-to-send-paste-mode": "Klikk for å sende {{descriptor}}",
- "connection-hash": "Sammenlign dette sikkerhetsnummeret på begge enhetene for å bekrefte ende-til-ende -krypteringen."
- }
-}
diff --git a/public_included_ws_fallback/lang/nl.json b/public_included_ws_fallback/lang/nl.json
deleted file mode 100644
index 573b93b..0000000
--- a/public_included_ws_fallback/lang/nl.json
+++ /dev/null
@@ -1,159 +0,0 @@
-{
- "footer": {
- "webrtc": "als WebRTC niet beschikbaar is.",
- "public-room-devices_title": "U kan door apparaten gevonden worden in deze openbare ruimte, ongeacht van het netwerk.",
- "display-name_data-placeholder": "Laden…",
- "display-name_title": "Bewerk uw apparaatnaam permanent",
- "traffic": "Dataverkeer is",
- "paired-devices_title": "U kan gevonden worden door gekoppelde apparaten, ongeacht van het netwerk.",
- "public-room-devices": "in kamer {{roomId}}",
- "paired-devices": "door gekoppelde apparaten",
- "on-this-network": "op dit netwerk",
- "routed": "door de server geleid",
- "discovery": "U bent zichtbaar:",
- "on-this-network_title": "U kan door iedereen gevonden worden op dit netwerk.",
- "known-as": "U staat bekend als:"
- },
- "notifications": {
- "request-title": "{{name}} zou graag {{count}}{{descriptor}} overdragen",
- "unfinished-transfers-warning": "Nog niet alle overdrachten zijn compleet. Weet u zeker dat u PairDrop sluiten?",
- "message-received": "Bericht ontvangen van {{name}} - Klik om te kopiëren",
- "rate-limit-join-key": "Tempolimiet bereikt. Wacht 10 seconde en probeer opnieuw.",
- "connecting": "Verbinden…",
- "pairing-key-invalidated": "Sleutel {{key}} ongeldig.",
- "pairing-key-invalid": "Ongeldige sleutel",
- "connected": "Verbonden.",
- "pairing-not-persistent": "Gekoppelde apparaten zijn niet persistent.",
- "text-content-incorrect": "Tekst inhoud is incorrect.",
- "message-transfer-completed": "Berichtsoverdracht compleet.",
- "file-transfer-completed": "Bestandsoverdracht compleet.",
- "file-content-incorrect": "Bestandsinhoud is incorrect.",
- "files-incorrect": "Bestanden zijn incorrect.",
- "selected-peer-left": "Gekozen peer is vertrokken.",
- "link-received": "Link van {{name}} ontvangen - Klik om te openen",
- "online": "U bent terug online",
- "public-room-left": "Openbare ruimte {{publicRoomId}} verlaten",
- "copied-text": "Tekst naar klembord gekopieërd",
- "display-name-random-again": "De weergavenaam is opnieuw willekeurig gegenereerd.",
- "display-name-changed-permanently": "De weergavenaam is permanent gewijzigd.",
- "copied-to-clipboard-error": "Kopiëren is niet mogelijk. Kopieer handmatig.",
- "pairing-success": "Apparaten gekoppeld.",
- "clipboard-content-incorrect": "De inhoud van het klembord is incorrect.",
- "display-name-changed-temporarily": "De weergavenaam is alleen voor deze sessie gewijzigd.",
- "copied-to-clipboard": "Gekopieerd naar klembord",
- "offline": "U bent offline",
- "pairing-tabs-error": "Twee webbrowser tabbladen koppelen in is onmogelijk.",
- "public-room-id-invalid": "Ongeldig kamer ID",
- "click-to-download": "Klik om te downloaden",
- "pairing-cleared": "Alle apparaten ontkoppeld.",
- "notifications-enabled": "Meldingen geactiveerd.",
- "online-requirement-pairing": "U moet online zijn om apparaten te koppelen.",
- "ios-memory-limit": "Bestandsoverdrachten naar iOS kunnen slechts met 200 MB per keer",
- "online-requirement-public-room": "U moet online zijn om een openbare kamer te maken.",
- "copied-text-error": "Schrijven naar klembord mislukt. Kopieer handmatig!",
- "download-successful": "{{descriptor}} downloaden",
- "click-to-show": "Klik om te tonen"
- },
- "header": {
- "cancel-paste-mode": "Klaar",
- "theme-auto_title": "Gebruik systeemstijl",
- "install_title": "PairDrop installeren",
- "theme-dark_title": "Altijd donkere modus gebruiken",
- "pair-device_title": "Koppel uw apparaten permanent",
- "join-public-room_title": "Openbare ruimte tijdelijk betreden",
- "notification_title": "Meldingen inschakelen",
- "edit-paired-devices_title": "Gekoppelde apparaten bewerken",
- "language-selector_title": "Taal Selecteren",
- "about_title": "Over PairDrop",
- "about_aria-label": "Open Over PairDrop",
- "theme-light_title": "Altijd lichte modus gebruiken"
- },
- "instructions": {
- "x-instructions_mobile": "Tik om bestanden te versturen of houdt vast om een bericht te sturen",
- "click-to-send": "Klik om te verzenden",
- "activate-paste-mode-and-other-files": "en {{count}} andere bestanden",
- "tap-to-send": "Tik om te verzenden",
- "activate-paste-mode-base": "Open PairDrop op andere apparaten om te verzenden",
- "no-peers-subtitle": "Koppel apparaten of betreed een openbare ruimte om op andere netwerken zichtbaar te worden",
- "activate-paste-mode-shared-text": "gedeelde tekst",
- "x-instructions_desktop": "Klik om bestanden te versturen of rechtsklik om een bericht te sturen",
- "no-peers-title": "Open PairDrop op andere apparaten om bestanden te versturen",
- "x-instructions_data-drop-peer": "Laat los om naar peer te versturen",
- "x-instructions_data-drop-bg": "Loslaten om ontvanger te selecteren",
- "no-peers_data-drop-bg": "Loslaten om ontvanger te kiezen"
- },
- "peer-ui": {
- "processing": "Verwerken…",
- "click-to-send-paste-mode": "Klik om {{descriptor}} te versturen",
- "click-to-send": "Klik om bestanden te versturen of rechtsklik om een bericht te versturen",
- "waiting": "Wachten…",
- "connection-hash": "Vergelijk dit veiligheidsnummer op beide apparaten, om de beveiliging van de eind-tot-eind versleuteling te verifiëren",
- "preparing": "Voorbereiden…",
- "transferring": "Overdragen…"
- },
- "dialogs": {
- "base64-paste-to-send": "Plak hier om {{type}} te versturen",
- "auto-accept-instructions-2": "om automatisch alle bestanden van dat apparaat te accepteren.",
- "receive-text-title": "Bericht Ontvangen",
- "edit-paired-devices-title": "Gekoppelde Apparaten Bewerken",
- "cancel": "Annuleer",
- "auto-accept-instructions-1": "Activeer",
- "pair-devices-title": "Koppel Apparaten Permanent",
- "download": "Download",
- "title-file": "Bestand",
- "base64-processing": "Verwerken…",
- "decline": "Afwijzen",
- "receive-title": "{{descriptor}} Ontvangen",
- "leave": "Verlaten",
- "join": "Betreden",
- "title-image-plural": "Afbeeldingen",
- "send": "Verzenden",
- "base64-tap-to-paste": "Hier tikken om {{type}} te plakken",
- "base64-text": "tekst",
- "copy": "Kopiëren",
- "file-other-description-image": "en één andere afbeelding",
- "temporary-public-room-title": "Tijdelijke Openbare Ruimte",
- "base64-files": "bestanden",
- "has-sent": "stuurde het volgende:",
- "file-other-description-file": "en één ander bestand",
- "close": "Sluiten",
- "system-language": "Systeemtaal",
- "unpair": "Ontkoppel",
- "title-image": "Afbeelding",
- "file-other-description-file-plural": "en {{count}} andere bestanden",
- "would-like-to-share": "zou graag het volgende willen delen",
- "send-message-to": "Verstuur een bericht naar",
- "language-selector-title": "Taal Instellen",
- "pair": "Koppel",
- "hr-or": "OF",
- "scan-qr-code": "of scan de QR-code.",
- "input-key-on-this-device": "Voer deze sleutel in op een ander apparaat",
- "download-again": "Download opnieuw",
- "accept": "Accepteren",
- "paired-devices-wrapper_data-empty": "Geen gekoppelde apparaten.",
- "enter-key-from-another-device": "Voer hier de sleutel van een ander apparaat in.",
- "share": "Delen",
- "auto-accept": "automatisch-accepteren",
- "title-file-plural": "Bestanden",
- "send-message-title": "Bericht Sturen",
- "input-room-id-on-another-device": "Voer de kamer ID in op een ander apparaat",
- "file-other-description-image-plural": "en {{count}} andere afbeeldingen",
- "enter-room-id-from-another-device": "Voer de kamer ID van een ander apparaat hier in."
- },
- "about": {
- "claim": "De makkelijkste manier om bestanden tussen apparaten te versturen",
- "tweet_title": "Tweet over PairDrop",
- "close-about_aria-label": "Sluit Over PairDrop",
- "buy-me-a-coffee_title": "Koop een kopje koffie voor mij!",
- "github_title": "PairDrop op GitHub",
- "faq_title": "Veel gestelde vragen"
- },
- "document-titles": {
- "file-transfer-requested": "Bestandsoverdracht verzocht",
- "image-transfer-requested": "Afbeeldingsoverdracht verzocht",
- "message-received-plural": "{{count}} berichten ontvangen",
- "message-received": "Bericht ontvangen",
- "file-received": "Bestand ontvangen",
- "file-received-plural": "{{count}} bestanden ontvangen"
- }
-}
diff --git a/public_included_ws_fallback/lang/ro.json b/public_included_ws_fallback/lang/ro.json
deleted file mode 100644
index ef364da..0000000
--- a/public_included_ws_fallback/lang/ro.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "footer": {
- "webrtc": "dacă WebRTC nu este disponibil.",
- "public-room-devices_title": "Poți fi descoperit de dispozitivele din această cameră publică, independent de rețea.",
- "display-name_data-placeholder": "Se încarcă…",
- "display-name_title": "Editați permanent numele dispozitivului tău",
- "traffic": "Traficul este",
- "paired-devices_title": "Poți fi descoperit în orice moment de dispozitivele cuplate, indiferent de rețea.",
- "public-room-devices": "în camera {{roomId}}",
- "paired-devices": "prin dispozitive împerecheate",
- "on-this-network": "în această rețea",
- "routed": "rutate prin server",
- "discovery": "Poți fi descoperit:",
- "on-this-network_title": "Poți fi descoperit de toată lumea din această rețea.",
- "known-as": "Ești cunoscut ca:"
- },
- "notifications": {
- "request-title": "{{name}} ar dori să transfere {{count}} {{descriptor}}",
- "unfinished-transfers-warning": "Există transferuri neterminate. Sigur vrei să închizi PairDrop?",
- "message-received": "Mesaj primit de {{name}} - Apasă pentru a copia",
- "rate-limit-join-key": "A fost atinsă limita ratei. Așteptați 10 secunde și încercați din nou.",
- "connecting": "Conectarea…",
- "pairing-key-invalidated": "Cheia {{key}} invalidată.",
- "pairing-key-invalid": "Cheie invalidă",
- "connected": "Conectat.",
- "pairing-not-persistent": "Dispozitivele cuplate nu sunt persistente.",
- "text-content-incorrect": "Conținutul textului este incorect.",
- "message-transfer-completed": "Transferul mesajului este finalizat.",
- "file-transfer-completed": "Transfer de fișiere finalizat.",
- "file-content-incorrect": "Conținutul fișierului este incorect.",
- "files-incorrect": "Fișierele sunt incorecte.",
- "selected-peer-left": "Selectat peer a plecat.",
- "link-received": "Link primit de {{name}} - Apasă pentru a deschide",
- "online": "Ați revenit online",
- "public-room-left": "Plecat din camera publică {{publicRoomId}}",
- "copied-text": "Text copiat în clipboard",
- "display-name-random-again": "Numele afișat este din nou generat aleatoriu.",
- "display-name-changed-permanently": "Numele afișat este schimbat permanent.",
- "copied-to-clipboard-error": "Copierea nu este posibilă. Copiați manual.",
- "pairing-success": "Dispozitive asociate.",
- "clipboard-content-incorrect": "Conținutul clipboard-ului este incorect.",
- "display-name-changed-temporarily": "Numele afișat se modifică numai pentru această sesiune.",
- "copied-to-clipboard": "Copiat în clipboard",
- "offline": "Ești offline",
- "pairing-tabs-error": "Cuplarea între două file de browser web este imposibilă.",
- "public-room-id-invalid": "ID-ul camerei invalid",
- "click-to-download": "Apasă pentru a descărca",
- "pairing-cleared": "Toate dispozitivele sunt decuplate.",
- "notifications-enabled": "Notificări activate.",
- "online-requirement-pairing": "Trebuie să fiți online pentru a asocia dispozitivele.",
- "ios-memory-limit": "Trimiterea de fișiere pe iOS este posibilă doar până la 200 MB simultan",
- "online-requirement-public-room": "Trebuie să fiți online pentru a crea o cameră publică.",
- "copied-text-error": "Scrierea în clipboard a eșuat. Copiați manual!",
- "download-successful": "{{descriptor}} descărcat",
- "click-to-show": "Apasă pentru a arăta",
- "notifications-permissions-error": "Permisiunea de notificare a fost blocată deoarece utilizatorul a respins de mai multe ori solicitarea de permisiune. Acest lucru poate fi resetat în Info pagină, care poate fi accesat făcând clic pe pictograma de blocare de lângă bara URL.",
- "pair-url-copied-to-clipboard": "Link pentru a asocia acest dispozitiv copiat în clipboard",
- "room-url-copied-to-clipboard": "Link către sala publică copiat în clipboard"
- },
- "header": {
- "cancel-paste-mode": "Gata",
- "theme-auto_title": "Adaptează tema la sistem",
- "install_title": "Instalează PairDrop",
- "theme-dark_title": "Utilizați mereu tema întunecoasă",
- "pair-device_title": "Împerechează-ți permanent dispozitivele",
- "join-public-room_title": "Alătură-te temporar camerei publice",
- "notification_title": "Activați notificări",
- "edit-paired-devices_title": "Editați dispozitivele împerecheate",
- "language-selector_title": "Setează Limba",
- "about_title": "Despre PairDrop",
- "about_aria-label": "Deschide Despre PairDrop",
- "theme-light_title": "Utilizați mereu tema luminoasă"
- },
- "instructions": {
- "x-instructions_mobile": "Atingeți pentru a trimite fișiere sau atingeți lung pentru a trimite un mesaj",
- "click-to-send": "Clic pentru a trimite",
- "activate-paste-mode-and-other-files": "și {{count}} alte fișiere",
- "tap-to-send": "Atinge pentru a trimite",
- "activate-paste-mode-base": "Deschideți PairDrop pe alte dispozitive pentru a trimite",
- "no-peers-subtitle": "Împerecheați dispozitive sau intrați într-o cameră publică pentru a fi descoperit în alte rețele",
- "activate-paste-mode-shared-text": "text partajat",
- "x-instructions_desktop": "Dați clic pentru a trimite fișiere sau dați clic dreapta pentru a trimite un mesaj",
- "no-peers-title": "Deschideți PairDrop pe alte dispozitive pentru a trimite fișiere",
- "x-instructions_data-drop-peer": "Eliberare pentru a trimite la peer",
- "x-instructions_data-drop-bg": "Eliberați pentru a selecta recipientul",
- "no-peers_data-drop-bg": "Eliberare pentru a selecta recipientul"
- },
- "peer-ui": {
- "processing": "Procesarea…",
- "click-to-send-paste-mode": "Apasă pentru a trimite {{descriptor}}",
- "click-to-send": "Apasă pentru a trimite fișiere sau apasă cu butonul din dreapta pentru a trimite un mesaj",
- "waiting": "Așteptând…",
- "connection-hash": "Pentru a verifica securitatea criptării end-to-end, comparați acest număr de securitate pe ambele dispozitive",
- "preparing": "Pregătirea…",
- "transferring": "Transferul…"
- },
- "dialogs": {
- "base64-paste-to-send": "Lipiți aici pentru a trimite {{type}}",
- "auto-accept-instructions-2": "pentru a accepta automat toate fișierele trimise de la dispozitivul respectiv.",
- "receive-text-title": "Mesaj primit",
- "edit-paired-devices-title": "Editați dispozitivele asociate",
- "cancel": "Anulează",
- "auto-accept-instructions-1": "Activează",
- "pair-devices-title": "Împerecherea permanentă a dispozitivelor",
- "download": "Descarcă",
- "title-file": "Fişier",
- "base64-processing": "Procesarea…",
- "decline": "Declin",
- "receive-title": "{{descriptor}} Primit",
- "leave": "Pleacă",
- "join": "Alătură-te",
- "title-image-plural": "Imagini",
- "send": "Trimite",
- "base64-tap-to-paste": "Atinge aici pentru a lipi {{type}}",
- "base64-text": "text",
- "copy": "Copiază",
- "file-other-description-image": "și 1 altă imagine",
- "temporary-public-room-title": "Cameră publică temporară",
- "base64-files": "fişiere",
- "has-sent": "a trimis:",
- "file-other-description-file": "și 1 alt fișier",
- "close": "Închide",
- "system-language": "Limba Sistemului",
- "unpair": "Decuplează",
- "title-image": "Imagine",
- "file-other-description-file-plural": "și {{count}} alte fișiere",
- "would-like-to-share": "ar dori să împărtășească",
- "send-message-to": "Trimite un mesaj la",
- "language-selector-title": "Setați Limba",
- "pair": "Cuplu",
- "hr-or": "SAU",
- "scan-qr-code": "sau scanați codul QR.",
- "input-key-on-this-device": "Introduceți această cheie pe un alt dispozitiv",
- "download-again": "Descarcă din nou",
- "accept": "Acceptă",
- "paired-devices-wrapper_data-empty": "Nu sunt dispozitive asociate.",
- "enter-key-from-another-device": "Introduceți aici cheia de la un alt dispozitiv.",
- "share": "Partajați",
- "auto-accept": "auto-acceptare",
- "title-file-plural": "Fişiere",
- "send-message-title": "Trimite un mesaj",
- "input-room-id-on-another-device": "Introduceți acest ID de cameră pe un alt dispozitiv",
- "file-other-description-image-plural": "și {{count}} alte imagini",
- "enter-room-id-from-another-device": "Introdu ID-ul camerei de pe un alt dispozitiv pentru a intra în cameră.",
- "message_title": "Inserați mesajul de trimis",
- "pair-devices-qr-code_title": "Dați clic pentru a copia link-ul pentru a asocia acest dispozitiv",
- "public-room-qr-code_title": "Dați clic pentru a copia link-ul în sala publică"
- },
- "about": {
- "claim": "Cel mai simplu mod de a transfera fișiere între dispozitive",
- "tweet_title": "Tweet despre PairDrop",
- "close-about_aria-label": "Închide Despre PairDrop",
- "buy-me-a-coffee_title": "Cumpără-mi o cafea!",
- "github_title": "PairDrop pe GitHub",
- "faq_title": "Întrebări frecvente"
- },
- "document-titles": {
- "file-transfer-requested": "Transfer de fișiere cerut",
- "message-received-plural": "{{count}}} Mesaje primite",
- "message-received": "Mesaj primit",
- "file-received": "Fișier Primit",
- "file-received-plural": "{{count}} Fișiere Primite",
- "image-transfer-requested": "Transfer de imagine solicitat"
- }
-}
diff --git a/public_included_ws_fallback/lang/ru.json b/public_included_ws_fallback/lang/ru.json
deleted file mode 100644
index a3006e9..0000000
--- a/public_included_ws_fallback/lang/ru.json
+++ /dev/null
@@ -1,167 +0,0 @@
-{
- "header": {
- "about_aria-label": "Открыть страницу \"О сервисе\"",
- "pair-device_title": "Связать ваши устройства навсегда",
- "install_title": "Установить PairDrop",
- "cancel-paste-mode": "Выполнено",
- "edit-paired-devices_title": "Редактировать связанные устройства",
- "notification_title": "Включить уведомления",
- "about_title": "О сервисе",
- "theme-auto_title": "Адаптировать тему к системной автоматически",
- "theme-dark_title": "Всегда использовать темную тему",
- "theme-light_title": "Всегда использовать светлую тему",
- "join-public-room_title": "Войти на время в публичную комнату",
- "language-selector_title": "Выбрать язык"
- },
- "instructions": {
- "x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение",
- "no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя",
- "click-to-send": "Нажмите, чтобы отправить",
- "x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя",
- "tap-to-send": "Прикоснитесь, чтобы отправить",
- "x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу",
- "x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение",
- "no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы",
- "no-peers-subtitle": "Свяжите устройства или войдите в публичную комнату, чтобы вас могли обнаружить из других сетей",
- "activate-paste-mode-and-other-files": "и {{count}} других файлов",
- "activate-paste-mode-base": "Откройте PairDrop на других устройствах, чтобы отправить",
- "activate-paste-mode-shared-text": "общий текст"
- },
- "footer": {
- "display-name_data-placeholder": "Загрузка…",
- "routed": "направляется через сервер",
- "webrtc": ", если WebRTC недоступен.",
- "traffic": "Трафик",
- "paired-devices": "связанными устройствами",
- "known-as": "Вы известны под именем:",
- "on-this-network": "в этой сети",
- "display-name_title": "Изменить имя вашего устройства навсегда",
- "public-room-devices_title": "Вы можете быть обнаружены устройствами в этой публичной комнате вне зависимости от сети.",
- "paired-devices_title": "Вы можете быть обнаружены связанными устройствами в любое время вне зависимости от сети.",
- "public-room-devices": "в комнате {{roomId}}",
- "discovery": "Вы можете быть обнаружены:",
- "on-this-network_title": "Вы можете быть обнаружены кем угодно в этой сети."
- },
- "dialogs": {
- "edit-paired-devices-title": "Редактировать Связанные Устройства",
- "auto-accept": "автоприем",
- "close": "Закрыть",
- "decline": "Отклонить",
- "share": "Поделиться",
- "would-like-to-share": "хотел бы поделиться",
- "has-sent": "отправил:",
- "paired-devices-wrapper_data-empty": "Нет связанных устройств.",
- "download": "Скачать",
- "receive-text-title": "Сообщение получено",
- "send": "Отправить",
- "send-message-to": "Отправить сообщение",
- "send-message-title": "Отправить сообщение",
- "copy": "Копировать",
- "base64-files": "файлы",
- "base64-paste-to-send": "Вставьте здесь, чтобы отправить {{type}}",
- "base64-processing": "Обработка…",
- "base64-tap-to-paste": "Прикоснитесь здесь, чтобы вставить {{type}}",
- "base64-text": "текст",
- "title-file": "Файл",
- "title-file-plural": "Файлы",
- "title-image": "Изображение",
- "title-image-plural": "Изображения",
- "download-again": "Скачать еще раз",
- "auto-accept-instructions-2": ", чтобы автоматически принимать все файлы, отправленные с того устройства.",
- "enter-key-from-another-device": "Введите сюда ключ с другого устройства.",
- "pair-devices-title": "Соединить устройства навсегда",
- "input-key-on-this-device": "На другом устройстве введите этот ключ",
- "scan-qr-code": "или отсканируйте QR-код.",
- "cancel": "Отменить",
- "pair": "Подключить",
- "accept": "Принять",
- "auto-accept-instructions-1": "Активировать",
- "file-other-description-file": "и 1 другой файл",
- "file-other-description-image-plural": "и {{count}} других изображений",
- "file-other-description-image": "и 1 другое изображение",
- "file-other-description-file-plural": "и {{count}} других файлов",
- "receive-title": "{{descriptor}} получен",
- "system-language": "Язык системы",
- "unpair": "Отвязать",
- "language-selector-title": "Установить язык",
- "hr-or": "ИЛИ",
- "input-room-id-on-another-device": "На другом устройстве введите этот ID комнаты",
- "leave": "Покинуть",
- "join": "Войти",
- "enter-room-id-from-another-device": "Введите ID комнаты с другого устройства, чтобы войти в нее.",
- "temporary-public-room-title": "Временная публичная комната",
- "message_title": "Вставьте сообщение для отправки",
- "pair-devices-qr-code_title": "Нажмите, чтобы скопировать ссылку для привязки этого устройства",
- "public-room-qr-code_title": "Нажмите, чтобы скопировать ссылку на публичную комнату"
- },
- "about": {
- "close-about-aria-label": "Закрыть страницу \"О сервисе\"",
- "claim": "Самый простой способ передачи файлов между устройствами",
- "close-about_aria-label": "Закрыть страницу \"О сервисе\"",
- "buy-me-a-coffee_title": "Купить мне кофе!",
- "github_title": "PairDrop на GitHub",
- "tweet_title": "Твит о PairDrop",
- "faq_title": "Часто задаваемые вопросы"
- },
- "notifications": {
- "display-name-changed-permanently": "Отображаемое имя было изменено навсегда.",
- "display-name-random-again": "Отображаемое имя сгенерировалось случайным образом снова.",
- "pairing-success": "Устройства связаны.",
- "pairing-tabs-error": "Связка двух вкладок браузера невозможна.",
- "copied-to-clipboard": "Скопировано в буфер обмена",
- "pairing-not-persistent": "Связанные устройства непостоянны.",
- "link-received": "Получена ссылка от {{name}} - нажмите, чтобы открыть",
- "notifications-enabled": "Уведомления включены.",
- "text-content-incorrect": "Содержание текста неверно.",
- "message-received": "Получено сообщение от {{name}} - нажмите, чтобы скопировать",
- "connected": "Подключено.",
- "copied-text": "Текст скопирован в буфер обмена",
- "online": "Вы снова в сети",
- "offline": "Вы находитесь вне сети",
- "online-requirement": "Для сопряжения устройств вам нужно быть в сети.",
- "files-incorrect": "Файлы неверны.",
- "message-transfer-completed": "Передача сообщения завершена.",
- "ios-memory-limit": "Отправка файлов на iOS устройства возможна только до 200 МБ за один раз",
- "selected-peer-left": "Выбранный узел вышел.",
- "request-title": "{{name}} хотел бы передать {{count}} {{descriptor}}",
- "rate-limit-join-key": "Достигнут предел скорости. Подождите 10 секунд и повторите попытку.",
- "unfinished-transfers-warning": "Есть незавершенные передачи. Вы уверены, что хотите закрыть PairDrop?",
- "copied-text-error": "Запись в буфер обмена не удалась. Скопируйте вручную!",
- "pairing-cleared": "Все устройства отвязаны.",
- "pairing-key-invalid": "Неверный ключ",
- "pairing-key-invalidated": "Ключ {{key}} признан недействительным.",
- "click-to-download": "Нажмите, чтобы скачать",
- "clipboard-content-incorrect": "Содержание буфера обмена неверно.",
- "click-to-show": "Нажмите, чтобы показать",
- "connecting": "Подключение…",
- "download-successful": "{{descriptor}} загружен",
- "display-name-changed-temporarily": "Отображаемое имя было изменено только для этой сессии.",
- "file-content-incorrect": "Содержимое файла неверно.",
- "file-transfer-completed": "Передача файла завершена.",
- "public-room-left": "Покинуть публичную комнату {{publicRoomId}}",
- "copied-to-clipboard-error": "Копирование невозможно. Скопируйте вручную.",
- "public-room-id-invalid": "Неверный ID комнаты",
- "online-requirement-pairing": "Для связки устройств необходимо находиться быть онлайн.",
- "online-requirement-public-room": "Для создания публичной комнаты необходимо быть онлайн.",
- "notifications-permissions-error": "Уведомления были заблокированы, так как пользователь отклонил запрос на их работу несколько раз. Это можно изменить в меню \"О странице\", которое может быть вызвано нажатием на иконку замочка рядом со строкой адреса сайта.",
- "pair-url-copied-to-clipboard": "Ссылка для привязки этого устройства была скопирована в буфер обмена",
- "room-url-copied-to-clipboard": "Ссылка на публичную комнату была скопирована в буфер обмена"
- },
- "peer-ui": {
- "click-to-send-paste-mode": "Нажмите, чтобы отправить {{descriptor}}",
- "preparing": "Подготовка…",
- "transferring": "Передача…",
- "processing": "Обработка…",
- "waiting": "Ожидание…",
- "connection-hash": "Чтобы проверить безопасность сквозного шифрования, сравните этот номер безопасности на обоих устройствах",
- "click-to-send": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение"
- },
- "document-titles": {
- "file-received-plural": "{{count}} файлов получено",
- "message-received-plural": "{{count}} сообщений получено",
- "file-received": "Файл получен",
- "file-transfer-requested": "Запрошена передача файлов",
- "message-received": "Сообщение получено",
- "image-transfer-requested": "Запрошена передача изображений"
- }
-}
diff --git a/public_included_ws_fallback/lang/tr.json b/public_included_ws_fallback/lang/tr.json
deleted file mode 100644
index fe54f5e..0000000
--- a/public_included_ws_fallback/lang/tr.json
+++ /dev/null
@@ -1,165 +0,0 @@
-{
- "header": {
- "about_title": "PairDrop Hakkında",
- "about_aria-label": "PairDrop Hakkında Aç",
- "theme-auto_title": "Temayı sisteme uyarla",
- "theme-light_title": "Daima açık tema kullan",
- "theme-dark_title": "Daima koyu tema kullan",
- "notification_title": "Bildirimleri etkinleştir",
- "install_title": "PairDrop'u Yükle",
- "pair-device_title": "Cihazı kalıcı olarak eşle",
- "edit-paired-devices_title": "Eşleştirilmiş cihazları düzenle",
- "cancel-paste-mode": "Bitti",
- "join-public-room_title": "Geçici olarak genel odaya katılın",
- "language-selector_title": "Dili Seç"
- },
- "instructions": {
- "no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın",
- "x-instructions_mobile": "Dosya göndermek için dokun veya mesaj göndermek için uzun dokun",
- "click-to-send": "Göndermek için tıkla",
- "activate-paste-mode-and-other-files": "ve {{count}} diğer dosya",
- "tap-to-send": "Göndermek için dokun",
- "activate-paste-mode-base": "Göndermek için diğer cihazlarda PairDrop'u açın",
- "no-peers-subtitle": "Diğer ağlarda keşfedilebilir olmak için cihazları eşleştirin veya ortak bir odaya girin",
- "activate-paste-mode-shared-text": "paylaşılan metin",
- "x-instructions_desktop": "Dosya göndermek için tıkla ya da mesaj göndermek için sağ tıkla",
- "no-peers-title": "Dosya göndermek için diğer cihazlarda PairDrop'u açın",
- "x-instructions_data-drop-peer": "Göndermek için serbest bırak",
- "x-instructions_data-drop-bg": "Alıcıyı seçmek için bırakın"
- },
- "footer": {
- "display-name_data-placeholder": "Yükleniyor…",
- "display-name_title": "Cihazının adını kalıcı olarak düzenle",
- "webrtc": "WebRTC mevcut değilse.",
- "public-room-devices_title": "Genel odada ağdan bağımsız olarak cihazlar tarafından keşfedilebilirsiniz.",
- "traffic": "Trafik",
- "paired-devices_title": "Eşleştirilmiş cihazlarda ağdan bağımsız olarak her zaman keşfedilebilirsiniz.",
- "public-room-devices": "{{roomId}} odası",
- "paired-devices": "eşleştirilmiş cihazlar tarafından",
- "on-this-network": "bu ağ üzerinde",
- "routed": "sunucu üzerinden yönlendirilir",
- "discovery": "Keşfedilebilirsin:",
- "on-this-network_title": "Bu ağdaki herkes tarafından keşfedilebilirsiniz.",
- "known-as": "Bilinen adın:"
- },
- "dialogs": {
- "cancel": "İptal",
- "edit-paired-devices-title": "Eşleştirilmiş Cihazları Düzenle",
- "base64-paste-to-send": "{{type}} göndermek için buraya yapıştır",
- "auto-accept-instructions-2": "böylelikle cihazdan gönderilen tüm dosyaları otomatik olarak kabul eder.",
- "receive-text-title": "Mesaj Alındı",
- "auto-accept-instructions-1": "Etkinleştir",
- "pair-devices-title": "Cihazları Kalıcı Eşleştir",
- "download": "İndir",
- "title-file": "Dosya",
- "base64-processing": "İşleniyor…",
- "decline": "Reddet",
- "receive-title": "{{descriptor}} Alındı",
- "leave": "Ayrıl",
- "message_title": "Göndermek için mesaj girin",
- "join": "Katıl",
- "title-image-plural": "Resimler",
- "send": "Gönder",
- "base64-tap-to-paste": "{{type}} yapıştırmak için buraya dokun",
- "base64-text": "metin",
- "copy": "Kopyala",
- "file-other-description-image": "ve 1 diğer resim",
- "pair-devices-qr-code_title": "Bu cihazı eşleştirmek üzere bağlantıyı kopyalamak için tıklayın",
- "temporary-public-room-title": "Geçici Genel Oda",
- "base64-files": "dosyalar",
- "has-sent": "gönderdi:",
- "file-other-description-file": "ve 1 diğer dosya",
- "public-room-qr-code_title": "Genel odanın bağlantı linkini kopyalamak için tıkla",
- "close": "Kapat",
- "system-language": "Sistem Dili",
- "unpair": "Kopar",
- "title-image": "Resim",
- "file-other-description-file-plural": "ve {{count}} diğer dosya",
- "would-like-to-share": "paylaşmak istiyor",
- "send-message-to": "Mesaj Gönderin",
- "language-selector-title": "Dili Seç",
- "pair": "Bağla",
- "hr-or": "VEYA",
- "scan-qr-code": "veya QR kodunu tarayın.",
- "input-key-on-this-device": "Bu anahtarı başka bir cihazda girin",
- "download-again": "Tekrar indir",
- "accept": "Kabul Et",
- "paired-devices-wrapper_data-empty": "Eşlenmiş cihaz yok.",
- "enter-key-from-another-device": "Buraya başka bir cihazdan anahtar girin.",
- "share": "Paylaş",
- "auto-accept": "otomatik-kabul",
- "title-file-plural": "Dosyalar",
- "send-message-title": "Mesaj Gönder",
- "input-room-id-on-another-device": "Bu ID'yi diğer cihaza girin",
- "file-other-description-image-plural": "ve {{count}} diğer resim",
- "enter-room-id-from-another-device": "Odaya katılmak için diğer cihazın ID'sini girin."
- },
- "notifications": {
- "request-title": "{{name}} {{count}} {{descriptor}} transfer etmek istiyor",
- "unfinished-transfers-warning": "Bitmemiş transferler var. PairDrop'u kapatmak istediğinize emin misiniz?",
- "message-received": "Mesaj {{name}} tarafından alındı - Kopyalamak için tıkla",
- "notifications-permissions-error": "Kullanıcı izin isteğini birkaç kez reddettiği için bildirimler izni engellenmiştir. URL çubuğunun yanındaki kilit simgesine tıklayarak sıfırlanabilir.",
- "rate-limit-join-key": "İstek sınırına ulaşıldı. 10 saniye bekleyin ve tekrar deneyin.",
- "pair-url-copied-to-clipboard": "Bu cihazı eşleştirmek için bağlantı linki panoya kopyalandı",
- "connecting": "Bağlanılıyor…",
- "pairing-key-invalidated": "{{key}} anahtarı geçersiz.",
- "pairing-key-invalid": "Geçersiz anahtar",
- "connected": "Bağlandı.",
- "pairing-not-persistent": "Eşleştirilmiş cihazlar kalıcı değildir.",
- "text-content-incorrect": "Metin içeriği yanlış.",
- "message-transfer-completed": "Mesaj transferi tamamlandı.",
- "file-transfer-completed": "Dosya transferi bitti.",
- "file-content-incorrect": "Dosya içeriği yanlış.",
- "files-incorrect": "Dosyalar yanlış.",
- "selected-peer-left": "Seçili aygıt ayrıldı.",
- "link-received": "Link {{name}} tarafından alındı - Açmak için tıkla",
- "online": "Tekrar çevrimiçisin",
- "public-room-left": "{{publicRoomId}} genel odasından ayrıldın",
- "copied-text": "Metin panoya kopyalandı",
- "display-name-random-again": "Görünen adın yine rastgele oluşturuldu.",
- "display-name-changed-permanently": "Görünen adın kalıcı olarak değiştirilir.",
- "copied-to-clipboard-error": "Kopyalama mümkün değil. Manuel olarak kopyalayın.",
- "pairing-success": "Cihazlar eşleştirildi.",
- "clipboard-content-incorrect": "Pano içeriği yanlış.",
- "display-name-changed-temporarily": "Görünen adın yalnızca bu oturum için değiştirilir.",
- "copied-to-clipboard": "Panoya kopyalandı",
- "offline": "Çevrimdışısın",
- "pairing-tabs-error": "İki web tarayıcı sekmesini eşleştirmek mümkün değildir.",
- "public-room-id-invalid": "Geçersiz oda ID'si",
- "click-to-download": "İndirmek için tıkla",
- "pairing-cleared": "Tüm cihazlar eşleştirmeden çıkarıldı.",
- "notifications-enabled": "Bildirimler etkinleştirildi.",
- "online-requirement-pairing": "Cihazları eşleştirmek için çevrimiçi olmanız lazım.",
- "ios-memory-limit": "iOS'a dosya göndermek tek seferde ancak 200 MB'a kadar mümkündür",
- "online-requirement-public-room": "Genel oda oluşturmak için çevrimiçi olmanız lazım.",
- "room-url-copied-to-clipboard": "Genel oda bağlantı linki panoya kopyalandı",
- "copied-text-error": "Panoya kopyalanamadı. Lütfen manuel olarak kopyalayın!",
- "download-successful": "{{descriptor}} indirildi",
- "click-to-show": "Göstermek için tıkla"
- },
- "peer-ui": {
- "processing": "İşleniyor…",
- "click-to-send-paste-mode": "{{descriptor}} göndermek için tıkla",
- "click-to-send": "Dosya göndermek için tıkla veya mesaj göndermek için sağ tıkla",
- "waiting": "Bekleniyor…",
- "connection-hash": "Uçtan uca şifrelemenin güvenliğini doğrulamak için her iki cihazda da bu güvenlik numarasını karşılaştırın",
- "preparing": "Hazırlanıyor…",
- "transferring": "Transfer ediliyor…"
- },
- "about": {
- "claim": "Cihazlar arasında dosya aktarmanın en kolay yolu",
- "tweet_title": "PairDrop hakkında tweet",
- "close-about_aria-label": "PairDrop Hakkında Kapat",
- "buy-me-a-coffee_title": "Bana bir kahve al!",
- "github_title": "GitHub'da PairDrop",
- "faq_title": "Sıkça sorulan sorular"
- },
- "document-titles": {
- "file-transfer-requested": "Dosya Transferi Talep Edildi",
- "image-transfer-requested": "Görüntü Aktarımı Talep Edildi",
- "message-received-plural": "{{count}} Mesaj Alındı",
- "message-received": "Mesaj Alındı",
- "file-received": "Dosya Alındı",
- "file-received-plural": "{{count}} Dosya Alındı"
- }
-}
diff --git a/public_included_ws_fallback/lang/zh-CN.json b/public_included_ws_fallback/lang/zh-CN.json
deleted file mode 100644
index 197a7cc..0000000
--- a/public_included_ws_fallback/lang/zh-CN.json
+++ /dev/null
@@ -1,166 +0,0 @@
-{
- "header": {
- "about_title": "关于 PairDrop",
- "about_aria-label": "打开 关于 PairDrop",
- "theme-light_title": "总是使用明亮主题",
- "install_title": "安装 PairDrop",
- "pair-device_title": "永久配对您的设备",
- "theme-auto_title": "主题适应系统",
- "theme-dark_title": "总是使用暗黑主题",
- "notification_title": "开启通知",
- "edit-paired-devices_title": "管理已配对设备",
- "cancel-paste-mode": "完成",
- "join-public-room_title": "暂时加入公共房间",
- "language-selector_title": "设置语言"
- },
- "instructions": {
- "x-instructions_data-drop-peer": "释放以发送到此设备",
- "no-peers_data-drop-bg": "释放来选择接收者",
- "no-peers-subtitle": "配对新设备 或 加入一个公共房间 以便在其他网络上可见",
- "no-peers-title": "在其他设备上打开 PairDrop 来发送文件",
- "x-instructions_desktop": "点击以发送文件 或 右键来发送信息",
- "x-instructions_mobile": "轻触以发送文件 或 长按来发送信息",
- "x-instructions_data-drop-bg": "释放来选择接收者",
- "click-to-send": "点击发送",
- "tap-to-send": "轻触发送",
- "activate-paste-mode-base": "在其他设备上打开 PairDrop 来发送",
- "activate-paste-mode-and-other-files": "和 {{count}} 个其他的文件",
- "activate-paste-mode-shared-text": "分享文本"
- },
- "footer": {
- "routed": "途径服务器",
- "webrtc": "如果 WebRTC 不可用。",
- "known-as": "你的名字是:",
- "display-name_data-placeholder": "加载中…",
- "display-name_title": "修改你的默认设备名",
- "on-this-network": "在此网络上",
- "paired-devices": "已配对的设备",
- "traffic": "流量将",
- "public-room-devices_title": "您可以被这个独立于网络的公共房间中的设备发现。",
- "paired-devices_title": "您可以在任何时候被已配对的设备发现,而不依赖于网络。",
- "public-room-devices": "在房间 {{roomId}} 中",
- "discovery": "您可以被发现:",
- "on-this-network_title": "您可以被这个网络上的每个人发现。"
- },
- "dialogs": {
- "pair-devices-title": "配对新设备(常驻)",
- "input-key-on-this-device": "在另一个设备上输入这串数字",
- "base64-text": "信息",
- "enter-key-from-another-device": "在此处输入从另一个设备上获得的数字。",
- "edit-paired-devices-title": "管理已配对的设备",
- "pair": "配对",
- "cancel": "取消",
- "scan-qr-code": "或者 扫描二维码。",
- "paired-devices-wrapper_data-empty": "无已配对设备。",
- "auto-accept-instructions-1": "启用",
- "auto-accept": "自动接收",
- "decline": "拒绝",
- "base64-processing": "处理中…",
- "base64-tap-to-paste": "轻触此处粘贴{{type}}",
- "base64-paste-to-send": "粘贴到此处以发送 {{type}}",
- "auto-accept-instructions-2": "以无需同意而自动接收从那个设备上发送的所有文件。",
- "would-like-to-share": "想要分享",
- "accept": "接收",
- "close": "关闭",
- "share": "分享",
- "download": "保存",
- "send": "发送",
- "receive-text-title": "收到信息",
- "copy": "复制",
- "send-message-title": "发送信息",
- "send-message-to": "发了一条信息给",
- "has-sent": "发送了:",
- "base64-files": "文件",
- "file-other-description-file": "和 1 个其他的文件",
- "file-other-description-image": "和 1 个其他的图片",
- "file-other-description-image-plural": "和 {{count}} 个其他的图片",
- "file-other-description-file-plural": "和 {{count}} 个其他的文件",
- "title-image-plural": "图片",
- "receive-title": "收到 {{descriptor}}",
- "title-image": "图片",
- "title-file": "文件",
- "title-file-plural": "文件",
- "download-again": "再次保存",
- "system-language": "跟随系统语言",
- "unpair": "取消配对",
- "language-selector-title": "设置语言",
- "hr-or": "或者",
- "input-room-id-on-another-device": "在另一个设备上输入这串房间号",
- "leave": "离开",
- "join": "加入",
- "temporary-public-room-title": "临时公共房间",
- "enter-room-id-from-another-device": "在另一个设备上输入这串房间号来加入房间。",
- "message_title": "插入要发送的消息",
- "pair-devices-qr-code_title": "单击复制和此设备配对的链接",
- "public-room-qr-code_title": "单击复制公共房间链接"
- },
- "about": {
- "faq_title": "常见问题",
- "close-about_aria-label": "关闭 关于 PairDrop",
- "github_title": "PairDrop 在 GitHub 上开源",
- "claim": "最简单的跨设备传输方案",
- "buy-me-a-coffee_title": "帮我买杯咖啡!",
- "tweet_title": "关于 PairDrop 的推特"
- },
- "notifications": {
- "display-name-changed-permanently": "展示的名字已经永久变更。",
- "display-name-changed-temporarily": "展示的名字仅在此会话中变更。",
- "display-name-random-again": "展示的名字已再次随机生成。",
- "download-successful": "{{descriptor}} 已下载",
- "pairing-tabs-error": "无法配对两个浏览器标签页。",
- "pairing-success": "新设备已配对。",
- "pairing-not-persistent": "配对的设备不是持久的。",
- "pairing-key-invalid": "无效配对码",
- "pairing-key-invalidated": "配对码 {{key}} 已失效。",
- "text-content-incorrect": "文本内容不合法。",
- "file-content-incorrect": "文件内容不合法。",
- "clipboard-content-incorrect": "剪贴板内容不合法。",
- "link-received": "收到来自 {{name}} 的链接 - 点击打开",
- "message-received": "收到来自 {{name}} 的信息 - 点击复制",
- "request-title": "{{name}} 想要发送 {{count}} 个 {{descriptor}}",
- "click-to-show": "点击展示",
- "copied-text": "复制到剪贴板",
- "selected-peer-left": "选择的设备已离开。",
- "pairing-cleared": "所有设备已解除配对。",
- "copied-to-clipboard": "已复制到剪贴板",
- "notifications-enabled": "通知已启用。",
- "copied-text-error": "写入剪贴板失败。请手动复制!",
- "click-to-download": "点击以保存",
- "unfinished-transfers-warning": "还有未完成的传输任务。你确定要关闭 PairDrop 吗?",
- "message-transfer-completed": "信息传输已完成。",
- "offline": "你未连接到网络",
- "online": "你已重新连接到网络",
- "connected": "已连接。",
- "online-requirement": "你需要连接网络来配对新设备。",
- "files-incorrect": "文件不合法。",
- "file-transfer-completed": "文件传输已完成。",
- "connecting": "连接中…",
- "ios-memory-limit": "向 iOS 发送文件 一次最多只能发送 200 MB",
- "rate-limit-join-key": "已达连接限制。请等待 10秒 后再试。",
- "public-room-left": "已退出公共房间 {{publicRoomId}}",
- "copied-to-clipboard-error": "无法复制。请手动复制。",
- "public-room-id-invalid": "无效的房间号",
- "online-requirement-pairing": "您需要连接到互联网来配对新设备。",
- "online-requirement-public-room": "您需要连接到互联网来创建一个公共房间。",
- "notifications-permissions-error": "因用户数次拒绝了权限授予提示,通知权限已被拦截。可以在“页面信息”中重置它,要访问“页面信息”请单击地址栏旁的挂锁图标。",
- "pair-url-copied-to-clipboard": "已将和此设备配对的链接复制到剪贴板",
- "room-url-copied-to-clipboard": "已将公共房间的链接复制到剪贴板"
- },
- "document-titles": {
- "message-received": "收到信息",
- "message-received-plural": "收到 {{count}} 条信息",
- "file-transfer-requested": "文件传输请求",
- "file-received-plural": "收到 {{count}} 个文件",
- "file-received": "收到文件",
- "image-transfer-requested": "图片传输请求"
- },
- "peer-ui": {
- "click-to-send-paste-mode": "点击发送 {{descriptor}}",
- "click-to-send": "点击以发送文件 或 右键来发送信息",
- "connection-hash": "若要验证端到端加密的安全性,请在两个设备上比较此安全编号",
- "preparing": "准备中…",
- "waiting": "请等待…",
- "transferring": "传输中…",
- "processing": "处理中…"
- }
-}
diff --git a/public_included_ws_fallback/manifest.json b/public_included_ws_fallback/manifest.json
deleted file mode 100644
index 7ac7e66..0000000
--- a/public_included_ws_fallback/manifest.json
+++ /dev/null
@@ -1,281 +0,0 @@
-{
- "name": "PairDrop",
- "short_name": "PairDrop",
- "icons": [{
- "src": "images/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },{
- "src": "images/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- },{
- "src": "images/android-chrome-192x192-maskable.png",
- "sizes": "192x192",
- "type": "image/png",
- "purpose": "maskable"
- },{
- "src": "images/android-chrome-512x512-maskable.png",
- "sizes": "512x512",
- "type": "image/png",
- "purpose": "maskable"
- },{
- "src": "images/favicon-96x96.png",
- "sizes": "96x96",
- "type": "image/png"
- }],
- "background_color": "#efefef",
- "start_url": "/",
- "scope": "/",
- "display": "minimal-ui",
- "theme_color": "#3367d6",
- "screenshots" : [
- {
- "src": "images/pairdrop_screenshot_mobile_1.png",
- "sizes": "1170x2532",
- "type": "image/png"
- },
- {
- "src": "images/pairdrop_screenshot_mobile_2.png",
- "sizes": "1170x2532",
- "type": "image/png"
- },
- {
- "src": "images/pairdrop_screenshot_mobile_3.png",
- "sizes": "1170x2532",
- "type": "image/png"
- },
- {
- "src": "images/pairdrop_screenshot_mobile_4.png",
- "sizes": "1170x2532",
- "type": "image/png"
- },
- {
- "src": "images/pairdrop_screenshot_mobile_5.png",
- "sizes": "1170x2532",
- "type": "image/png"
- },
- {
- "src": "images/pairdrop_screenshot_mobile_6.png",
- "sizes": "1170x2532",
- "type": "image/png"
- },
- {
- "src": "images/pairdrop_screenshot_mobile_7.png",
- "sizes": "1170x2532",
- "type": "image/png"
- }
- ],
- "share_target": {
- "action": "/",
- "method":"POST",
- "enctype": "multipart/form-data",
- "params": {
- "title": "title",
- "text": "text",
- "url": "url",
- "files": [{
- "name": "allfiles",
- "accept": ["*/*"]
- }]
- }
- },
- "file_handlers": [
- {
- "action": "/?file_handler",
- "name": "All Files",
- "accept": {
- "application/cpl+xml": [".cpl"],
- "application/gpx+xml": [".gpx"],
- "application/gzip": [".gz"],
- "application/java-archive": [".jar", ".war", ".ear"],
- "application/java-vm": [".class"],
- "application/javascript": [".js", ".mjs"],
- "application/json": [".json", ".map"],
- "application/manifest+json": [".webmanifest"],
- "application/msword": [".doc", ".dot", ".wiz"],
- "application/octet-stream": [".bin", ".dms", ".lrf", ".mar", ".so", ".dist", ".distz", ".pkg", ".bpk", ".dump", ".elc", ".deploy", ".exe", ".dll", ".deb", ".dmg", ".iso", ".img", ".msi", ".msp", ".msm", ".buffer"],
- "application/oda": [".oda"],
- "application/oxps": [".oxps"],
- "application/pdf": [".pdf"],
- "application/pgp-signature": [".asc", ".sig"],
- "application/pics-rules": [".prf"],
- "application/pkcs7-mime": [".p7c"],
- "application/pkix-cert": [".cer"],
- "application/postscript": [".ai", ".eps", ".ps"],
- "application/rtf": [".rtf"],
- "application/vnd.android.package-archive": [".apk"],
- "application/vnd.apple.mpegurl": [".m3u", ".m3u8"],
- "application/vnd.apple.pkpass": [".pkpass"],
- "application/vnd.google-earth.kml+xml": [".kml"],
- "application/vnd.google-earth.kmz": [".kmz"],
- "application/vnd.ms-cab-compressed": [".cab"],
- "application/vnd.ms-excel": [".xls", ".xlm", ".xla", ".xlc", ".xlt", ".xlw"],
- "application/vnd.ms-outlook": [".msg"],
- "application/vnd.ms-powerpoint": [".ppt", ".pot", ".ppa", ".pps", ".pwz"],
- "application/vnd.ms-project": [".mpp", ".mpt"],
- "application/vnd.ms-xpsdocument": [".xps"],
- "application/vnd.oasis.opendocument.database": [".odb"],
- "application/vnd.oasis.opendocument.spreadsheet": [".ods"],
- "application/vnd.oasis.opendocument.text": [".odt"],
- "application/vnd.openstreetmap.data+xml": [".osm"],
- "application/vnd.openxmlformats-officedocument.presentationml.presentation": [".pptx"],
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"],
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"],
- "application/vnd.tcpdump.pcap": [".pcap", ".cap", ".dmp"],
- "application/vnd.wordperfect": [".wpd"],
- "application/wasm": [".wasm"],
- "application/x-7z-compressed": [".7z"],
- "application/x-apple-diskimage": [".dmg"],
- "application/x-bcpio": [".bcpio"],
- "application/x-bittorrent": [".torrent"],
- "application/x-cbr": [".cbr", ".cba", ".cbt", ".cbz", ".cb7"],
- "application/x-cdlink": [".vcd"],
- "application/x-chrome-extension": [".crx"],
- "application/x-cpio": [".cpio"],
- "application/x-csh": [".csh"],
- "application/x-debian-package": [".deb", ".udeb"],
- "application/x-dvi": [".dvi"],
- "application/x-freearc": [".arc"],
- "application/x-gtar": [".gtar"],
- "application/x-hdf": [".hdf"],
- "application/x-hdf5": [".h5"],
- "application/x-httpd-php": [".php"],
- "application/x-iso9660-image": [".iso"],
- "application/x-iwork-keynote-sffkey": [".key"],
- "application/x-iwork-numbers-sffnumbers": [".numbers"],
- "application/x-iwork-pages-sffpages": [".pages"],
- "application/x-latex": [".latex"],
- "application/x-makeself": [".run"],
- "application/x-mif": [".mif"],
- "application/x-ms-shortcut": [".lnk"],
- "application/x-msaccess": [".mdb"],
- "application/x-msdownload": [".exe", ".dll", ".com", ".bat", ".msi"],
- "application/x-mspublisher": [".pub"],
- "application/x-netcdf": [".cdf", ".nc"],
- "application/x-perl": [".pl", ".pm"],
- "application/x-pilot": [".prc", ".pdb"],
- "application/x-pkcs12": [".p12", ".pfx"],
- "application/x-pn-realaudio": [".ram"],
- "application/x-python-code": [".pyc", ".pyo"],
- "application/x-rar-compressed": [".rar"],
- "application/x-redhat-package-manager": [".rpm"],
- "application/x-sh": [".sh"],
- "application/x-shar": [".shar"],
- "application/x-shockwave-flash": [".swf"],
- "application/x-sql": [".sql"],
- "application/x-subrip": [".srt"],
- "application/x-sv4cpio": [".sv4cpio"],
- "application/x-sv4crc": [".sv4crc"],
- "application/x-tads": [".gam"],
- "application/x-tar": [".tar"],
- "application/x-tcl": [".tcl"],
- "application/x-tex": [".tex"],
- "application/x-troff": [".roff", ".t", ".tr"],
- "application/x-troff-man": [".man"],
- "application/x-troff-me": [".me"],
- "application/x-troff-ms": [".ms"],
- "application/x-ustar": [".ustar"],
- "application/x-wais-source": [".src"],
- "application/x-xpinstall": [".xpi"],
- "application/xhtml+xml": [".xhtml", ".xht"],
- "application/xml": [".xsl", ".rdf", ".wsdl", ".xpdl"],
- "application/zip": [".zip"],
- "audio/3gpp": [".3gp", ".3gpp"],
- "audio/3gpp2": [".3g2", ".3gpp2"],
- "audio/aac": [".aac", ".adts", ".loas", ".ass"],
- "audio/basic": [".au", ".snd"],
- "audio/midi": [".mid", ".midi", ".kar", ".rmi"],
- "audio/mpeg": [".mpga", ".mp2", ".mp2a", ".mp3", ".m2a", ".m3a"],
- "audio/ogg": [".oga", ".ogg", ".spx", ".opus"],
- "audio/opus": [".opus"],
- "audio/x-aiff": [".aif", ".aifc", ".aiff"],
- "audio/x-flac": [".flac"],
- "audio/x-m4a": [".m4a"],
- "audio/x-mpegurl": [".m3u"],
- "audio/x-ms-wma": [".wma"],
- "audio/x-pn-realaudio": [".ra"],
- "audio/x-wav": [".wav"],
- "font/otf": [".otf"],
- "font/ttf": [".ttf"],
- "font/woff": [".woff"],
- "font/woff2": [".woff2"],
- "image/emf": [".emf"],
- "image/gif": [".gif"],
- "image/heic": [".heic"],
- "image/heif": [".heif"],
- "image/ief": [".ief"],
- "image/jpeg": [".jpeg", ".jpg"],
- "image/jpg": [".jpg"],
- "image/pict": [".pict", ".pct", ".pic"],
- "image/png": [".png"],
- "image/svg+xml": [".svg", ".svgz"],
- "image/tiff": [".tif", ".tiff"],
- "image/vnd.adobe.photoshop": [".psd"],
- "image/vnd.djvu": [".djvu", ".djv"],
- "image/vnd.dwg": [".dwg"],
- "image/vnd.dxf": [".dxf"],
- "image/vnd.microsoft.icon": [".ico"],
- "image/vnd.ms-dds": [".dds"],
- "image/x-3ds": [".3ds"],
- "image/x-cmu-raster": [".ras"],
- "image/x-icon": [".ico"],
- "image/x-ms-bmp": [".bmp"],
- "image/x-portable-anymap": [".pnm"],
- "image/x-portable-bitmap": [".pbm"],
- "image/x-portable-graymap": [".pgm"],
- "image/x-portable-pixmap": [".ppm"],
- "image/x-rgb": [".rgb"],
- "image/x-tga": [".tga"],
- "image/x-xbitmap": [".xbm"],
- "image/x-xpixmap": [".xpm"],
- "image/x-xwindowdump": [".xwd"],
- "message/rfc822": [".eml", ".mht", ".mhtml", ".nws"],
- "model/obj": [".obj"],
- "model/stl": [".stl"],
- "model/vnd.collada+xml": [".dae"],
- "text/calendar": [".ics", ".ifb"],
- "text/css": [".css"],
- "text/csv": [".csv"],
- "text/html": [".html", ".htm", ".shtml"],
- "text/markdown": [".markdown", ".md"],
- "text/plain": [".txt", ".text", ".conf", ".def", ".list", ".log", ".in", ".ini"],
- "text/richtext": [".rtx"],
- "text/rtf": [".rtf"],
- "text/tab-separated-values": [".tsv"],
- "text/x-c": [".c", ".cc", ".cxx", ".cpp", ".h", ".hh", ".dic"],
- "text/x-java-source": [".java"],
- "text/x-lua": [".lua"],
- "text/x-python": [".py"],
- "text/x-setext": [".etx"],
- "text/x-sgml": [".sgm", ".sgml"],
- "text/x-vcard": [".vcf"],
- "text/xml": [".xml"],
- "text/xul": [".xul"],
- "text/yaml": [".yaml", ".yml"],
- "video/3gpp": [".3gp", ".3gpp"],
- "video/mp2t": [".ts"],
- "video/mp4": [".mp4", ".mp4v", ".mpg4"],
- "video/mpeg": [".mpeg", ".m1v", ".mpa", ".mpe", ".mpg"],
- "video/quicktime": [".mov", ".qt"],
- "video/webm": [".webm"],
- "video/x-flv": [".flv"],
- "video/x-m4v": [".m4v"],
- "video/x-ms-asf": [".asf", ".asx"],
- "video/x-ms-vob": [".vob"],
- "video/x-ms-wmv": [".wmv"],
- "video/x-msvideo": [".avi"],
- "video/x-sgi-movie": [".*"]
- },
- "icons": [
- {
- "src": "/images/android-chrome-192x192.png",
- "sizes": "192x192"
- }
- ]
- }
- ],
- "launch_handler": {
- "client_mode": "focus-existing"
- }
-}
diff --git a/public_included_ws_fallback/robots.txt b/public_included_ws_fallback/robots.txt
deleted file mode 100644
index eb05362..0000000
--- a/public_included_ws_fallback/robots.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-User-agent: *
-Disallow:
diff --git a/public_included_ws_fallback/scripts/NoSleep.min.js b/public_included_ws_fallback/scripts/NoSleep.min.js
deleted file mode 100644
index 202c35c..0000000
--- a/public_included_ws_fallback/scripts/NoSleep.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! NoSleep.min.js v0.12.0 - git.io/vfn01 - Rich Tibbett - MIT license */
-!function(A,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.NoSleep=e():A.NoSleep=e()}(this,(function(){return function(A){var e={};function B(g){if(e[g])return e[g].exports;var o=e[g]={i:g,l:!1,exports:{}};return A[g].call(o.exports,o,o.exports,B),o.l=!0,o.exports}return B.m=A,B.c=e,B.d=function(A,e,g){B.o(A,e)||Object.defineProperty(A,e,{enumerable:!0,get:g})},B.r=function(A){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(A,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(A,"__esModule",{value:!0})},B.t=function(A,e){if(1&e&&(A=B(A)),8&e)return A;if(4&e&&"object"==typeof A&&A&&A.__esModule)return A;var g=Object.create(null);if(B.r(g),Object.defineProperty(g,"default",{enumerable:!0,value:A}),2&e&&"string"!=typeof A)for(var o in A)B.d(g,o,function(e){return A[e]}.bind(null,o));return g},B.n=function(A){var e=A&&A.__esModule?function(){return A.default}:function(){return A};return B.d(e,"a",e),e},B.o=function(A,e){return Object.prototype.hasOwnProperty.call(A,e)},B.p="",B(B.s=0)}([function(A,e,B){"use strict";var g=function(){function A(A,e){for(var B=0;B.5&&(e.noSleepVideo.currentTime=Math.random())}))})))}return g(A,[{key:"_addSourceToVideo",value:function(A,e,B){var g=document.createElement("source");g.src=B,g.type="video/"+e,A.appendChild(g)}},{key:"enable",value:function(){var A=this;return Q()?navigator.wakeLock.request("screen").then((function(e){A._wakeLock=e,A.enabled=!0,console.log("Wake Lock active."),A._wakeLock.addEventListener("release",(function(){console.log("Wake Lock released.")}))})).catch((function(e){throw A.enabled=!1,console.error(e.name+", "+e.message),e})):C()?(this.disable(),console.warn("\n NoSleep enabled for older iOS devices. This can interrupt\n active or long-running network requests from completing successfully.\n See https://github.com/richtr/NoSleep.js/issues/15 for more details.\n "),this.noSleepTimer=window.setInterval((function(){document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))}),15e3),this.enabled=!0,Promise.resolve()):this.noSleepVideo.play().then((function(e){return A.enabled=!0,e})).catch((function(e){throw A.enabled=!1,e}))}},{key:"disable",value:function(){Q()?(this._wakeLock&&this._wakeLock.release(),this._wakeLock=null):C()?this.noSleepTimer&&(console.warn("\n NoSleep now disabled for older iOS devices.\n "),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}},{key:"isEnabled",get:function(){return this.enabled}}]),A}();A.exports=i},function(A,e,B){"use strict";A.exports={webm:"data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBCEKChHdlYm1Ch4EEQoWBAhhTgGcBAAAAAAAVkhFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEwTbuMU6uEHFO7a1OsghV17AEAAAAAAACkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEUq17GDD0JATYCNTGF2ZjU1LjMzLjEwMFdBjUxhdmY1NS4zMy4xMDBzpJBlrrXf3DCDVB8KcgbMpcr+RImIQJBgAAAAAAAWVK5rAQAAAAAAD++uAQAAAAAAADLXgQFzxYEBnIEAIrWcg3VuZIaFVl9WUDiDgQEj44OEAmJaAOABAAAAAAAABrCBsLqBkK4BAAAAAAAPq9eBAnPFgQKcgQAitZyDdW5khohBX1ZPUkJJU4OBAuEBAAAAAAAAEZ+BArWIQOdwAAAAAABiZIEgY6JPbwIeVgF2b3JiaXMAAAAAAoC7AAAAAAAAgLUBAAAAAAC4AQN2b3JiaXMtAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAxMDExMDEgKFNjaGF1ZmVudWdnZXQpAQAAABUAAABlbmNvZGVyPUxhdmM1NS41Mi4xMDIBBXZvcmJpcyVCQ1YBAEAAACRzGCpGpXMWhBAaQlAZ4xxCzmvsGUJMEYIcMkxbyyVzkCGkoEKIWyiB0JBVAABAAACHQXgUhIpBCCGEJT1YkoMnPQghhIg5eBSEaUEIIYQQQgghhBBCCCGERTlokoMnQQgdhOMwOAyD5Tj4HIRFOVgQgydB6CCED0K4moOsOQghhCQ1SFCDBjnoHITCLCiKgsQwuBaEBDUojILkMMjUgwtCiJqDSTX4GoRnQXgWhGlBCCGEJEFIkIMGQcgYhEZBWJKDBjm4FITLQagahCo5CB+EIDRkFQCQAACgoiiKoigKEBqyCgDIAAAQQFEUx3EcyZEcybEcCwgNWQUAAAEACAAAoEiKpEiO5EiSJFmSJVmSJVmS5omqLMuyLMuyLMsyEBqyCgBIAABQUQxFcRQHCA1ZBQBkAAAIoDiKpViKpWiK54iOCISGrAIAgAAABAAAEDRDUzxHlETPVFXXtm3btm3btm3btm3btm1blmUZCA1ZBQBAAAAQ0mlmqQaIMAMZBkJDVgEACAAAgBGKMMSA0JBVAABAAACAGEoOogmtOd+c46BZDppKsTkdnEi1eZKbirk555xzzsnmnDHOOeecopxZDJoJrTnnnMSgWQqaCa0555wnsXnQmiqtOeeccc7pYJwRxjnnnCateZCajbU555wFrWmOmkuxOeecSLl5UptLtTnnnHPOOeecc84555zqxekcnBPOOeecqL25lpvQxTnnnE/G6d6cEM4555xzzjnnnHPOOeecIDRkFQAABABAEIaNYdwpCNLnaCBGEWIaMulB9+gwCRqDnELq0ehopJQ6CCWVcVJKJwgNWQUAAAIAQAghhRRSSCGFFFJIIYUUYoghhhhyyimnoIJKKqmooowyyyyzzDLLLLPMOuyssw47DDHEEEMrrcRSU2011lhr7jnnmoO0VlprrbVSSimllFIKQkNWAQAgAAAEQgYZZJBRSCGFFGKIKaeccgoqqIDQkFUAACAAgAAAAABP8hzRER3RER3RER3RER3R8RzPESVREiVREi3TMjXTU0VVdWXXlnVZt31b2IVd933d933d+HVhWJZlWZZlWZZlWZZlWZZlWZYgNGQVAAACAAAghBBCSCGFFFJIKcYYc8w56CSUEAgNWQUAAAIACAAAAHAUR3EcyZEcSbIkS9IkzdIsT/M0TxM9URRF0zRV0RVdUTdtUTZl0zVdUzZdVVZtV5ZtW7Z125dl2/d93/d93/d93/d93/d9XQdCQ1YBABIAADqSIymSIimS4ziOJElAaMgqAEAGAEAAAIriKI7jOJIkSZIlaZJneZaomZrpmZ4qqkBoyCoAABAAQAAAAAAAAIqmeIqpeIqoeI7oiJJomZaoqZoryqbsuq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq4LhIasAgAkAAB0JEdyJEdSJEVSJEdygNCQVQCADACAAAAcwzEkRXIsy9I0T/M0TxM90RM901NFV3SB0JBVAAAgAIAAAAAAAAAMybAUy9EcTRIl1VItVVMt1VJF1VNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVN0zRNEwgNWQkAkAEAkBBTLS3GmgmLJGLSaqugYwxS7KWxSCpntbfKMYUYtV4ah5RREHupJGOKQcwtpNApJq3WVEKFFKSYYyoVUg5SIDRkhQAQmgHgcBxAsixAsiwAAAAAAAAAkDQN0DwPsDQPAAAAAAAAACRNAyxPAzTPAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAA0DwP8DwR8EQRAAAAAAAAACzPAzTRAzxRBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAAsDwP8EQR0DwRAAAAAAAAACzPAzxRBDzRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEOAAABBgIRQasiIAiBMAcEgSJAmSBM0DSJYFTYOmwTQBkmVB06BpME0AAAAAAAAAAAAAJE2DpkHTIIoASdOgadA0iCIAAAAAAAAAAAAAkqZB06BpEEWApGnQNGgaRBEAAAAAAAAAAAAAzzQhihBFmCbAM02IIkQRpgkAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAGHAAAAgwoQwUGrIiAIgTAHA4imUBAIDjOJYFAACO41gWAABYliWKAABgWZooAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAYcAAACDChDBQashIAiAIAcCiKZQHHsSzgOJYFJMmyAJYF0DyApgFEEQAIAAAocAAACLBBU2JxgEJDVgIAUQAABsWxLE0TRZKkaZoniiRJ0zxPFGma53meacLzPM80IYqiaJoQRVE0TZimaaoqME1VFQAAUOAAABBgg6bE4gCFhqwEAEICAByKYlma5nmeJ4qmqZokSdM8TxRF0TRNU1VJkqZ5niiKommapqqyLE3zPFEURdNUVVWFpnmeKIqiaaqq6sLzPE8URdE0VdV14XmeJ4qiaJqq6roQRVE0TdNUTVV1XSCKpmmaqqqqrgtETxRNU1Vd13WB54miaaqqq7ouEE3TVFVVdV1ZBpimaaqq68oyQFVV1XVdV5YBqqqqruu6sgxQVdd1XVmWZQCu67qyLMsCAAAOHAAAAoygk4wqi7DRhAsPQKEhKwKAKAAAwBimFFPKMCYhpBAaxiSEFEImJaXSUqogpFJSKRWEVEoqJaOUUmopVRBSKamUCkIqJZVSAADYgQMA2IGFUGjISgAgDwCAMEYpxhhzTiKkFGPOOScRUoox55yTSjHmnHPOSSkZc8w556SUzjnnnHNSSuacc845KaVzzjnnnJRSSuecc05KKSWEzkEnpZTSOeecEwAAVOAAABBgo8jmBCNBhYasBABSAQAMjmNZmuZ5omialiRpmud5niiapiZJmuZ5nieKqsnzPE8URdE0VZXneZ4oiqJpqirXFUXTNE1VVV2yLIqmaZqq6rowTdNUVdd1XZimaaqq67oubFtVVdV1ZRm2raqq6rqyDFzXdWXZloEsu67s2rIAAPAEBwCgAhtWRzgpGgssNGQlAJABAEAYg5BCCCFlEEIKIYSUUggJAAAYcAAACDChDBQashIASAUAAIyx1lprrbXWQGettdZaa62AzFprrbXWWmuttdZaa6211lJrrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmstpZRSSimllFJKKaWUUkoppZRSSgUA+lU4APg/2LA6wknRWGChISsBgHAAAMAYpRhzDEIppVQIMeacdFRai7FCiDHnJKTUWmzFc85BKCGV1mIsnnMOQikpxVZjUSmEUlJKLbZYi0qho5JSSq3VWIwxqaTWWoutxmKMSSm01FqLMRYjbE2ptdhqq7EYY2sqLbQYY4zFCF9kbC2m2moNxggjWywt1VprMMYY3VuLpbaaizE++NpSLDHWXAAAd4MDAESCjTOsJJ0VjgYXGrISAAgJACAQUooxxhhzzjnnpFKMOeaccw5CCKFUijHGnHMOQgghlIwx5pxzEEIIIYRSSsaccxBCCCGEkFLqnHMQQgghhBBKKZ1zDkIIIYQQQimlgxBCCCGEEEoopaQUQgghhBBCCKmklEIIIYRSQighlZRSCCGEEEIpJaSUUgohhFJCCKGElFJKKYUQQgillJJSSimlEkoJJYQSUikppRRKCCGUUkpKKaVUSgmhhBJKKSWllFJKIYQQSikFAAAcOAAABBhBJxlVFmGjCRcegEJDVgIAZAAAkKKUUiktRYIipRikGEtGFXNQWoqocgxSzalSziDmJJaIMYSUk1Qy5hRCDELqHHVMKQYtlRhCxhik2HJLoXMOAAAAQQCAgJAAAAMEBTMAwOAA4XMQdAIERxsAgCBEZohEw0JweFAJEBFTAUBigkIuAFRYXKRdXECXAS7o4q4DIQQhCEEsDqCABByccMMTb3jCDU7QKSp1IAAAAAAADADwAACQXAAREdHMYWRobHB0eHyAhIiMkAgAAAAAABcAfAAAJCVAREQ0cxgZGhscHR4fICEiIyQBAIAAAgAAAAAggAAEBAQAAAAAAAIAAAAEBB9DtnUBAAAAAAAEPueBAKOFggAAgACjzoEAA4BwBwCdASqwAJAAAEcIhYWIhYSIAgIABhwJ7kPfbJyHvtk5D32ych77ZOQ99snIe+2TkPfbJyHvtk5D32ych77ZOQ99YAD+/6tQgKOFggADgAqjhYIAD4AOo4WCACSADqOZgQArADECAAEQEAAYABhYL/QACIBDmAYAAKOFggA6gA6jhYIAT4AOo5mBAFMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAGSADqOFggB6gA6jmYEAewAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIAj4AOo5mBAKMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAKSADqOFggC6gA6jmYEAywAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIAz4AOo4WCAOSADqOZgQDzADECAAEQEAAYABhYL/QACIBDmAYAAKOFggD6gA6jhYIBD4AOo5iBARsAEQIAARAQFGAAYWC/0AAiAQ5gGACjhYIBJIAOo4WCATqADqOZgQFDADECAAEQEAAYABhYL/QACIBDmAYAAKOFggFPgA6jhYIBZIAOo5mBAWsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAXqADqOFggGPgA6jmYEBkwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIBpIAOo4WCAbqADqOZgQG7ADECAAEQEAAYABhYL/QACIBDmAYAAKOFggHPgA6jmYEB4wAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIB5IAOo4WCAfqADqOZgQILADECAAEQEAAYABhYL/QACIBDmAYAAKOFggIPgA6jhYICJIAOo5mBAjMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAjqADqOFggJPgA6jmYECWwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYICZIAOo4WCAnqADqOZgQKDADECAAEQEAAYABhYL/QACIBDmAYAAKOFggKPgA6jhYICpIAOo5mBAqsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCArqADqOFggLPgA6jmIEC0wARAgABEBAUYABhYL/QACIBDmAYAKOFggLkgA6jhYIC+oAOo5mBAvsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAw+ADqOZgQMjADECAAEQEAAYABhYL/QACIBDmAYAAKOFggMkgA6jhYIDOoAOo5mBA0sAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCA0+ADqOFggNkgA6jmYEDcwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIDeoAOo4WCA4+ADqOZgQObADECAAEQEAAYABhYL/QACIBDmAYAAKOFggOkgA6jhYIDuoAOo5mBA8MAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCA8+ADqOFggPkgA6jhYID+oAOo4WCBA+ADhxTu2sBAAAAAAAAEbuPs4EDt4r3gQHxghEr8IEK",mp4:"data:video/mp4;base64,AAAAHGZ0eXBNNFYgAAACAGlzb21pc28yYXZjMQAAAAhmcmVlAAAGF21kYXTeBAAAbGliZmFhYyAxLjI4AABCAJMgBDIARwAAArEGBf//rdxF6b3m2Ui3lizYINkj7u94MjY0IC0gY29yZSAxNDIgcjIgOTU2YzhkOCAtIEguMjY0L01QRUctNCBBVkMgY29kZWMgLSBDb3B5bGVmdCAyMDAzLTIwMTQgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwgLSBvcHRpb25zOiBjYWJhYz0wIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDE6MHgxMTEgbWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5nZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTAgY3FtPTAgZGVhZHpvbmU9MjEsMTEgZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJlYWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MCB3ZWlnaHRwPTAga2V5aW50PTI1MCBrZXlpbnRfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCB2YnZfbWF4cmF0ZT03NjggdmJ2X2J1ZnNpemU9MzAwMCBjcmZfbWF4PTAuMCBuYWxfaHJkPW5vbmUgZmlsbGVyPTAgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAAFZliIQL8mKAAKvMnJycnJycnJycnXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXiEASZACGQAjgCEASZACGQAjgAAAAAdBmjgX4GSAIQBJkAIZACOAAAAAB0GaVAX4GSAhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZpgL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGagC/AySEASZACGQAjgAAAAAZBmqAvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZrAL8DJIQBJkAIZACOAAAAABkGa4C/AySEASZACGQAjgCEASZACGQAjgAAAAAZBmwAvwMkhAEmQAhkAI4AAAAAGQZsgL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGbQC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBm2AvwMkhAEmQAhkAI4AAAAAGQZuAL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGboC/AySEASZACGQAjgAAAAAZBm8AvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZvgL8DJIQBJkAIZACOAAAAABkGaAC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBmiAvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZpAL8DJIQBJkAIZACOAAAAABkGaYC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBmoAvwMkhAEmQAhkAI4AAAAAGQZqgL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGawC/AySEASZACGQAjgAAAAAZBmuAvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZsAL8DJIQBJkAIZACOAAAAABkGbIC/AySEASZACGQAjgCEASZACGQAjgAAAAAZBm0AvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZtgL8DJIQBJkAIZACOAAAAABkGbgCvAySEASZACGQAjgCEASZACGQAjgAAAAAZBm6AnwMkhAEmQAhkAI4AhAEmQAhkAI4AhAEmQAhkAI4AhAEmQAhkAI4AAAAhubW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAABDcAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAzB0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAA+kAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAALAAAACQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAPpAAAAAAABAAAAAAKobWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAB1MAAAdU5VxAAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAACU21pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAhNzdGJsAAAAr3N0c2QAAAAAAAAAAQAAAJ9hdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAALAAkABIAAAASAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAALWF2Y0MBQsAN/+EAFWdCwA3ZAsTsBEAAAPpAADqYA8UKkgEABWjLg8sgAAAAHHV1aWRraEDyXyRPxbo5pRvPAyPzAAAAAAAAABhzdHRzAAAAAAAAAAEAAAAeAAAD6QAAABRzdHNzAAAAAAAAAAEAAAABAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAAIxzdHN6AAAAAAAAAAAAAAAeAAADDwAAAAsAAAALAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAAiHN0Y28AAAAAAAAAHgAAAEYAAANnAAADewAAA5gAAAO0AAADxwAAA+MAAAP2AAAEEgAABCUAAARBAAAEXQAABHAAAASMAAAEnwAABLsAAATOAAAE6gAABQYAAAUZAAAFNQAABUgAAAVkAAAFdwAABZMAAAWmAAAFwgAABd4AAAXxAAAGDQAABGh0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAACAAAAAAAABDcAAAAAAAAAAAAAAAEBAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAQkAAADcAABAAAAAAPgbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAC7gAAAykBVxAAAAAAALWhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAABTb3VuZEhhbmRsZXIAAAADi21pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAADT3N0YmwAAABnc3RzZAAAAAAAAAABAAAAV21wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAAC7gAAAAAAAM2VzZHMAAAAAA4CAgCIAAgAEgICAFEAVBbjYAAu4AAAADcoFgICAAhGQBoCAgAECAAAAIHN0dHMAAAAAAAAAAgAAADIAAAQAAAAAAQAAAkAAAAFUc3RzYwAAAAAAAAAbAAAAAQAAAAEAAAABAAAAAgAAAAIAAAABAAAAAwAAAAEAAAABAAAABAAAAAIAAAABAAAABgAAAAEAAAABAAAABwAAAAIAAAABAAAACAAAAAEAAAABAAAACQAAAAIAAAABAAAACgAAAAEAAAABAAAACwAAAAIAAAABAAAADQAAAAEAAAABAAAADgAAAAIAAAABAAAADwAAAAEAAAABAAAAEAAAAAIAAAABAAAAEQAAAAEAAAABAAAAEgAAAAIAAAABAAAAFAAAAAEAAAABAAAAFQAAAAIAAAABAAAAFgAAAAEAAAABAAAAFwAAAAIAAAABAAAAGAAAAAEAAAABAAAAGQAAAAIAAAABAAAAGgAAAAEAAAABAAAAGwAAAAIAAAABAAAAHQAAAAEAAAABAAAAHgAAAAIAAAABAAAAHwAAAAQAAAABAAAA4HN0c3oAAAAAAAAAAAAAADMAAAAaAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAACMc3RjbwAAAAAAAAAfAAAALAAAA1UAAANyAAADhgAAA6IAAAO+AAAD0QAAA+0AAAQAAAAEHAAABC8AAARLAAAEZwAABHoAAASWAAAEqQAABMUAAATYAAAE9AAABRAAAAUjAAAFPwAABVIAAAVuAAAFgQAABZ0AAAWwAAAFzAAABegAAAX7AAAGFwAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTUuMzMuMTAw"}}])}));
diff --git a/public_included_ws_fallback/scripts/QRCode.min.js b/public_included_ws_fallback/scripts/QRCode.min.js
deleted file mode 100644
index 569a867..0000000
--- a/public_included_ws_fallback/scripts/QRCode.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! qrcode-svg v1.1.0 | https://github.com/papnkukn/qrcode-svg | MIT license */
-function QR8bitByte(t){this.mode=QRMode.MODE_8BIT_BYTE,this.data=t,this.parsedData=[];for(var e=0,r=this.data.length;e65536?(o[0]=240|(1835008&n)>>>18,o[1]=128|(258048&n)>>>12,o[2]=128|(4032&n)>>>6,o[3]=128|63&n):n>2048?(o[0]=224|(61440&n)>>>12,o[1]=128|(4032&n)>>>6,o[2]=128|63&n):n>128?(o[0]=192|(1984&n)>>>6,o[1]=128|63&n):o[0]=n,this.parsedData.push(o)}this.parsedData=Array.prototype.concat.apply([],this.parsedData),this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function QRCodeModel(t,e){this.typeNumber=t,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}QR8bitByte.prototype={getLength:function(t){return this.parsedData.length},write:function(t){for(var e=0,r=this.parsedData.length;e=7&&this.setupTypeNumber(t),null==this.dataCache&&(this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,e)},setupPositionProbePattern:function(t,e){for(var r=-1;r<=7;r++)if(!(t+r<=-1||this.moduleCount<=t+r))for(var o=-1;o<=7;o++)e+o<=-1||this.moduleCount<=e+o||(this.modules[t+r][e+o]=0<=r&&r<=6&&(0==o||6==o)||0<=o&&o<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=o&&o<=4)},getBestMaskPattern:function(){for(var t=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var o=QRUtil.getLostPoint(this);(0==r||t>o)&&(t=o,e=r)}return e},createMovieClip:function(t,e,r){var o=t.createEmptyMovieClip(e,r);this.make();for(var n=0;n>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=o}for(r=0;r<18;r++){o=!t&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=o}},setupTypeInfo:function(t,e){for(var r=this.errorCorrectLevel<<3|e,o=QRUtil.getBCHTypeInfo(r),n=0;n<15;n++){var i=!t&&1==(o>>n&1);n<6?this.modules[n][8]=i:n<8?this.modules[n+1][8]=i:this.modules[this.moduleCount-15+n][8]=i}for(n=0;n<15;n++){i=!t&&1==(o>>n&1);n<8?this.modules[8][this.moduleCount-n-1]=i:n<9?this.modules[8][15-n-1+1]=i:this.modules[8][15-n-1]=i}this.modules[this.moduleCount-8][8]=!t},mapData:function(t,e){for(var r=-1,o=this.moduleCount-1,n=7,i=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var s=0;s<2;s++)if(null==this.modules[o][a-s]){var h=!1;i>>n&1)),QRUtil.getMask(e,o,a-s)&&(h=!h),this.modules[o][a-s]=h,-1==--n&&(i++,n=7)}if((o+=r)<0||this.moduleCount<=o){o-=r,r=-r;break}}}},QRCodeModel.PAD0=236,QRCodeModel.PAD1=17,QRCodeModel.createData=function(t,e,r){for(var o=QRRSBlock.getRSBlocks(t,e),n=new QRBitBuffer,i=0;i8*s)throw new Error("code length overflow. ("+n.getLengthInBits()+">"+8*s+")");for(n.getLengthInBits()+4<=8*s&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*s||(n.put(QRCodeModel.PAD0,8),n.getLengthInBits()>=8*s));)n.put(QRCodeModel.PAD1,8);return QRCodeModel.createBytes(n,o)},QRCodeModel.createBytes=function(t,e){for(var r=0,o=0,n=0,i=new Array(e.length),a=new Array(e.length),s=0;s=0?d.get(f):0}}var c=0;for(u=0;u=0;)e^=QRUtil.G15<=0;)e^=QRUtil.G18<>>=1;return e},getPatternPosition:function(t){return QRUtil.PATTERN_POSITION_TABLE[t-1]},getMask:function(t,e,r){switch(t){case QRMaskPattern.PATTERN000:return(e+r)%2==0;case QRMaskPattern.PATTERN001:return e%2==0;case QRMaskPattern.PATTERN010:return r%3==0;case QRMaskPattern.PATTERN011:return(e+r)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(e/2)+Math.floor(r/3))%2==0;case QRMaskPattern.PATTERN101:return e*r%2+e*r%3==0;case QRMaskPattern.PATTERN110:return(e*r%2+e*r%3)%2==0;case QRMaskPattern.PATTERN111:return(e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}},getErrorCorrectPolynomial:function(t){for(var e=new QRPolynomial([1],0),r=0;r5&&(r+=3+i-5)}for(o=0;o=256;)t-=255;return QRMath.EXP_TABLE[t]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},i=0;i<8;i++)QRMath.EXP_TABLE[i]=1<>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];function QRCode(t){if(this.options={padding:4,width:256,height:256,typeNumber:4,color:"#000000",background:"#ffffff",ecl:"M"},"string"==typeof t&&(t={content:t}),t)for(var e in t)this.options[e]=t[e];if("string"!=typeof this.options.content)throw new Error("Expected 'content' as string!");if(0===this.options.content.length)throw new Error("Expected 'content' to be non-empty!");if(!(this.options.padding>=0))throw new Error("Expected 'padding' value to be non-negative!");if(!(this.options.width>0&&this.options.height>0))throw new Error("Expected 'width' or 'height' value to be higher than zero!");var r=this.options.content,o=function(t,e){for(var r=function(t){var e=encodeURI(t).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return e.length+(e.length!=t?3:0)}(t),o=1,n=0,i=0,a=QRCodeLimitLength.length;i<=a;i++){var s=QRCodeLimitLength[i];if(!s)throw new Error("Content too long: expected "+n+" but got "+r);switch(e){case"L":n=s[0];break;case"M":n=s[1];break;case"Q":n=s[2];break;case"H":n=s[3];break;default:throw new Error("Unknwon error correction level: "+e)}if(r<=n)break;o++}if(o>QRCodeLimitLength.length)throw new Error("Content too long");return o}(r,this.options.ecl),n=function(t){switch(t){case"L":return QRErrorCorrectLevel.L;case"M":return QRErrorCorrectLevel.M;case"Q":return QRErrorCorrectLevel.Q;case"H":return QRErrorCorrectLevel.H;default:throw new Error("Unknwon error correction level: "+t)}}(this.options.ecl);this.qrcode=new QRCodeModel(o,n),this.qrcode.addData(r),this.qrcode.make()}QRCode.prototype.svg=function(t){var e=this.options||{},r=this.qrcode.modules;void 0===t&&(t={container:e.container||"svg"});for(var o=void 0===e.pretty||!!e.pretty,n=o?" ":"",i=o?"\r\n":"",a=e.width,s=e.height,h=r.length,l=a/(h+2*e.padding),u=s/(h+2*e.padding),g=void 0!==e.join&&!!e.join,d=void 0!==e.swap&&!!e.swap,f=void 0===e.xmlDeclaration||!!e.xmlDeclaration,c=void 0!==e.predefined&&!!e.predefined,R=c?n+' '+i:"",p=n+' '+i,m="",Q="",v=0;v '+i:n+' '+i}}g&&(m=n+' ');var T="";switch(t.container){case"svg":f&&(T+=''+i),T+=''+i,T+=R+p+m,T+=" ";break;case"svg-viewbox":f&&(T+=''+i),T+=''+i,T+=R+p+m,T+=" ";break;case"g":T+=''+i,T+=R+p+m,T+=" ";break;default:T+=(R+p+m).replace(/^\s+/,"")}return T},QRCode.prototype.save=function(t,e){var r=this.svg();"function"!=typeof e&&(e=function(t,e){});try{require("fs").writeFile(t,r,e)}catch(t){e(t)}},"undefined"!=typeof module&&(module.exports=QRCode);
\ No newline at end of file
diff --git a/public_included_ws_fallback/scripts/localization.js b/public_included_ws_fallback/scripts/localization.js
deleted file mode 100644
index d8390c1..0000000
--- a/public_included_ws_fallback/scripts/localization.js
+++ /dev/null
@@ -1,172 +0,0 @@
-class Localization {
- constructor() {
- Localization.defaultLocale = "en";
- Localization.supportedLocales = ["ar", "de", "en", "es", "fr", "id", "it", "ja", "nb", "nl", "ro", "ru", "tr", "zh-CN"];
- Localization.supportedLocalesRTL = ["ar"];
-
- Localization.translations = {};
- Localization.defaultTranslations = {};
-
- Localization.systemLocale = Localization.getSupportedOrDefault(navigator.languages);
-
- let storedLanguageCode = localStorage.getItem("language-code");
-
- Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode)
- ? storedLanguageCode
- : Localization.systemLocale;
-
- Localization.setTranslation(Localization.initialLocale)
- .then(_ => {
- console.log("Initial translation successful.");
- Events.fire("initial-translation-loaded");
- });
- }
-
- static isSupported(locale) {
- return Localization.supportedLocales.indexOf(locale) > -1;
- }
-
- static isRTLLanguage(locale) {
- return Localization.supportedLocalesRTL.indexOf(locale) > -1;
- }
-
- static getSupportedOrDefault(locales) {
- let localesGeneric = locales
- .map(locale => locale.split("-")[0])
- .filter(locale => locales.indexOf(locale) === -1);
-
- return locales.find(Localization.isSupported)
- || localesGeneric.find(Localization.isSupported)
- || Localization.defaultLocale;
- }
-
- static async setTranslation(locale) {
- if (!locale) locale = Localization.systemLocale;
-
- await Localization.setLocale(locale)
- await Localization.translatePage();
-
- const htmlRootNode = document.querySelector('html');
-
- if (Localization.isRTLLanguage(locale)) {
- htmlRootNode.setAttribute('dir', 'rtl');
- } else {
- htmlRootNode.removeAttribute('dir');
- }
-
- htmlRootNode.setAttribute('lang', locale);
-
-
- console.log("Page successfully translated",
- `System language: ${Localization.systemLocale}`,
- `Selected language: ${locale}`
- );
-
- Events.fire("translation-loaded");
- }
-
- static async setLocale(newLocale) {
- if (newLocale === Localization.locale) return false;
-
- Localization.defaultTranslations = await Localization.fetchTranslationsFor(Localization.defaultLocale);
-
- const newTranslations = await Localization.fetchTranslationsFor(newLocale);
-
- if(!newTranslations) return false;
-
- Localization.locale = newLocale;
- Localization.translations = newTranslations;
- }
-
- static getLocale() {
- return Localization.locale;
- }
-
- static isSystemLocale() {
- return !localStorage.getItem('language-code');
- }
-
- static async fetchTranslationsFor(newLocale) {
- const response = await fetch(`lang/${newLocale}.json`, {
- method: 'GET',
- credentials: 'include',
- mode: 'no-cors',
- });
-
- if (response.redirected === true || response.status !== 200) return false;
-
- return await response.json();
- }
-
- static async translatePage() {
- document
- .querySelectorAll("[data-i18n-key]")
- .forEach(element => Localization.translateElement(element));
- }
-
- static async translateElement(element) {
- const key = element.getAttribute("data-i18n-key");
- const attrs = element.getAttribute("data-i18n-attrs").split(" ");
-
- for (let i in attrs) {
- let attr = attrs[i];
- if (attr === "text") {
- element.innerText = Localization.getTranslation(key);
- } else {
- if (attr.startsWith("data-")) {
- let dataAttr = attr.substring(5);
- element.dataset.dataAttr = Localization.getTranslation(key, attr);
- } {
- element.setAttribute(attr, Localization.getTranslation(key, attr));
- }
- }
- }
- }
-
- static getTranslation(key, attr=null, data={}, useDefault=false) {
- const keys = key.split(".");
-
- let translationCandidates = useDefault
- ? Localization.defaultTranslations
- : Localization.translations;
-
- let translation;
-
- try {
- for (let i = 0; i < keys.length - 1; i++) {
- translationCandidates = translationCandidates[keys[i]]
- }
-
- let lastKey = keys[keys.length - 1];
-
- if (attr) lastKey += "_" + attr;
-
- translation = translationCandidates[lastKey];
-
- for (let j in data) {
- translation = translation.replace(`{{${j}}}`, data[j]);
- }
- } catch (e) {
- translation = "";
- }
-
- if (!translation) {
- if (!useDefault) {
- translation = this.getTranslation(key, attr, data, true);
- console.warn(`Missing translation entry for your language ${Localization.locale.toUpperCase()}. Using ${Localization.defaultLocale.toUpperCase()} instead.`, key, attr);
- console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`)
- console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/");
- } else {
- console.warn("Missing translation in default language:", key, attr);
- }
- }
-
- return Localization.escapeHTML(translation);
- }
-
- static escapeHTML(unsafeText) {
- let div = document.createElement('div');
- div.innerText = unsafeText;
- return div.innerHTML;
- }
-}
diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js
deleted file mode 100644
index 6e68035..0000000
--- a/public_included_ws_fallback/scripts/network.js
+++ /dev/null
@@ -1,1261 +0,0 @@
-window.URL = window.URL || window.webkitURL;
-window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
-
-window.hiddenProperty = 'hidden' in document ? 'hidden' :
- 'webkitHidden' in document ? 'webkitHidden' :
- 'mozHidden' in document ? 'mozHidden' :
- null;
-window.visibilityChangeEvent = 'visibilitychange' in document ? 'visibilitychange' :
- 'webkitvisibilitychange' in document ? 'webkitvisibilitychange' :
- 'mozvisibilitychange' in document ? 'mozvisibilitychange' :
- null;
-
-class ServerConnection {
-
- constructor() {
- this._connect();
- Events.on('pagehide', _ => this._disconnect());
- document.addEventListener(window.visibilityChangeEvent, _ => this._onVisibilityChange());
- if (navigator.connection) navigator.connection.addEventListener('change', _ => this._reconnect());
- Events.on('room-secrets', e => this.send({ type: 'room-secrets', roomSecrets: e.detail }));
- Events.on('join-ip-room', e => this.send({ type: 'join-ip-room'}));
- Events.on('room-secrets-deleted', e => this.send({ type: 'room-secrets-deleted', roomSecrets: e.detail}));
- Events.on('regenerate-room-secret', e => this.send({ type: 'regenerate-room-secret', roomSecret: e.detail}));
- Events.on('pair-device-initiate', _ => this._onPairDeviceInitiate());
- Events.on('pair-device-join', e => this._onPairDeviceJoin(e.detail));
- Events.on('pair-device-cancel', _ => this.send({ type: 'pair-device-cancel' }));
-
- Events.on('create-public-room', _ => this._onCreatePublicRoom());
- Events.on('join-public-room', e => this._onJoinPublicRoom(e.detail.roomId, e.detail.createIfInvalid));
- Events.on('leave-public-room', _ => this._onLeavePublicRoom());
-
- Events.on('offline', _ => clearTimeout(this._reconnectTimer));
- Events.on('online', _ => this._connect());
- }
-
- _connect() {
- clearTimeout(this._reconnectTimer);
- if (this._isConnected() || this._isConnecting() || this._isOffline()) return;
- if (this._isReconnect) {
- Events.fire('notify-user', {
- message: Localization.getTranslation("notifications.connecting"),
- persistent: true
- });
- }
- const ws = new WebSocket(this._endpoint());
- ws.binaryType = 'arraybuffer';
- ws.onopen = _ => this._onOpen();
- ws.onmessage = e => this._onMessage(e.data);
- ws.onclose = _ => this._onDisconnect();
- ws.onerror = e => this._onError(e);
- this._socket = ws;
- }
-
- _onOpen() {
- console.log('WS: server connected');
- Events.fire('ws-connected');
- if (this._isReconnect) Events.fire('notify-user', Localization.getTranslation("notifications.connected"));
- }
-
- _onPairDeviceInitiate() {
- if (!this._isConnected()) {
- Events.fire('notify-user', Localization.getTranslation("notifications.online-requirement-pairing"));
- return;
- }
- this.send({ type: 'pair-device-initiate' });
- }
-
- _onPairDeviceJoin(pairKey) {
- if (!this._isConnected()) {
- setTimeout(_ => this._onPairDeviceJoin(pairKey), 1000);
- return;
- }
- this.send({ type: 'pair-device-join', pairKey: pairKey });
- }
-
- _onCreatePublicRoom() {
- if (!this._isConnected()) {
- Events.fire('notify-user', Localization.getTranslation("notifications.online-requirement-public-room"));
- return;
- }
- this.send({ type: 'create-public-room' });
- }
-
- _onJoinPublicRoom(roomId, createIfInvalid) {
- if (!this._isConnected()) {
- setTimeout(_ => this._onJoinPublicRoom(roomId), 1000);
- return;
- }
- this.send({ type: 'join-public-room', publicRoomId: roomId, createIfInvalid: createIfInvalid });
- }
-
- _onLeavePublicRoom() {
- if (!this._isConnected()) {
- setTimeout(_ => this._onLeavePublicRoom(), 1000);
- return;
- }
- this.send({ type: 'leave-public-room' });
- }
-
- _setRtcConfig(config) {
- window.rtcConfig = config;
- }
-
- _onMessage(msg) {
- msg = JSON.parse(msg);
- if (msg.type !== 'ping') console.log('WS receive:', msg);
- switch (msg.type) {
- case 'rtc-config':
- this._setRtcConfig(msg.config);
- break;
- case 'peers':
- this._onPeers(msg);
- break;
- case 'peer-joined':
- Events.fire('peer-joined', msg);
- break;
- case 'peer-left':
- Events.fire('peer-left', msg);
- break;
- case 'signal':
- Events.fire('signal', msg);
- break;
- case 'ping':
- this.send({ type: 'pong' });
- break;
- case 'display-name':
- this._onDisplayName(msg);
- break;
- case 'pair-device-initiated':
- Events.fire('pair-device-initiated', msg);
- break;
- case 'pair-device-joined':
- Events.fire('pair-device-joined', msg);
- break;
- case 'pair-device-join-key-invalid':
- Events.fire('pair-device-join-key-invalid');
- break;
- case 'pair-device-canceled':
- Events.fire('pair-device-canceled', msg.pairKey);
- break;
- case 'join-key-rate-limit':
- Events.fire('notify-user', Localization.getTranslation("notifications.rate-limit-join-key"));
- break;
- case 'secret-room-deleted':
- Events.fire('secret-room-deleted', msg.roomSecret);
- break;
- case 'room-secret-regenerated':
- Events.fire('room-secret-regenerated', msg);
- break;
- case 'public-room-id-invalid':
- Events.fire('public-room-id-invalid', msg.publicRoomId);
- break;
- case 'public-room-created':
- Events.fire('public-room-created', msg.roomId);
- break;
- case 'public-room-left':
- Events.fire('public-room-left');
- break;
- case 'request':
- case 'header':
- case 'partition':
- case 'partition-received':
- case 'progress':
- case 'files-transfer-response':
- case 'file-transfer-complete':
- case 'message-transfer-complete':
- case 'text':
- case 'display-name-changed':
- case 'ws-chunk':
- Events.fire('ws-relay', JSON.stringify(msg));
- break;
- default:
- console.error('WS receive: unknown message type', msg);
- }
- }
-
- send(msg) {
- if (!this._isConnected()) return;
- if (msg.type !== 'pong') console.log("WS send:", msg)
- this._socket.send(JSON.stringify(msg));
- }
-
- _onPeers(msg) {
- Events.fire('peers', msg);
- }
-
- _onDisplayName(msg) {
- // Add peerId and peerIdHash to sessionStorage to authenticate as the same device on page reload
- sessionStorage.setItem('peer_id', msg.message.peerId);
- sessionStorage.setItem('peer_id_hash', msg.message.peerIdHash);
-
- // Add peerId to localStorage to mark it for other PairDrop tabs on the same browser
- BrowserTabsConnector.addPeerIdToLocalStorage().then(peerId => {
- if (!peerId) return;
- console.log("successfully added peerId to localStorage");
-
- // Only now join rooms
- Events.fire('join-ip-room');
- PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
- Events.fire('room-secrets', roomSecrets);
- });
- });
-
- Events.fire('display-name', msg);
- }
-
- _endpoint() {
- // hack to detect if deployment or development environment
- const protocol = location.protocol.startsWith('https') ? 'wss' : 'ws';
- const webrtc = window.isRtcSupported ? '/webrtc' : '/fallback';
- let ws_url = new URL(protocol + '://' + location.host + location.pathname + 'server' + webrtc);
- const peerId = sessionStorage.getItem('peer_id');
- const peerIdHash = sessionStorage.getItem('peer_id_hash');
- if (peerId && peerIdHash) {
- ws_url.searchParams.append('peer_id', peerId);
- ws_url.searchParams.append('peer_id_hash', peerIdHash);
- }
- return ws_url.toString();
- }
-
- _disconnect() {
- this.send({ type: 'disconnect' });
-
- const peerId = sessionStorage.getItem('peer_id');
- BrowserTabsConnector.removePeerIdFromLocalStorage(peerId).then(_ => {
- console.log("successfully removed peerId from localStorage");
- });
-
- if (!this._socket) return;
-
- this._socket.onclose = null;
- this._socket.close();
- this._socket = null;
- Events.fire('ws-disconnected');
- this._isReconnect = true;
- }
-
- _onDisconnect() {
- console.log('WS: server disconnected');
- setTimeout(() => {
- this._isReconnect = true;
- Events.fire('ws-disconnected');
- this._reconnectTimer = setTimeout(_ => this._connect(), 1000);
- }, 100); //delay for 100ms to prevent flickering on page reload
- }
-
- _onVisibilityChange() {
- if (window.hiddenProperty) return;
- this._connect();
- }
-
- _isConnected() {
- return this._socket && this._socket.readyState === this._socket.OPEN;
- }
-
- _isConnecting() {
- return this._socket && this._socket.readyState === this._socket.CONNECTING;
- }
-
- _isOffline() {
- return !navigator.onLine;
- }
-
- _onError(e) {
- console.error(e);
- }
-
- _reconnect() {
- this._disconnect();
- this._connect();
- }
-}
-
-class Peer {
-
- constructor(serverConnection, isCaller, peerId, roomType, roomId) {
- this._server = serverConnection;
- this._isCaller = isCaller;
- this._peerId = peerId;
-
- this._roomIds = {};
- this._updateRoomIds(roomType, roomId);
-
- this._filesQueue = [];
- this._busy = false;
-
- // evaluate auto accept
- this._evaluateAutoAccept();
- }
-
- sendJSON(message) {
- this._send(JSON.stringify(message));
- }
-
- sendDisplayName(displayName) {
- this.sendJSON({type: 'display-name-changed', displayName: displayName});
- }
-
- _isSameBrowser() {
- return BrowserTabsConnector.peerIsSameBrowser(this._peerId);
- }
-
- _isPaired() {
- return !!this._roomIds['secret'];
- }
-
- _getPairSecret() {
- return this._roomIds['secret'];
- }
-
- _getRoomTypes() {
- return Object.keys(this._roomIds);
- }
-
- _updateRoomIds(roomType, roomId) {
- // if peer is another browser tab, peer is not identifiable with roomSecret as browser tabs share all roomSecrets
- // -> do not delete duplicates and do not regenerate room secrets
- if (!this._isSameBrowser() && roomType === "secret" && this._isPaired() && this._getPairSecret() !== roomId) {
- // multiple roomSecrets with same peer -> delete old roomSecret
- PersistentStorage.deleteRoomSecret(this._getPairSecret())
- .then(deletedRoomSecret => {
- if (deletedRoomSecret) console.log("Successfully deleted duplicate room secret with same peer: ", deletedRoomSecret);
- });
- }
-
- this._roomIds[roomType] = roomId;
-
- if (!this._isSameBrowser() && roomType === "secret" && this._isPaired() && this._getPairSecret().length !== 256 && this._isCaller) {
- // increase security by initiating the increase of the roomSecret length from 64 chars ( {
- const autoAccept = roomSecretEntry
- ? roomSecretEntry.entry.auto_accept
- : false;
- this._setAutoAccept(autoAccept);
- })
- .catch(_ => {
- this._setAutoAccept(false);
- });
- }
-
- _setAutoAccept(autoAccept) {
- this._autoAccept = !this._isSameBrowser()
- ? autoAccept
- : false;
- }
-
- getResizedImageDataUrl(file, width = undefined, height = undefined, quality = 0.7) {
- return new Promise((resolve, reject) => {
- let image = new Image();
- image.src = URL.createObjectURL(file);
- image.onload = _ => {
- let imageWidth = image.width;
- let imageHeight = image.height;
- let canvas = document.createElement('canvas');
-
- // resize the canvas and draw the image data into it
- if (width && height) {
- canvas.width = width;
- canvas.height = height;
- } else if (width) {
- canvas.width = width;
- canvas.height = Math.floor(imageHeight * width / imageWidth)
- } else if (height) {
- canvas.width = Math.floor(imageWidth * height / imageHeight);
- canvas.height = height;
- } else {
- canvas.width = imageWidth;
- canvas.height = imageHeight
- }
-
- var ctx = canvas.getContext("2d");
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
-
- let dataUrl = canvas.toDataURL("image/jpeg", quality);
- resolve(dataUrl);
- }
- image.onerror = _ => reject(`Could not create an image thumbnail from type ${file.type}`);
- }).then(dataUrl => {
- return dataUrl;
- }).catch(e => console.error(e));
- }
-
- async requestFileTransfer(files) {
- let header = [];
- let totalSize = 0;
- let imagesOnly = true
- for (let i=0; i this._send(chunk),
- offset => this._onPartitionEnd(offset));
- this._chunker.nextPartition();
- }
-
- _onPartitionEnd(offset) {
- this.sendJSON({ type: 'partition', offset: offset });
- }
-
- _onReceivedPartitionEnd(offset) {
- this.sendJSON({ type: 'partition-received', offset: offset });
- }
-
- _sendNextPartition() {
- if (!this._chunker || this._chunker.isFileEnd()) return;
- this._chunker.nextPartition();
- }
-
- _sendProgress(progress) {
- this.sendJSON({ type: 'progress', progress: progress });
- }
-
- _onMessage(message) {
- if (typeof message !== 'string') {
- this._onChunkReceived(message);
- return;
- }
- const messageJSON = JSON.parse(message);
- switch (messageJSON.type) {
- case 'request':
- this._onFilesTransferRequest(messageJSON);
- break;
- case 'header':
- this._onFileHeader(messageJSON);
- break;
- case 'partition':
- this._onReceivedPartitionEnd(messageJSON);
- break;
- case 'partition-received':
- this._sendNextPartition();
- break;
- case 'progress':
- this._onDownloadProgress(messageJSON.progress);
- break;
- case 'files-transfer-response':
- this._onFileTransferRequestResponded(messageJSON);
- break;
- case 'file-transfer-complete':
- this._onFileTransferCompleted();
- break;
- case 'message-transfer-complete':
- this._onMessageTransferCompleted();
- break;
- case 'text':
- this._onTextReceived(messageJSON);
- break;
- case 'display-name-changed':
- this._onDisplayNameChanged(messageJSON);
- break;
- }
- }
-
- _onFilesTransferRequest(request) {
- if (this._requestPending) {
- // Only accept one request at a time per peer
- this.sendJSON({type: 'files-transfer-response', accepted: false});
- return;
- }
- if (window.iOS && request.totalSize >= 200*1024*1024) {
- // iOS Safari can only put 400MB at once to memory.
- // Request to send them in chunks of 200MB instead:
- this.sendJSON({type: 'files-transfer-response', accepted: false, reason: 'ios-memory-limit'});
- return;
- }
-
- this._requestPending = request;
-
- if (this._autoAccept) {
- // auto accept if set via Edit Paired Devices Dialog
- this._respondToFileTransferRequest(true);
- return;
- }
-
- // default behavior: show user transfer request
- Events.fire('files-transfer-request', {
- request: request,
- peerId: this._peerId
- });
- }
-
- _respondToFileTransferRequest(accepted) {
- this.sendJSON({type: 'files-transfer-response', accepted: accepted});
- if (accepted) {
- this._requestAccepted = this._requestPending;
- this._totalBytesReceived = 0;
- this._busy = true;
- this._filesReceived = [];
- }
- this._requestPending = null;
- }
-
- _onFileHeader(header) {
- if (this._requestAccepted && this._requestAccepted.header.length) {
- this._lastProgress = 0;
- this._digester = new FileDigester({size: header.size, name: header.name, mime: header.mime},
- this._requestAccepted.totalSize,
- this._totalBytesReceived,
- fileBlob => this._onFileReceived(fileBlob)
- );
- }
- }
-
- _abortTransfer() {
- Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
- Events.fire('notify-user', Localization.getTranslation("notifications.files-incorrect"));
- this._filesReceived = [];
- this._requestAccepted = null;
- this._digester = null;
- throw new Error("Received files differ from requested files. Abort!");
- }
-
- _onChunkReceived(chunk) {
- if(!this._digester || !(chunk.byteLength || chunk.size)) return;
-
- this._digester.unchunk(chunk);
- const progress = this._digester.progress;
-
- if (progress > 1) {
- this._abortTransfer();
- }
-
- this._onDownloadProgress(progress);
-
- // occasionally notify sender about our progress
- if (progress - this._lastProgress < 0.005 && progress !== 1) return;
- this._lastProgress = progress;
- this._sendProgress(progress);
- }
-
- _onDownloadProgress(progress) {
- Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
- }
-
- async _onFileReceived(fileBlob) {
- const acceptedHeader = this._requestAccepted.header.shift();
- this._totalBytesReceived += fileBlob.size;
-
- this.sendJSON({type: 'file-transfer-complete'});
-
- const sameSize = fileBlob.size === acceptedHeader.size;
- const sameName = fileBlob.name === acceptedHeader.name
- if (!sameSize || !sameName) {
- this._abortTransfer();
- }
-
- // include for compatibility with 'Snapdrop & PairDrop for Android' app
- Events.fire('file-received', fileBlob);
-
- this._filesReceived.push(fileBlob);
- if (!this._requestAccepted.header.length) {
- this._busy = false;
- Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
- Events.fire('files-received', {peerId: this._peerId, files: this._filesReceived, imagesOnly: this._requestAccepted.imagesOnly, totalSize: this._requestAccepted.totalSize});
- this._filesReceived = [];
- this._requestAccepted = null;
- }
- }
-
- _onFileTransferCompleted() {
- this._chunker = null;
- if (!this._filesQueue.length) {
- this._busy = false;
- Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
- Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
- } else {
- this._dequeueFile();
- }
- }
-
- _onFileTransferRequestResponded(message) {
- if (!message.accepted) {
- Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
- this._filesRequested = null;
- if (message.reason === 'ios-memory-limit') {
- Events.fire('notify-user', Localization.getTranslation("notifications.ios-memory-limit"));
- }
- return;
- }
- Events.fire('file-transfer-accepted');
- Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'});
- this.sendFiles();
- }
-
- _onMessageTransferCompleted() {
- Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
- }
-
- sendText(text) {
- const unescaped = btoa(unescape(encodeURIComponent(text)));
- this.sendJSON({ type: 'text', text: unescaped });
- }
-
- _onTextReceived(message) {
- if (!message.text) return;
- const escaped = decodeURIComponent(escape(atob(message.text)));
- Events.fire('text-received', { text: escaped, peerId: this._peerId });
- this.sendJSON({ type: 'message-transfer-complete' });
- }
-
- _onDisplayNameChanged(message) {
- const displayNameHasChanged = this._displayName !== message.displayName
-
- if (message.displayName && displayNameHasChanged) {
- this._displayName = message.displayName;
- }
-
- Events.fire('peer-display-name-changed', {peerId: this._peerId, displayName: message.displayName});
-
- if (!displayNameHasChanged) return;
- Events.fire('notify-peer-display-name-changed', this._peerId);
- }
-}
-
-class RTCPeer extends Peer {
-
- constructor(serverConnection, isCaller, peerId, roomType, roomId) {
- super(serverConnection, isCaller, peerId, roomType, roomId);
- this.rtcSupported = true;
- if (!this._isCaller) return; // we will listen for a caller
- this._connect();
- }
-
- _connect() {
- if (!this._conn || this._conn.signalingState === "closed") this._openConnection();
-
- if (this._isCaller) {
- this._openChannel();
- } else {
- this._conn.ondatachannel = e => this._onChannelOpened(e);
- }
- }
-
- _openConnection() {
- this._conn = new RTCPeerConnection(window.rtcConfig);
- this._conn.onicecandidate = e => this._onIceCandidate(e);
- this._conn.onicecandidateerror = e => this._onError(e);
- this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
- this._conn.oniceconnectionstatechange = e => this._onIceConnectionStateChange(e);
- }
-
- _openChannel() {
- if (!this._conn) return;
-
- const channel = this._conn.createDataChannel('data-channel', {
- ordered: true,
- reliable: true // Obsolete. See https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/reliable
- });
- channel.onopen = e => this._onChannelOpened(e);
- channel.onerror = e => this._onError(e);
-
- this._conn.createOffer()
- .then(d => this._onDescription(d))
- .catch(e => this._onError(e));
- }
-
- _onDescription(description) {
- // description.sdp = description.sdp.replace('b=AS:30', 'b=AS:1638400');
- this._conn.setLocalDescription(description)
- .then(_ => this._sendSignal({ sdp: description }))
- .catch(e => this._onError(e));
- }
-
- _onIceCandidate(event) {
- if (!event.candidate) return;
- this._sendSignal({ ice: event.candidate });
- }
-
- onServerMessage(message) {
- if (!this._conn) this._connect();
-
- if (message.sdp) {
- this._conn.setRemoteDescription(message.sdp)
- .then( _ => {
- if (message.sdp.type === 'offer') {
- return this._conn.createAnswer()
- .then(d => this._onDescription(d));
- }
- })
- .catch(e => this._onError(e));
- } else if (message.ice) {
- this._conn.addIceCandidate(new RTCIceCandidate(message.ice))
- .catch(e => this._onError(e));
- }
- }
-
- _onChannelOpened(event) {
- console.log('RTC: channel opened with', this._peerId);
- const channel = event.channel || event.target;
- channel.binaryType = 'arraybuffer';
- channel.onmessage = e => this._onMessage(e.data);
- channel.onclose = _ => this._onChannelClosed();
- this._channel = channel;
- Events.on('beforeunload', e => this._onBeforeUnload(e));
- Events.on('pagehide', _ => this._onPageHide());
- Events.fire('peer-connected', {peerId: this._peerId, connectionHash: this.getConnectionHash()});
- }
-
- _onMessage(message) {
- if (typeof message === 'string') {
- console.log('RTC:', JSON.parse(message));
- }
- super._onMessage(message);
- }
-
- getConnectionHash() {
- const localDescriptionLines = this._conn.localDescription.sdp.split("\r\n");
- const remoteDescriptionLines = this._conn.remoteDescription.sdp.split("\r\n");
- let localConnectionFingerprint, remoteConnectionFingerprint;
- for (let i=0; i this._onMessage(e.detail));
- Events.on('peers', e => this._onPeers(e.detail));
- Events.on('files-selected', e => this._onFilesSelected(e.detail));
- Events.on('respond-to-files-transfer-request', e => this._onRespondToFileTransferRequest(e.detail))
- Events.on('send-text', e => this._onSendText(e.detail));
- Events.on('peer-left', e => this._onPeerLeft(e.detail));
- Events.on('peer-joined', e => this._onPeerJoined(e.detail));
- Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId));
- Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
-
- // this device closes connection
- Events.on('room-secrets-deleted', e => this._onRoomSecretsDeleted(e.detail));
- Events.on('leave-public-room', e => this._onLeavePublicRoom(e.detail));
-
- // peer closes connection
- Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail));
-
- Events.on('room-secret-regenerated', e => this._onRoomSecretRegenerated(e.detail));
- Events.on('display-name', e => this._onDisplayName(e.detail.message.displayName));
- Events.on('self-display-name-changed', e => this._notifyPeersDisplayNameChanged(e.detail));
- Events.on('notify-peer-display-name-changed', e => this._notifyPeerDisplayNameChanged(e.detail));
- Events.on('auto-accept-updated', e => this._onAutoAcceptUpdated(e.detail.roomSecret, e.detail.autoAccept));
- Events.on('ws-disconnected', _ => this._onWsDisconnected());
- Events.on('ws-relay', e => this._onWsRelay(e.detail));
- }
-
- _onMessage(message) {
- const peerId = message.sender.id;
- this.peers[peerId].onServerMessage(message);
- }
-
- _refreshPeer(peer, roomType, roomId) {
- if (!peer) return false;
-
- const roomTypesDiffer = Object.keys(peer._roomIds)[0] !== roomType;
- const roomIdsDiffer = peer._roomIds[roomType] !== roomId;
-
- // if roomType or roomId for roomType differs peer is already connected
- // -> only update roomSecret and reevaluate auto accept
- if (roomTypesDiffer || roomIdsDiffer) {
- peer._updateRoomIds(roomType, roomId);
- peer._evaluateAutoAccept();
-
- return true;
- }
-
- return true;
- }
-
- _createOrRefreshPeer(isCaller, peerId, roomType, roomId, rtcSupported) {
- const peer = this.peers[peerId];
- if (peer) {
- this._refreshPeer(peer, roomType, roomId);
- return;
- }
-
- if (window.isRtcSupported && rtcSupported) {
- this.peers[peerId] = new RTCPeer(this._server,isCaller, peerId, roomType, roomId);
- } else {
- this.peers[peerId] = new WSPeer(this._server, isCaller, peerId, roomType, roomId);
- }
- }
-
- _onPeerJoined(message) {
- this._createOrRefreshPeer(false, message.peer.id, message.roomType, message.roomId, message.peer.rtcSupported);
- }
-
- _onPeers(message) {
- message.peers.forEach(peer => {
- this._createOrRefreshPeer(true, peer.id, message.roomType, message.roomId, peer.rtcSupported);
- })
- }
-
- _onWsRelay(message) {
- const messageJSON = JSON.parse(message);
- if (messageJSON.type === 'ws-chunk') message = base64ToArrayBuffer(messageJSON.chunk);
- this.peers[messageJSON.sender.id]._onMessage(message);
- }
-
- _onRespondToFileTransferRequest(detail) {
- this.peers[detail.to]._respondToFileTransferRequest(detail.accepted);
- }
-
- _onFilesSelected(message) {
- let inputFiles = Array.from(message.files);
- delete message.files;
- let files = [];
- const l = inputFiles.length;
- for (let i=0; i {
- if (!peerIds) return;
- console.log("successfully removed other peerIds from localStorage");
- });
- }
- }
- }
-
- _onPeerConnected(peerId) {
- this._notifyPeerDisplayNameChanged(peerId);
- }
-
- _onWsDisconnected() {
- for (const peerId in this.peers) {
- if (this.peers[peerId] && (!this.peers[peerId].rtcSupported || !window.isRtcSupported)) {
- Events.fire('peer-disconnected', peerId);
- }
- }
- }
-
- _onPeerDisconnected(peerId) {
- const peer = this.peers[peerId];
- delete this.peers[peerId];
- if (!peer || !peer._conn) return;
- if (peer._channel) peer._channel.onclose = null;
- peer._conn.close();
- peer._busy = false;
- peer._roomIds = {};
- }
-
- _onRoomSecretsDeleted(roomSecrets) {
- for (let i=0; i 1) {
- peer._removeRoomType(roomType);
- } else {
- Events.fire('peer-disconnected', peerId);
- }
- }
-
- _onRoomSecretRegenerated(message) {
- PersistentStorage.updateRoomSecret(message.oldRoomSecret, message.newRoomSecret).then(_ => {
- console.log("successfully regenerated room secret");
- Events.fire("room-secrets", [message.newRoomSecret]);
- })
- }
-
- _notifyPeersDisplayNameChanged(newDisplayName) {
- this._displayName = newDisplayName ? newDisplayName : this._originalDisplayName;
- for (const peerId in this.peers) {
- this._notifyPeerDisplayNameChanged(peerId);
- }
- }
-
- _notifyPeerDisplayNameChanged(peerId) {
- const peer = this.peers[peerId];
- if (!peer) return;
- this.peers[peerId].sendDisplayName(this._displayName);
- }
-
- _onDisplayName(displayName) {
- this._originalDisplayName = displayName;
- // if the displayName has not been changed (yet) set the displayName to the original displayName
- if (!this._displayName) this._displayName = displayName;
- }
-
- _onAutoAcceptUpdated(roomSecret, autoAccept) {
- const peerId = this._getPeerIdsFromRoomId(roomSecret)[0];
-
- if (!peerId) return;
-
- this.peers[peerId]._setAutoAccept(autoAccept);
- }
-
- _getPeerIdsFromRoomId(roomId) {
- if (!roomId) return [];
-
- let peerIds = []
- for (const peerId in this.peers) {
- const peer = this.peers[peerId];
-
- // peer must have same roomId.
- if (Object.values(peer._roomIds).includes(roomId)) {
- peerIds.push(peer._peerId);
- }
- }
- return peerIds;
- }
-}
-
-class FileChunker {
-
- constructor(file, onChunk, onPartitionEnd) {
- this._chunkSize = 64000; // 64 KB
- this._maxPartitionSize = 1e6; // 1 MB
- this._offset = 0;
- this._partitionSize = 0;
- this._file = file;
- this._onChunk = onChunk;
- this._onPartitionEnd = onPartitionEnd;
- this._reader = new FileReader();
- this._reader.addEventListener('load', e => this._onChunkRead(e.target.result));
- }
-
- nextPartition() {
- this._partitionSize = 0;
- this._readChunk();
- }
-
- _readChunk() {
- const chunk = this._file.slice(this._offset, this._offset + this._chunkSize);
- this._reader.readAsArrayBuffer(chunk);
- }
-
- _onChunkRead(chunk) {
- this._offset += chunk.byteLength;
- this._partitionSize += chunk.byteLength;
- this._onChunk(chunk);
- if (this.isFileEnd()) return;
- if (this._isPartitionEnd()) {
- this._onPartitionEnd(this._offset);
- return;
- }
- this._readChunk();
- }
-
- repeatPartition() {
- this._offset -= this._partitionSize;
- this.nextPartition();
- }
-
- _isPartitionEnd() {
- return this._partitionSize >= this._maxPartitionSize;
- }
-
- isFileEnd() {
- return this._offset >= this._file.size;
- }
-}
-
-class FileDigester {
-
- constructor(meta, totalSize, totalBytesReceived, callback) {
- this._buffer = [];
- this._bytesReceived = 0;
- this._size = meta.size;
- this._name = meta.name;
- this._mime = meta.mime;
- this._totalSize = totalSize;
- this._totalBytesReceived = totalBytesReceived;
- this._callback = callback;
- }
-
- unchunk(chunk) {
- this._buffer.push(chunk);
- this._bytesReceived += chunk.byteLength || chunk.size;
- this.progress = (this._totalBytesReceived + this._bytesReceived) / this._totalSize;
- if (isNaN(this.progress)) this.progress = 1
-
- if (this._bytesReceived < this._size) return;
- // we are done
- const blob = new Blob(this._buffer)
- this._buffer = null;
- this._callback(new File([blob], this._name, {
- type: this._mime,
- lastModified: new Date().getTime()
- }));
- }
-
-}
-
-class Events {
- static fire(type, detail = {}) {
- window.dispatchEvent(new CustomEvent(type, { detail: detail }));
- }
-
- static on(type, callback, options = false) {
- return window.addEventListener(type, callback, options);
- }
-
- static off(type, callback, options = false) {
- return window.removeEventListener(type, callback, options);
- }
-}
diff --git a/public_included_ws_fallback/scripts/theme.js b/public_included_ws_fallback/scripts/theme.js
deleted file mode 100644
index 81cf0b2..0000000
--- a/public_included_ws_fallback/scripts/theme.js
+++ /dev/null
@@ -1,78 +0,0 @@
-(function(){
-
- const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
- const prefersLightTheme = window.matchMedia('(prefers-color-scheme: light)').matches;
-
- const $themeAuto = document.getElementById('theme-auto');
- const $themeLight = document.getElementById('theme-light');
- const $themeDark = document.getElementById('theme-dark');
-
- let currentTheme = localStorage.getItem('theme');
-
- if (currentTheme === 'dark') {
- setModeToDark();
- } else if (currentTheme === 'light') {
- setModeToLight();
- }
-
- $themeAuto.addEventListener('click', _ => {
- if (currentTheme) {
- setModeToAuto();
- } else {
- setModeToDark();
- }
- });
- $themeLight.addEventListener('click', _ => {
- if (currentTheme !== 'light') {
- setModeToLight();
- } else {
- setModeToAuto();
- }
- });
- $themeDark.addEventListener('click', _ => {
- if (currentTheme !== 'dark') {
- setModeToDark();
- } else {
- setModeToLight();
- }
- });
-
- function setModeToDark() {
- document.body.classList.remove('light-theme');
- document.body.classList.add('dark-theme');
- localStorage.setItem('theme', 'dark');
- currentTheme = 'dark';
-
- $themeAuto.classList.remove("selected");
- $themeLight.classList.remove("selected");
- $themeDark.classList.add("selected");
- }
-
- function setModeToLight() {
- document.body.classList.remove('dark-theme');
- document.body.classList.add('light-theme');
- localStorage.setItem('theme', 'light');
- currentTheme = 'light';
-
- $themeAuto.classList.remove("selected");
- $themeLight.classList.add("selected");
- $themeDark.classList.remove("selected");
- }
-
- function setModeToAuto() {
- document.body.classList.remove('dark-theme');
- document.body.classList.remove('light-theme');
- if (prefersDarkTheme) {
- document.body.classList.add('dark-theme');
- } else if (prefersLightTheme) {
- document.body.classList.add('light-theme');
- }
- localStorage.removeItem('theme');
- currentTheme = undefined;
-
- $themeAuto.classList.add("selected");
- $themeLight.classList.remove("selected");
- $themeDark.classList.remove("selected");
- }
-
-})();
diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js
deleted file mode 100644
index 9fe21c7..0000000
--- a/public_included_ws_fallback/scripts/ui.js
+++ /dev/null
@@ -1,2841 +0,0 @@
-const $ = query => document.getElementById(query);
-const $$ = query => document.body.querySelector(query);
-window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-window.android = /android/i.test(navigator.userAgent);
-window.isMobile = window.iOS || window.android;
-window.pasteMode = {};
-window.pasteMode.activated = false;
-
-// set display name
-Events.on('display-name', e => {
- const me = e.detail.message;
- const $displayName = $('display-name');
- $displayName.setAttribute('placeholder', me.displayName);
-});
-
-class PeersUI {
-
- constructor() {
- Events.on('peer-joined', e => this._onPeerJoined(e.detail));
- Events.on('peer-connected', e => this._onPeerConnected(e.detail.peerId, e.detail.connectionHash));
- Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
- Events.on('peers', e => this._onPeers(e.detail));
- Events.on('set-progress', e => this._onSetProgress(e.detail));
- Events.on('paste', e => this._onPaste(e));
- Events.on('room-type-removed', e => this._onRoomTypeRemoved(e.detail.peerId, e.detail.roomType));
- Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text));
- this.peers = {};
-
- this.$cancelPasteModeBtn = $('cancel-paste-mode');
- this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode());
-
- Events.on('dragover', e => this._onDragOver(e));
- Events.on('dragleave', _ => this._onDragEnd());
- Events.on('dragend', _ => this._onDragEnd());
-
- Events.on('drop', e => this._onDrop(e));
- Events.on('keydown', e => this._onKeyDown(e));
-
- this.$xPeers = $$('x-peers');
- this.$xNoPeers = $$('x-no-peers');
- this.$xInstructions = $$('x-instructions');
- this.$center = $$('#center');
- this.$footer = $$('footer');
- this.$discoveryWrapper = $$('footer .discovery-wrapper');
-
- Events.on('peer-added', _ => this._evaluateOverflowing());
- Events.on('bg-resize', _ => this._evaluateOverflowing());
-
- this.$displayName = $('display-name');
-
- this.$displayName.setAttribute("placeholder", this.$displayName.dataset.placeholder);
-
- this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e));
- this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e));
- this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText));
-
- Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail));
- Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
-
- // Load saved display name on page load
- this._getSavedDisplayName().then(displayName => {
- console.log("Retrieved edited display name:", displayName)
- if (displayName) Events.fire('self-display-name-changed', displayName);
- });
-
- Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges())
-
- this.fadedIn = false;
-
- this.$header = document.querySelector('header.opacity-0');
- Events.on('header-evaluated', e => this._fadeInHeader(e.detail));
-
- // wait for evaluation of notification and edit-paired-devices buttons
- this.evaluateHeader = ["notification", "edit-paired-devices"];
-
- if (!('Notification' in window)) this.evaluateHeader.splice(this.evaluateHeader.indexOf("notification"), 1);
- }
-
- _fadeInHeader(id) {
- this.evaluateHeader.splice(this.evaluateHeader.indexOf(id), 1);
- console.log(`Header btn ${id} evaluated. ${this.evaluateHeader.length} to go.`);
-
- if (this.evaluateHeader.length !== 0) return;
-
- this.$header.classList.remove('opacity-0');
- }
-
- _fadeInUI() {
- if (this.fadedIn) return;
-
- this.fadedIn = true;
-
- this.$center.classList.remove('opacity-0');
- this.$footer.classList.remove('opacity-0');
-
- // Prevent flickering on load
- setTimeout(_ => {
- this.$xNoPeers.classList.remove('no-animation-on-load');
- }, 600);
-
- Events.fire('ui-faded-in');
- }
-
- _evaluateFooterBadges() {
- if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) {
- this.$discoveryWrapper.classList.remove('row');
- this.$discoveryWrapper.classList.add('column');
- } else {
- this.$discoveryWrapper.classList.remove('column');
- this.$discoveryWrapper.classList.add('row');
- }
- Events.fire('redraw-canvas');
- this._fadeInUI();
- }
-
- _insertDisplayName(displayName) {
- this.$displayName.textContent = displayName;
- }
-
- _onKeyDownDisplayName(e) {
- if (e.key === "Enter" || e.key === "Escape") {
- e.preventDefault();
- e.target.blur();
- }
- }
-
- _onKeyUpDisplayName(e) {
- // fix for Firefox inserting a linebreak into div on edit which prevents the placeholder from showing automatically when it is empty
- if (/^(\n|\r|\r\n)$/.test(e.target.innerText)) e.target.innerText = '';
- }
-
- async _saveDisplayName(newDisplayName) {
- newDisplayName = newDisplayName.replace(/(\n|\r|\r\n)/, '')
- const savedDisplayName = await this._getSavedDisplayName();
- if (newDisplayName === savedDisplayName) return;
-
- if (newDisplayName) {
- PersistentStorage.set('editedDisplayName', newDisplayName)
- .then(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently"));
- })
- .catch(_ => {
- console.log("This browser does not support IndexedDB. Use localStorage instead.");
- localStorage.setItem('editedDisplayName', newDisplayName);
- Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily"));
- })
- .finally(_ => {
- Events.fire('self-display-name-changed', newDisplayName);
- Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName});
- });
- } else {
- PersistentStorage.delete('editedDisplayName')
- .catch(_ => {
- console.log("This browser does not support IndexedDB. Use localStorage instead.")
- localStorage.removeItem('editedDisplayName');
- })
- .finally(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again"));
- Events.fire('self-display-name-changed', '');
- Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''});
- });
- }
- }
-
- _getSavedDisplayName() {
- return new Promise((resolve) => {
- PersistentStorage.get('editedDisplayName')
- .then(displayName => {
- if (!displayName) displayName = "";
- resolve(displayName);
- })
- .catch(_ => {
- let displayName = localStorage.getItem('editedDisplayName');
- if (!displayName) displayName = "";
- resolve(displayName);
- })
- });
- }
-
- _changePeerDisplayName(peerId, displayName) {
- this.peers[peerId].name.displayName = displayName;
- const peerIdNode = $(peerId);
- if (peerIdNode && displayName) peerIdNode.querySelector('.name').textContent = displayName;
- this._redrawPeerRoomTypes(peerId);
- }
-
- _onPeerDisplayNameChanged(e) {
- if (!e.detail.displayName) return;
- this._changePeerDisplayName(e.detail.peerId, e.detail.displayName);
- }
-
- _onKeyDown(e) {
- if (document.querySelectorAll('x-dialog[show]').length === 0 && window.pasteMode.activated && e.code === "Escape") {
- Events.fire('deactivate-paste-mode');
- }
-
- // close About PairDrop page on Escape
- if (e.key === "Escape") {
- window.location.hash = '#';
- }
- }
-
- _onPeerJoined(msg) {
- this._joinPeer(msg.peer, msg.roomType, msg.roomId);
- }
-
- _joinPeer(peer, roomType, roomId) {
- const existingPeer = this.peers[peer.id];
- if (existingPeer) {
- // peer already exists. Abort but add roomType to GUI
- existingPeer._roomIds[roomType] = roomId;
- this._redrawPeerRoomTypes(peer.id);
- return;
- }
-
- peer._isSameBrowser = _ => BrowserTabsConnector.peerIsSameBrowser(peer.id);
- peer._roomIds = {};
-
- peer._roomIds[roomType] = roomId;
- this.peers[peer.id] = peer;
- }
-
- _onPeerConnected(peerId, connectionHash) {
- if (!this.peers[peerId] || $(peerId)) return;
-
- const peer = this.peers[peerId];
-
- new PeerUI(peer, connectionHash);
- }
-
- _redrawPeerRoomTypes(peerId) {
- const peer = this.peers[peerId];
- const peerNode = $(peerId);
-
- if (!peer || !peerNode) return;
-
- peerNode.classList.remove('type-ip', 'type-secret', 'type-public-id', 'type-same-browser');
-
- if (peer._isSameBrowser()) {
- peerNode.classList.add(`type-same-browser`);
- }
-
- Object.keys(peer._roomIds).forEach(roomType => peerNode.classList.add(`type-${roomType}`));
- }
-
- _evaluateOverflowing() {
- if (this.$xPeers.clientHeight < this.$xPeers.scrollHeight) {
- this.$xPeers.classList.add('overflowing');
- } else {
- this.$xPeers.classList.remove('overflowing');
- }
- }
-
- _onPeers(msg) {
- msg.peers.forEach(peer => this._joinPeer(peer, msg.roomType, msg.roomId));
- }
-
- _onPeerDisconnected(peerId) {
- const $peer = $(peerId);
- if (!$peer) return;
- $peer.remove();
- this._evaluateOverflowing();
- }
-
- _onRoomTypeRemoved(peerId, roomType) {
- const peer = this.peers[peerId];
-
- if (!peer) return;
-
- delete peer._roomIds[roomType];
-
- this._redrawPeerRoomTypes(peerId)
- }
-
- _onSetProgress(progress) {
- const $peer = $(progress.peerId);
- if (!$peer) return;
- $peer.ui.setProgress(progress.progress, progress.status)
- }
-
- _onDrop(e) {
- e.preventDefault();
- if (!$$('x-peer') || !$$('x-peer').contains(e.target)) {
- this._activatePasteMode(e.dataTransfer.files, '')
- }
- this._onDragEnd();
- }
-
- _onDragOver(e) {
- e.preventDefault();
- this.$xInstructions.setAttribute('drop-bg', 1);
- this.$xNoPeers.setAttribute('drop-bg', 1);
- }
-
- _onDragEnd() {
- this.$xInstructions.removeAttribute('drop-bg', 1);
- this.$xNoPeers.removeAttribute('drop-bg');
- }
-
- _onPaste(e) {
- if(document.querySelectorAll('x-dialog[show]').length === 0) {
- // prevent send on paste when dialog is open
- e.preventDefault()
- const files = e.clipboardData.files;
- const text = e.clipboardData.getData("Text");
- if (files.length === 0 && text.length === 0) return;
- this._activatePasteMode(files, text);
- }
- }
-
- _activatePasteMode(files, text) {
- if (!window.pasteMode.activated && (files.length > 0 || text.length > 0)) {
- const openPairDrop = Localization.getTranslation("instructions.activate-paste-mode-base");
- const andOtherFiles = Localization.getTranslation("instructions.activate-paste-mode-and-other-files", null, {count: files.length-1});
- const sharedText = Localization.getTranslation("instructions.activate-paste-mode-shared-text");
- const clickToSend = Localization.getTranslation("instructions.click-to-send")
- const tapToSend = Localization.getTranslation("instructions.tap-to-send")
-
- let descriptor;
-
- if (files.length === 1) {
- descriptor = `${files[0].name} `;
- } else if (files.length > 1) {
- descriptor = `${files[0].name} ${andOtherFiles}`;
- } else {
- descriptor = sharedText;
- }
-
- this.$xInstructions.querySelector('p').innerHTML = `${descriptor} `;
- this.$xInstructions.querySelector('p').style.display = 'block';
- this.$xInstructions.setAttribute('desktop', clickToSend);
- this.$xInstructions.setAttribute('mobile', tapToSend);
-
- this.$xNoPeers.querySelector('h2').innerHTML = `${openPairDrop} ${descriptor}`;
-
- const _callback = (e) => this._sendClipboardData(e, files, text);
- Events.on('paste-pointerdown', _callback);
- Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true });
-
- this.$cancelPasteModeBtn.removeAttribute('hidden');
-
- window.pasteMode.descriptor = descriptor;
- window.pasteMode.activated = true;
-
- console.log('Paste mode activated.');
- Events.fire('paste-mode-changed');
- }
- }
-
- _cancelPasteMode() {
- Events.fire('deactivate-paste-mode');
- }
-
- _deactivatePasteMode(_callback) {
- if (window.pasteMode.activated) {
- window.pasteMode.descriptor = undefined;
- window.pasteMode.activated = false;
- Events.off('paste-pointerdown', _callback);
-
- this.$xInstructions.querySelector('p').innerText = '';
- this.$xInstructions.querySelector('p').style.display = 'none';
-
- this.$xInstructions.setAttribute('desktop', Localization.getTranslation("instructions.x-instructions", "desktop"));
- this.$xInstructions.setAttribute('mobile', Localization.getTranslation("instructions.x-instructions", "mobile"));
-
- this.$xNoPeers.querySelector('h2').innerHTML = Localization.getTranslation("instructions.no-peers-title");
-
- this.$cancelPasteModeBtn.setAttribute('hidden', "");
-
- console.log('Paste mode deactivated.')
- Events.fire('paste-mode-changed');
- }
- }
-
- _sendClipboardData(e, files, text) {
- // send the pasted file/text content
- const peerId = e.detail.peerId;
-
- if (files.length > 0) {
- Events.fire('files-selected', {
- files: files,
- to: peerId
- });
- } else if (text.length > 0) {
- Events.fire('send-text', {
- text: text,
- to: peerId
- });
- }
- }
-}
-
-class PeerUI {
-
- constructor(peer, connectionHash) {
- this._peer = peer;
- this._connectionHash =
- `${connectionHash.substring(0, 4)} ${connectionHash.substring(4, 8)} ${connectionHash.substring(8, 12)} ${connectionHash.substring(12, 16)}`;
- this._initDom();
- this._bindListeners();
-
- $$('x-peers').appendChild(this.$el)
- Events.fire('peer-added');
- this.$xInstructions = $$('x-instructions');
- }
-
- html() {
- let title;
- let input = '';
- if (window.pasteMode.activated) {
- title = Localization.getTranslation("peer-ui.click-to-send-paste-mode", null, {descriptor: window.pasteMode.descriptor});
- } else {
- title = Localization.getTranslation("peer-ui.click-to-send");
- input = ' ';
- }
- this.$el.innerHTML = `
-
- ${input}
-
-
-
-
-
-
-
-
- `;
-
- this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon());
- this.$el.querySelector('.name').textContent = this._displayName();
- this.$el.querySelector('.device-name').textContent = this._deviceName();
- this.$el.querySelector('.connection-hash').textContent = this._connectionHash;
- }
-
- addTypesToClassList() {
- if (this._peer._isSameBrowser()) {
- this.$el.classList.add(`type-same-browser`);
- }
-
- Object.keys(this._peer._roomIds).forEach(roomType => this.$el.classList.add(`type-${roomType}`));
-
- if (!this._peer.rtcSupported || !window.isRtcSupported) this.$el.classList.add('ws-peer');
- }
-
- _initDom() {
- this.$el = document.createElement('x-peer');
- this.$el.id = this._peer.id;
- this.$el.ui = this;
- this.$el.classList.add('center');
-
- this.addTypesToClassList();
-
- this.html();
-
- this._callbackInput = e => this._onFilesSelected(e)
- this._callbackClickSleep = _ => NoSleepUI.enable()
- this._callbackTouchStartSleep = _ => NoSleepUI.enable()
- this._callbackDrop = e => this._onDrop(e)
- this._callbackDragEnd = e => this._onDragEnd(e)
- this._callbackDragLeave = e => this._onDragEnd(e)
- this._callbackDragOver = e => this._onDragOver(e)
- this._callbackContextMenu = e => this._onRightClick(e)
- this._callbackTouchStart = e => this._onTouchStart(e)
- this._callbackTouchEnd = e => this._onTouchEnd(e)
- this._callbackPointerDown = e => this._onPointerDown(e)
-
- // PasteMode
- Events.on('paste-mode-changed', _ => this._onPasteModeChanged());
- }
-
- _onPasteModeChanged() {
- this.html();
- this._bindListeners();
- }
-
- _bindListeners() {
- if(!window.pasteMode.activated) {
- // Remove Events Paste Mode
- this.$el.removeEventListener('pointerdown', this._callbackPointerDown);
-
- // Add Events Normal Mode
- this.$el.querySelector('input').addEventListener('change', this._callbackInput);
- this.$el.addEventListener('click', this._callbackClickSleep);
- this.$el.addEventListener('touchstart', this._callbackTouchStartSleep);
- this.$el.addEventListener('drop', this._callbackDrop);
- this.$el.addEventListener('dragend', this._callbackDragEnd);
- this.$el.addEventListener('dragleave', this._callbackDragLeave);
- this.$el.addEventListener('dragover', this._callbackDragOver);
- this.$el.addEventListener('contextmenu', this._callbackContextMenu);
- this.$el.addEventListener('touchstart', this._callbackTouchStart);
- this.$el.addEventListener('touchend', this._callbackTouchEnd);
- } else {
- // Remove Events Normal Mode
- this.$el.removeEventListener('click', this._callbackClickSleep);
- this.$el.removeEventListener('touchstart', this._callbackTouchStartSleep);
- this.$el.removeEventListener('drop', this._callbackDrop);
- this.$el.removeEventListener('dragend', this._callbackDragEnd);
- this.$el.removeEventListener('dragleave', this._callbackDragLeave);
- this.$el.removeEventListener('dragover', this._callbackDragOver);
- this.$el.removeEventListener('contextmenu', this._callbackContextMenu);
- this.$el.removeEventListener('touchstart', this._callbackTouchStart);
- this.$el.removeEventListener('touchend', this._callbackTouchEnd);
-
- // Add Events Paste Mode
- this.$el.addEventListener('pointerdown', this._callbackPointerDown);
- }
- }
-
- _onPointerDown(e) {
- // Prevents triggering of event twice on touch devices
- e.stopPropagation();
- e.preventDefault();
- Events.fire('paste-pointerdown', {
- peerId: this._peer.id
- });
- }
-
- _displayName() {
- return this._peer.name.displayName;
- }
-
- _deviceName() {
- return this._peer.name.deviceName;
- }
-
- _badgeClassName() {
- const roomTypes = Object.keys(this._peer._roomIds);
- return roomTypes.includes('secret')
- ? 'badge-room-secret'
- : roomTypes.includes('ip')
- ? 'badge-room-ip'
- : 'badge-room-public-id';
- }
-
- _icon() {
- const device = this._peer.name.device || this._peer.name;
- if (device.type === 'mobile') {
- return '#phone-iphone';
- }
- if (device.type === 'tablet') {
- return '#tablet-mac';
- }
- return '#desktop-mac';
- }
-
- _onFilesSelected(e) {
- const $input = e.target;
- const files = $input.files;
- Events.fire('files-selected', {
- files: files,
- to: this._peer.id
- });
- $input.files = null; // reset input
- }
-
- setProgress(progress, status) {
- const $progress = this.$el.querySelector('.progress');
- if (0.5 < progress && progress < 1) {
- $progress.classList.add('over50');
- } else {
- $progress.classList.remove('over50');
- }
- if (progress < 1) {
- if (status !== this.currentStatus) {
- let statusName = {
- "prepare": Localization.getTranslation("peer-ui.preparing"),
- "transfer": Localization.getTranslation("peer-ui.transferring"),
- "process": Localization.getTranslation("peer-ui.processing"),
- "wait": Localization.getTranslation("peer-ui.waiting")
- }[status];
-
- this.$el.setAttribute('status', status);
- this.$el.querySelector('.status').innerText = statusName;
- this.currentStatus = status;
- }
- } else {
- this.$el.removeAttribute('status');
- this.$el.querySelector('.status').innerHTML = '';
- progress = 0;
- this.currentStatus = null;
- }
- const degrees = `rotate(${360 * progress}deg)`;
- $progress.style.setProperty('--progress', degrees);
- }
-
- _onDrop(e) {
- e.preventDefault();
- Events.fire('files-selected', {
- files: e.dataTransfer.files,
- to: this._peer.id
- });
- this._onDragEnd();
- }
-
- _onDragOver() {
- this.$el.setAttribute('drop', 1);
- this.$xInstructions.setAttribute('drop-peer', 1);
- }
-
- _onDragEnd() {
- this.$el.removeAttribute('drop');
- this.$xInstructions.removeAttribute('drop-peer', 1);
- }
-
- _onRightClick(e) {
- e.preventDefault();
- Events.fire('text-recipient', {
- peerId: this._peer.id,
- deviceName: e.target.closest('x-peer').querySelector('.name').innerText
- });
- }
-
- _onTouchStart(e) {
- this._touchStart = Date.now();
- this._touchTimer = setTimeout(_ => this._onTouchEnd(e), 610);
- }
-
- _onTouchEnd(e) {
- if (Date.now() - this._touchStart < 500) {
- clearTimeout(this._touchTimer);
- } else if (this._touchTimer) { // this was a long tap
- e.preventDefault();
- Events.fire('text-recipient', {
- peerId: this._peer.id,
- deviceName: e.target.closest('x-peer').querySelector('.name').innerText
- });
- }
- this._touchTimer = null;
- }
-}
-
-class Dialog {
- constructor(id) {
- this.$el = $(id);
- this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', _ => this.hide()));
- this.$autoFocus = this.$el.querySelector('[autofocus]');
-
- Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
- }
-
- show() {
- this.$el.setAttribute('show', 1);
- if (!window.isMobile && this.$autoFocus) this.$autoFocus.focus();
- }
-
- isShown() {
- return !!this.$el.attributes["show"];
- }
-
- hide() {
- this.$el.removeAttribute('show');
- if (!window.isMobile && this.$autoFocus) {
- document.activeElement.blur();
- window.blur();
- }
- document.title = 'PairDrop';
- changeFavicon("images/favicon-96x96.png");
- this.correspondingPeerId = undefined;
- }
-
- _onPeerDisconnected(peerId) {
- if (this.isShown() && this.correspondingPeerId === peerId) {
- this.hide();
- Events.fire('notify-user', Localization.getTranslation("notifications.selected-peer-left"));
- }
- }
-}
-
-class LanguageSelectDialog extends Dialog {
-
- constructor() {
- super('language-select-dialog');
-
- this.$languageSelectBtn = $('language-selector');
- this.$languageSelectBtn.addEventListener('click', _ => this.show());
-
- this.$languageButtons = this.$el.querySelectorAll(".language-buttons button");
- this.$languageButtons.forEach($btn => {
- $btn.addEventListener("click", e => this.selectLanguage(e));
- })
- Events.on('keydown', e => this._onKeyDown(e));
- }
-
- _onKeyDown(e) {
- if (this.isShown() && e.code === "Escape") {
- this.hide();
- }
- }
-
- show() {
- if (Localization.isSystemLocale()) {
- this.$languageButtons[0].focus();
- } else {
- let locale = Localization.getLocale();
- for (let i=0; i this.hide());
- }
-}
-
-class ReceiveDialog extends Dialog {
- constructor(id) {
- super(id);
- this.$fileDescription = this.$el.querySelector('.file-description');
- this.$displayName = this.$el.querySelector('.display-name');
- this.$fileStem = this.$el.querySelector('.file-stem');
- this.$fileExtension = this.$el.querySelector('.file-extension');
- this.$fileOther = this.$el.querySelector('.file-other');
- this.$fileSize = this.$el.querySelector('.file-size');
- this.$previewBox = this.$el.querySelector('.file-preview');
- this.$receiveTitle = this.$el.querySelector('h2:first-of-type');
- }
-
- _formatFileSize(bytes) {
- // 1 GB = 1024 MB = 1024^2 KB = 1024^3 B
- // 1024^2 = 104876; 1024^3 = 1073741824
- if (bytes >= 1073741824) {
- return Math.round(10 * bytes / 1073741824) / 10 + ' GB';
- } else if (bytes >= 1048576) {
- return Math.round(bytes / 1048576) + ' MB';
- } else if (bytes > 1024) {
- return Math.round(bytes / 1024) + ' KB';
- } else {
- return bytes + ' Bytes';
- }
- }
-
- _parseFileData(displayName, connectionHash, files, imagesOnly, totalSize, badgeClassName) {
- let fileOther = "";
-
- if (files.length === 2) {
- fileOther = imagesOnly
- ? Localization.getTranslation("dialogs.file-other-description-image")
- : Localization.getTranslation("dialogs.file-other-description-file");
- } else if (files.length >= 2) {
- fileOther = imagesOnly
- ? Localization.getTranslation("dialogs.file-other-description-image-plural", null, {count: files.length - 1})
- : Localization.getTranslation("dialogs.file-other-description-file-plural", null, {count: files.length - 1});
- }
-
- this.$fileOther.innerText = fileOther;
-
- const fileName = files[0].name;
- const fileNameSplit = fileName.split('.');
- const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
- this.$fileStem.innerText = fileName.substring(0, fileName.length - fileExtension.length);
- this.$fileExtension.innerText = fileExtension;
- this.$fileSize.innerText = this._formatFileSize(totalSize);
- this.$displayName.innerText = displayName;
- this.$displayName.title = connectionHash;
- this.$displayName.classList.remove("badge-room-ip", "badge-room-secret", "badge-room-public-id");
- this.$displayName.classList.add(badgeClassName)
- }
-}
-
-class ReceiveFileDialog extends ReceiveDialog {
-
- constructor() {
- super('receive-file-dialog');
-
- this.$downloadBtn = this.$el.querySelector('#download-btn');
- this.$shareBtn = this.$el.querySelector('#share-btn');
-
- Events.on('files-received', e => this._onFilesReceived(e.detail.peerId, e.detail.files, e.detail.imagesOnly, e.detail.totalSize));
- this._filesQueue = [];
- }
-
- _onFilesReceived(peerId, files, imagesOnly, totalSize) {
- const displayName = $(peerId).ui._displayName();
- const connectionHash = $(peerId).ui._connectionHash;
- const badgeClassName = $(peerId).ui._badgeClassName();
-
- this._filesQueue.push({
- peerId: peerId,
- displayName: displayName,
- connectionHash: connectionHash,
- files: files,
- imagesOnly: imagesOnly,
- totalSize: totalSize,
- badgeClassName: badgeClassName
- });
-
- this._nextFiles();
-
- window.blop.play();
- }
-
- _nextFiles() {
- if (this._busy) return;
- this._busy = true;
- const {peerId, displayName, connectionHash, files, imagesOnly, totalSize, badgeClassName} = this._filesQueue.shift();
- this._displayFiles(peerId, displayName, connectionHash, files, imagesOnly, totalSize, badgeClassName);
- }
-
- _dequeueFile() {
- if (!this._filesQueue.length) { // nothing to do
- this._busy = false;
- return;
- }
- // dequeue next file
- setTimeout(_ => {
- this._busy = false;
- this._nextFiles();
- }, 300);
- }
-
- createPreviewElement(file) {
- return new Promise((resolve, reject) => {
- try {
- let mime = file.type.split('/')[0]
- let previewElement = {
- image: 'img',
- audio: 'audio',
- video: 'video'
- }
-
- if (Object.keys(previewElement).indexOf(mime) === -1) {
- resolve(false);
- } else {
- let element = document.createElement(previewElement[mime]);
- element.controls = true;
- element.onload = _ => {
- this.$previewBox.appendChild(element);
- resolve(true);
- };
- element.onloadeddata = _ => {
- this.$previewBox.appendChild(element);
- resolve(true);
- };
- element.onerror = _ => {
- reject(`${mime} preview could not be loaded from type ${file.type}`);
- };
- element.src = URL.createObjectURL(file);
- }
- } catch (e) {
- reject(`preview could not be loaded from type ${file.type}`);
- }
- });
- }
-
- async _displayFiles(peerId, displayName, connectionHash, files, imagesOnly, totalSize, badgeClassName) {
- this._parseFileData(displayName, connectionHash, files, imagesOnly, totalSize, badgeClassName);
-
- let descriptor, url, filenameDownload;
- if (files.length === 1) {
- descriptor = imagesOnly
- ? Localization.getTranslation("dialogs.title-image")
- : Localization.getTranslation("dialogs.title-file");
- } else {
- descriptor = imagesOnly
- ? Localization.getTranslation("dialogs.title-image-plural")
- : Localization.getTranslation("dialogs.title-file-plural");
- }
- this.$receiveTitle.innerText = Localization.getTranslation("dialogs.receive-title", null, {descriptor: descriptor});
-
- const canShare = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
- if (canShare) {
- this.$shareBtn.removeAttribute('hidden');
- this.$shareBtn.onclick = _ => {
- navigator.share({files: files})
- .catch(err => {
- console.error(err);
- });
- }
- }
-
- let downloadZipped = false;
- if (files.length > 1) {
- downloadZipped = true;
- try {
- let bytesCompleted = 0;
- zipper.createNewZipWriter();
- for (let i=0; i {
- Events.fire('set-progress', {
- peerId: peerId,
- progress: (bytesCompleted + progress) / totalSize,
- status: 'process'
- })
- }
- });
- bytesCompleted += files[i].size;
- }
- url = await zipper.getBlobURL();
-
- let now = new Date(Date.now());
- let year = now.getFullYear().toString();
- let month = (now.getMonth()+1).toString();
- month = month.length < 2 ? "0" + month : month;
- let date = now.getDate().toString();
- date = date.length < 2 ? "0" + date : date;
- let hours = now.getHours().toString();
- hours = hours.length < 2 ? "0" + hours : hours;
- let minutes = now.getMinutes().toString();
- minutes = minutes.length < 2 ? "0" + minutes : minutes;
- filenameDownload = `PairDrop_files_${year+month+date}_${hours+minutes}.zip`;
- } catch (e) {
- console.error(e);
- downloadZipped = false;
- }
- }
-
- this.$downloadBtn.innerText = Localization.getTranslation("dialogs.download");
- this.$downloadBtn.onclick = _ => {
- if (downloadZipped) {
- let tmpZipBtn = document.createElement("a");
- tmpZipBtn.download = filenameDownload;
- tmpZipBtn.href = url;
- tmpZipBtn.click();
- } else {
- this._downloadFilesIndividually(files);
- }
-
- if (!canShare) {
- this.$downloadBtn.innerText = Localization.getTranslation("dialogs.download-again");
- }
- Events.fire('notify-user', Localization.getTranslation("notifications.download-successful", null, {descriptor: descriptor}));
- this.$downloadBtn.style.pointerEvents = "none";
- setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
- };
-
- document.title = files.length === 1
- ? `${ Localization.getTranslation("document-titles.file-received") } - PairDrop`
- : `${ Localization.getTranslation("document-titles.file-received-plural", null, {count: files.length}) } - PairDrop`;
- changeFavicon("images/favicon-96x96-notification.png");
-
- Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
- this.show();
-
- setTimeout(_ => {
- if (canShare) {
- this.$shareBtn.click();
- } else {
- this.$downloadBtn.click();
- }
- }, 500);
-
- this.createPreviewElement(files[0])
- .then(canPreview => {
- if (canPreview) {
- console.log('the file is able to preview');
- } else {
- console.log('the file is not able to preview');
- }
- })
- .catch(r => console.error(r));
- }
-
- _downloadFilesIndividually(files) {
- let tmpBtn = document.createElement("a");
- for (let i=0; i this._respondToFileTransferRequest(true));
- this.$declineRequestBtn.addEventListener('click', _ => this._respondToFileTransferRequest(false));
-
- Events.on('files-transfer-request', e => this._onRequestFileTransfer(e.detail.request, e.detail.peerId))
- Events.on('keydown', e => this._onKeyDown(e));
- this._filesTransferRequestQueue = [];
- }
-
- _onKeyDown(e) {
- if (this.isShown() && e.code === "Escape") {
- this._respondToFileTransferRequest(false);
- }
- }
-
- _onRequestFileTransfer(request, peerId) {
- this._filesTransferRequestQueue.push({request: request, peerId: peerId});
- if (this.isShown()) return;
- this._dequeueRequests();
- }
-
- _dequeueRequests() {
- if (!this._filesTransferRequestQueue.length) return;
- let {request, peerId} = this._filesTransferRequestQueue.shift();
- this._showRequestDialog(request, peerId)
- }
-
- _showRequestDialog(request, peerId) {
- this.correspondingPeerId = peerId;
-
- const displayName = $(peerId).ui._displayName();
- const connectionHash = $(peerId).ui._connectionHash;
-
- const badgeClassName = $(peerId).ui._badgeClassName();
-
- this._parseFileData(displayName, connectionHash, request.header, request.imagesOnly, request.totalSize, badgeClassName);
-
- if (request.thumbnailDataUrl && request.thumbnailDataUrl.substring(0, 22) === "data:image/jpeg;base64") {
- let element = document.createElement('img');
- element.src = request.thumbnailDataUrl;
- this.$previewBox.appendChild(element)
- }
-
- const transferRequestTitle= request.imagesOnly
- ? Localization.getTranslation('document-titles.image-transfer-requested')
- : Localization.getTranslation('document-titles.file-transfer-requested');
-
- this.$receiveTitle.innerText = transferRequestTitle;
-
- document.title = `${transferRequestTitle} - PairDrop`;
- changeFavicon("images/favicon-96x96-notification.png");
- this.show();
- }
-
- _respondToFileTransferRequest(accepted) {
- Events.fire('respond-to-files-transfer-request', {
- to: this.correspondingPeerId,
- accepted: accepted
- })
- if (accepted) {
- Events.fire('set-progress', {peerId: this.correspondingPeerId, progress: 0, status: 'wait'});
- NoSleepUI.enable();
- }
- this.hide();
- }
-
- hide() {
- // clear previewBox after dialog is closed
- setTimeout(_ => this.$previewBox.innerHTML = '', 300);
-
- super.hide();
-
- // show next request
- setTimeout(_ => this._dequeueRequests(), 500);
- }
-}
-
-class InputKeyContainer {
- constructor(inputKeyContainer, evaluationRegex, onAllCharsFilled, onNoAllCharsFilled, onLastCharFilled) {
-
- this.$inputKeyContainer = inputKeyContainer;
- this.$inputKeyChars = inputKeyContainer.querySelectorAll('input');
-
- this.$inputKeyChars.forEach(char => char.addEventListener('input', e => this._onCharsInput(e)));
- this.$inputKeyChars.forEach(char => char.addEventListener('keydown', e => this._onCharsKeyDown(e)));
- this.$inputKeyChars.forEach(char => char.addEventListener('keyup', e => this._onCharsKeyUp(e)));
- this.$inputKeyChars.forEach(char => char.addEventListener('focus', e => e.target.select()));
- this.$inputKeyChars.forEach(char => char.addEventListener('click', e => e.target.select()));
-
- this.evalRgx = evaluationRegex
-
- this._onAllCharsFilled = onAllCharsFilled;
- this._onNotAllCharsFilled = onNoAllCharsFilled;
- this._onLastCharFilled = onLastCharFilled;
- }
-
- _enableChars() {
- this.$inputKeyChars.forEach(char => char.removeAttribute("disabled"));
- }
-
- _disableChars() {
- this.$inputKeyChars.forEach(char => char.setAttribute("disabled", ""));
- }
-
- _clearChars() {
- this.$inputKeyChars.forEach(char => char.value = '');
- }
-
- _cleanUp() {
- this._clearChars();
- this._disableChars();
- }
-
- _onCharsInput(e) {
- if (!e.target.value.match(this.evalRgx)) {
- e.target.value = '';
- return;
- }
- this._evaluateKeyChars();
-
- let nextSibling = e.target.nextElementSibling;
- if (nextSibling) {
- e.preventDefault();
- nextSibling.focus();
- }
- }
-
- _onCharsKeyDown(e) {
- let previousSibling = e.target.previousElementSibling;
- let nextSibling = e.target.nextElementSibling;
- if (e.key === "Backspace" && previousSibling && !e.target.value) {
- previousSibling.value = '';
- previousSibling.focus();
- } else if (e.key === "ArrowRight" && nextSibling) {
- e.preventDefault();
- nextSibling.focus();
- } else if (e.key === "ArrowLeft" && previousSibling) {
- e.preventDefault();
- previousSibling.focus();
- }
- }
-
- _onCharsKeyUp(e) {
- // deactivate submit btn when e.g. using backspace to clear element
- if (!e.target.value) {
- this._evaluateKeyChars();
- }
- }
-
- _getInputKey() {
- let key = "";
- this.$inputKeyChars.forEach(char => {
- key += char.value;
- })
- return key;
- }
-
- _onPaste(pastedKey) {
- let rgx = new RegExp("(?!" + this.evalRgx.source + ").", "g");
- pastedKey = pastedKey.replace(rgx,'').substring(0, this.$inputKeyChars.length)
- for (let i = 0; i < pastedKey.length; i++) {
- document.activeElement.value = pastedKey.charAt(i);
- let nextSibling = document.activeElement.nextElementSibling;
- if (!nextSibling) break;
- nextSibling.focus();
- }
- this._evaluateKeyChars();
- }
-
- _evaluateKeyChars() {
- if (this.$inputKeyContainer.querySelectorAll('input:placeholder-shown').length > 0) {
- this._onNotAllCharsFilled();
- } else {
- this._onAllCharsFilled();
-
- const lastCharFocused = document.activeElement === this.$inputKeyChars[this.$inputKeyChars.length - 1];
- if (lastCharFocused) {
- this._onLastCharFilled();
- }
- }
- }
-
- focusLastChar() {
- let lastChar = this.$inputKeyChars[this.$inputKeyChars.length-1];
- lastChar.focus();
- }
-}
-
-class PairDeviceDialog extends Dialog {
- constructor() {
- super('pair-device-dialog');
- this.$pairDeviceHeaderBtn = $('pair-device');
- this.$editPairedDevicesHeaderBtn = $('edit-paired-devices');
- this.$footerInstructionsPairedDevices = $$('.discovery-wrapper .badge-room-secret');
-
- this.$key = this.$el.querySelector('.key');
- this.$qrCode = this.$el.querySelector('.key-qr-code');
- this.$form = this.$el.querySelector('form');
- this.$closeBtn = this.$el.querySelector('[close]')
- this.$pairSubmitBtn = this.$el.querySelector('button[type="submit"]');
-
- this.inputKeyContainer = new InputKeyContainer(
- this.$el.querySelector('.input-key-container'),
- /\d/,
- () => this.$pairSubmitBtn.removeAttribute("disabled"),
- () => this.$pairSubmitBtn.setAttribute("disabled", ""),
- () => this._submit()
- );
-
- this.$pairDeviceHeaderBtn.addEventListener('click', _ => this._pairDeviceInitiate());
- this.$form.addEventListener('submit', e => this._onSubmit(e));
- this.$closeBtn.addEventListener('click', _ => this._close());
-
- Events.on('keydown', e => this._onKeyDown(e));
- Events.on('ws-disconnected', _ => this.hide());
- Events.on('pair-device-initiated', e => this._onPairDeviceInitiated(e.detail));
- Events.on('pair-device-joined', e => this._onPairDeviceJoined(e.detail.peerId, e.detail.roomSecret));
- Events.on('peers', e => this._onPeers(e.detail));
- Events.on('peer-joined', e => this._onPeerJoined(e.detail));
- Events.on('pair-device-join-key-invalid', _ => this._onPublicRoomJoinKeyInvalid());
- Events.on('pair-device-canceled', e => this._onPairDeviceCanceled(e.detail));
- Events.on('evaluate-number-room-secrets', _ => this._evaluateNumberRoomSecrets())
- Events.on('secret-room-deleted', e => this._onSecretRoomDeleted(e.detail));
- this.$el.addEventListener('paste', e => this._onPaste(e));
- this.$qrCode.addEventListener('click', _ => this._copyPairUrl());
-
- this.evaluateUrlAttributes();
-
- this.pairPeer = {};
-
- this._evaluateNumberRoomSecrets();
- }
-
- _onKeyDown(e) {
- if (this.isShown() && e.code === "Escape") {
- // Timeout to prevent paste mode from getting cancelled simultaneously
- setTimeout(_ => this._close(), 50);
- }
- }
-
- _onPaste(e) {
- e.preventDefault();
- let pastedKey = e.clipboardData.getData("Text").replace(/\D/g,'').substring(0, 6);
- this.inputKeyContainer._onPaste(pastedKey);
- }
-
- evaluateUrlAttributes() {
- const urlParams = new URLSearchParams(window.location.search);
- if (urlParams.has('pair_key')) {
- this._pairDeviceJoin(urlParams.get('pair_key'));
- const url = getUrlWithoutArguments();
- window.history.replaceState({}, "Rewrite URL", url); //remove pair_key from url
- }
- }
-
- _pairDeviceInitiate() {
- Events.fire('pair-device-initiate');
- }
-
- _onPairDeviceInitiated(msg) {
- this.pairKey = msg.pairKey;
- this.roomSecret = msg.roomSecret;
- this.$key.innerText = `${this.pairKey.substring(0,3)} ${this.pairKey.substring(3,6)}`
- // Display the QR code for the url
- const qr = new QRCode({
- content: this._getPairUrl(),
- width: 150,
- height: 150,
- padding: 1,
- background: 'rgb(250,250,250)',
- color: 'rgb(18, 18, 18)',
- ecl: "L",
- join: true
- });
- this.$qrCode.innerHTML = qr.svg();
- this.inputKeyContainer._enableChars();
- this.show();
- }
-
- _getPairUrl() {
- let url = new URL(location.href);
- url.searchParams.append('pair_key', this.pairKey)
- return url.href;
- }
-
- _copyPairUrl() {
- navigator.clipboard.writeText(this._getPairUrl())
- .then(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.pair-url-copied-to-clipboard"));
- })
- .catch(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard-error"));
- })
- }
-
- _onSubmit(e) {
- e.preventDefault();
- this._submit();
- }
-
- _submit() {
- let inputKey = this.inputKeyContainer._getInputKey();
- this._pairDeviceJoin(inputKey);
- }
-
- _pairDeviceJoin(pairKey) {
- if (/^\d{6}$/g.test(pairKey)) {
- Events.fire('pair-device-join', pairKey);
- this.inputKeyContainer.focusLastChar();
- }
- }
-
- _onPairDeviceJoined(peerId, roomSecret) {
- // abort if peer is another tab on the same browser and remove room-type from gui
- if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
- this._cleanUp();
- this.hide();
-
- Events.fire('room-secrets-deleted', [roomSecret]);
-
- Events.fire('notify-user', Localization.getTranslation("notifications.pairing-tabs-error"));
- return;
- }
-
- // save pairPeer and wait for it to connect to ensure both devices have gotten the roomSecret
- this.pairPeer = {
- "peerId": peerId,
- "roomSecret": roomSecret
- };
- }
-
- _onPeers(message) {
- message.peers.forEach(messagePeer => {
- this._evaluateJoinedPeer(messagePeer.id, message.roomType, message.roomId);
- });
- }
-
- _onPeerJoined(message) {
- this._evaluateJoinedPeer(message.peer.id, message.roomType, message.roomId);
- }
-
- _evaluateJoinedPeer(peerId, roomType, roomId) {
- const noPairPeerSaved = !Object.keys(this.pairPeer);
-
- if (!peerId || !roomType || !roomId || noPairPeerSaved) return;
-
- const samePeerId = peerId === this.pairPeer.peerId;
- const sameRoomSecret = roomId === this.pairPeer.roomSecret;
- const typeIsSecret = roomType === "secret";
-
- if (!samePeerId || !sameRoomSecret || !typeIsSecret) return;
-
- this._onPairPeerJoined(peerId, roomId);
- this.pairPeer = {};
- }
-
- _onPairPeerJoined(peerId, roomSecret) {
- // if devices are paired that are already connected we must save the names at this point
- const $peer = $(peerId);
- let displayName, deviceName;
- if ($peer) {
- displayName = $peer.ui._peer.name.displayName;
- deviceName = $peer.ui._peer.name.deviceName;
- }
-
- PersistentStorage.addRoomSecret(roomSecret, displayName, deviceName)
- .then(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success"));
- this._evaluateNumberRoomSecrets();
- })
- .finally(_ => {
- this._cleanUp();
- this.hide();
- })
- .catch(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.pairing-not-persistent"));
- PersistentStorage.logBrowserNotCapable();
- });
- }
-
- _onPublicRoomJoinKeyInvalid() {
- Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalid"));
- }
-
- _close() {
- this._pairDeviceCancel();
- }
-
- _pairDeviceCancel() {
- this.hide();
- this._cleanUp();
- Events.fire('pair-device-cancel');
- }
-
- _onPairDeviceCanceled(pairKey) {
- Events.fire('notify-user', Localization.getTranslation("notifications.pairing-key-invalidated", null, {key: pairKey}));
- }
-
- _cleanUp() {
- this.roomSecret = null;
- this.pairKey = null;
- this.inputKeyContainer._cleanUp();
- this.pairPeer = {};
- }
-
- _onSecretRoomDeleted(roomSecret) {
- PersistentStorage.deleteRoomSecret(roomSecret).then(_ => {
- this._evaluateNumberRoomSecrets();
- });
- }
-
- _evaluateNumberRoomSecrets() {
- PersistentStorage.getAllRoomSecrets()
- .then(roomSecrets => {
- if (roomSecrets.length > 0) {
- this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
- this.$footerInstructionsPairedDevices.removeAttribute('hidden');
- } else {
- this.$editPairedDevicesHeaderBtn.setAttribute('hidden', '');
- this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
- }
- Events.fire('evaluate-footer-badges');
- Events.fire('header-evaluated', 'edit-paired-devices');
- });
- }
-}
-
-class EditPairedDevicesDialog extends Dialog {
- constructor() {
- super('edit-paired-devices-dialog');
- this.$pairedDevicesWrapper = this.$el.querySelector('.paired-devices-wrapper');
- this.$footerBadgePairedDevices = $$('.discovery-wrapper .badge-room-secret');
-
- $('edit-paired-devices').addEventListener('click', _ => this._onEditPairedDevices());
- this.$footerBadgePairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
-
- Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
- Events.on('keydown', e => this._onKeyDown(e));
- }
-
- _onKeyDown(e) {
- if (this.isShown() && e.code === "Escape") {
- this.hide();
- }
- }
-
- async _initDOM() {
- const unpairString = Localization.getTranslation("dialogs.unpair").toUpperCase();
- const autoAcceptString = Localization.getTranslation("dialogs.auto-accept").toLowerCase();
- const roomSecretsEntries = await PersistentStorage.getAllRoomSecretEntries();
-
- roomSecretsEntries.forEach(roomSecretsEntry => {
- let $pairedDevice = document.createElement('div');
- $pairedDevice.classList = ["paired-device"];
-
- $pairedDevice.innerHTML = `
-
- ${roomSecretsEntry.display_name}
-
-
- ${roomSecretsEntry.device_name}
-
-
- ${autoAcceptString}
-
-
- ${unpairString}
-
`
-
- $pairedDevice.querySelector('input[type="checkbox"]').addEventListener('click', e => {
- PersistentStorage.updateRoomSecretAutoAccept(roomSecretsEntry.secret, e.target.checked).then(roomSecretsEntry => {
- Events.fire('auto-accept-updated', {
- 'roomSecret': roomSecretsEntry.entry.secret,
- 'autoAccept': e.target.checked
- });
- });
- });
-
- $pairedDevice.querySelector('button').addEventListener('click', e => {
- PersistentStorage.deleteRoomSecret(roomSecretsEntry.secret).then(roomSecret => {
- Events.fire('room-secrets-deleted', [roomSecret]);
- Events.fire('evaluate-number-room-secrets');
- e.target.parentNode.parentNode.remove();
- });
- })
-
- this.$pairedDevicesWrapper.html = "";
- this.$pairedDevicesWrapper.appendChild($pairedDevice)
- })
-
- }
-
- hide() {
- super.hide();
- setTimeout(_ => {
- this.$pairedDevicesWrapper.innerHTML = ""
- }, 300);
- }
-
- _onEditPairedDevices() {
- this._initDOM().then(_ => this.show());
- }
-
- _clearRoomSecrets() {
- PersistentStorage.getAllRoomSecrets()
- .then(roomSecrets => {
- PersistentStorage.clearRoomSecrets().finally(_ => {
- Events.fire('room-secrets-deleted', roomSecrets);
- Events.fire('evaluate-number-room-secrets');
- Events.fire('notify-user', Localization.getTranslation("notifications.pairing-cleared"));
- this.hide();
- })
- });
- }
-
- _onPeerDisplayNameChanged(e) {
- const peerId = e.detail.peerId;
- const peerNode = $(peerId);
-
- if (!peerNode) return;
-
- const peer = peerNode.ui._peer;
-
- if (!peer || !peer._roomIds["secret"]) return;
-
- PersistentStorage.updateRoomSecretNames(peer._roomIds["secret"], peer.name.displayName, peer.name.deviceName).then(roomSecretEntry => {
- console.log(`Successfully updated DisplayName and DeviceName for roomSecretEntry ${roomSecretEntry.key}`);
- })
- }
-}
-
-class PublicRoomDialog extends Dialog {
- constructor() {
- super('public-room-dialog');
-
- this.$key = this.$el.querySelector('.key');
- this.$qrCode = this.$el.querySelector('.key-qr-code');
- this.$form = this.$el.querySelector('form');
- this.$closeBtn = this.$el.querySelector('[close]');
- this.$leaveBtn = this.$el.querySelector('.leave-room');
- this.$joinSubmitBtn = this.$el.querySelector('button[type="submit"]');
- this.$headerBtnJoinPublicRoom = $('join-public-room');
- this.$footerBadgePublicRoomDevices = $$('.discovery-wrapper .badge-room-public-id');
-
-
- this.$form.addEventListener('submit', e => this._onSubmit(e));
- this.$closeBtn.addEventListener('click', _ => this.hide());
- this.$leaveBtn.addEventListener('click', _ => this._leavePublicRoom())
-
- this.$headerBtnJoinPublicRoom.addEventListener('click', _ => this._onHeaderBtnClick());
- this.$footerBadgePublicRoomDevices.addEventListener('click', _ => this._onHeaderBtnClick());
-
- this.inputKeyContainer = new InputKeyContainer(
- this.$el.querySelector('.input-key-container'),
- /[a-z|A-Z]/,
- () => this.$joinSubmitBtn.removeAttribute("disabled"),
- () => this.$joinSubmitBtn.setAttribute("disabled", ""),
- () => this._submit()
- );
-
- Events.on('keydown', e => this._onKeyDown(e));
- Events.on('public-room-created', e => this._onPublicRoomCreated(e.detail));
- Events.on('peers', e => this._onPeers(e.detail));
- Events.on('peer-joined', e => this._onPeerJoined(e.detail));
- Events.on('public-room-id-invalid', e => this._onPublicRoomIdInvalid(e.detail));
- Events.on('public-room-left', _ => this._onPublicRoomLeft());
- this.$el.addEventListener('paste', e => this._onPaste(e));
- this.$qrCode.addEventListener('click', _ => this._copyShareRoomUrl());
-
- this.evaluateUrlAttributes();
-
- Events.on('ws-connected', _ => this._onWsConnected());
- Events.on('translation-loaded', _ => this.setFooterBadge());
- }
-
- _onKeyDown(e) {
- if (this.isShown() && e.code === "Escape") {
- this.hide();
- }
- }
-
- _onPaste(e) {
- e.preventDefault();
- let pastedKey = e.clipboardData.getData("Text");
- this.inputKeyContainer._onPaste(pastedKey);
- }
-
- _onHeaderBtnClick() {
- if (this.roomId) {
- this.show();
- } else {
- this._createPublicRoom();
- }
- }
-
- _createPublicRoom() {
- Events.fire('create-public-room');
- }
-
- _onPublicRoomCreated(roomId) {
- this.roomId = roomId;
-
- this.setIdAndQrCode();
-
- this.show();
-
- sessionStorage.setItem('public_room_id', roomId);
- }
-
- setIdAndQrCode() {
- if (!this.roomId) return;
-
- this.$key.innerText = this.roomId.toUpperCase();
-
- // Display the QR code for the url
- const qr = new QRCode({
- content: this._getShareRoomUrl(),
- width: 150,
- height: 150,
- padding: 1,
- background: 'rgb(250,250,250)',
- color: 'rgb(18, 18, 18)',
- ecl: "L",
- join: true
- });
- this.$qrCode.innerHTML = qr.svg();
-
- this.setFooterBadge();
- }
-
- setFooterBadge() {
- if (!this.roomId) return;
-
- this.$footerBadgePublicRoomDevices.innerText = Localization.getTranslation("footer.public-room-devices", null, {
- roomId: this.roomId.toUpperCase()
- });
- this.$footerBadgePublicRoomDevices.removeAttribute('hidden');
-
- Events.fire('evaluate-footer-badges');
- }
-
- _getShareRoomUrl() {
- let url = new URL(location.href);
- url.searchParams.append('room_id', this.roomId)
- return url.href;
- }
-
- _copyShareRoomUrl() {
- navigator.clipboard.writeText(this._getShareRoomUrl())
- .then(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.room-url-copied-to-clipboard"));
- })
- .catch(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard-error"));
- })
- }
-
- evaluateUrlAttributes() {
- const urlParams = new URLSearchParams(window.location.search);
- if (urlParams.has('room_id')) {
- this._joinPublicRoom(urlParams.get('room_id'));
- const url = getUrlWithoutArguments();
- window.history.replaceState({}, "Rewrite URL", url); //remove pair_key from url
- }
- }
-
- _onWsConnected() {
- let roomId = sessionStorage.getItem('public_room_id');
-
- if (!roomId) return;
-
- this.roomId = roomId;
- this.setIdAndQrCode();
-
- this._joinPublicRoom(roomId, true);
- }
-
- _onSubmit(e) {
- e.preventDefault();
- this._submit();
- }
-
- _submit() {
- let inputKey = this.inputKeyContainer._getInputKey();
- this._joinPublicRoom(inputKey);
- }
-
- _joinPublicRoom(roomId, createIfInvalid = false) {
- roomId = roomId.toLowerCase();
- if (/^[a-z]{5}$/g.test(roomId)) {
- this.roomIdJoin = roomId;
-
- this.inputKeyContainer.focusLastChar();
-
- Events.fire('join-public-room', {
- roomId: roomId,
- createIfInvalid: createIfInvalid
- });
- }
- }
-
- _onPeers(message) {
- message.peers.forEach(messagePeer => {
- this._evaluateJoinedPeer(messagePeer.id, message.roomId);
- });
- }
-
- _onPeerJoined(message) {
- this._evaluateJoinedPeer(message.peer.id, message.roomId);
- }
-
- _evaluateJoinedPeer(peerId, roomId) {
- const isInitiatedRoomId = roomId === this.roomId;
- const isJoinedRoomId = roomId === this.roomIdJoin;
-
- if (!peerId || !roomId || !(isInitiatedRoomId || isJoinedRoomId)) return;
-
- this.hide();
-
- sessionStorage.setItem('public_room_id', roomId);
-
- if (isJoinedRoomId) {
- this.roomId = roomId;
- this.roomIdJoin = false;
- this.setIdAndQrCode();
- }
- }
-
- _onPublicRoomIdInvalid(roomId) {
- Events.fire('notify-user', Localization.getTranslation("notifications.public-room-id-invalid"));
- if (roomId === sessionStorage.getItem('public_room_id')) {
- sessionStorage.removeItem('public_room_id');
- }
- }
-
- _leavePublicRoom() {
- Events.fire('leave-public-room', this.roomId);
- }
-
- _onPublicRoomLeft() {
- let publicRoomId = this.roomId.toUpperCase();
- this.hide();
- this._cleanUp();
- Events.fire('notify-user', Localization.getTranslation("notifications.public-room-left", null, {publicRoomId: publicRoomId}));
- }
-
- show() {
- this.inputKeyContainer._enableChars();
- super.show();
- }
-
- hide() {
- this.inputKeyContainer._cleanUp();
- super.hide();
- }
-
- _cleanUp() {
- this.roomId = null;
- this.inputKeyContainer._cleanUp();
- sessionStorage.removeItem('public_room_id');
- this.$footerBadgePublicRoomDevices.setAttribute('hidden', '');
- Events.fire('evaluate-footer-badges');
- }
-}
-
-class SendTextDialog extends Dialog {
- constructor() {
- super('send-text-dialog');
- Events.on('text-recipient', e => this._onRecipient(e.detail.peerId, e.detail.deviceName));
- this.$text = this.$el.querySelector('#text-input');
- this.$peerDisplayName = this.$el.querySelector('.display-name');
- this.$form = this.$el.querySelector('form');
- this.$submit = this.$el.querySelector('button[type="submit"]');
- this.$form.addEventListener('submit', e => this._onSubmit(e));
- this.$text.addEventListener('input', e => this._onChange(e));
- Events.on('keydown', e => this._onKeyDown(e));
- }
-
- async _onKeyDown(e) {
- if (!this.isShown()) return;
-
- if (e.code === "Escape") {
- this.hide();
- } else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
- if (this._textInputEmpty()) return;
- this._send();
- }
- }
-
- _textInputEmpty() {
- return !this.$text.innerText || this.$text.innerText === "\n";
- }
-
- _onChange(e) {
- if (this._textInputEmpty()) {
- this.$submit.setAttribute('disabled', '');
- } else {
- this.$submit.removeAttribute('disabled');
- }
- }
-
- _onRecipient(peerId, deviceName) {
- this.correspondingPeerId = peerId;
- this.$peerDisplayName.innerText = deviceName;
- this.$peerDisplayName.classList.remove("badge-room-ip", "badge-room-secret", "badge-room-public-id");
- this.$peerDisplayName.classList.add($(peerId).ui._badgeClassName());
-
- this.show();
-
- const range = document.createRange();
- const sel = window.getSelection();
-
- range.selectNodeContents(this.$text);
- sel.removeAllRanges();
- sel.addRange(range);
- }
-
- _onSubmit(e) {
- e.preventDefault();
- this._send();
- }
-
- _send() {
- Events.fire('send-text', {
- to: this.correspondingPeerId,
- text: this.$text.innerText
- });
- this.$text.innerText = "";
- this.hide();
- }
-}
-
-class ReceiveTextDialog extends Dialog {
- constructor() {
- super('receive-text-dialog');
- Events.on('text-received', e => this._onText(e.detail.text, e.detail.peerId));
- this.$text = this.$el.querySelector('#text');
- this.$copy = this.$el.querySelector('#copy');
- this.$close = this.$el.querySelector('#close');
-
- this.$copy.addEventListener('click', _ => this._onCopy());
- this.$close.addEventListener('click', _ => this.hide());
-
- Events.on('keydown', e => this._onKeyDown(e));
-
- this.$displayName = this.$el.querySelector('.display-name');
- this._receiveTextQueue = [];
- }
-
- async _onKeyDown(e) {
- if (this.isShown()) {
- if (e.code === "KeyC" && (e.ctrlKey || e.metaKey)) {
- await this._onCopy()
- this.hide();
- } else if (e.code === "Escape") {
- this.hide();
- }
- }
- }
-
- _onText(text, peerId) {
- window.blop.play();
- this._receiveTextQueue.push({text: text, peerId: peerId});
- this._setDocumentTitleMessages();
- if (this.isShown()) return;
- this._dequeueRequests();
- }
-
- _dequeueRequests() {
- if (!this._receiveTextQueue.length) return;
- let {text, peerId} = this._receiveTextQueue.shift();
- this._showReceiveTextDialog(text, peerId);
- }
-
- _showReceiveTextDialog(text, peerId) {
- this.$displayName.innerText = $(peerId).ui._displayName();
- this.$displayName.classList.remove("badge-room-ip", "badge-room-secret", "badge-room-public-id");
- this.$displayName.classList.add($(peerId).ui._badgeClassName());
-
- this.$text.innerText = text;
- this.$text.classList.remove('text-center');
-
- // Beautify text if text is short
- if (text.length < 2000) {
- // replace urls with actual links
- this.$text.innerHTML = this.$text.innerHTML.replace(/((https?:\/\/|www)[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)/g, url => {
- return `${url} `;
- });
- }
-
- this._setDocumentTitleMessages();
-
- changeFavicon("images/favicon-96x96-notification.png");
- this.show();
- }
-
- _setDocumentTitleMessages() {
- document.title = !this._receiveTextQueue.length
- ? `${ Localization.getTranslation("document-titles.message-received") } - PairDrop`
- : `${ Localization.getTranslation("document-titles.message-received-plural", null, {count: this._receiveTextQueue.length + 1}) } - PairDrop`;
- }
-
- async _onCopy() {
- const sanitizedText = this.$text.innerText.replace(/\u00A0/gm, ' ');
- navigator.clipboard.writeText(sanitizedText)
- .then(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard"));
- this.hide();
- })
- .catch(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.copied-to-clipboard-error"));
- });
- }
-
- hide() {
- super.hide();
- setTimeout(_ => this._dequeueRequests(), 500);
- }
-}
-
-class Base64ZipDialog extends Dialog {
-
- constructor() {
- super('base64-paste-dialog');
- const urlParams = new URL(window.location).searchParams;
- const base64Text = urlParams.get('base64text');
- const base64Zip = urlParams.get('base64zip');
- const base64Hash = window.location.hash.substring(1);
-
- this.$pasteBtn = this.$el.querySelector('#base64-paste-btn');
- this.$fallbackTextarea = this.$el.querySelector('.textarea');
-
- if (base64Text) {
- this.show();
- if (base64Text === 'paste') {
- // ?base64text=paste
- // base64 encoded string is ready to be pasted from clipboard
- this.preparePasting('text');
- } else if (base64Text === 'hash') {
- // ?base64text=hash#BASE64ENCODED
- // base64 encoded string is url hash which is never sent to server and faster (recommended)
- this.processBase64Text(base64Hash)
- .catch(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
- console.log("Text content incorrect.");
- }).finally(_ => {
- this.hide();
- });
- } else {
- // ?base64text=BASE64ENCODED
- // base64 encoded string was part of url param (not recommended)
- this.processBase64Text(base64Text)
- .catch(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
- console.log("Text content incorrect.");
- }).finally(_ => {
- this.hide();
- });
- }
- } else if (base64Zip) {
- this.show();
- if (base64Zip === "hash") {
- // ?base64zip=hash#BASE64ENCODED
- // base64 encoded zip file is url hash which is never sent to the server
- this.processBase64Zip(base64Hash)
- .catch(_ => {
- Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect"));
- console.log("File content incorrect.");
- }).finally(_ => {
- this.hide();
- });
- } else {
- // ?base64zip=paste || ?base64zip=true
- this.preparePasting('files');
- }
- }
- }
-
- _setPasteBtnToProcessing() {
- this.$pasteBtn.style.pointerEvents = "none";
- this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-processing");
- }
-
- preparePasting(type) {
- const translateType = type === 'text'
- ? Localization.getTranslation("dialogs.base64-text")
- : Localization.getTranslation("dialogs.base64-files");
-
- if (navigator.clipboard.readText) {
- this.$pasteBtn.innerText = Localization.getTranslation("dialogs.base64-tap-to-paste", null, {type: translateType});
- this._clickCallback = _ => this.processClipboard(type);
- this.$pasteBtn.addEventListener('click', _ => this._clickCallback());
- } else {
- console.log("`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience.")
- this.$pasteBtn.setAttribute('hidden', '');
- this.$fallbackTextarea.setAttribute('placeholder', Localization.getTranslation("dialogs.base64-paste-to-send", null, {type: translateType}));
- this.$fallbackTextarea.removeAttribute('hidden');
- this._inputCallback = _ => this.processInput(type);
- this.$fallbackTextarea.addEventListener('input', _ => this._inputCallback());
- this.$fallbackTextarea.focus();
- }
- }
-
- async processInput(type) {
- const base64 = this.$fallbackTextarea.textContent;
- this.$fallbackTextarea.textContent = '';
- await this.processBase64(type, base64);
- }
-
- async processClipboard(type) {
- const base64 = await navigator.clipboard.readText();
- await this.processBase64(type, base64);
- }
-
- isValidBase64(base64) {
- try {
- // check if input is base64 encoded
- window.atob(base64);
- return true;
- } catch (e) {
- // input is not base64 string.
- return false;
- }
- }
-
- async processBase64(type, base64) {
- if (!base64 || !this.isValidBase64(base64)) return;
- this._setPasteBtnToProcessing();
- try {
- if (type === 'text') {
- await this.processBase64Text(base64);
- } else {
- await this.processBase64Zip(base64);
- }
- } catch(_) {
- Events.fire('notify-user', Localization.getTranslation("notifications.clipboard-content-incorrect"));
- console.log("Clipboard content is incorrect.")
- }
- this.hide();
- }
-
- processBase64Text(base64Text){
- return new Promise((resolve) => {
- this._setPasteBtnToProcessing();
- let decodedText = decodeURIComponent(escape(window.atob(base64Text)));
- Events.fire('activate-paste-mode', {files: [], text: decodedText});
- resolve();
- });
- }
-
- async processBase64Zip(base64zip) {
- this._setPasteBtnToProcessing();
- let bstr = atob(base64zip), n = bstr.length, u8arr = new Uint8Array(n);
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n);
- }
-
- const zipBlob = new File([u8arr], 'archive.zip');
-
- let files = [];
- const zipEntries = await zipper.getEntries(zipBlob);
- for (let i = 0; i < zipEntries.length; i++) {
- let fileBlob = await zipper.getData(zipEntries[i]);
- files.push(new File([fileBlob], zipEntries[i].filename));
- }
- Events.fire('activate-paste-mode', {files: files, text: ""});
- }
-
- clearBrowserHistory() {
- const url = getUrlWithoutArguments();
- window.history.replaceState({}, "Rewrite URL", url);
- }
-
- hide() {
- this.clearBrowserHistory();
- this.$pasteBtn.removeEventListener('click', _ => this._clickCallback());
- this.$fallbackTextarea.removeEventListener('input', _ => this._inputCallback());
- super.hide();
- }
-}
-
-class Toast extends Dialog {
- constructor() {
- super('toast');
- Events.on('notify-user', e => this._onNotify(e.detail));
- }
-
- _onNotify(message) {
- if (this.hideTimeout) clearTimeout(this.hideTimeout);
- this.$el.innerText = typeof message === "object" ? message.message : message;
- this.show();
-
- if (typeof message === "object" && message.persistent) return;
-
- this.hideTimeout = setTimeout(() => this.hide(), 5000);
- }
-}
-
-class Notifications {
-
- constructor() {
- // Check if the browser supports notifications
- if (!('Notification' in window)) return;
-
- // Check whether notification permissions have already been granted
- if (Notification.permission !== 'granted') {
- this.$headerNotificationButton = $('notification');
- this.$headerNotificationButton.removeAttribute('hidden');
- this.$headerNotificationButton.addEventListener('click', _ => this._requestPermission());
- }
-
- Events.fire('header-evaluated', 'notification');
-
- Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
- Events.on('files-received', e => this._downloadNotification(e.detail.files));
- Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId));
- }
-
- _requestPermission() {
- Notification.requestPermission(permission => {
- if (permission !== 'granted') {
- Events.fire('notify-user', Localization.getTranslation("notifications.notifications-permissions-error"));
- return;
- }
- Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
- this.$headerNotificationButton.setAttribute('hidden', "");
- });
- }
-
- _notify(title, body) {
- const config = {
- body: body,
- icon: '/images/logo_transparent_128x128.png',
- }
- let notification;
- try {
- notification = new Notification(title, config);
- } catch (e) {
- // Android doesn't support "new Notification" if service worker is installed
- if (!serviceWorker || !serviceWorker.showNotification) return;
- notification = serviceWorker.showNotification(title, config);
- }
-
- // Notification is persistent on Android. We have to close it manually
- const visibilitychangeHandler = () => {
- if (document.visibilityState === 'visible') {
- notification.close();
- Events.off('visibilitychange', visibilitychangeHandler);
- }
- };
- Events.on('visibilitychange', visibilitychangeHandler);
-
- return notification;
- }
-
- _messageNotification(message, peerId) {
- if (document.visibilityState !== 'visible') {
- const peerDisplayName = $(peerId).ui._displayName();
- if (/^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/.test(message.toLowerCase())) {
- const notification = this._notify(Localization.getTranslation("notifications.link-received", null, {name: peerDisplayName}), message);
- this._bind(notification, _ => window.open(message, '_blank', null, true));
- } else {
- const notification = this._notify(Localization.getTranslation("notifications.message-received", null, {name: peerDisplayName}), message);
- this._bind(notification, _ => this._copyText(message, notification));
- }
- }
- }
-
- _downloadNotification(files) {
- if (document.visibilityState !== 'visible') {
- let imagesOnly = true;
- for(let i=0; i this._download(notification));
- }
- }
-
- _requestNotification(request, peerId) {
- if (document.visibilityState !== 'visible') {
- let imagesOnly = true;
- for(let i=0; i serviceWorker.getNotifications().then(_ => {
- serviceWorker.addEventListener('notificationclick', handler);
- }));
- } else {
- notification.onclick = handler;
- }
- }
-}
-
-class NetworkStatusUI {
-
- constructor() {
- Events.on('offline', _ => this._showOfflineMessage());
- Events.on('online', _ => this._showOnlineMessage());
- if (!navigator.onLine) this._showOfflineMessage();
- }
-
- _showOfflineMessage() {
- Events.fire('notify-user', {
- message: Localization.getTranslation("notifications.offline"),
- persistent: true
- });
- }
-
- _showOnlineMessage() {
- Events.fire('notify-user', Localization.getTranslation("notifications.online"));
- }
-}
-
-class WebShareTargetUI {
- constructor() {
- const urlParams = new URL(window.location).searchParams;
- const share_target_type = urlParams.get("share-target")
- if (share_target_type) {
- if (share_target_type === "text") {
- const title = urlParams.get('title') || '';
- const text = urlParams.get('text') || '';
- const url = urlParams.get('url') || '';
- let shareTargetText;
-
- if (url) {
- shareTargetText = url; // we share only the link - no text.
- } else if (title && text) {
- shareTargetText = title + '\r\n' + text;
- } else {
- shareTargetText = title + text;
- }
-
- Events.fire('activate-paste-mode', {files: [], text: shareTargetText})
- } else if (share_target_type === "files") {
- let openRequest = window.indexedDB.open('pairdrop_store')
- openRequest.onsuccess = e => {
- const db = e.target.result;
- const tx = db.transaction('share_target_files', 'readwrite');
- const store = tx.objectStore('share_target_files');
- const request = store.getAll();
- request.onsuccess = _ => {
- const fileObjects = request.result;
- let filesReceived = [];
- for (let i=0; i db.close();
-
- Events.fire('activate-paste-mode', {files: filesReceived, text: ""})
- }
- }
- }
- const url = getUrlWithoutArguments();
- window.history.replaceState({}, "Rewrite URL", url);
- }
- }
-}
-
-class WebFileHandlersUI {
- constructor() {
- const urlParams = new URL(window.location).searchParams;
- if (urlParams.has("file_handler") && "launchQueue" in window) {
- launchQueue.setConsumer(async launchParams => {
- console.log("Launched with: ", launchParams);
- if (!launchParams.files.length)
- return;
- let files = [];
-
- for (let i=0; i NoSleepUI.disable(), 10000);
- }
- }
-
- static disable() {
- if ($$('x-peer[status]') === null) {
- clearInterval(NoSleepUI._interval);
- NoSleepUI._nosleep.disable();
- }
- }
-}
-
-class PersistentStorage {
- constructor() {
- if (!('indexedDB' in window)) {
- PersistentStorage.logBrowserNotCapable();
- return;
- }
- const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4);
- DBOpenRequest.onerror = (e) => {
- PersistentStorage.logBrowserNotCapable();
- console.log('Error initializing database: ');
- console.log(e)
- };
- DBOpenRequest.onsuccess = () => {
- console.log('Database initialised.');
- };
- DBOpenRequest.onupgradeneeded = (e) => {
- const db = e.target.result;
- const txn = e.target.transaction;
-
- db.onerror = e => console.log('Error loading database: ' + e);
-
- console.log(`Upgrading IndexedDB database from version ${e.oldVersion} to version ${e.newVersion}`);
-
- if (e.oldVersion === 0) {
- // initiate v1
- db.createObjectStore('keyval');
- let roomSecretsObjectStore1 = db.createObjectStore('room_secrets', {autoIncrement: true});
- roomSecretsObjectStore1.createIndex('secret', 'secret', { unique: true });
- }
- if (e.oldVersion <= 1) {
- // migrate to v2
- db.createObjectStore('share_target_files');
- }
- if (e.oldVersion <= 2) {
- // migrate to v3
- db.deleteObjectStore('share_target_files');
- db.createObjectStore('share_target_files', {autoIncrement: true});
- }
- if (e.oldVersion <= 3) {
- // migrate to v4
- let roomSecretsObjectStore4 = txn.objectStore('room_secrets');
- roomSecretsObjectStore4.createIndex('display_name', 'display_name');
- roomSecretsObjectStore4.createIndex('auto_accept', 'auto_accept');
- }
- }
- }
-
- static logBrowserNotCapable() {
- console.log("This browser does not support IndexedDB. Paired devices will be gone after the browser is closed.");
- }
-
- static set(key, value) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('keyval', 'readwrite');
- const objectStore = transaction.objectStore('keyval');
- const objectStoreRequest = objectStore.put(value, key);
- objectStoreRequest.onsuccess = _ => {
- console.log(`Request successful. Added key-pair: ${key} - ${value}`);
- resolve(value);
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static get(key) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('keyval', 'readonly');
- const objectStore = transaction.objectStore('keyval');
- const objectStoreRequest = objectStore.get(key);
- objectStoreRequest.onsuccess = _ => {
- console.log(`Request successful. Retrieved key-pair: ${key} - ${objectStoreRequest.result}`);
- resolve(objectStoreRequest.result);
- }
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- });
- }
-
- static delete(key) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('keyval', 'readwrite');
- const objectStore = transaction.objectStore('keyval');
- const objectStoreRequest = objectStore.delete(key);
- objectStoreRequest.onsuccess = _ => {
- console.log(`Request successful. Deleted key: ${key}`);
- resolve();
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static addRoomSecret(roomSecret, displayName, deviceName) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequest = objectStore.add({
- 'secret': roomSecret,
- 'display_name': displayName,
- 'device_name': deviceName,
- 'auto_accept': false
- });
- objectStoreRequest.onsuccess = e => {
- console.log(`Request successful. RoomSecret added: ${e.target.result}`);
- resolve();
- }
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static async getAllRoomSecrets() {
- try {
- const roomSecrets = await this.getAllRoomSecretEntries();
- let secrets = [];
- for (let i = 0; i < roomSecrets.length; i++) {
- secrets.push(roomSecrets[i].secret);
- }
- console.log(`Request successful. Retrieved ${secrets.length} room_secrets`);
- return(secrets);
- } catch (e) {
- this.logBrowserNotCapable();
- return 0;
- }
- }
-
- static getAllRoomSecretEntries() {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readonly');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequest = objectStore.getAll();
- objectStoreRequest.onsuccess = e => {
- resolve(e.target.result);
- }
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- });
- }
-
- static getRoomSecretEntry(roomSecret) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readonly');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequestKey = objectStore.index("secret").getKey(roomSecret);
- objectStoreRequestKey.onsuccess = e => {
- const key = e.target.result;
- if (!key) {
- console.log(`Nothing to retrieve. Entry for room_secret not existing: ${roomSecret}`);
- resolve();
- return;
- }
- const objectStoreRequestRetrieval = objectStore.get(key);
- objectStoreRequestRetrieval.onsuccess = e => {
- console.log(`Request successful. Retrieved entry for room_secret: ${key}`);
- resolve({
- "entry": e.target.result,
- "key": key
- });
- }
- objectStoreRequestRetrieval.onerror = (e) => {
- reject(e);
- }
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- });
- }
-
- static deleteRoomSecret(roomSecret) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequestKey = objectStore.index("secret").getKey(roomSecret);
- objectStoreRequestKey.onsuccess = e => {
- if (!e.target.result) {
- console.log(`Nothing to delete. room_secret not existing: ${roomSecret}`);
- resolve();
- return;
- }
- const key = e.target.result;
- const objectStoreRequestDeletion = objectStore.delete(key);
- objectStoreRequestDeletion.onsuccess = _ => {
- console.log(`Request successful. Deleted room_secret: ${key}`);
- resolve(roomSecret);
- }
- objectStoreRequestDeletion.onerror = (e) => {
- reject(e);
- }
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static clearRoomSecrets() {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- const objectStoreRequest = objectStore.clear();
- objectStoreRequest.onsuccess = _ => {
- console.log('Request successful. All room_secrets cleared');
- resolve();
- };
- }
- DBOpenRequest.onerror = (e) => {
- reject(e);
- }
- })
- }
-
- static updateRoomSecretNames(roomSecret, displayName, deviceName) {
- return this.updateRoomSecret(roomSecret, undefined, displayName, deviceName);
- }
-
- static updateRoomSecretAutoAccept(roomSecret, autoAccept) {
- return this.updateRoomSecret(roomSecret, undefined, undefined, undefined, autoAccept);
- }
-
- static updateRoomSecret(roomSecret, updatedRoomSecret = undefined, updatedDisplayName = undefined, updatedDeviceName = undefined, updatedAutoAccept = undefined) {
- return new Promise((resolve, reject) => {
- const DBOpenRequest = window.indexedDB.open('pairdrop_store');
- DBOpenRequest.onsuccess = (e) => {
- const db = e.target.result;
- this.getRoomSecretEntry(roomSecret)
- .then(roomSecretEntry => {
- if (!roomSecretEntry) {
- resolve(false);
- return;
- }
- const transaction = db.transaction('room_secrets', 'readwrite');
- const objectStore = transaction.objectStore('room_secrets');
- // Do not use `updatedRoomSecret ?? roomSecretEntry.entry.secret` to ensure compatibility with older browsers
- const updatedRoomSecretEntry = {
- 'secret': updatedRoomSecret !== undefined ? updatedRoomSecret : roomSecretEntry.entry.secret,
- 'display_name': updatedDisplayName !== undefined ? updatedDisplayName : roomSecretEntry.entry.display_name,
- 'device_name': updatedDeviceName !== undefined ? updatedDeviceName : roomSecretEntry.entry.device_name,
- 'auto_accept': updatedAutoAccept !== undefined ? updatedAutoAccept : roomSecretEntry.entry.auto_accept
- };
-
- const objectStoreRequestUpdate = objectStore.put(updatedRoomSecretEntry, roomSecretEntry.key);
-
- objectStoreRequestUpdate.onsuccess = e => {
- console.log(`Request successful. Updated room_secret: ${roomSecretEntry.key}`);
- resolve({
- "entry": updatedRoomSecretEntry,
- "key": roomSecretEntry.key
- });
- }
-
- objectStoreRequestUpdate.onerror = (e) => {
- reject(e);
- }
- })
- .catch(e => reject(e));
- };
-
- DBOpenRequest.onerror = e => reject(e);
- })
- }
-}
-
-class BrowserTabsConnector {
- constructor() {
- this.bc = new BroadcastChannel('pairdrop');
- this.bc.addEventListener('message', e => this._onMessage(e));
- Events.on('broadcast-send', e => this._broadcastSend(e.detail));
- }
-
- _broadcastSend(message) {
- this.bc.postMessage(message);
- }
-
- _onMessage(e) {
- console.log('Broadcast:', e.data)
- switch (e.data.type) {
- case 'self-display-name-changed':
- Events.fire('self-display-name-changed', e.data.detail);
- break;
- }
- }
-
- static peerIsSameBrowser(peerId) {
- let peerIdsBrowser = JSON.parse(localStorage.getItem("peer_ids_browser"));
- return peerIdsBrowser
- ? peerIdsBrowser.indexOf(peerId) !== -1
- : false;
- }
-
- static async addPeerIdToLocalStorage() {
- const peerId = sessionStorage.getItem("peer_id");
- if (!peerId) return false;
-
- let peerIdsBrowser = [];
- let peerIdsBrowserOld = JSON.parse(localStorage.getItem("peer_ids_browser"));
-
- if (peerIdsBrowserOld) peerIdsBrowser.push(...peerIdsBrowserOld);
- peerIdsBrowser.push(peerId);
- peerIdsBrowser = peerIdsBrowser.filter(onlyUnique);
- localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
-
- return peerIdsBrowser;
- }
-
- static async removePeerIdFromLocalStorage(peerId) {
- let peerIdsBrowser = JSON.parse(localStorage.getItem("peer_ids_browser"));
- const index = peerIdsBrowser.indexOf(peerId);
- peerIdsBrowser.splice(index, 1);
- localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
- return peerId;
- }
-
-
- static async removeOtherPeerIdsFromLocalStorage() {
- const peerId = sessionStorage.getItem("peer_id");
- if (!peerId) return false;
-
- let peerIdsBrowser = [peerId];
- localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
- return peerIdsBrowser;
- }
-}
-
-class BackgroundCanvas {
- constructor() {
- this.c = $$('canvas');
- this.cCtx = this.c.getContext('2d');
- this.$footer = $$('footer');
-
- Events.on('bg-resize', _ => this.init());
- Events.on('redraw-canvas', _ => this.init());
- Events.on('translation-loaded', _ => this.init());
-
- //fade-in on load
- Events.on('ui-faded-in', _ => this._fadeIn());
-
- window.onresize = _ => Events.fire('bg-resize');
- }
-
- _fadeIn() {
- this.c.classList.remove('opacity-0');
- }
-
- init() {
- let oldW = this.w;
- let oldH = this.h;
- let oldOffset = this.offset
- this.w = document.documentElement.clientWidth;
- this.h = document.documentElement.clientHeight;
- this.offset = this.$footer.offsetHeight - 27;
- if (this.h >= 800) this.offset += 10;
-
- if (oldW === this.w && oldH === this.h && oldOffset === this.offset) return; // nothing has changed
-
- this.c.width = this.w;
- this.c.height = this.h;
- this.x0 = this.w / 2;
- this.y0 = this.h - this.offset;
- this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13);
-
- this.drawCircles(this.cCtx);
- }
-
-
- drawCircle(ctx, radius) {
- ctx.beginPath();
- ctx.lineWidth = 2;
- let opacity = Math.max(0, 0.3 * (1 - 1 * radius / Math.max(this.w, this.h)));
- ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`;
- ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI);
- ctx.stroke();
- }
-
- drawCircles(ctx) {
- ctx.clearRect(0, 0, this.w, this.h);
- for (let i = 0; i < 13; i++) {
- this.drawCircle(ctx, this.dw * i + 33 + 66);
- }
- }
-}
-
-class PairDrop {
- constructor() {
- Events.on('initial-translation-loaded', _ => {
- const server = new ServerConnection();
- const peers = new PeersManager(server);
- const peersUI = new PeersUI();
- const backgroundCanvas = new BackgroundCanvas();
- const languageSelectDialog = new LanguageSelectDialog();
- const receiveFileDialog = new ReceiveFileDialog();
- const receiveRequestDialog = new ReceiveRequestDialog();
- const sendTextDialog = new SendTextDialog();
- const receiveTextDialog = new ReceiveTextDialog();
- const pairDeviceDialog = new PairDeviceDialog();
- const clearDevicesDialog = new EditPairedDevicesDialog();
- const publicRoomDialog = new PublicRoomDialog();
- const base64ZipDialog = new Base64ZipDialog();
- const toast = new Toast();
- const notifications = new Notifications();
- const networkStatusUI = new NetworkStatusUI();
- const webShareTargetUI = new WebShareTargetUI();
- const webFileHandlersUI = new WebFileHandlersUI();
- const noSleepUI = new NoSleepUI();
- const broadCast = new BrowserTabsConnector();
- });
- }
-}
-
-const persistentStorage = new PersistentStorage();
-const pairDrop = new PairDrop();
-const localization = new Localization();
-
-if ('serviceWorker' in navigator) {
- navigator.serviceWorker.register('/service-worker.js')
- .then(serviceWorker => {
- console.log('Service Worker registered');
- window.serviceWorker = serviceWorker
- });
-}
-
-window.addEventListener('beforeinstallprompt', installEvent => {
- if (!window.matchMedia('(display-mode: minimal-ui)').matches) {
- // only display install btn when not installed
- const installBtn = document.querySelector('#install')
- installBtn.removeAttribute('hidden');
- installBtn.addEventListener('click', () => {
- installBtn.setAttribute('hidden', '');
- installEvent.prompt();
- });
- }
- return installEvent.preventDefault();
-});
\ No newline at end of file
diff --git a/public_included_ws_fallback/scripts/util.js b/public_included_ws_fallback/scripts/util.js
deleted file mode 100644
index 00d3883..0000000
--- a/public_included_ws_fallback/scripts/util.js
+++ /dev/null
@@ -1,433 +0,0 @@
-// Polyfill for Navigator.clipboard.writeText
-if (!navigator.clipboard) {
- navigator.clipboard = {
- writeText: text => {
-
- // A contains the text to copy
- const span = document.createElement('span');
- span.innerText = text;
- span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines
-
- // Paint the span outside the viewport
- span.style.position = 'absolute';
- span.style.left = '-9999px';
- span.style.top = '-9999px';
-
- const win = window;
- const selection = win.getSelection();
- win.document.body.appendChild(span);
-
- const range = win.document.createRange();
- selection.removeAllRanges();
- range.selectNode(span);
- selection.addRange(range);
-
- let success = false;
- try {
- success = win.document.execCommand('copy');
- } catch (err) {
- return Promise.error();
- }
-
- selection.removeAllRanges();
- span.remove();
-
- return Promise.resolve();
- }
- }
-}
-
-const zipper = (() => {
-
- let zipWriter;
- return {
- createNewZipWriter() {
- zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), { bufferedWrite: true, level: 0 });
- },
- addFile(file, options) {
- return zipWriter.add(file.name, new zip.BlobReader(file), options);
- },
- async getBlobURL() {
- if (zipWriter) {
- const blobURL = URL.createObjectURL(await zipWriter.close());
- zipWriter = null;
- return blobURL;
- } else {
- throw new Error("Zip file closed");
- }
- },
- async getZipFile(filename = "archive.zip") {
- if (zipWriter) {
- const file = new File([await zipWriter.close()], filename, {type: "application/zip"});
- zipWriter = null;
- return file;
- } else {
- throw new Error("Zip file closed");
- }
- },
- async getEntries(file, options) {
- return await (new zip.ZipReader(new zip.BlobReader(file))).getEntries(options);
- },
- async getData(entry, options) {
- return await entry.getData(new zip.BlobWriter(), options);
- },
- };
-
-})();
-
-const mime = (() => {
-
- return {
- getMimeByFilename(filename) {
- try {
- const arr = filename.split('.');
- const suffix = arr[arr.length - 1].toLowerCase();
- return {
- "cpl": "application/cpl+xml",
- "gpx": "application/gpx+xml",
- "gz": "application/gzip",
- "jar": "application/java-archive",
- "war": "application/java-archive",
- "ear": "application/java-archive",
- "class": "application/java-vm",
- "js": "application/javascript",
- "mjs": "application/javascript",
- "json": "application/json",
- "map": "application/json",
- "webmanifest": "application/manifest+json",
- "doc": "application/msword",
- "dot": "application/msword",
- "wiz": "application/msword",
- "bin": "application/octet-stream",
- "dms": "application/octet-stream",
- "lrf": "application/octet-stream",
- "mar": "application/octet-stream",
- "so": "application/octet-stream",
- "dist": "application/octet-stream",
- "distz": "application/octet-stream",
- "pkg": "application/octet-stream",
- "bpk": "application/octet-stream",
- "dump": "application/octet-stream",
- "elc": "application/octet-stream",
- "deploy": "application/octet-stream",
- "img": "application/octet-stream",
- "msp": "application/octet-stream",
- "msm": "application/octet-stream",
- "buffer": "application/octet-stream",
- "oda": "application/oda",
- "oxps": "application/oxps",
- "pdf": "application/pdf",
- "asc": "application/pgp-signature",
- "sig": "application/pgp-signature",
- "prf": "application/pics-rules",
- "p7c": "application/pkcs7-mime",
- "cer": "application/pkix-cert",
- "ai": "application/postscript",
- "eps": "application/postscript",
- "ps": "application/postscript",
- "apk": "application/vnd.android.package-archive",
- "m3u8": "application/vnd.apple.mpegurl",
- "pkpass": "application/vnd.apple.pkpass",
- "kml": "application/vnd.google-earth.kml+xml",
- "kmz": "application/vnd.google-earth.kmz",
- "cab": "application/vnd.ms-cab-compressed",
- "xls": "application/vnd.ms-excel",
- "xlm": "application/vnd.ms-excel",
- "xla": "application/vnd.ms-excel",
- "xlc": "application/vnd.ms-excel",
- "xlt": "application/vnd.ms-excel",
- "xlw": "application/vnd.ms-excel",
- "msg": "application/vnd.ms-outlook",
- "ppt": "application/vnd.ms-powerpoint",
- "pot": "application/vnd.ms-powerpoint",
- "ppa": "application/vnd.ms-powerpoint",
- "pps": "application/vnd.ms-powerpoint",
- "pwz": "application/vnd.ms-powerpoint",
- "mpp": "application/vnd.ms-project",
- "mpt": "application/vnd.ms-project",
- "xps": "application/vnd.ms-xpsdocument",
- "odb": "application/vnd.oasis.opendocument.database",
- "ods": "application/vnd.oasis.opendocument.spreadsheet",
- "odt": "application/vnd.oasis.opendocument.text",
- "osm": "application/vnd.openstreetmap.data+xml",
- "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
- "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- "pcap": "application/vnd.tcpdump.pcap",
- "cap": "application/vnd.tcpdump.pcap",
- "dmp": "application/vnd.tcpdump.pcap",
- "wpd": "application/vnd.wordperfect",
- "wasm": "application/wasm",
- "7z": "application/x-7z-compressed",
- "dmg": "application/x-apple-diskimage",
- "bcpio": "application/x-bcpio",
- "torrent": "application/x-bittorrent",
- "cbr": "application/x-cbr",
- "cba": "application/x-cbr",
- "cbt": "application/x-cbr",
- "cbz": "application/x-cbr",
- "cb7": "application/x-cbr",
- "vcd": "application/x-cdlink",
- "crx": "application/x-chrome-extension",
- "cpio": "application/x-cpio",
- "csh": "application/x-csh",
- "deb": "application/x-debian-package",
- "udeb": "application/x-debian-package",
- "dvi": "application/x-dvi",
- "arc": "application/x-freearc",
- "gtar": "application/x-gtar",
- "hdf": "application/x-hdf",
- "h5": "application/x-hdf5",
- "php": "application/x-httpd-php",
- "iso": "application/x-iso9660-image",
- "key": "application/x-iwork-keynote-sffkey",
- "numbers": "application/x-iwork-numbers-sffnumbers",
- "pages": "application/x-iwork-pages-sffpages",
- "latex": "application/x-latex",
- "run": "application/x-makeself",
- "mif": "application/x-mif",
- "lnk": "application/x-ms-shortcut",
- "mdb": "application/x-msaccess",
- "exe": "application/x-msdownload",
- "dll": "application/x-msdownload",
- "com": "application/x-msdownload",
- "bat": "application/x-msdownload",
- "msi": "application/x-msdownload",
- "pub": "application/x-mspublisher",
- "cdf": "application/x-netcdf",
- "nc": "application/x-netcdf",
- "pl": "application/x-perl",
- "pm": "application/x-perl",
- "prc": "application/x-pilot",
- "pdb": "application/x-pilot",
- "p12": "application/x-pkcs12",
- "pfx": "application/x-pkcs12",
- "ram": "application/x-pn-realaudio",
- "pyc": "application/x-python-code",
- "pyo": "application/x-python-code",
- "rar": "application/x-rar-compressed",
- "rpm": "application/x-redhat-package-manager",
- "sh": "application/x-sh",
- "shar": "application/x-shar",
- "swf": "application/x-shockwave-flash",
- "sql": "application/x-sql",
- "srt": "application/x-subrip",
- "sv4cpio": "application/x-sv4cpio",
- "sv4crc": "application/x-sv4crc",
- "gam": "application/x-tads",
- "tar": "application/x-tar",
- "tcl": "application/x-tcl",
- "tex": "application/x-tex",
- "roff": "application/x-troff",
- "t": "application/x-troff",
- "tr": "application/x-troff",
- "man": "application/x-troff-man",
- "me": "application/x-troff-me",
- "ms": "application/x-troff-ms",
- "ustar": "application/x-ustar",
- "src": "application/x-wais-source",
- "xpi": "application/x-xpinstall",
- "xhtml": "application/xhtml+xml",
- "xht": "application/xhtml+xml",
- "xsl": "application/xml",
- "rdf": "application/xml",
- "wsdl": "application/xml",
- "xpdl": "application/xml",
- "zip": "application/zip",
- "3gp": "audio/3gp",
- "3gpp": "audio/3gpp",
- "3g2": "audio/3gpp2",
- "3gpp2": "audio/3gpp2",
- "aac": "audio/aac",
- "adts": "audio/aac",
- "loas": "audio/aac",
- "ass": "audio/aac",
- "au": "audio/basic",
- "snd": "audio/basic",
- "mid": "audio/midi",
- "midi": "audio/midi",
- "kar": "audio/midi",
- "rmi": "audio/midi",
- "mpga": "audio/mpeg",
- "mp2": "audio/mpeg",
- "mp2a": "audio/mpeg",
- "mp3": "audio/mpeg",
- "m2a": "audio/mpeg",
- "m3a": "audio/mpeg",
- "oga": "audio/ogg",
- "ogg": "audio/ogg",
- "spx": "audio/ogg",
- "opus": "audio/opus",
- "aif": "audio/x-aiff",
- "aifc": "audio/x-aiff",
- "aiff": "audio/x-aiff",
- "flac": "audio/x-flac",
- "m4a": "audio/x-m4a",
- "m3u": "audio/x-mpegurl",
- "wma": "audio/x-ms-wma",
- "ra": "audio/x-pn-realaudio",
- "wav": "audio/x-wav",
- "otf": "font/otf",
- "ttf": "font/ttf",
- "woff": "font/woff",
- "woff2": "font/woff2",
- "emf": "image/emf",
- "gif": "image/gif",
- "heic": "image/heic",
- "heif": "image/heif",
- "ief": "image/ief",
- "jpeg": "image/jpeg",
- "jpg": "image/jpeg",
- "pict": "image/pict",
- "pct": "image/pict",
- "pic": "image/pict",
- "png": "image/png",
- "svg": "image/svg+xml",
- "svgz": "image/svg+xml",
- "tif": "image/tiff",
- "tiff": "image/tiff",
- "psd": "image/vnd.adobe.photoshop",
- "djvu": "image/vnd.djvu",
- "djv": "image/vnd.djvu",
- "dwg": "image/vnd.dwg",
- "dxf": "image/vnd.dxf",
- "dds": "image/vnd.ms-dds",
- "webp": "image/webp",
- "3ds": "image/x-3ds",
- "ras": "image/x-cmu-raster",
- "ico": "image/x-icon",
- "bmp": "image/x-ms-bmp",
- "pnm": "image/x-portable-anymap",
- "pbm": "image/x-portable-bitmap",
- "pgm": "image/x-portable-graymap",
- "ppm": "image/x-portable-pixmap",
- "rgb": "image/x-rgb",
- "tga": "image/x-tga",
- "xbm": "image/x-xbitmap",
- "xpm": "image/x-xpixmap",
- "xwd": "image/x-xwindowdump",
- "eml": "message/rfc822",
- "mht": "message/rfc822",
- "mhtml": "message/rfc822",
- "nws": "message/rfc822",
- "obj": "model/obj",
- "stl": "model/stl",
- "dae": "model/vnd.collada+xml",
- "ics": "text/calendar",
- "ifb": "text/calendar",
- "css": "text/css",
- "csv": "text/csv",
- "html": "text/html",
- "htm": "text/html",
- "shtml": "text/html",
- "markdown": "text/markdown",
- "md": "text/markdown",
- "txt": "text/plain",
- "text": "text/plain",
- "conf": "text/plain",
- "def": "text/plain",
- "list": "text/plain",
- "log": "text/plain",
- "in": "text/plain",
- "ini": "text/plain",
- "rtx": "text/richtext",
- "rtf": "text/rtf",
- "tsv": "text/tab-separated-values",
- "c": "text/x-c",
- "cc": "text/x-c",
- "cxx": "text/x-c",
- "cpp": "text/x-c",
- "h": "text/x-c",
- "hh": "text/x-c",
- "dic": "text/x-c",
- "java": "text/x-java-source",
- "lua": "text/x-lua",
- "py": "text/x-python",
- "etx": "text/x-setext",
- "sgm": "text/x-sgml",
- "sgml": "text/x-sgml",
- "vcf": "text/x-vcard",
- "xml": "text/xml",
- "xul": "text/xul",
- "yaml": "text/yaml",
- "yml": "text/yaml",
- "ts": "video/mp2t",
- "mp4": "video/mp4",
- "mp4v": "video/mp4",
- "mpg4": "video/mp4",
- "mpeg": "video/mpeg",
- "m1v": "video/mpeg",
- "mpa": "video/mpeg",
- "mpe": "video/mpeg",
- "mpg": "video/mpeg",
- "mov": "video/quicktime",
- "qt": "video/quicktime",
- "webm": "video/webm",
- "flv": "video/x-flv",
- "m4v": "video/x-m4v",
- "asf": "video/x-ms-asf",
- "asx": "video/x-ms-asf",
- "vob": "video/x-ms-vob",
- "wmv": "video/x-ms-wmv",
- "avi": "video/x-msvideo",
- "*": "video/x-sgi-movie",
- }[suffix] || '';
- } catch (e) {
- console.error(e);
- return '';
- }
- }
- };
-
-})();
-
-/*
- cyrb53 (c) 2018 bryc (github.com/bryc)
- A fast and simple hash function with decent collision resistance.
- Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
- Public domain. Attribution appreciated.
-*/
-const cyrb53 = function(str, seed = 0) {
- let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
- for (let i = 0, ch; i < str.length; i++) {
- ch = str.charCodeAt(i);
- h1 = Math.imul(h1 ^ ch, 2654435761);
- h2 = Math.imul(h2 ^ ch, 1597334677);
- }
- h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
- h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
- return 4294967296 * (2097151 & h2) + (h1>>>0);
-};
-
-function onlyUnique (value, index, array) {
- return array.indexOf(value) === index;
-}
-
-function getUrlWithoutArguments() {
- return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
-}
-
-function changeFavicon(src) {
- document.querySelector('[rel="icon"]').href = src;
- document.querySelector('[rel="shortcut icon"]').href = src;
-}
-
-function arrayBufferToBase64(buffer) {
- var binary = '';
- var bytes = new Uint8Array(buffer);
- var len = bytes.byteLength;
- for (var i = 0; i < len; i++) {
- binary += String.fromCharCode(bytes[i]);
- }
- return window.btoa( binary );
-}
-
-function base64ToArrayBuffer(base64) {
- var binary_string = window.atob(base64);
- var len = binary_string.length;
- var bytes = new Uint8Array(len);
- for (var i = 0; i < len; i++) {
- bytes[i] = binary_string.charCodeAt(i);
- }
- return bytes.buffer;
-}
\ No newline at end of file
diff --git a/public_included_ws_fallback/scripts/zip.min.js b/public_included_ws_fallback/scripts/zip.min.js
deleted file mode 100644
index b9db6df..0000000
--- a/public_included_ws_fallback/scripts/zip.min.js
+++ /dev/null
@@ -1 +0,0 @@
-((e,t)=>{"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).zip={})})(this,(function(e){"use strict";const{Array:t,Object:n,String:r,Number:s,BigInt:i,Math:a,Date:o,Map:c,Set:l,Response:u,URL:f,Error:d,Uint8Array:w,Uint16Array:h,Uint32Array:p,DataView:g,Blob:m,Promise:y,TextEncoder:b,TextDecoder:S,document:k,crypto:z,btoa:x,TransformStream:v,ReadableStream:C,WritableStream:_,CompressionStream:D,DecompressionStream:R,navigator:F,Worker:E}="undefined"!=typeof globalThis?globalThis:this||self,I=4294967295,A=65535,T=67324752,H=134695760,O=33639248,U=101075792,N=117853008,W=21589,q=2048,L="/",M=new o(2107,11,31),P=new o(1980,0,1),V=void 0,B="undefined",K="function";class Y{constructor(e){return class extends v{constructor(t,n){const r=new e(n);super({transform(e,t){t.enqueue(r.append(e))},flush(e){const t=r.flush();t&&e.enqueue(t)}})}}}}let Z=2;try{typeof F!=B&&F.hardwareConcurrency&&(Z=F.hardwareConcurrency)}catch(e){}const X={chunkSize:524288,maxWorkers:Z,terminateWorkerTimeout:5e3,useWebWorkers:!0,useCompressionStream:!0,workerScripts:V,CompressionStreamNative:typeof D!=B&&D,DecompressionStreamNative:typeof R!=B&&R},G=n.assign({},X);function j(){return G}function J(e){return a.max(e.chunkSize,64)}function Q(e){const{baseURL:n,chunkSize:r,maxWorkers:s,terminateWorkerTimeout:i,useCompressionStream:a,useWebWorkers:o,Deflate:c,Inflate:l,CompressionStream:u,DecompressionStream:f,workerScripts:w}=e;if($("baseURL",n),$("chunkSize",r),$("maxWorkers",s),$("terminateWorkerTimeout",i),$("useCompressionStream",a),$("useWebWorkers",o),c&&(G.CompressionStream=new Y(c)),l&&(G.DecompressionStream=new Y(l)),$("CompressionStream",u),$("DecompressionStream",f),w!==V){const{deflate:e,inflate:n}=w;if((e||n)&&(G.workerScripts||(G.workerScripts={})),e){if(!t.isArray(e))throw new d("workerScripts.deflate must be an array");G.workerScripts.deflate=e}if(n){if(!t.isArray(n))throw new d("workerScripts.inflate must be an array");G.workerScripts.inflate=n}}}function $(e,t){t!==V&&(G[e]=t)}function ee(e,t,r){return class{constructor(s){const i=this;n.hasOwn(s,"level")&&void 0===s.level&&delete s.level,i.codec=new e(n.assign({},t,s)),r(i.codec,(e=>{if(i.pendingData){const t=i.pendingData;i.pendingData=new w(t.length+e.length);const{pendingData:n}=i;n.set(t,0),n.set(e,t.length)}else i.pendingData=new w(e)}))}append(e){return this.codec.push(e),s(this)}flush(){return this.codec.push(new w,!0),s(this)}};function s(e){if(e.pendingData){const t=e.pendingData;return e.pendingData=null,t}return new w}}const te=[];for(let e=0;256>e;e++){let t=e;for(let e=0;8>e;e++)1&t?t=t>>>1^3988292384:t>>>=1;te[e]=t}class ne{constructor(e){this.crc=e||-1}append(e){let t=0|this.crc;for(let n=0,r=0|e.length;r>n;n++)t=t>>>8^te[255&(t^e[n])];this.crc=t}get(){return~this.crc}}class re extends v{constructor(){const e=new ne;super({transform(t){e.append(t)},flush(t){const n=new w(4);new g(n.buffer).setUint32(0,e.get()),t.enqueue(n)}})}}function se(e){if(void 0===b){const t=new w((e=unescape(encodeURIComponent(e))).length);for(let n=0;n0&&t&&(e[n-1]=ie.partial(t,e[n-1]&2147483648>>t-1,1)),e},partial:(e,t,n)=>32===e?t:(n?0|t:t<<32-e)+1099511627776*e,getPartial:e=>a.round(e/1099511627776)||32,_shiftRight(e,t,n,r){for(void 0===r&&(r=[]);t>=32;t-=32)r.push(n),n=0;if(0===t)return r.concat(e);for(let s=0;s>>t),n=e[s]<<32-t;const s=e.length?e[e.length-1]:0,i=ie.getPartial(s);return r.push(ie.partial(t+i&31,t+i>32?n:r.pop(),1)),r}},ae={bytes:{fromBits(e){const t=ie.bitLength(e)/8,n=new w(t);let r;for(let s=0;t>s;s++)0==(3&s)&&(r=e[s/4]),n[s]=r>>>24,r<<=8;return n},toBits(e){const t=[];let n,r=0;for(n=0;n{let t=987654321;const n=4294967295;return()=>(t=36969*(65535&t)+(t>>16)&n,(((t<<16)+(e=18e3*(65535&e)+(e>>16)&n)&n)/4294967296+.5)*(a.random()>.5?1:-1))};for(let r,s=0;snew ce.hmacSha1(ae.bytes.toBits(e)),pbkdf2(e,t,n,r){if(n=n||1e4,0>r||0>n)throw new d("invalid params to pbkdf2");const s=1+(r>>5)<<2;let i,a,o,c,l;const u=new ArrayBuffer(s),f=new g(u);let w=0;const h=ie;for(t=ae.bytes.toBits(t),l=1;(s||1)>w;l++){for(i=a=e.encrypt(h.concat(t,[l])),o=1;n>o;o++)for(a=e.encrypt(a),c=0;cw&&o9007199254740991)throw new d("Cannot hash more than 2^53 - 1 bits");const i=new p(n);let a=0;for(let e=t.blockSize+r-(t.blockSize+r&t.blockSize-1);s>=e;e+=t.blockSize)t._block(i.subarray(16*a,16*(a+1))),a+=1;return n.splice(0,16*a),t}finalize(){const e=this;let t=e._buffer;const n=e._h;t=ie.concat(t,[ie.partial(1,1)]);for(let e=t.length+2;15&e;e++)t.push(0);for(t.push(a.floor(e._length/4294967296)),t.push(0|e._length);t.length;)e._block(t.splice(0,16));return e.reset(),n}_f(e,t,n,r){return e>19?e>39?e>59?e>79?void 0:t^n^r:t&n|t&r|n&r:t^n^r:t&n|~t&r}_S(e,t){return t<>>32-e}_block(e){const n=this,r=n._h,s=t(80);for(let t=0;16>t;t++)s[t]=e[t];let i=r[0],o=r[1],c=r[2],l=r[3],u=r[4];for(let e=0;79>=e;e++){16>e||(s[e]=n._S(1,s[e-3]^s[e-8]^s[e-14]^s[e-16]));const t=n._S(5,i)+n._f(e,o,c,l)+u+s[e]+n._key[a.floor(e/20)]|0;u=l,l=c,c=n._S(30,o),o=i,i=t}r[0]=r[0]+i|0,r[1]=r[1]+o|0,r[2]=r[2]+c|0,r[3]=r[3]+l|0,r[4]=r[4]+u|0}},s=[[],[]];n._baseHash=[new r,new r];const i=n._baseHash[0].blockSize/32;e.length>i&&(e=r.hash(e));for(let t=0;i>t;t++)s[0][t]=909522486^e[t],s[1][t]=1549556828^e[t];n._baseHash[0].update(s[0]),n._baseHash[1].update(s[1]),n._resultHash=new r(n._baseHash[0])}reset(){const e=this;e._resultHash=new e._hash(e._baseHash[0]),e._updated=!1}update(e){this._updated=!0,this._resultHash.update(e)}digest(){const e=this,t=e._resultHash.finalize(),n=new e._hash(e._baseHash[1]).update(t).finalize();return e.reset(),n}encrypt(e){if(this._updated)throw new d("encrypt on already updated hmac called!");return this.update(e),this.digest(e)}}},le=void 0!==z&&"function"==typeof z.getRandomValues,ue="Invalid password",fe="Invalid signature";function de(e){return le?z.getRandomValues(e):oe.getRandomValues(e)}const we=16,he={name:"PBKDF2"},pe=n.assign({hash:{name:"HMAC"}},he),ge=n.assign({iterations:1e3,hash:{name:"SHA-1"}},he),me=["deriveBits"],ye=[8,12,16],be=[16,24,32],Se=10,ke=[0,0,0,0],ze="undefined",xe="function",ve=typeof z!=ze,Ce=ve&&z.subtle,_e=ve&&typeof Ce!=ze,De=ae.bytes,Re=class{constructor(e){const t=this;t._tables=[[[],[],[],[],[]],[[],[],[],[],[]]],t._tables[0][0][0]||t._precompute();const n=t._tables[0][4],r=t._tables[1],s=e.length;let i,a,o,c=1;if(4!==s&&6!==s&&8!==s)throw new d("invalid aes key size");for(t._key=[a=e.slice(0),o=[]],i=s;4*s+28>i;i++){let e=a[i-1];(i%s==0||8===s&&i%s==4)&&(e=n[e>>>24]<<24^n[e>>16&255]<<16^n[e>>8&255]<<8^n[255&e],i%s==0&&(e=e<<8^e>>>24^c<<24,c=c<<1^283*(c>>7))),a[i]=a[i-s]^e}for(let e=0;i;e++,i--){const t=a[3&e?i:i-4];o[e]=4>=i||4>e?t:r[0][n[t>>>24]]^r[1][n[t>>16&255]]^r[2][n[t>>8&255]]^r[3][n[255&t]]}}encrypt(e){return this._crypt(e,0)}decrypt(e){return this._crypt(e,1)}_precompute(){const e=this._tables[0],t=this._tables[1],n=e[4],r=t[4],s=[],i=[];let a,o,c,l;for(let e=0;256>e;e++)i[(s[e]=e<<1^283*(e>>7))^e]=e;for(let u=a=0;!n[u];u^=o||1,a=i[a]||1){let i=a^a<<1^a<<2^a<<3^a<<4;i=i>>8^255&i^99,n[u]=i,r[i]=u,l=s[c=s[o=s[u]]];let f=16843009*l^65537*c^257*o^16843008*u,d=257*s[i]^16843008*i;for(let n=0;4>n;n++)e[n][u]=d=d<<24^d>>>8,t[n][i]=f=f<<24^f>>>8}for(let n=0;5>n;n++)e[n]=e[n].slice(0),t[n]=t[n].slice(0)}_crypt(e,t){if(4!==e.length)throw new d("invalid aes block size");const n=this._key[t],r=n.length/4-2,s=[0,0,0,0],i=this._tables[t],a=i[0],o=i[1],c=i[2],l=i[3],u=i[4];let f,w,h,p=e[0]^n[0],g=e[t?3:1]^n[1],m=e[2]^n[2],y=e[t?1:3]^n[3],b=4;for(let e=0;r>e;e++)f=a[p>>>24]^o[g>>16&255]^c[m>>8&255]^l[255&y]^n[b],w=a[g>>>24]^o[m>>16&255]^c[y>>8&255]^l[255&p]^n[b+1],h=a[m>>>24]^o[y>>16&255]^c[p>>8&255]^l[255&g]^n[b+2],y=a[y>>>24]^o[p>>16&255]^c[g>>8&255]^l[255&m]^n[b+3],b+=4,p=f,g=w,m=h;for(let e=0;4>e;e++)s[t?3&-e:e]=u[p>>>24]<<24^u[g>>16&255]<<16^u[m>>8&255]<<8^u[255&y]^n[b++],f=p,p=g,g=m,m=y,y=f;return s}},Fe=class{constructor(e,t){this._prf=e,this._initIv=t,this._iv=t}reset(){this._iv=this._initIv}update(e){return this.calculate(this._prf,e,this._iv)}incWord(e){if(255==(e>>24&255)){let t=e>>16&255,n=e>>8&255,r=255&e;255===t?(t=0,255===n?(n=0,255===r?r=0:++r):++n):++t,e=0,e+=t<<16,e+=n<<8,e+=r}else e+=1<<24;return e}incCounter(e){0===(e[0]=this.incWord(e[0]))&&(e[1]=this.incWord(e[1]))}calculate(e,t,n){let r;if(!(r=t.length))return[];const s=ie.bitLength(t);for(let s=0;r>s;s+=4){this.incCounter(n);const r=e.encrypt(n);t[s]^=r[0],t[s+1]^=r[1],t[s+2]^=r[2],t[s+3]^=r[3]}return ie.clamp(t,s)}},Ee=ce.hmacSha1;let Ie=ve&&_e&&typeof Ce.importKey==xe,Ae=ve&&_e&&typeof Ce.deriveBits==xe;class Te extends v{constructor({password:e,signed:t,encryptionStrength:r}){super({start(){n.assign(this,{ready:new y((e=>this.resolveReady=e)),password:e,signed:t,strength:r-1,pending:new w})},async transform(e,t){const n=this,{password:r,strength:s,resolveReady:i,ready:a}=n;r?(await(async(e,t,n,r)=>{const s=await Ue(e,t,n,We(r,0,ye[t])),i=We(r,ye[t]);if(s[0]!=i[0]||s[1]!=i[1])throw new d(ue)})(n,s,r,We(e,0,ye[s]+2)),e=We(e,ye[s]+2),i()):await a;const o=new w(e.length-Se-(e.length-Se)%we);t.enqueue(Oe(n,e,o,0,Se,!0))},async flush(e){const{signed:t,ctr:n,hmac:r,pending:s,ready:i}=this;await i;const a=We(s,0,s.length-Se),o=We(s,s.length-Se);let c=new w;if(a.length){const e=Le(De,a);r.update(e);const t=n.update(e);c=qe(De,t)}if(t){const e=We(qe(De,r.digest()),0,Se);for(let t=0;Se>t;t++)if(e[t]!=o[t])throw new d(fe)}e.enqueue(c)}})}}class He extends v{constructor({password:e,encryptionStrength:t}){let r;super({start(){n.assign(this,{ready:new y((e=>this.resolveReady=e)),password:e,strength:t-1,pending:new w})},async transform(e,t){const n=this,{password:r,strength:s,resolveReady:i,ready:a}=n;let o=new w;r?(o=await(async(e,t,n)=>{const r=de(new w(ye[t]));return Ne(r,await Ue(e,t,n,r))})(n,s,r),i()):await a;const c=new w(o.length+e.length-e.length%we);c.set(o,0),t.enqueue(Oe(n,e,c,o.length,0))},async flush(e){const{ctr:t,hmac:n,pending:s,ready:i}=this;await i;let a=new w;if(s.length){const e=t.update(Le(De,s));n.update(e),a=qe(De,e)}r.signature=qe(De,n.digest()).slice(0,Se),e.enqueue(Ne(a,r.signature))}}),r=this}}function Oe(e,t,n,r,s,i){const{ctr:a,hmac:o,pending:c}=e,l=t.length-s;let u;for(c.length&&(t=Ne(c,t),n=((e,t)=>{if(t&&t>e.length){const n=e;(e=new w(t)).set(n,0)}return e})(n,l-l%we)),u=0;l-we>=u;u+=we){const e=Le(De,We(t,u,u+we));i&&o.update(e);const s=a.update(e);i||o.update(s),n.set(qe(De,s),u+r)}return e.pending=We(t,u),n}async function Ue(e,r,s,i){e.password=null;const a=se(s),o=await(async(e,t,n,r,s)=>{if(!Ie)return ce.importKey(t);try{return await Ce.importKey("raw",t,n,!1,s)}catch(e){return Ie=!1,ce.importKey(t)}})(0,a,pe,0,me),c=await(async(e,t,n)=>{if(!Ae)return ce.pbkdf2(t,e.salt,ge.iterations,n);try{return await Ce.deriveBits(e,t,n)}catch(r){return Ae=!1,ce.pbkdf2(t,e.salt,ge.iterations,n)}})(n.assign({salt:i},ge),o,8*(2*be[r]+2)),l=new w(c),u=Le(De,We(l,0,be[r])),f=Le(De,We(l,be[r],2*be[r])),d=We(l,2*be[r]);return n.assign(e,{keys:{key:u,authentication:f,passwordVerification:d},ctr:new Fe(new Re(u),t.from(ke)),hmac:new Ee(f)}),d}function Ne(e,t){let n=e;return e.length+t.length&&(n=new w(e.length+t.length),n.set(e,0),n.set(t,e.length)),n}function We(e,t,n){return e.subarray(t,n)}function qe(e,t){return e.fromBits(t)}function Le(e,t){return e.toBits(t)}class Me extends v{constructor({password:e,passwordVerification:t}){super({start(){n.assign(this,{password:e,passwordVerification:t}),Ke(this,e)},transform(e,t){const n=this;if(n.password){const t=Ve(n,e.subarray(0,12));if(n.password=null,t[11]!=n.passwordVerification)throw new d(ue);e=e.subarray(12)}t.enqueue(Ve(n,e))}})}}class Pe extends v{constructor({password:e,passwordVerification:t}){super({start(){n.assign(this,{password:e,passwordVerification:t}),Ke(this,e)},transform(e,t){const n=this;let r,s;if(n.password){n.password=null;const t=de(new w(12));t[11]=n.passwordVerification,r=new w(e.length+t.length),r.set(Be(n,t),0),s=12}else r=new w(e.length),s=0;r.set(Be(n,e),s),t.enqueue(r)}})}}function Ve(e,t){const n=new w(t.length);for(let r=0;r>>24]),s=~e.crcKey2.get(),e.keys=[n,r,s]}function Ze(e){const t=2|e.keys[2];return Xe(a.imul(t,1^t)>>>8)}function Xe(e){return 255&e}function Ge(e){return 4294967295&e}const je="deflate-raw";class Je extends v{constructor(e,{chunkSize:t,CompressionStream:n,CompressionStreamNative:r}){super({});const{compressed:s,encrypted:i,useCompressionStream:a,zipCrypto:o,signed:c,level:l}=e,u=this;let f,d,w=$e(super.readable);i&&!o||!c||([w,f]=w.tee(),f=nt(f,new re)),s&&(w=tt(w,a,{level:l,chunkSize:t},r,n)),i&&(o?w=nt(w,new Pe(e)):(d=new He(e),w=nt(w,d))),et(u,w,(async()=>{let e;i&&!o&&(e=d.signature),i&&!o||!c||(e=await f.getReader().read(),e=new g(e.value.buffer).getUint32(0)),u.signature=e}))}}class Qe extends v{constructor(e,{chunkSize:t,DecompressionStream:n,DecompressionStreamNative:r}){super({});const{zipCrypto:s,encrypted:i,signed:a,signature:o,compressed:c,useCompressionStream:l}=e;let u,f,w=$e(super.readable);i&&(s?w=nt(w,new Me(e)):(f=new Te(e),w=nt(w,f))),c&&(w=tt(w,l,{chunkSize:t},r,n)),i&&!s||!a||([w,u]=w.tee(),u=nt(u,new re)),et(this,w,(async()=>{if((!i||s)&&a){const e=await u.getReader().read(),t=new g(e.value.buffer);if(o!=t.getUint32(0,!1))throw new d(fe)}}))}}function $e(e){return nt(e,new v({transform(e,t){e&&e.length&&t.enqueue(e)}}))}function et(e,t,r){t=nt(t,new v({flush:r})),n.defineProperty(e,"readable",{get:()=>t})}function tt(e,t,n,r,s){try{e=nt(e,new(t&&r?r:s)(je,n))}catch(r){if(!t)throw r;e=nt(e,new s(je,n))}return e}function nt(e,t){return e.pipeThrough(t)}const rt="data",st="deflate",it="inflate";class at extends v{constructor(e,t){super({});const r=this,{codecType:s}=e;let i;s.startsWith(st)?i=Je:s.startsWith(it)&&(i=Qe);let a=0;const o=new i(e,t),c=super.readable,l=new v({transform(e,t){e&&e.length&&(a+=e.length,t.enqueue(e))},flush(){const{signature:e}=o;n.assign(r,{signature:e,size:a})}});n.defineProperty(r,"readable",{get:()=>c.pipeThrough(o).pipeThrough(l)})}}const ot=typeof E!=B;class ct{constructor(e,{readable:t,writable:r},{options:s,config:i,streamOptions:a,useWebWorkers:o,transferStreams:c,scripts:l},u){const{signal:f}=a;return n.assign(e,{busy:!0,readable:t.pipeThrough(new lt(t,a,i),{signal:f}),writable:r,options:n.assign({},s),scripts:l,transferStreams:c,terminate(){const{worker:t,busy:n}=e;t&&!n&&(t.terminate(),e.interface=null)},onTaskFinished(){e.busy=!1,u(e)}}),(o&&ot?dt:ft)(e,i)}}class lt extends v{constructor(e,{onstart:t,onprogress:n,size:r,onend:s},{chunkSize:i}){let a=0;super({start(){t&&ut(t,r)},async transform(e,t){a+=e.length,n&&await ut(n,a,r),t.enqueue(e)},flush(){e.size=a,s&&ut(s,a)}},{highWaterMark:1,size:()=>i})}}async function ut(e,...t){try{await e(...t)}catch(e){}}function ft(e,t){return{run:()=>(async({options:e,readable:t,writable:n,onTaskFinished:r},s)=>{const i=new at(e,s);try{await t.pipeThrough(i).pipeTo(n,{preventClose:!0,preventAbort:!0});const{signature:e,size:s}=i;return{signature:e,size:s}}finally{r()}})(e,t)}}function dt(e,{baseURL:t,chunkSize:r}){return e.interface||n.assign(e,{worker:pt(e.scripts[0],t,e),interface:{run:()=>(async(e,t)=>{let r,s;const i=new y(((e,t)=>{r=e,s=t}));n.assign(e,{reader:null,writer:null,resolveResult:r,rejectResult:s,result:i});const{readable:a,options:o,scripts:c}=e,{writable:l,closed:u}=(e=>{const t=e.getWriter();let n;const r=new y((e=>n=e));return{writable:new _({async write(e){await t.ready,await t.write(e)},close(){t.releaseLock(),n()},abort:e=>t.abort(e)}),closed:r}})(e.writable);gt({type:"start",scripts:c.slice(1),options:o,config:t,readable:a,writable:l},e)||n.assign(e,{reader:a.getReader(),writer:l.getWriter()});const f=await i;try{await l.close()}catch(e){}return await u,f})(e,{chunkSize:r})}}),e.interface}let wt=!0,ht=!0;function pt(e,t,r){const s={type:"module"};let i,a;typeof e==K&&(e=e());try{i=new f(e,t)}catch(t){i=e}if(wt)try{a=new E(i)}catch(e){wt=!1,a=new E(i,s)}else a=new E(i,s);return a.addEventListener("message",(e=>(async({data:e},t)=>{const{type:r,value:s,messageId:i,result:a,error:o}=e,{reader:c,writer:l,resolveResult:u,rejectResult:f,onTaskFinished:h}=t;try{if(o){const{message:e,stack:t,code:r,name:s}=o,i=new d(e);n.assign(i,{stack:t,code:r,name:s}),p(i)}else{if("pull"==r){const{value:e,done:n}=await c.read();gt({type:rt,value:e,done:n,messageId:i},t)}r==rt&&(await l.ready,await l.write(new w(s)),gt({type:"ack",messageId:i},t)),"close"==r&&p(null,a)}}catch(o){p(o)}function p(e,t){e?f(e):u(t),l&&l.releaseLock(),h()}})(e,r))),a}function gt(e,{worker:t,writer:n,onTaskFinished:r,transferStreams:s}){try{let{value:n,readable:r,writable:i}=e;const a=[];if(n){const{buffer:t,length:r}=n;r!=t.byteLength&&(n=new w(n)),e.value=n.buffer,a.push(e.value)}if(s&&ht?(r&&a.push(r),i&&a.push(i)):e.readable=e.writable=null,a.length)try{return t.postMessage(e,a),!0}catch(n){ht=!1,e.readable=e.writable=null,t.postMessage(e)}else t.postMessage(e)}catch(e){throw n&&n.releaseLock(),r(),e}}let mt=[];const yt=[];let bt=0;async function St(e,t){const{options:n,config:r}=t,{transferStreams:i,useWebWorkers:a,useCompressionStream:o,codecType:c,compressed:l,signed:u,encrypted:f}=n,{workerScripts:d,maxWorkers:w,terminateWorkerTimeout:h}=r;t.transferStreams=i||i===V;const p=!(l||u||f||t.transferStreams);let g;t.useWebWorkers=!p&&(a||a===V&&r.useWebWorkers),t.scripts=t.useWebWorkers&&d?d[c]:[],n.useCompressionStream=o||o===V&&r.useCompressionStream;const m=mt.find((e=>!e.busy));if(m)kt(m),g=new ct(m,e,t,b);else if(mt.lengthyt.push({resolve:n,stream:e,workerOptions:t})));return g.run();function b(e){if(yt.length){const[{resolve:t,stream:n,workerOptions:r}]=yt.splice(0,1);t(new ct(e,n,r,b))}else e.worker?(kt(e),s.isFinite(h)&&h>=0&&(e.terminateTimeout=setTimeout((()=>{mt=mt.filter((t=>t!=e)),e.terminate()}),h))):mt=mt.filter((t=>t!=e))}}function kt(e){const{terminateTimeout:t}=e;t&&(clearTimeout(t),e.terminateTimeout=null)}const zt="HTTP error ",xt="HTTP Range not supported",vt="Writer iterator completed too soon",Ct="GET",_t=65536,Dt="writable";class Rt{constructor(){this.size=0}init(){this.initialized=!0}}class Ft extends Rt{get readable(){const e=this,{chunkSize:t=_t}=e,n=new C({start(){this.chunkOffset=0},async pull(r){const{offset:s=0,size:i,diskNumberStart:o}=n,{chunkOffset:c}=this;r.enqueue(await Qt(e,s+c,a.min(t,i-c),o)),c+t>i?r.close():this.chunkOffset+=t}});return n}}class Et extends Rt{constructor(){super();const e=this,t=new _({write:t=>e.writeUint8Array(t)});n.defineProperty(e,Dt,{get:()=>t})}writeUint8Array(){}}class It extends Ft{constructor(e){super(),n.assign(this,{blob:e,size:e.size})}async readUint8Array(e,t){const n=this,r=e+t,s=e||rt.writable}),this.blob=new u(t.readable,{headers:r}).blob()}getData(){return this.blob}}class Tt extends Ft{constructor(e,t){super(),Ot(this,e,t)}async init(){super.init(),await Ut(this,Bt,Lt)}readUint8Array(e,t){return Nt(this,e,t,Bt,Lt)}}class Ht extends Ft{constructor(e,t){super(),Ot(this,e,t)}async init(){super.init(),await Ut(this,Kt,Mt)}readUint8Array(e,t){return Nt(this,e,t,Kt,Mt)}}function Ot(e,t,r){const{preventHeadRequest:s,useRangeHeader:i,forceRangeRequests:a}=r;delete(r=n.assign({},r)).preventHeadRequest,delete r.useRangeHeader,delete r.forceRangeRequests,delete r.useXHR,n.assign(e,{url:t,options:r,preventHeadRequest:s,useRangeHeader:i,forceRangeRequests:a})}async function Ut(e,t,n){const{url:r,useRangeHeader:i,forceRangeRequests:a}=e;if((e=>{const{baseURL:t}=j(),{protocol:n}=new f(e,t);return"http:"==n||"https:"==n})(r)&&(i||a)){const{headers:r}=await t(Ct,e,Wt(e));if(!a&&"bytes"!=r.get("Accept-Ranges"))throw new d(xt);{let i;const a=r.get("Content-Range");if(a){const e=a.trim().split(/\s*\/\s*/);if(e.length){const t=e[1];t&&"*"!=t&&(i=s(t))}}i===V?await Vt(e,t,n):e.size=i}}else await Vt(e,t,n)}async function Nt(e,t,n,r,s){const{useRangeHeader:i,forceRangeRequests:a,options:o}=e;if(i||a){const s=await r(Ct,e,Wt(e,t,n));if(206!=s.status)throw new d(xt);return new w(await s.arrayBuffer())}{const{data:r}=e;return r||await s(e,o),new w(e.data.subarray(t,t+n))}}function Wt(e,t=0,r=1){return n.assign({},qt(e),{Range:"bytes="+t+"-"+(t+r-1)})}function qt({options:e}){const{headers:t}=e;if(t)return Symbol.iterator in t?n.fromEntries(t):t}async function Lt(e){await Pt(e,Bt)}async function Mt(e){await Pt(e,Kt)}async function Pt(e,t){const n=await t(Ct,e,qt(e));e.data=new w(await n.arrayBuffer()),e.size||(e.size=e.data.length)}async function Vt(e,t,n){if(e.preventHeadRequest)await n(e,e.options);else{const r=(await t("HEAD",e,qt(e))).headers.get("Content-Length");r?e.size=s(r):await n(e,e.options)}}async function Bt(e,{options:t,url:r},s){const i=await fetch(r,n.assign({},t,{method:e,headers:s}));if(400>i.status)return i;throw 416==i.status?new d(xt):new d(zt+(i.statusText||i.status))}function Kt(e,{url:t},r){return new y(((s,i)=>{const a=new XMLHttpRequest;if(a.addEventListener("load",(()=>{if(400>a.status){const e=[];a.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach((t=>{const n=t.trim().split(/\s*:\s*/);n[0]=n[0].trim().replace(/^[a-z]|-[a-z]/g,(e=>e.toUpperCase())),e.push(n)})),s({status:a.status,arrayBuffer:()=>a.response,headers:new c(e)})}else i(416==a.status?new d(xt):new d(zt+(a.statusText||a.status)))}),!1),a.addEventListener("error",(e=>i(e.detail.error)),!1),a.open(e,t),r)for(const e of n.entries(r))a.setRequestHeader(e[0],e[1]);a.responseType="arraybuffer",a.send()}))}class Yt extends Ft{constructor(e,t={}){super(),n.assign(this,{url:e,reader:t.useXHR?new Ht(e,t):new Tt(e,t)})}set size(e){}get size(){return this.reader.size}async init(){super.init(),await this.reader.init()}readUint8Array(e,t){return this.reader.readUint8Array(e,t)}}class Zt extends Ft{constructor(e){super(),this.readers=e}async init(){super.init();const e=this,{readers:t}=e;e.lastDiskNumber=0,await y.all(t.map((async t=>{await t.init(),e.size+=t.size})))}async readUint8Array(e,t,n=0){const r=this,{readers:s}=this;let i,o=n;-1==o&&(o=s.length-1);let c=e;for(;c>=s[o].size;)c-=s[o].size,o++;const l=s[o],u=l.size;if(c+t>u){const s=u-c;i=new w(t),i.set(await Qt(l,c,s)),i.set(await r.readUint8Array(e+s,t-s,n),s)}else i=await Qt(l,c,t);return r.lastDiskNumber=a.max(o,r.lastDiskNumber),i}}class Xt extends Rt{constructor(e,t=4294967295){super();const r=this;let s,i,a;n.assign(r,{diskNumber:0,diskOffset:0,size:0,maxSize:t,availableSize:t});const o=new _({async write(t){const{availableSize:n}=r;if(a)t.lengtho})}}async function Gt(e,t){e.init&&!e.initialized&&await e.init(t)}function jt(e){return t.isArray(e)&&(e=new Zt(e)),e instanceof C&&(e={readable:e}),e}function Jt(e){e.writable===V&&typeof e.next==K&&(e=new Xt(e)),e instanceof _&&(e={writable:e});const{writable:t}=e;return t.size===V&&(t.size=0),e instanceof Xt||n.assign(e,{diskNumber:0,diskOffset:0,availableSize:1/0,maxSize:1/0}),e}function Qt(e,t,n,r){return e.readUint8Array(t,n,r)}const $t=Zt,en=Xt,tn="\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split("");function nn(e,t){return t&&"cp437"==t.trim().toLowerCase()?(e=>{{let t="";for(let n=0;nthis[t]=e[t]))}}const zn="File format is not recognized",xn="End of central directory not found",vn="End of Zip64 central directory not found",Cn="End of Zip64 central directory locator not found",_n="Central directory header not found",Dn="Local file header not found",Rn="Zip64 extra field not found",Fn="File contains encrypted entry",En="Encryption method not supported",In="Compression method not supported",An="Split zip file",Tn="utf-8",Hn="cp437",On=[[cn,I],[ln,I],[un,I],[fn,A]],Un={[A]:{getValue:Yn,bytes:4},[I]:{getValue:Zn,bytes:8}};class Nn{constructor(e,t,r){n.assign(this,{reader:e,config:t,options:r})}async getData(e,t,r={}){const s=this,{reader:i,offset:a,diskNumberStart:o,extraFieldAES:c,compressionMethod:l,config:u,bitFlag:f,signature:h,rawLastModDate:p,uncompressedSize:g,compressedSize:m}=s,y=s.localDirectory={},b=Xn(await Qt(i,a,30,o));let S=Mn(s,r,"password");if(S=S&&S.length&&S,c&&99!=c.originalCompressionMethod)throw new d(In);if(0!=l&&8!=l)throw new d(In);if(Yn(b,0)!=T)throw new d(Dn);Wn(y,b,4),y.rawExtraField=y.extraFieldLength?await Qt(i,a+30+y.filenameLength,y.extraFieldLength,o):new w,await qn(s,y,b,4),n.assign(t,{lastAccessDate:y.lastAccessDate,creationDate:y.creationDate});const k=s.encrypted&&y.encrypted,z=k&&!c;if(k){if(!z&&c.strength===V)throw new d(En);if(!S)throw new d(Fn)}const x=a+30+y.filenameLength+y.extraFieldLength,v=i.readable;v.diskNumberStart=o,v.offset=x;const C=v.size=m,_=Mn(s,r,"signal");e=Jt(e),await Gt(e,g);const{writable:D}=e,{onstart:R,onprogress:F,onend:E}=r,I={options:{codecType:it,password:S,zipCrypto:z,encryptionStrength:c&&c.strength,signed:Mn(s,r,"checkSignature"),passwordVerification:z&&(f.dataDescriptor?p>>>8&255:h>>>24&255),signature:h,compressed:0!=l,encrypted:k,useWebWorkers:Mn(s,r,"useWebWorkers"),useCompressionStream:Mn(s,r,"useCompressionStream"),transferStreams:Mn(s,r,"transferStreams")},config:u,streamOptions:{signal:_,size:C,onstart:R,onprogress:F,onend:E}};return D.size+=(await St({readable:v,writable:D},I)).size,Mn(s,r,"preventClose")||await D.close(),e.getData?e.getData():D}}function Wn(e,t,r){const s=e.rawBitFlag=Kn(t,r+2),i=1==(1&s),a=Yn(t,r+6);n.assign(e,{encrypted:i,version:Kn(t,r),bitFlag:{level:(6&s)>>1,dataDescriptor:8==(8&s),languageEncodingFlag:(s&q)==q},rawLastModDate:a,lastModDate:Pn(a),filenameLength:Kn(t,r+22),extraFieldLength:Kn(t,r+24)})}async function qn(e,t,r,s){const{rawExtraField:i}=t,a=t.extraField=new c,l=Xn(new w(i));let u=0;try{for(;u{t.zip64=!0;const n=Xn(e.data),r=On.filter((([e,n])=>t[e]==n));for(let s=0,i=0;s{const s=Xn(e.data),i=Bn(s,4);n.assign(e,{vendorVersion:Bn(s,0),vendorId:Bn(s,2),strength:i,originalCompressionMethod:r,compressionMethod:Kn(s,5)}),t.compressionMethod=e.compressionMethod})(m,t,f),t.extraFieldAES=m):t.compressionMethod=f;const y=a.get(10);y&&(((e,t)=>{const r=Xn(e.data);let s,i=4;try{for(;i{const n=Xn(e.data),r=Bn(n,0),s=[],i=[];1==(1&r)&&(s.push(dn),i.push(wn)),2==(2&r)&&(s.push(hn),i.push("rawLastAccessDate")),4==(4&r)&&(s.push(pn),i.push("rawCreationDate"));let a=1;s.forEach(((r,s)=>{if(e.data.length>=a+4){const c=Yn(n,a);t[r]=e[r]=new o(1e3*c);const l=i[s];e[l]=c}a+=4}))})(b,t),t.extraFieldExtendedTimestamp=b)}async function Ln(e,t,r,s,i){const a=Xn(e.data),o=new ne;o.append(i[r]);const c=Xn(new w(4));c.setUint32(0,o.get(),!0),n.assign(e,{version:Bn(a,0),signature:Yn(a,1),[t]:await nn(e.data.subarray(5)),valid:!i.bitFlag.languageEncodingFlag&&e.signature==Yn(c,0)}),e.valid&&(s[t]=e[t],s[t+"UTF8"]=!0)}function Mn(e,t,n){return t[n]===V?e.options[n]:t[n]}function Pn(e){const t=(4294901760&e)>>16,n=65535&e;try{return new o(1980+((65024&t)>>9),((480&t)>>5)-1,31&t,(63488&n)>>11,(2016&n)>>5,2*(31&n),0)}catch(e){}}function Vn(e){return new o(s(e/i(1e4)-i(116444736e5)))}function Bn(e,t){return e.getUint8(t)}function Kn(e,t){return e.getUint16(t,!0)}function Yn(e,t){return e.getUint32(t,!0)}function Zn(e,t){return s(e.getBigUint64(t,!0))}function Xn(e){return new g(e.buffer)}const Gn="File already exists",jn="Zip file comment exceeds 64KB",Jn="File entry comment exceeds 64KB",Qn="File entry name exceeds 64KB",$n="Version exceeds 65535",er="The strength must equal 1, 2, or 3",tr="Extra field type exceeds 65535",nr="Extra field data exceeds 64KB",rr="Zip64 is not supported (make sure 'keepOrder' is set to 'true')",sr=new w([7,0,2,0,65,69,3,0,0]);let ir=0;const ar=[];async function or(e,t){const n=e.getWriter();await n.ready,e.size+=gr(t),await n.write(t),n.releaseLock()}function cr(e){if(e)return(i(e.getTime())+i(116444736e5))*i(1e4)}function lr(e,t,n,r){const s=t[n]===V?e.options[n]:t[n];return s===V?r:s}function ur(e,t,n){e.setUint8(t,n)}function fr(e,t,n){e.setUint16(t,n,!0)}function dr(e,t,n){e.setUint32(t,n,!0)}function wr(e,t,n){e.setBigUint64(t,n,!0)}function hr(e,t,n){e.set(t,n)}function pr(e){return new g(e.buffer)}function gr(...e){let t=0;return e.forEach((e=>e&&(t+=e.length))),t}let mr;try{mr=void 0===k&&"undefined"==typeof location?new(require("url").URL)("file:"+__filename).href:void 0===k?location.href:k.currentScript&&k.currentScript.src||new f("zip.min.js",k.baseURI).href}catch(e){}Q({baseURL:mr}),(e=>{const t=()=>f.createObjectURL(new m(['const{Array:e,Object:t,Number:n,Math:r,Error:s,Uint8Array:i,Uint16Array:o,Uint32Array:c,Int32Array:f,Map:a,DataView:l,Promise:u,TextEncoder:w,crypto:h,postMessage:d,TransformStream:p,ReadableStream:y,WritableStream:m,CompressionStream:b,DecompressionStream:g}=self;class k{constructor(e){return class extends p{constructor(t,n){const r=new e(n);super({transform(e,t){t.enqueue(r.append(e))},flush(e){const t=r.flush();t&&e.enqueue(t)}})}}}}const v=[];for(let e=0;256>e;e++){let t=e;for(let e=0;8>e;e++)1&t?t=t>>>1^3988292384:t>>>=1;v[e]=t}class S{constructor(e){this.t=e||-1}append(e){let t=0|this.t;for(let n=0,r=0|e.length;r>n;n++)t=t>>>8^v[255&(t^e[n])];this.t=t}get(){return~this.t}}class z extends p{constructor(){const e=new S;super({transform(t){e.append(t)},flush(t){const n=new i(4);new l(n.buffer).setUint32(0,e.get()),t.enqueue(n)}})}}const C={concat(e,t){if(0===e.length||0===t.length)return e.concat(t);const n=e[e.length-1],r=C.i(n);return 32===r?e.concat(t):C.o(t,r,0|n,e.slice(0,e.length-1))},l(e){const t=e.length;if(0===t)return 0;const n=e[t-1];return 32*(t-1)+C.i(n)},u(e,t){if(32*e.length0&&t&&(e[n-1]=C.h(t,e[n-1]&2147483648>>t-1,1)),e},h:(e,t,n)=>32===e?t:(n?0|t:t<<32-e)+1099511627776*e,i:e=>r.round(e/1099511627776)||32,o(e,t,n,r){for(void 0===r&&(r=[]);t>=32;t-=32)r.push(n),n=0;if(0===t)return r.concat(e);for(let s=0;s>>t),n=e[s]<<32-t;const s=e.length?e[e.length-1]:0,i=C.i(s);return r.push(C.h(t+i&31,t+i>32?n:r.pop(),1)),r}},I={p:{m(e){const t=C.l(e)/8,n=new i(t);let r;for(let s=0;t>s;s++)0==(3&s)&&(r=e[s/4]),n[s]=r>>>24,r<<=8;return n},g(e){const t=[];let n,r=0;for(n=0;n{let t=987654321;const n=4294967295;return()=>(t=36969*(65535&t)+(t>>16)&n,(((t<<16)+(e=18e3*(65535&e)+(e>>16)&n)&n)/4294967296+.5)*(r.random()>.5?1:-1))};for(let s,i=0;inew _.k(I.p.g(e)),v(e,t,n,r){if(n=n||1e4,0>r||0>n)throw new s("invalid params to pbkdf2");const i=1+(r>>5)<<2;let o,c,f,a,u;const w=new ArrayBuffer(i),h=new l(w);let d=0;const p=C;for(t=I.p.g(t),u=1;(i||1)>d;u++){for(o=c=e.encrypt(p.concat(t,[u])),f=1;n>f;f++)for(c=e.encrypt(c),a=0;ad&&f9007199254740991)throw new s("Cannot hash more than 2^53 - 1 bits");const o=new c(n);let f=0;for(let e=t.blockSize+r-(t.blockSize+r&t.blockSize-1);i>=e;e+=t.blockSize)t.R(o.subarray(16*f,16*(f+1))),f+=1;return n.splice(0,16*f),t}B(){const e=this;let t=e.A;const n=e._;t=C.concat(t,[C.h(1,1)]);for(let e=t.length+2;15&e;e++)t.push(0);for(t.push(r.floor(e.D/4294967296)),t.push(0|e.D);t.length;)e.R(t.splice(0,16));return e.reset(),n}M(e,t,n,r){return e>19?e>39?e>59?e>79?void 0:t^n^r:t&n|t&r|n&r:t^n^r:t&n|~t&r}K(e,t){return t<>>32-e}R(t){const n=this,s=n._,i=e(80);for(let e=0;16>e;e++)i[e]=t[e];let o=s[0],c=s[1],f=s[2],a=s[3],l=s[4];for(let e=0;79>=e;e++){16>e||(i[e]=n.K(1,i[e-3]^i[e-8]^i[e-14]^i[e-16]));const t=n.K(5,o)+n.M(e,c,f,a)+l+i[e]+n.I[r.floor(e/20)]|0;l=a,a=f,f=n.K(30,c),c=o,o=t}s[0]=s[0]+o|0,s[1]=s[1]+c|0,s[2]=s[2]+f|0,s[3]=s[3]+a|0,s[4]=s[4]+l|0}},o=[[],[]];n.P=[new i,new i];const f=n.P[0].blockSize/32;t.length>f&&(t=i.hash(t));for(let e=0;f>e;e++)o[0][e]=909522486^t[e],o[1][e]=1549556828^t[e];n.P[0].update(o[0]),n.P[1].update(o[1]),n.U=new i(n.P[0])}reset(){const e=this;e.U=new e.S(e.P[0]),e.N=!1}update(e){this.N=!0,this.U.update(e)}digest(){const e=this,t=e.U.B(),n=new e.S(e.P[1]).update(t).B();return e.reset(),n}encrypt(e){if(this.N)throw new s("encrypt on already updated hmac called!");return this.update(e),this.digest(e)}}},A=void 0!==h&&"function"==typeof h.getRandomValues;function D(e){return A?h.getRandomValues(e):x.getRandomValues(e)}const V={name:"PBKDF2"},R=t.assign({hash:{name:"HMAC"}},V),B=t.assign({iterations:1e3,hash:{name:"SHA-1"}},V),E=["deriveBits"],M=[8,12,16],K=[16,24,32],P=[0,0,0,0],U=void 0!==h,N=U&&h.subtle,T=U&&void 0!==N,W=I.p,H=class{constructor(e){const t=this;t.T=[[[],[],[],[],[]],[[],[],[],[],[]]],t.T[0][0][0]||t.W();const n=t.T[0][4],r=t.T[1],i=e.length;let o,c,f,a=1;if(4!==i&&6!==i&&8!==i)throw new s("invalid aes key size");for(t.I=[c=e.slice(0),f=[]],o=i;4*i+28>o;o++){let e=c[o-1];(o%i==0||8===i&&o%i==4)&&(e=n[e>>>24]<<24^n[e>>16&255]<<16^n[e>>8&255]<<8^n[255&e],o%i==0&&(e=e<<8^e>>>24^a<<24,a=a<<1^283*(a>>7))),c[o]=c[o-i]^e}for(let e=0;o;e++,o--){const t=c[3&e?o:o-4];f[e]=4>=o||4>e?t:r[0][n[t>>>24]]^r[1][n[t>>16&255]]^r[2][n[t>>8&255]]^r[3][n[255&t]]}}encrypt(e){return this.H(e,0)}decrypt(e){return this.H(e,1)}W(){const e=this.T[0],t=this.T[1],n=e[4],r=t[4],s=[],i=[];let o,c,f,a;for(let e=0;256>e;e++)i[(s[e]=e<<1^283*(e>>7))^e]=e;for(let l=o=0;!n[l];l^=c||1,o=i[o]||1){let i=o^o<<1^o<<2^o<<3^o<<4;i=i>>8^255&i^99,n[l]=i,r[i]=l,a=s[f=s[c=s[l]]];let u=16843009*a^65537*f^257*c^16843008*l,w=257*s[i]^16843008*i;for(let n=0;4>n;n++)e[n][l]=w=w<<24^w>>>8,t[n][i]=u=u<<24^u>>>8}for(let n=0;5>n;n++)e[n]=e[n].slice(0),t[n]=t[n].slice(0)}H(e,t){if(4!==e.length)throw new s("invalid aes block size");const n=this.I[t],r=n.length/4-2,i=[0,0,0,0],o=this.T[t],c=o[0],f=o[1],a=o[2],l=o[3],u=o[4];let w,h,d,p=e[0]^n[0],y=e[t?3:1]^n[1],m=e[2]^n[2],b=e[t?1:3]^n[3],g=4;for(let e=0;r>e;e++)w=c[p>>>24]^f[y>>16&255]^a[m>>8&255]^l[255&b]^n[g],h=c[y>>>24]^f[m>>16&255]^a[b>>8&255]^l[255&p]^n[g+1],d=c[m>>>24]^f[b>>16&255]^a[p>>8&255]^l[255&y]^n[g+2],b=c[b>>>24]^f[p>>16&255]^a[y>>8&255]^l[255&m]^n[g+3],g+=4,p=w,y=h,m=d;for(let e=0;4>e;e++)i[t?3&-e:e]=u[p>>>24]<<24^u[y>>16&255]<<16^u[m>>8&255]<<8^u[255&b]^n[g++],w=p,p=y,y=m,m=b,b=w;return i}},L=class{constructor(e,t){this.L=e,this.j=t,this.F=t}reset(){this.F=this.j}update(e){return this.O(this.L,e,this.F)}q(e){if(255==(e>>24&255)){let t=e>>16&255,n=e>>8&255,r=255&e;255===t?(t=0,255===n?(n=0,255===r?r=0:++r):++n):++t,e=0,e+=t<<16,e+=n<<8,e+=r}else e+=1<<24;return e}G(e){0===(e[0]=this.q(e[0]))&&(e[1]=this.q(e[1]))}O(e,t,n){let r;if(!(r=t.length))return[];const s=C.l(t);for(let s=0;r>s;s+=4){this.G(n);const r=e.encrypt(n);t[s]^=r[0],t[s+1]^=r[1],t[s+2]^=r[2],t[s+3]^=r[3]}return C.u(t,s)}},j=_.k;let F=U&&T&&"function"==typeof N.importKey,O=U&&T&&"function"==typeof N.deriveBits;class q extends p{constructor({password:e,signed:n,encryptionStrength:r}){super({start(){t.assign(this,{ready:new u((e=>this.J=e)),password:e,signed:n,X:r-1,pending:new i})},async transform(e,t){const n=this,{password:r,X:o,J:c,ready:f}=n;r?(await(async(e,t,n,r)=>{const i=await Q(e,t,n,Y(r,0,M[t])),o=Y(r,M[t]);if(i[0]!=o[0]||i[1]!=o[1])throw new s("Invalid password")})(n,o,r,Y(e,0,M[o]+2)),e=Y(e,M[o]+2),c()):await f;const a=new i(e.length-10-(e.length-10)%16);t.enqueue(J(n,e,a,0,10,!0))},async flush(e){const{signed:t,Y:n,Z:r,pending:o,ready:c}=this;await c;const f=Y(o,0,o.length-10),a=Y(o,o.length-10);let l=new i;if(f.length){const e=$(W,f);r.update(e);const t=n.update(e);l=Z(W,t)}if(t){const e=Y(Z(W,r.digest()),0,10);for(let t=0;10>t;t++)if(e[t]!=a[t])throw new s("Invalid signature")}e.enqueue(l)}})}}class G extends p{constructor({password:e,encryptionStrength:n}){let r;super({start(){t.assign(this,{ready:new u((e=>this.J=e)),password:e,X:n-1,pending:new i})},async transform(e,t){const n=this,{password:r,X:s,J:o,ready:c}=n;let f=new i;r?(f=await(async(e,t,n)=>{const r=D(new i(M[t]));return X(r,await Q(e,t,n,r))})(n,s,r),o()):await c;const a=new i(f.length+e.length-e.length%16);a.set(f,0),t.enqueue(J(n,e,a,f.length,0))},async flush(e){const{Y:t,Z:n,pending:s,ready:o}=this;await o;let c=new i;if(s.length){const e=t.update($(W,s));n.update(e),c=Z(W,e)}r.signature=Z(W,n.digest()).slice(0,10),e.enqueue(X(c,r.signature))}}),r=this}}function J(e,t,n,r,s,o){const{Y:c,Z:f,pending:a}=e,l=t.length-s;let u;for(a.length&&(t=X(a,t),n=((e,t)=>{if(t&&t>e.length){const n=e;(e=new i(t)).set(n,0)}return e})(n,l-l%16)),u=0;l-16>=u;u+=16){const e=$(W,Y(t,u,u+16));o&&f.update(e);const s=c.update(e);o||f.update(s),n.set(Z(W,s),u+r)}return e.pending=Y(t,u),n}async function Q(n,r,s,o){n.password=null;const c=(e=>{if(void 0===w){const t=new i((e=unescape(encodeURIComponent(e))).length);for(let n=0;n{if(!F)return _.importKey(t);try{return await N.importKey("raw",t,n,!1,s)}catch(e){return F=!1,_.importKey(t)}})(0,c,R,0,E),a=await(async(e,t,n)=>{if(!O)return _.v(t,e.salt,B.iterations,n);try{return await N.deriveBits(e,t,n)}catch(r){return O=!1,_.v(t,e.salt,B.iterations,n)}})(t.assign({salt:o},B),f,8*(2*K[r]+2)),l=new i(a),u=$(W,Y(l,0,K[r])),h=$(W,Y(l,K[r],2*K[r])),d=Y(l,2*K[r]);return t.assign(n,{keys:{key:u,$:h,passwordVerification:d},Y:new L(new H(u),e.from(P)),Z:new j(h)}),d}function X(e,t){let n=e;return e.length+t.length&&(n=new i(e.length+t.length),n.set(e,0),n.set(t,e.length)),n}function Y(e,t,n){return e.subarray(t,n)}function Z(e,t){return e.m(t)}function $(e,t){return e.g(t)}class ee extends p{constructor({password:e,passwordVerification:n}){super({start(){t.assign(this,{password:e,passwordVerification:n}),se(this,e)},transform(e,t){const n=this;if(n.password){const t=ne(n,e.subarray(0,12));if(n.password=null,t[11]!=n.passwordVerification)throw new s("Invalid password");e=e.subarray(12)}t.enqueue(ne(n,e))}})}}class te extends p{constructor({password:e,passwordVerification:n}){super({start(){t.assign(this,{password:e,passwordVerification:n}),se(this,e)},transform(e,t){const n=this;let r,s;if(n.password){n.password=null;const t=D(new i(12));t[11]=n.passwordVerification,r=new i(e.length+t.length),r.set(re(n,t),0),s=12}else r=new i(e.length),s=0;r.set(re(n,e),s),t.enqueue(r)}})}}function ne(e,t){const n=new i(t.length);for(let r=0;r>>24]),i=~e.te.get(),e.keys=[n,s,i]}function oe(e){const t=2|e.keys[2];return ce(r.imul(t,1^t)>>>8)}function ce(e){return 255&e}function fe(e){return 4294967295&e}class ae extends p{constructor(e,{chunkSize:t,CompressionStream:n,CompressionStreamNative:r}){super({});const{compressed:s,encrypted:i,useCompressionStream:o,zipCrypto:c,signed:f,level:a}=e,u=this;let w,h,d=ue(super.readable);i&&!c||!f||([d,w]=d.tee(),w=de(w,new z)),s&&(d=he(d,o,{level:a,chunkSize:t},r,n)),i&&(c?d=de(d,new te(e)):(h=new G(e),d=de(d,h))),we(u,d,(async()=>{let e;i&&!c&&(e=h.signature),i&&!c||!f||(e=await w.getReader().read(),e=new l(e.value.buffer).getUint32(0)),u.signature=e}))}}class le extends p{constructor(e,{chunkSize:t,DecompressionStream:n,DecompressionStreamNative:r}){super({});const{zipCrypto:i,encrypted:o,signed:c,signature:f,compressed:a,useCompressionStream:u}=e;let w,h,d=ue(super.readable);o&&(i?d=de(d,new ee(e)):(h=new q(e),d=de(d,h))),a&&(d=he(d,u,{chunkSize:t},r,n)),o&&!i||!c||([d,w]=d.tee(),w=de(w,new z)),we(this,d,(async()=>{if((!o||i)&&c){const e=await w.getReader().read(),t=new l(e.value.buffer);if(f!=t.getUint32(0,!1))throw new s("Invalid signature")}}))}}function ue(e){return de(e,new p({transform(e,t){e&&e.length&&t.enqueue(e)}}))}function we(e,n,r){n=de(n,new p({flush:r})),t.defineProperty(e,"readable",{get:()=>n})}function he(e,t,n,r,s){try{e=de(e,new(t&&r?r:s)("deflate-raw",n))}catch(r){if(!t)throw r;e=de(e,new s("deflate-raw",n))}return e}function de(e,t){return e.pipeThrough(t)}class pe extends p{constructor(e,n){super({});const r=this,{codecType:s}=e;let i;s.startsWith("deflate")?i=ae:s.startsWith("inflate")&&(i=le);let o=0;const c=new i(e,n),f=super.readable,a=new p({transform(e,t){e&&e.length&&(o+=e.length,t.enqueue(e))},flush(){const{signature:e}=c;t.assign(r,{signature:e,size:o})}});t.defineProperty(r,"readable",{get:()=>f.pipeThrough(c).pipeThrough(a)})}}const ye=new a,me=new a;let be=0;async function ge(e){try{const{options:t,scripts:r,config:s}=e;r&&r.length&&importScripts.apply(void 0,r),self.initCodec&&self.initCodec(),s.CompressionStreamNative=self.CompressionStream,s.DecompressionStreamNative=self.DecompressionStream,self.Deflate&&(s.CompressionStream=new k(self.Deflate)),self.Inflate&&(s.DecompressionStream=new k(self.Inflate));const i={highWaterMark:1,size:()=>s.chunkSize},o=e.readable||new y({async pull(e){const t=new u((e=>ye.set(be,e)));ke({type:"pull",messageId:be}),be=(be+1)%n.MAX_SAFE_INTEGER;const{value:r,done:s}=await t;e.enqueue(r),s&&e.close()}},i),c=e.writable||new m({async write(e){let t;const r=new u((e=>t=e));me.set(be,t),ke({type:"data",value:e,messageId:be}),be=(be+1)%n.MAX_SAFE_INTEGER,await r}},i),f=new pe(t,s);await o.pipeThrough(f).pipeTo(c,{preventAbort:!0});try{await c.close()}catch(e){}const{signature:a,size:l}=f;ke({type:"close",result:{signature:a,size:l}})}catch(e){ve(e)}}function ke(e){let{value:t}=e;if(t)if(t.length)try{t=new i(t),e.value=t.buffer,d(e,[e.value])}catch(t){d(e)}else d(e);else d(e)}function ve(e){const{message:t,stack:n,code:r,name:s}=e;d({error:{message:t,stack:n,code:r,name:s}})}function Se(t){return ze(t.map((([t,n])=>new e(t).fill(n,0,t))))}function ze(t){return t.reduce(((t,n)=>t.concat(e.isArray(n)?ze(n):n)),[])}addEventListener("message",(({data:e})=>{const{type:t,messageId:n,value:r,done:s}=e;try{if("start"==t&&ge(e),"data"==t){const e=ye.get(n);ye.delete(n),e({value:new i(r),done:s})}if("ack"==t){const e=me.get(n);me.delete(n),e()}}catch(e){ve(e)}}));const Ce=[0,1,2,3].concat(...Se([[2,4],[2,5],[4,6],[4,7],[8,8],[8,9],[16,10],[16,11],[32,12],[32,13],[64,14],[64,15],[2,0],[1,16],[1,17],[2,18],[2,19],[4,20],[4,21],[8,22],[8,23],[16,24],[16,25],[32,26],[32,27],[64,28],[64,29]]));function Ie(){const e=this;function t(e,t){let n=0;do{n|=1&e,e>>>=1,n<<=1}while(--t>0);return n>>>1}e.ne=n=>{const s=e.re,i=e.ie.se,o=e.ie.oe;let c,f,a,l=-1;for(n.ce=0,n.fe=573,c=0;o>c;c++)0!==s[2*c]?(n.ae[++n.ce]=l=c,n.le[c]=0):s[2*c+1]=0;for(;2>n.ce;)a=n.ae[++n.ce]=2>l?++l:0,s[2*a]=1,n.le[a]=0,n.ue--,i&&(n.we-=i[2*a+1]);for(e.he=l,c=r.floor(n.ce/2);c>=1;c--)n.de(s,c);a=o;do{c=n.ae[1],n.ae[1]=n.ae[n.ce--],n.de(s,1),f=n.ae[1],n.ae[--n.fe]=c,n.ae[--n.fe]=f,s[2*a]=s[2*c]+s[2*f],n.le[a]=r.max(n.le[c],n.le[f])+1,s[2*c+1]=s[2*f+1]=a,n.ae[1]=a++,n.de(s,1)}while(n.ce>=2);n.ae[--n.fe]=n.ae[1],(t=>{const n=e.re,r=e.ie.se,s=e.ie.pe,i=e.ie.ye,o=e.ie.me;let c,f,a,l,u,w,h=0;for(l=0;15>=l;l++)t.be[l]=0;for(n[2*t.ae[t.fe]+1]=0,c=t.fe+1;573>c;c++)f=t.ae[c],l=n[2*n[2*f+1]+1]+1,l>o&&(l=o,h++),n[2*f+1]=l,f>e.he||(t.be[l]++,u=0,i>f||(u=s[f-i]),w=n[2*f],t.ue+=w*(l+u),r&&(t.we+=w*(r[2*f+1]+u)));if(0!==h){do{for(l=o-1;0===t.be[l];)l--;t.be[l]--,t.be[l+1]+=2,t.be[o]--,h-=2}while(h>0);for(l=o;0!==l;l--)for(f=t.be[l];0!==f;)a=t.ae[--c],a>e.he||(n[2*a+1]!=l&&(t.ue+=(l-n[2*a+1])*n[2*a],n[2*a+1]=l),f--)}})(n),((e,n,r)=>{const s=[];let i,o,c,f=0;for(i=1;15>=i;i++)s[i]=f=f+r[i-1]<<1;for(o=0;n>=o;o++)c=e[2*o+1],0!==c&&(e[2*o]=t(s[c]++,c))})(s,e.he,n.be)}}function xe(e,t,n,r,s){const i=this;i.se=e,i.pe=t,i.ye=n,i.oe=r,i.me=s}Ie.ge=[0,1,2,3,4,5,6,7].concat(...Se([[2,8],[2,9],[2,10],[2,11],[4,12],[4,13],[4,14],[4,15],[8,16],[8,17],[8,18],[8,19],[16,20],[16,21],[16,22],[16,23],[32,24],[32,25],[32,26],[31,27],[1,28]])),Ie.ke=[0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224,0],Ie.ve=[0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576],Ie.Se=e=>256>e?Ce[e]:Ce[256+(e>>>7)],Ie.ze=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],Ie.Ce=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],Ie.Ie=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],Ie.xe=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];const _e=Se([[144,8],[112,9],[24,7],[8,8]]);xe._e=ze([12,140,76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,19,275,147,403,83,339,211,467,51,307,179,435,115,371,243,499,11,267,139,395,75,331,203,459,43,299,171,427,107,363,235,491,27,283,155,411,91,347,219,475,59,315,187,443,123,379,251,507,7,263,135,391,71,327,199,455,39,295,167,423,103,359,231,487,23,279,151,407,87,343,215,471,55,311,183,439,119,375,247,503,15,271,143,399,79,335,207,463,47,303,175,431,111,367,239,495,31,287,159,415,95,351,223,479,63,319,191,447,127,383,255,511,0,64,32,96,16,80,48,112,8,72,40,104,24,88,56,120,4,68,36,100,20,84,52,116,3,131,67,195,35,163,99,227].map(((e,t)=>[e,_e[t]])));const Ae=Se([[30,5]]);function De(e,t,n,r,s){const i=this;i.Ae=e,i.De=t,i.Ve=n,i.Re=r,i.Be=s}xe.Ee=ze([0,16,8,24,4,20,12,28,2,18,10,26,6,22,14,30,1,17,9,25,5,21,13,29,3,19,11,27,7,23].map(((e,t)=>[e,Ae[t]]))),xe.Me=new xe(xe._e,Ie.ze,257,286,15),xe.Ke=new xe(xe.Ee,Ie.Ce,0,30,15),xe.Pe=new xe(null,Ie.Ie,0,19,7);const Ve=[new De(0,0,0,0,0),new De(4,4,8,4,1),new De(4,5,16,8,1),new De(4,6,32,32,1),new De(4,4,16,16,2),new De(8,16,32,32,2),new De(8,16,128,128,2),new De(8,32,128,256,2),new De(32,128,258,1024,2),new De(32,258,258,4096,2)],Re=["need dictionary","stream end","","","stream error","data error","","buffer error","",""];function Be(e,t,n,r){const s=e[2*t],i=e[2*n];return i>s||s==i&&r[t]<=r[n]}function Ee(){const e=this;let t,n,s,c,f,a,l,u,w,h,d,p,y,m,b,g,k,v,S,z,C,I,x,_,A,D,V,R,B,E,M,K,P;const U=new Ie,N=new Ie,T=new Ie;let W,H,L,j,F,O;function q(){let t;for(t=0;286>t;t++)M[2*t]=0;for(t=0;30>t;t++)K[2*t]=0;for(t=0;19>t;t++)P[2*t]=0;M[512]=1,e.ue=e.we=0,H=L=0}function G(e,t){let n,r=-1,s=e[1],i=0,o=7,c=4;0===s&&(o=138,c=3),e[2*(t+1)+1]=65535;for(let f=0;t>=f;f++)n=s,s=e[2*(f+1)+1],++ii?P[2*n]+=i:0!==n?(n!=r&&P[2*n]++,P[32]++):i>10?P[36]++:P[34]++,i=0,r=n,0===s?(o=138,c=3):n==s?(o=6,c=3):(o=7,c=4))}function J(t){e.Ue[e.pending++]=t}function Q(e){J(255&e),J(e>>>8&255)}function X(e,t){let n;const r=t;O>16-r?(n=e,F|=n<>>16-O,O+=r-16):(F|=e<=n;n++)if(r=i,i=e[2*(n+1)+1],++o>=c||r!=i){if(f>o)do{Y(r,P)}while(0!=--o);else 0!==r?(r!=s&&(Y(r,P),o--),Y(16,P),X(o-3,2)):o>10?(Y(18,P),X(o-11,7)):(Y(17,P),X(o-3,3));o=0,s=r,0===i?(c=138,f=3):r==i?(c=6,f=3):(c=7,f=4)}}function $(){16==O?(Q(F),F=0,O=0):8>O||(J(255&F),F>>>=8,O-=8)}function ee(t,n){let s,i,o;if(e.Ne[H]=t,e.Te[H]=255&n,H++,0===t?M[2*n]++:(L++,t--,M[2*(Ie.ge[n]+256+1)]++,K[2*Ie.Se(t)]++),0==(8191&H)&&V>2){for(s=8*H,i=C-k,o=0;30>o;o++)s+=K[2*o]*(5+Ie.Ce[o]);if(s>>>=3,Lc);Y(256,t),j=t[513]}function ne(){O>8?Q(F):O>0&&J(255&F),F=0,O=0}function re(t,n,r){X(0+(r?1:0),3),((t,n)=>{ne(),j=8,Q(n),Q(~n),e.Ue.set(u.subarray(t,t+n),e.pending),e.pending+=n})(t,n)}function se(n){((t,n,r)=>{let s,i,o=0;V>0?(U.ne(e),N.ne(e),o=(()=>{let t;for(G(M,U.he),G(K,N.he),T.ne(e),t=18;t>=3&&0===P[2*Ie.xe[t]+1];t--);return e.ue+=14+3*(t+1),t})(),s=e.ue+3+7>>>3,i=e.we+3+7>>>3,i>s||(s=i)):s=i=n+5,n+4>s||-1==t?i==s?(X(2+(r?1:0),3),te(xe._e,xe.Ee)):(X(4+(r?1:0),3),((e,t,n)=>{let r;for(X(e-257,5),X(t-1,5),X(n-4,4),r=0;n>r;r++)X(P[2*Ie.xe[r]+1],3);Z(M,e-1),Z(K,t-1)})(U.he+1,N.he+1,o+1),te(M,K)):re(t,n,r),q(),r&&ne()})(0>k?-1:k,C-k,n),k=C,t.We()}function ie(){let e,n,r,s;do{if(s=w-x-C,0===s&&0===C&&0===x)s=f;else if(-1==s)s--;else if(C>=f+f-262){u.set(u.subarray(f,f+f),0),I-=f,C-=f,k-=f,e=y,r=e;do{n=65535&d[--r],d[r]=f>n?0:n-f}while(0!=--e);e=f,r=e;do{n=65535&h[--r],h[r]=f>n?0:n-f}while(0!=--e);s+=f}if(0===t.He)return;e=t.Le(u,C+x,s),x+=e,3>x||(p=255&u[C],p=(p<x&&0!==t.He)}function oe(e){let t,n,r=A,s=C,i=_;const o=C>f-262?C-(f-262):0;let c=E;const a=l,w=C+258;let d=u[s+i-1],p=u[s+i];B>_||(r>>=2),c>x&&(c=x);do{if(t=e,u[t+i]==p&&u[t+i-1]==d&&u[t]==u[s]&&u[++t]==u[s+1]){s+=2,t++;do{}while(u[++s]==u[++t]&&u[++s]==u[++t]&&u[++s]==u[++t]&&u[++s]==u[++t]&&u[++s]==u[++t]&&u[++s]==u[++t]&&u[++s]==u[++t]&&u[++s]==u[++t]&&w>s);if(n=258-(w-s),s=w-258,n>i){if(I=e,i=n,n>=c)break;d=u[s+i-1],p=u[s+i]}}}while((e=65535&h[e&a])>o&&0!=--r);return i>x?x:i}e.le=[],e.be=[],e.ae=[],M=[],K=[],P=[],e.de=(t,n)=>{const r=e.ae,s=r[n];let i=n<<1;for(;i<=e.ce&&(i(H||(H=8),L||(L=8),G||(G=0),t.Fe=null,-1==S&&(S=6),1>L||L>9||8!=H||9>I||I>15||0>S||S>9||0>G||G>2?-2:(t.Oe=e,a=I,f=1<(t.qe=t.Ge=0,t.Fe=null,e.pending=0,e.Je=0,n=113,c=0,U.re=M,U.ie=xe.Me,N.re=K,N.ie=xe.Ke,T.re=P,T.ie=xe.Pe,F=0,O=0,j=8,q(),(()=>{w=2*f,d[y-1]=0;for(let e=0;y-1>e;e++)d[e]=0;D=Ve[V].De,B=Ve[V].Ae,E=Ve[V].Ve,A=Ve[V].Re,C=0,k=0,x=0,v=_=2,z=0,p=0})(),0))(t))),e.Qe=()=>42!=n&&113!=n&&666!=n?-2:(e.Te=null,e.Ne=null,e.Ue=null,d=null,h=null,u=null,e.Oe=null,113==n?-3:0),e.Xe=(e,t,n)=>{let r=0;return-1==t&&(t=6),0>t||t>9||0>n||n>2?-2:(Ve[V].Be!=Ve[t].Be&&0!==e.qe&&(r=e.Ye(1)),V!=t&&(V=t,D=Ve[V].De,B=Ve[V].Ae,E=Ve[V].Ve,A=Ve[V].Re),R=n,r)},e.Ze=(e,t,r)=>{let s,i=r,o=0;if(!t||42!=n)return-2;if(3>i)return 0;for(i>f-262&&(i=f-262,o=r-i),u.set(t.subarray(o,o+i),0),C=i,k=i,p=255&u[0],p=(p<=s;s++)p=(p<{let o,w,m,A,B;if(i>4||0>i)return-2;if(!r.$e||!r.et&&0!==r.He||666==n&&4!=i)return r.Fe=Re[4],-2;if(0===r.tt)return r.Fe=Re[7],-5;var E;if(t=r,A=c,c=i,42==n&&(w=8+(a-8<<4)<<8,m=(V-1&255)>>1,m>3&&(m=3),w|=m<<6,0!==C&&(w|=32),w+=31-w%31,n=113,J((E=w)>>8&255),J(255&E)),0!==e.pending){if(t.We(),0===t.tt)return c=-1,0}else if(0===t.He&&A>=i&&4!=i)return t.Fe=Re[7],-5;if(666==n&&0!==t.He)return r.Fe=Re[7],-5;if(0!==t.He||0!==x||0!=i&&666!=n){switch(B=-1,Ve[V].Be){case 0:B=(e=>{let n,r=65535;for(r>s-5&&(r=s-5);;){if(1>=x){if(ie(),0===x&&0==e)return 0;if(0===x)break}if(C+=x,x=0,n=k+r,(0===C||C>=n)&&(x=C-n,C=n,se(!1),0===t.tt))return 0;if(C-k>=f-262&&(se(!1),0===t.tt))return 0}return se(4==e),0===t.tt?4==e?2:0:4==e?3:1})(i);break;case 1:B=(e=>{let n,r=0;for(;;){if(262>x){if(ie(),262>x&&0==e)return 0;if(0===x)break}if(3>x||(p=(p<f-262||2!=R&&(v=oe(r)),3>v)n=ee(0,255&u[C]),x--,C++;else if(n=ee(C-I,v-3),x-=v,v>D||3>x)C+=v,v=0,p=255&u[C],p=(p<{let n,r,s=0;for(;;){if(262>x){if(ie(),262>x&&0==e)return 0;if(0===x)break}if(3>x||(p=(p<_&&f-262>=(C-s&65535)&&(2!=R&&(v=oe(s)),5>=v&&(1==R||3==v&&C-I>4096)&&(v=2)),3>_||v>_)if(0!==z){if(n=ee(0,255&u[C-1]),n&&se(!1),C++,x--,0===t.tt)return 0}else z=1,C++,x--;else{r=C+x-3,n=ee(C-1-S,_-3),x-=_-1,_-=2;do{++C>r||(p=(p<1+j+10-O&&(X(2,3),Y(256,xe._e),$()),j=7;else if(re(0,0,!1),3==i)for(o=0;y>o;o++)d[o]=0;if(t.We(),0===t.tt)return c=-1,0}}return 4!=i?0:1}}function Me(){const e=this;e.nt=0,e.rt=0,e.He=0,e.qe=0,e.tt=0,e.Ge=0}function Ke(e){const t=new Me,n=(o=e&&e.chunkSize?e.chunkSize:65536)+5*(r.floor(o/16383)+1);var o;const c=new i(n);let f=e?e.level:-1;void 0===f&&(f=-1),t.je(f),t.$e=c,this.append=(e,r)=>{let o,f,a=0,l=0,u=0;const w=[];if(e.length){t.nt=0,t.et=e,t.He=e.length;do{if(t.rt=0,t.tt=n,o=t.Ye(0),0!=o)throw new s("deflating: "+t.Fe);t.rt&&(t.rt==n?w.push(new i(c)):w.push(c.slice(0,t.rt))),u+=t.rt,r&&t.nt>0&&t.nt!=a&&(r(t.nt),a=t.nt)}while(t.He>0||0===t.tt);return w.length>1?(f=new i(u),w.forEach((e=>{f.set(e,l),l+=e.length}))):f=w[0]||new i,f}},this.flush=()=>{let e,r,o=0,f=0;const a=[];do{if(t.rt=0,t.tt=n,e=t.Ye(4),1!=e&&0!=e)throw new s("deflating: "+t.Fe);n-t.tt>0&&a.push(c.slice(0,t.rt)),f+=t.rt}while(t.He>0||0===t.tt);return t.Qe(),r=new i(f),a.forEach((e=>{r.set(e,o),o+=e.length})),r}}Me.prototype={je(e,t){const n=this;return n.Oe=new Ee,t||(t=15),n.Oe.je(n,e,t)},Ye(e){const t=this;return t.Oe?t.Oe.Ye(t,e):-2},Qe(){const e=this;if(!e.Oe)return-2;const t=e.Oe.Qe();return e.Oe=null,t},Xe(e,t){const n=this;return n.Oe?n.Oe.Xe(n,e,t):-2},Ze(e,t){const n=this;return n.Oe?n.Oe.Ze(n,e,t):-2},Le(e,t,n){const r=this;let s=r.He;return s>n&&(s=n),0===s?0:(r.He-=s,e.set(r.et.subarray(r.nt,r.nt+s),t),r.nt+=s,r.qe+=s,s)},We(){const e=this;let t=e.Oe.pending;t>e.tt&&(t=e.tt),0!==t&&(e.$e.set(e.Oe.Ue.subarray(e.Oe.Je,e.Oe.Je+t),e.rt),e.rt+=t,e.Oe.Je+=t,e.Ge+=t,e.tt-=t,e.Oe.pending-=t,0===e.Oe.pending&&(e.Oe.Je=0))}};const Pe=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],Ue=[96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,192,80,7,10,0,8,96,0,8,32,0,9,160,0,8,0,0,8,128,0,8,64,0,9,224,80,7,6,0,8,88,0,8,24,0,9,144,83,7,59,0,8,120,0,8,56,0,9,208,81,7,17,0,8,104,0,8,40,0,9,176,0,8,8,0,8,136,0,8,72,0,9,240,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,200,81,7,13,0,8,100,0,8,36,0,9,168,0,8,4,0,8,132,0,8,68,0,9,232,80,7,8,0,8,92,0,8,28,0,9,152,84,7,83,0,8,124,0,8,60,0,9,216,82,7,23,0,8,108,0,8,44,0,9,184,0,8,12,0,8,140,0,8,76,0,9,248,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,196,81,7,11,0,8,98,0,8,34,0,9,164,0,8,2,0,8,130,0,8,66,0,9,228,80,7,7,0,8,90,0,8,26,0,9,148,84,7,67,0,8,122,0,8,58,0,9,212,82,7,19,0,8,106,0,8,42,0,9,180,0,8,10,0,8,138,0,8,74,0,9,244,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,204,81,7,15,0,8,102,0,8,38,0,9,172,0,8,6,0,8,134,0,8,70,0,9,236,80,7,9,0,8,94,0,8,30,0,9,156,84,7,99,0,8,126,0,8,62,0,9,220,82,7,27,0,8,110,0,8,46,0,9,188,0,8,14,0,8,142,0,8,78,0,9,252,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,194,80,7,10,0,8,97,0,8,33,0,9,162,0,8,1,0,8,129,0,8,65,0,9,226,80,7,6,0,8,89,0,8,25,0,9,146,83,7,59,0,8,121,0,8,57,0,9,210,81,7,17,0,8,105,0,8,41,0,9,178,0,8,9,0,8,137,0,8,73,0,9,242,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,202,81,7,13,0,8,101,0,8,37,0,9,170,0,8,5,0,8,133,0,8,69,0,9,234,80,7,8,0,8,93,0,8,29,0,9,154,84,7,83,0,8,125,0,8,61,0,9,218,82,7,23,0,8,109,0,8,45,0,9,186,0,8,13,0,8,141,0,8,77,0,9,250,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,198,81,7,11,0,8,99,0,8,35,0,9,166,0,8,3,0,8,131,0,8,67,0,9,230,80,7,7,0,8,91,0,8,27,0,9,150,84,7,67,0,8,123,0,8,59,0,9,214,82,7,19,0,8,107,0,8,43,0,9,182,0,8,11,0,8,139,0,8,75,0,9,246,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,206,81,7,15,0,8,103,0,8,39,0,9,174,0,8,7,0,8,135,0,8,71,0,9,238,80,7,9,0,8,95,0,8,31,0,9,158,84,7,99,0,8,127,0,8,63,0,9,222,82,7,27,0,8,111,0,8,47,0,9,190,0,8,15,0,8,143,0,8,79,0,9,254,96,7,256,0,8,80,0,8,16,84,8,115,82,7,31,0,8,112,0,8,48,0,9,193,80,7,10,0,8,96,0,8,32,0,9,161,0,8,0,0,8,128,0,8,64,0,9,225,80,7,6,0,8,88,0,8,24,0,9,145,83,7,59,0,8,120,0,8,56,0,9,209,81,7,17,0,8,104,0,8,40,0,9,177,0,8,8,0,8,136,0,8,72,0,9,241,80,7,4,0,8,84,0,8,20,85,8,227,83,7,43,0,8,116,0,8,52,0,9,201,81,7,13,0,8,100,0,8,36,0,9,169,0,8,4,0,8,132,0,8,68,0,9,233,80,7,8,0,8,92,0,8,28,0,9,153,84,7,83,0,8,124,0,8,60,0,9,217,82,7,23,0,8,108,0,8,44,0,9,185,0,8,12,0,8,140,0,8,76,0,9,249,80,7,3,0,8,82,0,8,18,85,8,163,83,7,35,0,8,114,0,8,50,0,9,197,81,7,11,0,8,98,0,8,34,0,9,165,0,8,2,0,8,130,0,8,66,0,9,229,80,7,7,0,8,90,0,8,26,0,9,149,84,7,67,0,8,122,0,8,58,0,9,213,82,7,19,0,8,106,0,8,42,0,9,181,0,8,10,0,8,138,0,8,74,0,9,245,80,7,5,0,8,86,0,8,22,192,8,0,83,7,51,0,8,118,0,8,54,0,9,205,81,7,15,0,8,102,0,8,38,0,9,173,0,8,6,0,8,134,0,8,70,0,9,237,80,7,9,0,8,94,0,8,30,0,9,157,84,7,99,0,8,126,0,8,62,0,9,221,82,7,27,0,8,110,0,8,46,0,9,189,0,8,14,0,8,142,0,8,78,0,9,253,96,7,256,0,8,81,0,8,17,85,8,131,82,7,31,0,8,113,0,8,49,0,9,195,80,7,10,0,8,97,0,8,33,0,9,163,0,8,1,0,8,129,0,8,65,0,9,227,80,7,6,0,8,89,0,8,25,0,9,147,83,7,59,0,8,121,0,8,57,0,9,211,81,7,17,0,8,105,0,8,41,0,9,179,0,8,9,0,8,137,0,8,73,0,9,243,80,7,4,0,8,85,0,8,21,80,8,258,83,7,43,0,8,117,0,8,53,0,9,203,81,7,13,0,8,101,0,8,37,0,9,171,0,8,5,0,8,133,0,8,69,0,9,235,80,7,8,0,8,93,0,8,29,0,9,155,84,7,83,0,8,125,0,8,61,0,9,219,82,7,23,0,8,109,0,8,45,0,9,187,0,8,13,0,8,141,0,8,77,0,9,251,80,7,3,0,8,83,0,8,19,85,8,195,83,7,35,0,8,115,0,8,51,0,9,199,81,7,11,0,8,99,0,8,35,0,9,167,0,8,3,0,8,131,0,8,67,0,9,231,80,7,7,0,8,91,0,8,27,0,9,151,84,7,67,0,8,123,0,8,59,0,9,215,82,7,19,0,8,107,0,8,43,0,9,183,0,8,11,0,8,139,0,8,75,0,9,247,80,7,5,0,8,87,0,8,23,192,8,0,83,7,51,0,8,119,0,8,55,0,9,207,81,7,15,0,8,103,0,8,39,0,9,175,0,8,7,0,8,135,0,8,71,0,9,239,80,7,9,0,8,95,0,8,31,0,9,159,84,7,99,0,8,127,0,8,63,0,9,223,82,7,27,0,8,111,0,8,47,0,9,191,0,8,15,0,8,143,0,8,79,0,9,255],Ne=[80,5,1,87,5,257,83,5,17,91,5,4097,81,5,5,89,5,1025,85,5,65,93,5,16385,80,5,3,88,5,513,84,5,33,92,5,8193,82,5,9,90,5,2049,86,5,129,192,5,24577,80,5,2,87,5,385,83,5,25,91,5,6145,81,5,7,89,5,1537,85,5,97,93,5,24577,80,5,4,88,5,769,84,5,49,92,5,12289,82,5,13,90,5,3073,86,5,193,192,5,24577],Te=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],We=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,112,112],He=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],Le=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];function je(){let e,t,n,r,s,i;function o(e,t,o,c,f,a,l,u,w,h,d){let p,y,m,b,g,k,v,S,z,C,I,x,_,A,D;C=0,g=o;do{n[e[t+C]]++,C++,g--}while(0!==g);if(n[0]==o)return l[0]=-1,u[0]=0,0;for(S=u[0],k=1;15>=k&&0===n[k];k++);for(v=k,k>S&&(S=k),g=15;0!==g&&0===n[g];g--);for(m=g,S>g&&(S=g),u[0]=S,A=1<k;k++,A<<=1)if(0>(A-=n[k]))return-3;if(0>(A-=n[g]))return-3;for(n[g]+=A,i[1]=k=0,C=1,_=2;0!=--g;)i[_]=k+=n[C],_++,C++;g=0,C=0;do{0!==(k=e[t+C])&&(d[i[k]++]=g),C++}while(++g=v;v++)for(p=n[v];0!=p--;){for(;v>x+S;){if(b++,x+=S,D=m-x,D=D>S?S:D,(y=1<<(k=v-x))>p+1&&(y-=p+1,_=v,D>k))for(;++kn[++_];)y-=n[_];if(D=1<1440)return-3;s[b]=I=h[0],h[0]+=D,0!==b?(i[b]=g,r[0]=k,r[1]=S,k=g>>>x-S,r[2]=I-s[b-1]-k,w.set(r,3*(s[b-1]+k))):l[0]=I}for(r[1]=v-x,o>C?d[C]d[C]?0:96,r[2]=d[C++]):(r[0]=a[d[C]-c]+16+64,r[2]=f[d[C++]-c]):r[0]=192,y=1<>>x;D>k;k+=y)w.set(r,3*(I+k));for(k=1<>>=1)g^=k;for(g^=k,z=(1<c;c++)t[c]=0;for(c=0;16>c;c++)n[c]=0;for(c=0;3>c;c++)r[c]=0;s.set(n.subarray(0,15),0),i.set(n.subarray(0,16),0)}this.st=(n,r,s,i,f)=>{let a;return c(19),e[0]=0,a=o(n,0,19,19,null,null,s,r,i,e,t),-3==a?f.Fe="oversubscribed dynamic bit lengths tree":-5!=a&&0!==r[0]||(f.Fe="incomplete dynamic bit lengths tree",a=-3),a},this.it=(n,r,s,i,f,a,l,u,w)=>{let h;return c(288),e[0]=0,h=o(s,0,n,257,Te,We,a,i,u,e,t),0!=h||0===i[0]?(-3==h?w.Fe="oversubscribed literal/length tree":-4!=h&&(w.Fe="incomplete literal/length tree",h=-3),h):(c(288),h=o(s,n,r,0,He,Le,l,f,u,e,t),0!=h||0===f[0]&&n>257?(-3==h?w.Fe="oversubscribed distance tree":-5==h?(w.Fe="incomplete distance tree",h=-3):-4!=h&&(w.Fe="empty distance tree with lengths",h=-3),h):0)}}function Fe(){const e=this;let t,n,r,s,i=0,o=0,c=0,f=0,a=0,l=0,u=0,w=0,h=0,d=0;function p(e,t,n,r,s,i,o,c){let f,a,l,u,w,h,d,p,y,m,b,g,k,v,S,z;d=c.nt,p=c.He,w=o.ot,h=o.ct,y=o.write,m=yh;)p--,w|=(255&c.ft(d++))<>=a[z+1],h-=a[z+1],0!=(16&u)){for(u&=15,k=a[z+2]+(w&Pe[u]),w>>=u,h-=u;15>h;)p--,w|=(255&c.ft(d++))<>=a[z+1],h-=a[z+1],0!=(16&u)){for(u&=15;u>h;)p--,w|=(255&c.ft(d++))<>=u,h-=u,m-=k,v>y){S=y-v;do{S+=o.end}while(0>S);if(u=o.end-S,k>u){if(k-=u,y-S>0&&u>y-S)do{o.lt[y++]=o.lt[S++]}while(0!=--u);else o.lt.set(o.lt.subarray(S,S+u),y),y+=u,S+=u,u=0;S=0}}else S=y-v,y-S>0&&2>y-S?(o.lt[y++]=o.lt[S++],o.lt[y++]=o.lt[S++],k-=2):(o.lt.set(o.lt.subarray(S,S+2),y),y+=2,S+=2,k-=2);if(y-S>0&&k>y-S)do{o.lt[y++]=o.lt[S++]}while(0!=--k);else o.lt.set(o.lt.subarray(S,S+k),y),y+=k,S+=k,k=0;break}if(0!=(64&u))return c.Fe="invalid distance code",k=c.He-p,k=k>h>>3?h>>3:k,p+=k,d-=k,h-=k<<3,o.ot=w,o.ct=h,c.He=p,c.qe+=d-c.nt,c.nt=d,o.write=y,-3;f+=a[z+2],f+=w&Pe[u],z=3*(l+f),u=a[z]}break}if(0!=(64&u))return 0!=(32&u)?(k=c.He-p,k=k>h>>3?h>>3:k,p+=k,d-=k,h-=k<<3,o.ot=w,o.ct=h,c.He=p,c.qe+=d-c.nt,c.nt=d,o.write=y,1):(c.Fe="invalid literal/length code",k=c.He-p,k=k>h>>3?h>>3:k,p+=k,d-=k,h-=k<<3,o.ot=w,o.ct=h,c.He=p,c.qe+=d-c.nt,c.nt=d,o.write=y,-3);if(f+=a[z+2],f+=w&Pe[u],z=3*(l+f),0===(u=a[z])){w>>=a[z+1],h-=a[z+1],o.lt[y++]=a[z+2],m--;break}}else w>>=a[z+1],h-=a[z+1],o.lt[y++]=a[z+2],m--}while(m>=258&&p>=10);return k=c.He-p,k=k>h>>3?h>>3:k,p+=k,d-=k,h-=k<<3,o.ot=w,o.ct=h,c.He=p,c.qe+=d-c.nt,c.nt=d,o.write=y,0}e.init=(e,i,o,c,f,a)=>{t=0,u=e,w=i,r=o,h=c,s=f,d=a,n=null},e.ut=(e,y,m)=>{let b,g,k,v,S,z,C,I=0,x=0,_=0;for(_=y.nt,v=y.He,I=e.ot,x=e.ct,S=e.write,z=S=258&&v>=10&&(e.ot=I,e.ct=x,y.He=v,y.qe+=_-y.nt,y.nt=_,e.write=S,m=p(u,w,r,h,s,d,e,y),_=y.nt,v=y.He,I=e.ot,x=e.ct,S=e.write,z=Sx;){if(0===v)return e.ot=I,e.ct=x,y.He=v,y.qe+=_-y.nt,y.nt=_,e.write=S,e.wt(y,m);m=0,v--,I|=(255&y.ft(_++))<>>=n[g+1],x-=n[g+1],k=n[g],0===k){f=n[g+2],t=6;break}if(0!=(16&k)){a=15&k,i=n[g+2],t=2;break}if(0==(64&k)){c=k,o=g/3+n[g+2];break}if(0!=(32&k)){t=7;break}return t=9,y.Fe="invalid literal/length code",m=-3,e.ot=I,e.ct=x,y.He=v,y.qe+=_-y.nt,y.nt=_,e.write=S,e.wt(y,m);case 2:for(b=a;b>x;){if(0===v)return e.ot=I,e.ct=x,y.He=v,y.qe+=_-y.nt,y.nt=_,e.write=S,e.wt(y,m);m=0,v--,I|=(255&y.ft(_++))<>=b,x-=b,c=w,n=s,o=d,t=3;case 3:for(b=c;b>x;){if(0===v)return e.ot=I,e.ct=x,y.He=v,y.qe+=_-y.nt,y.nt=_,e.write=S,e.wt(y,m);m=0,v--,I|=(255&y.ft(_++))<>=n[g+1],x-=n[g+1],k=n[g],0!=(16&k)){a=15&k,l=n[g+2],t=4;break}if(0==(64&k)){c=k,o=g/3+n[g+2];break}return t=9,y.Fe="invalid distance code",m=-3,e.ot=I,e.ct=x,y.He=v,y.qe+=_-y.nt,y.nt=_,e.write=S,e.wt(y,m);case 4:for(b=a;b>x;){if(0===v)return e.ot=I,e.ct=x,y.He=v,y.qe+=_-y.nt,y.nt=_,e.write=S,e.wt(y,m);m=0,v--,I|=(255&y.ft(_++))<>=b,x-=b,t=5;case 5:for(C=S-l;0>C;)C+=e.end;for(;0!==i;){if(0===z&&(S==e.end&&0!==e.read&&(S=0,z=S7&&(x-=8,v++,_--),e.write=S,m=e.wt(y,m),S=e.write,z=S{}}je.dt=(e,t,n,r)=>(e[0]=9,t[0]=5,n[0]=Ue,r[0]=Ne,0);const Oe=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];function qe(e,t){const n=this;let r,s=0,o=0,c=0,a=0;const l=[0],u=[0],w=new Fe;let h=0,d=new f(4320);const p=new je;n.ct=0,n.ot=0,n.lt=new i(t),n.end=t,n.read=0,n.write=0,n.reset=(e,t)=>{t&&(t[0]=0),6==s&&w.ht(e),s=0,n.ct=0,n.ot=0,n.read=n.write=0},n.reset(e,null),n.wt=(e,t)=>{let r,s,i;return s=e.rt,i=n.read,r=(i>n.write?n.end:n.write)-i,r>e.tt&&(r=e.tt),0!==r&&-5==t&&(t=0),e.tt-=r,e.Ge+=r,e.$e.set(n.lt.subarray(i,i+r),s),s+=r,i+=r,i==n.end&&(i=0,n.write==n.end&&(n.write=0),r=n.write-i,r>e.tt&&(r=e.tt),0!==r&&-5==t&&(t=0),e.tt-=r,e.Ge+=r,e.$e.set(n.lt.subarray(i,i+r),s),s+=r,i+=r),e.rt=s,n.read=i,t},n.ut=(e,t)=>{let i,f,y,m,b,g,k,v;for(m=e.nt,b=e.He,f=n.ot,y=n.ct,g=n.write,k=gy;){if(0===b)return n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);t=0,b--,f|=(255&e.ft(m++))<>>1){case 0:f>>>=3,y-=3,i=7&y,f>>>=i,y-=i,s=1;break;case 1:S=[],z=[],C=[[]],I=[[]],je.dt(S,z,C,I),w.init(S[0],z[0],C[0],0,I[0],0),f>>>=3,y-=3,s=6;break;case 2:f>>>=3,y-=3,s=3;break;case 3:return f>>>=3,y-=3,s=9,e.Fe="invalid block type",t=-3,n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t)}break;case 1:for(;32>y;){if(0===b)return n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);t=0,b--,f|=(255&e.ft(m++))<>>16&65535)!=(65535&f))return s=9,e.Fe="invalid stored block lengths",t=-3,n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);o=65535&f,f=y=0,s=0!==o?2:0!==h?7:0;break;case 2:if(0===b)return n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);if(0===k&&(g==n.end&&0!==n.read&&(g=0,k=gb&&(i=b),i>k&&(i=k),n.lt.set(e.Le(m,i),g),m+=i,b-=i,g+=i,k-=i,0!=(o-=i))break;s=0!==h?7:0;break;case 3:for(;14>y;){if(0===b)return n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);t=0,b--,f|=(255&e.ft(m++))<29||(i>>5&31)>29)return s=9,e.Fe="too many length or distance symbols",t=-3,n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);if(i=258+(31&i)+(i>>5&31),!r||r.lengthv;v++)r[v]=0;f>>>=14,y-=14,a=0,s=4;case 4:for(;4+(c>>>10)>a;){for(;3>y;){if(0===b)return n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);t=0,b--,f|=(255&e.ft(m++))<>>=3,y-=3}for(;19>a;)r[Oe[a++]]=0;if(l[0]=7,i=p.st(r,l,u,d,e),0!=i)return-3==(t=i)&&(r=null,s=9),n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);a=0,s=5;case 5:for(;i=c,258+(31&i)+(i>>5&31)>a;){let o,w;for(i=l[0];i>y;){if(0===b)return n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);t=0,b--,f|=(255&e.ft(m++))<w)f>>>=i,y-=i,r[a++]=w;else{for(v=18==w?7:w-14,o=18==w?11:3;i+v>y;){if(0===b)return n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);t=0,b--,f|=(255&e.ft(m++))<>>=i,y-=i,o+=f&Pe[v],f>>>=v,y-=v,v=a,i=c,v+o>258+(31&i)+(i>>5&31)||16==w&&1>v)return r=null,s=9,e.Fe="invalid bit length repeat",t=-3,n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);w=16==w?r[v-1]:0;do{r[v++]=w}while(0!=--o);a=v}}if(u[0]=-1,x=[],_=[],A=[],D=[],x[0]=9,_[0]=6,i=c,i=p.it(257+(31&i),1+(i>>5&31),r,x,_,A,D,d,e),0!=i)return-3==i&&(r=null,s=9),t=i,n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,n.wt(e,t);w.init(x[0],_[0],d,A[0],d,D[0]),s=6;case 6:if(n.ot=f,n.ct=y,e.He=b,e.qe+=m-e.nt,e.nt=m,n.write=g,1!=(t=w.ut(n,e,t)))return n.wt(e,t);if(t=0,w.ht(e),m=e.nt,b=e.He,f=n.ot,y=n.ct,g=n.write,k=g{n.reset(e,null),n.lt=null,d=null},n.yt=(e,t,r)=>{n.lt.set(e.subarray(t,t+r),0),n.read=n.write=r},n.bt=()=>1==s?1:0}const Ge=[0,0,255,255];function Je(){const e=this;function t(e){return e&&e.gt?(e.qe=e.Ge=0,e.Fe=null,e.gt.mode=7,e.gt.kt.reset(e,null),0):-2}e.mode=0,e.method=0,e.vt=[0],e.St=0,e.marker=0,e.zt=0,e.Ct=t=>(e.kt&&e.kt.ht(t),e.kt=null,0),e.It=(n,r)=>(n.Fe=null,e.kt=null,8>r||r>15?(e.Ct(n),-2):(e.zt=r,n.gt.kt=new qe(n,1<{let n,r;if(!e||!e.gt||!e.et)return-2;const s=e.gt;for(t=4==t?-5:0,n=-5;;)switch(s.mode){case 0:if(0===e.He)return n;if(n=t,e.He--,e.qe++,8!=(15&(s.method=e.ft(e.nt++)))){s.mode=13,e.Fe="unknown compression method",s.marker=5;break}if(8+(s.method>>4)>s.zt){s.mode=13,e.Fe="invalid win size",s.marker=5;break}s.mode=1;case 1:if(0===e.He)return n;if(n=t,e.He--,e.qe++,r=255&e.ft(e.nt++),((s.method<<8)+r)%31!=0){s.mode=13,e.Fe="incorrect header check",s.marker=5;break}if(0==(32&r)){s.mode=7;break}s.mode=2;case 2:if(0===e.He)return n;n=t,e.He--,e.qe++,s.St=(255&e.ft(e.nt++))<<24&4278190080,s.mode=3;case 3:if(0===e.He)return n;n=t,e.He--,e.qe++,s.St+=(255&e.ft(e.nt++))<<16&16711680,s.mode=4;case 4:if(0===e.He)return n;n=t,e.He--,e.qe++,s.St+=(255&e.ft(e.nt++))<<8&65280,s.mode=5;case 5:return 0===e.He?n:(n=t,e.He--,e.qe++,s.St+=255&e.ft(e.nt++),s.mode=6,2);case 6:return s.mode=13,e.Fe="need dictionary",s.marker=0,-2;case 7:if(n=s.kt.ut(e,n),-3==n){s.mode=13,s.marker=0;break}if(0==n&&(n=t),1!=n)return n;n=t,s.kt.reset(e,s.vt),s.mode=12;case 12:return e.He=0,1;case 13:return-3;default:return-2}},e._t=(e,t,n)=>{let r=0,s=n;if(!e||!e.gt||6!=e.gt.mode)return-2;const i=e.gt;return s<1<{let n,r,s,i,o;if(!e||!e.gt)return-2;const c=e.gt;if(13!=c.mode&&(c.mode=13,c.marker=0),0===(n=e.He))return-5;for(r=e.nt,s=c.marker;0!==n&&4>s;)e.ft(r)==Ge[s]?s++:s=0!==e.ft(r)?0:4-s,r++,n--;return e.qe+=r-e.nt,e.nt=r,e.He=n,c.marker=s,4!=s?-3:(i=e.qe,o=e.Ge,t(e),e.qe=i,e.Ge=o,c.mode=7,0)},e.Dt=e=>e&&e.gt&&e.gt.kt?e.gt.kt.bt():-2}function Qe(){}function Xe(e){const t=new Qe,n=e&&e.chunkSize?r.floor(2*e.chunkSize):131072,o=new i(n);let c=!1;t.It(),t.$e=o,this.append=(e,r)=>{const f=[];let a,l,u=0,w=0,h=0;if(0!==e.length){t.nt=0,t.et=e,t.He=e.length;do{if(t.rt=0,t.tt=n,0!==t.He||c||(t.nt=0,c=!0),a=t.xt(0),c&&-5===a){if(0!==t.He)throw new s("inflating: bad input")}else if(0!==a&&1!==a)throw new s("inflating: "+t.Fe);if((c||1===a)&&t.He===e.length)throw new s("inflating: bad input");t.rt&&(t.rt===n?f.push(new i(o)):f.push(o.slice(0,t.rt))),h+=t.rt,r&&t.nt>0&&t.nt!=u&&(r(t.nt),u=t.nt)}while(t.He>0||0===t.tt);return f.length>1?(l=new i(h),f.forEach((e=>{l.set(e,w),w+=e.length}))):l=f[0]||new i,l}},this.flush=()=>{t.Ct()}}Qe.prototype={It(e){const t=this;return t.gt=new Je,e||(e=15),t.gt.It(t,e)},xt(e){const t=this;return t.gt?t.gt.xt(t,e):-2},Ct(){const e=this;if(!e.gt)return-2;const t=e.gt.Ct(e);return e.gt=null,t},At(){const e=this;return e.gt?e.gt.At(e):-2},_t(e,t){const n=this;return n.gt?n.gt._t(n,e,t):-2},ft(e){return this.et[e]},Le(e,t){return this.et.subarray(e,e+t)}},self.initCodec=()=>{self.Deflate=Ke,self.Inflate=Xe};\n'],{type:"text/javascript"}));e({workerScripts:{inflate:[t],deflate:[t]}})})(Q),e.BlobReader=It,e.BlobWriter=At,e.Data64URIReader=class extends Ft{constructor(e){super();let t=e.length;for(;"="==e.charAt(t-1);)t--;const r=e.indexOf(",")+1;n.assign(this,{dataURI:e,dataStart:r,size:a.floor(.75*(t-r))})}readUint8Array(e,t){const{dataStart:n,dataURI:r}=this,s=new w(t),i=4*a.floor(e/3),o=atob(r.substring(i+n,4*a.ceil((e+t)/3)+n)),c=e-3*a.floor(i/4);for(let e=c;c+t>e;e++)s[e-c]=o.charCodeAt(e);return s}},e.Data64URIWriter=class extends Et{constructor(e){super(),n.assign(this,{data:"data:"+(e||"")+";base64,",pending:[]})}writeUint8Array(e){const t=this;let n=0,s=t.pending;const i=t.pending.length;for(t.pending="",n=0;n<3*a.floor((i+e.length)/3)-i;n++)s+=r.fromCharCode(e[n]);for(;n2?t.data+=x(s):t.pending=s}getData(){return this.data+x(this.pending)}},e.ERR_BAD_FORMAT=zn,e.ERR_CENTRAL_DIRECTORY_NOT_FOUND=_n,e.ERR_DUPLICATED_NAME=Gn,e.ERR_ENCRYPTED=Fn,e.ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND=Cn,e.ERR_EOCDR_NOT_FOUND=xn,e.ERR_EOCDR_ZIP64_NOT_FOUND=vn,e.ERR_EXTRAFIELD_ZIP64_NOT_FOUND=Rn,e.ERR_HTTP_RANGE=xt,e.ERR_INVALID_COMMENT=jn,e.ERR_INVALID_ENCRYPTION_STRENGTH=er,e.ERR_INVALID_ENTRY_COMMENT=Jn,e.ERR_INVALID_ENTRY_NAME=Qn,e.ERR_INVALID_EXTRAFIELD_DATA=nr,e.ERR_INVALID_EXTRAFIELD_TYPE=tr,e.ERR_INVALID_PASSWORD=ue,e.ERR_INVALID_SIGNATURE=fe,e.ERR_INVALID_VERSION=$n,e.ERR_ITERATOR_COMPLETED_TOO_SOON=vt,e.ERR_LOCAL_FILE_HEADER_NOT_FOUND=Dn,e.ERR_SPLIT_ZIP_FILE=An,e.ERR_UNSUPPORTED_COMPRESSION=In,e.ERR_UNSUPPORTED_ENCRYPTION=En,e.ERR_UNSUPPORTED_FORMAT=rr,e.HttpRangeReader=class extends Yt{constructor(e,t={}){t.useRangeHeader=!0,super(e,t)}},e.HttpReader=Yt,e.Reader=Ft,e.SplitDataReader=Zt,e.SplitDataWriter=Xt,e.SplitZipReader=$t,e.SplitZipWriter=en,e.TextReader=class extends It{constructor(e){super(new m([e],{type:"text/plain"}))}},e.TextWriter=class extends At{constructor(e){super(e),n.assign(this,{encoding:e,utf8:!e||"utf-8"==e.toLowerCase()})}async getData(){const{encoding:e,utf8:t}=this,r=await super.getData();if(r.text&&t)return r.text();{const t=new FileReader;return new y(((s,i)=>{n.assign(t,{onload:({target:e})=>s(e.result),onerror:()=>i(t.error)}),t.readAsText(r,e)}))}}},e.Uint8ArrayReader=class extends Ft{constructor(e){super(),n.assign(this,{array:e,size:e.length})}readUint8Array(e,t){return this.array.slice(e,e+t)}},e.Uint8ArrayWriter=class extends Et{init(e=0){super.init(),n.assign(this,{offset:0,array:new w(e)})}writeUint8Array(e){const t=this;if(t.offset+e.length>t.array.length){const n=t.array;t.array=new w(n.length+e.length),t.array.set(n)}t.array.set(e,t.offset),t.offset+=e.length}getData(){return this.array}},e.Writer=Et,e.ZipReader=class{constructor(e,t={}){n.assign(this,{reader:jt(e),options:t,config:j()})}async*getEntriesGenerator(e={}){const t=this;let{reader:r}=t;const{config:s}=t;if(await Gt(r),r.size!==V&&r.readUint8Array||(r=new It(await new u(r.readable).blob()),await Gt(r)),22>r.size)throw new d(zn);r.chunkSize=J(s);const i=await(async(e,t,n)=>{const r=new w(4);return Xn(r).setUint32(0,101010256,!0),await s(22)||await s(a.min(1048582,n));async function s(t){const s=n-t,i=await Qt(e,s,t);for(let e=i.length-22;e>=0;e--)if(i[e]==r[0]&&i[e+1]==r[1]&&i[e+2]==r[2]&&i[e+3]==r[3])return{offset:s+e,buffer:i.slice(e,e+22).buffer}}})(r,0,r.size);if(!i)throw Yn(Xn(await Qt(r,0,4)))==H?new d(An):new d(xn);const o=Xn(i);let c=Yn(o,12),l=Yn(o,16);const f=i.offset,h=Kn(o,20),p=f+22+h;let g=Kn(o,4);const m=r.lastDiskNumber||0;let b=Kn(o,6),S=Kn(o,8),k=0,z=0;if(l==I||c==I||S==A||b==A){const e=Xn(await Qt(r,i.offset-20,20));if(Yn(e,0)!=N)throw new d(vn);l=Zn(e,8);let t=await Qt(r,l,56,-1),n=Xn(t);const s=i.offset-20-56;if(Yn(n,0)!=U&&l!=s){const e=l;l=s,k=l-e,t=await Qt(r,l,56,-1),n=Xn(t)}if(Yn(n,0)!=U)throw new d(Cn);g==A&&(g=Yn(n,16)),b==A&&(b=Yn(n,20)),S==A&&(S=Zn(n,32)),c==I&&(c=Zn(n,40)),l-=c}if(m!=g)throw new d(An);if(0>l||l>=r.size)throw new d(zn);let x=0,v=await Qt(r,l,c,b),C=Xn(v);if(c){const e=i.offset-c;if(Yn(C,x)!=O&&l!=e){const t=l;l=e,k=l-t,v=await Qt(r,l,c,b),C=Xn(v)}}if(0>l||l>=r.size)throw new d(zn);const _=Mn(t,e,"filenameEncoding"),D=Mn(t,e,"commentEncoding");for(let i=0;S>i;i++){const o=new Nn(r,s,t.options);if(Yn(C,x)!=O)throw new d(_n);Wn(o,C,x+6);const c=!!o.bitFlag.languageEncodingFlag,l=x+46,u=l+o.filenameLength,f=u+o.extraFieldLength,w=Kn(C,x+4),h=0==(0&w),p=v.subarray(l,u),g=Kn(C,x+32),m=f+g,b=v.subarray(f,m),R=c,F=c,E=h&&16==(16&Bn(C,x+38)),I=Yn(C,x+42)+k;n.assign(o,{versionMadeBy:w,msDosCompatible:h,compressedSize:0,uncompressedSize:0,commentLength:g,directory:E,offset:I,diskNumberStart:Kn(C,x+34),internalFileAttribute:Kn(C,x+36),externalFileAttribute:Yn(C,x+38),rawFilename:p,filenameUTF8:R,commentUTF8:F,rawExtraField:v.subarray(u,f)});const[A,T]=await y.all([nn(p,R?Tn:_||Hn),nn(b,F?Tn:D||Hn)]);n.assign(o,{rawComment:b,filename:A,comment:T,directory:E||A.endsWith(L)}),z=a.max(I,z),await qn(o,o,C,x+6);const H=new kn(o);H.getData=(e,t)=>o.getData(e,H,t),x=m;const{onprogress:U}=e;if(U)try{await U(i+1,S,new kn(o))}catch(e){}yield H}const R=Mn(t,e,"extractPrependedData"),F=Mn(t,e,"extractAppendedData");return R&&(t.prependedData=z>0?await Qt(r,0,z):new w),t.comment=h?await Qt(r,f+22,h):new w,F&&(t.appendedData=par.push(e)));try{if(e=e.trim(),c.filenames.has(e))throw new d(Gn);return c.filenames.add(e),f=(async(e,r,s,c)=>{r=r.trim(),c.directory&&!r.endsWith(L)?r+=L:c.directory=r.endsWith(L);const l=se(r);if(gr(l)>A)throw new d(Qn);const u=c.comment||"",f=se(u);if(gr(f)>A)throw new d(Jn);const m=lr(e,c,"version",20);if(m>A)throw new d($n);const b=lr(e,c,"versionMadeBy",20);if(b>A)throw new d($n);const S=lr(e,c,dn,new o),k=lr(e,c,hn),z=lr(e,c,pn),x=lr(e,c,yn,!0),v=lr(e,c,gn,0),C=lr(e,c,mn,0),_=lr(e,c,"password"),D=lr(e,c,"encryptionStrength",3),R=lr(e,c,"zipCrypto"),F=lr(e,c,"extendedTimestamp",!0),E=lr(e,c,"keepOrder",!0),O=lr(e,c,"level"),U=lr(e,c,"useWebWorkers"),N=lr(e,c,"bufferedWrite"),B=lr(e,c,"dataDescriptorSignature",!1),K=lr(e,c,"signal"),Y=lr(e,c,"useCompressionStream");let Z=lr(e,c,"dataDescriptor",!0),X=lr(e,c,bn);if(_!==V&&D!==V&&(1>D||D>3))throw new d(er);let G=new w;const{extraField:j}=c;if(j){let e=0,t=0;j.forEach((t=>e+=4+gr(t))),G=new w(e),j.forEach(((e,n)=>{if(n>A)throw new d(tr);if(gr(e)>A)throw new d(nr);hr(G,new h([n]),t),hr(G,new h([gr(e)]),t+2),hr(G,e,t+4),t+=4+gr(e)}))}let Q=0,$=0,ee=0;const te=!0===X;s&&(s=jt(s),await Gt(s),s.size===V?(Z=!0,(X||X===V)&&(X=!0,Q=I)):(ee=s.size,Q=(e=>e+5*(a.floor(e/16383)+1))(ee)));const{diskOffset:ne,diskNumber:re,maxSize:ie}=e.writer,ae=te||ee>=I,oe=te||Q>=I,ce=te||e.offset+e.pendingEntriesSize-ne>=I,le=lr(e,c,"supportZip64SplitFile",!0)&&te||re+a.ceil(e.pendingEntriesSize/ie)>=A;if(ce||ae||oe||le){if(!1===X||!E)throw new d(rr);X=!0}X=X||!1;const ue=(e=>{const{rawFilename:t,lastModDate:n,lastAccessDate:r,creationDate:s,password:i,level:o,zip64:c,zipCrypto:l,dataDescriptor:u,directory:f,rawExtraField:d,encryptionStrength:h,extendedTimestamp:g}=e,m=0!==o&&!f,y=!(!i||!gr(i));let b,S,k,z=e.version;if(y&&!l){b=new w(gr(sr)+2);const e=pr(b);fr(e,0,39169),hr(b,sr,2),ur(e,8,h)}else b=new w;if(g){k=new w(9+(r?4:0)+(s?4:0));const e=pr(k);fr(e,0,W),fr(e,2,gr(k)-4),ur(e,4,1+(r?2:0)+(s?4:0)),dr(e,5,a.floor(n.getTime()/1e3)),r&&dr(e,9,a.floor(r.getTime()/1e3)),s&&dr(e,13,a.floor(s.getTime()/1e3));try{S=new w(36);const e=pr(S),t=cr(n);fr(e,0,10),fr(e,2,32),fr(e,8,1),fr(e,10,24),wr(e,12,t),wr(e,20,cr(r)||t),wr(e,28,cr(s)||t)}catch(e){S=new w}}else S=k=new w;let x=q;u&&(x|=8);let v=0;m&&(v=8),c&&(z=z>45?z:45),y&&(x|=1,l||(z=z>51?z:51,v=99,m&&(b[9]=8)));const C=new w(26),_=pr(C);fr(_,0,z),fr(_,2,x),fr(_,4,v);const D=new p(1),R=pr(D);let F;F=P>n?P:n>M?M:n,fr(R,0,(F.getHours()<<6|F.getMinutes())<<5|F.getSeconds()/2),fr(R,2,(F.getFullYear()-1980<<4|F.getMonth()+1)<<5|F.getDate());const E=D[0];dr(_,6,E),fr(_,22,gr(t));const I=gr(b,k,S,d);fr(_,24,I);const A=new w(30+gr(t)+I);return dr(pr(A),0,T),hr(A,C,4),hr(A,t,30),hr(A,b,30+gr(t)),hr(A,k,30+gr(t,b)),hr(A,S,30+gr(t,b,k)),hr(A,d,30+gr(t,b,k,S)),{localHeaderArray:A,headerArray:C,headerView:_,lastModDate:n,rawLastModDate:E,encrypted:y,compressed:m,version:z,compressionMethod:v,rawExtraFieldExtendedTimestamp:k,rawExtraFieldNTFS:S,rawExtraFieldAES:b}})(c=n.assign({},c,{rawFilename:l,rawComment:f,version:m,versionMadeBy:b,lastModDate:S,lastAccessDate:k,creationDate:z,rawExtraField:G,zip64:X,zip64UncompressedSize:ae,zip64CompressedSize:oe,zip64Offset:ce,zip64DiskNumberStart:le,password:_,level:O,useWebWorkers:U,encryptionStrength:D,extendedTimestamp:F,zipCrypto:R,bufferedWrite:N,keepOrder:E,dataDescriptor:Z,dataDescriptorSignature:B,signal:K,msDosCompatible:x,internalFileAttribute:v,externalFileAttribute:C,useCompressionStream:Y})),fe=(e=>{const{zip64:t,dataDescriptor:n,dataDescriptorSignature:r}=e;let s,i=new w,a=0;return n&&(i=new w(t?r?24:20:r?16:12),s=pr(i),r&&(a=4,dr(s,0,134695760))),{dataDescriptorArray:i,dataDescriptorView:s,dataDescriptorOffset:a}})(c);let de;$=gr(ue.localHeaderArray,fe.dataDescriptorArray)+Q,e.pendingEntriesSize+=$;try{de=await(async(e,r,s,a,o)=>{const{files:c,writer:l}=e,{keepOrder:u,dataDescriptor:f,signal:h}=o,{headerInfo:p}=a,m=t.from(c.values()).pop();let b,S,k,z,x,v,C={};c.set(r,C);try{let t;u&&(t=m&&m.lock,C.lock=new y((e=>k=e))),o.bufferedWrite||e.writerLocked||e.bufferedWrites&&u||!f?(v=new At,v.writable.size=0,b=!0,e.bufferedWrites++,await Gt(l)):(v=l,await _()),await Gt(v);const{writable:p}=l;let{diskOffset:R}=l;if(e.addSplitZipSignature){delete e.addSplitZipSignature;const t=new w(4);dr(pr(t),0,H),await or(p,t),e.offset+=4}b||(await t,await D(p));const{diskNumber:F}=l;if(x=!0,C.diskNumberStart=F,C=await(async(e,t,{diskNumberStart:r,lock:s},a,o,c)=>{const{headerInfo:l,dataDescriptorInfo:u}=a,{localHeaderArray:f,headerArray:d,lastModDate:h,rawLastModDate:p,encrypted:g,compressed:m,version:y,compressionMethod:b,rawExtraFieldExtendedTimestamp:S,rawExtraFieldNTFS:k,rawExtraFieldAES:z}=l,{dataDescriptorArray:x}=u,{rawFilename:v,lastAccessDate:C,creationDate:_,password:D,level:R,zip64:F,zip64UncompressedSize:E,zip64CompressedSize:A,zip64Offset:T,zip64DiskNumberStart:H,zipCrypto:O,dataDescriptor:U,directory:N,versionMadeBy:W,rawComment:q,rawExtraField:L,useWebWorkers:M,onstart:P,onprogress:B,onend:K,signal:Y,encryptionStrength:Z,extendedTimestamp:X,msDosCompatible:G,internalFileAttribute:j,externalFileAttribute:Q,useCompressionStream:$}=c,ee={lock:s,versionMadeBy:W,zip64:F,directory:!!N,filenameUTF8:!0,rawFilename:v,commentUTF8:!0,rawComment:q,rawExtraFieldExtendedTimestamp:S,rawExtraFieldNTFS:k,rawExtraFieldAES:z,rawExtraField:L,extendedTimestamp:X,msDosCompatible:G,internalFileAttribute:j,externalFileAttribute:Q,diskNumberStart:r};let te,ne=0,re=0;const{writable:se}=t;if(e){e.chunkSize=J(o),await or(se,f);const t=e.readable,n=t.size=e.size,r={options:{codecType:st,level:R,password:D,encryptionStrength:Z,zipCrypto:g&&O,passwordVerification:g&&O&&p>>8&255,signed:!0,compressed:m,encrypted:g,useWebWorkers:M,useCompressionStream:$,transferStreams:!1},config:o,streamOptions:{signal:Y,size:n,onstart:P,onprogress:B,onend:K}},s=await St({readable:t,writable:se},r);se.size+=s.size,te=s.signature,re=e.size=t.size,ne=s.size}else await or(se,f);let ie;if(F){let e=4;E&&(e+=8),A&&(e+=8),T&&(e+=8),H&&(e+=4),ie=new w(e)}else ie=new w;return e&&((e,t)=>{const{signature:n,rawExtraFieldZip64:r,compressedSize:s,uncompressedSize:a,headerInfo:o,dataDescriptorInfo:c}=e,{headerView:l,encrypted:u}=o,{dataDescriptorView:f,dataDescriptorOffset:d}=c,{zip64:w,zip64UncompressedSize:h,zip64CompressedSize:p,zipCrypto:g,dataDescriptor:m}=t;if(u&&!g||n===V||(dr(l,10,n),m&&dr(f,d,n)),w){const e=pr(r);fr(e,0,1),fr(e,2,r.length-4);let t=4;h&&(dr(l,18,I),wr(e,t,i(a)),t+=8),p&&(dr(l,14,I),wr(e,t,i(s))),m&&(wr(f,d+4,i(s)),wr(f,d+12,i(a)))}else dr(l,14,s),dr(l,18,a),m&&(dr(f,d+4,s),dr(f,d+8,a))})({signature:te,rawExtraFieldZip64:ie,compressedSize:ne,uncompressedSize:re,headerInfo:l,dataDescriptorInfo:u},c),U&&await or(se,x),n.assign(ee,{uncompressedSize:re,compressedSize:ne,lastModDate:h,rawLastModDate:p,creationDate:_,lastAccessDate:C,encrypted:g,length:gr(f,x)+ne,compressionMethod:b,version:y,headerArray:d,signature:te,rawExtraFieldZip64:ie,zip64UncompressedSize:E,zip64CompressedSize:A,zip64Offset:T,zip64DiskNumberStart:H}),ee})(s,v,C,a,e.config,o),x=!1,c.set(r,C),C.filename=r,b){await v.writable.close();let e=await v.getData();await t,await _(),z=!0,f||(e=await(async(e,t,n,{zipCrypto:r})=>{const s=await(e=>e.slice(0,26).arrayBuffer())(t),i=new g(s);return e.encrypted&&!r||dr(i,14,e.signature),e.zip64?(dr(i,18,I),dr(i,22,I)):(dr(i,18,e.compressedSize),dr(i,22,e.uncompressedSize)),await or(n,new w(s)),t.slice(s.byteLength)})(C,e,p,o)),await D(p),C.diskNumberStart=l.diskNumber,R=l.diskOffset,await e.stream().pipeTo(p,{preventClose:!0,signal:h}),p.size+=e.size,z=!1}if(C.offset=e.offset-R,C.zip64)((e,t)=>{const{rawExtraFieldZip64:n,offset:r,diskNumberStart:s}=e,{zip64UncompressedSize:a,zip64CompressedSize:o,zip64Offset:c,zip64DiskNumberStart:l}=t,u=pr(n);let f=4;a&&(f+=8),o&&(f+=8),c&&(wr(u,f,i(r)),f+=8),l&&dr(u,f,s)})(C,o);else if(C.offset>=I)throw new d(rr);return e.offset+=C.length,C}catch(t){throw(b&&z||!b&&x)&&(e.hasCorruptedEntries=!0,t&&(t.corruptedEntry=!0),b?e.offset+=v.writable.size:e.offset=v.writable.size),c.delete(r),t}finally{b&&e.bufferedWrites--,k&&k(),S&&S()}async function _(){e.writerLocked=!0;const{lockWriter:t}=e;e.lockWriter=new y((t=>S=()=>{e.writerLocked=!1,t()})),await t}async function D(e){p.localHeaderArray.length>l.availableSize&&(l.availableSize=0,await or(e,new w))}})(e,r,s,{headerInfo:ue,dataDescriptorInfo:fe},c)}finally{e.pendingEntriesSize-=$}return n.assign(de,{name:r,comment:u,extraField:j}),new kn(de)})(c,e,r,s),l.add(f),await f}catch(t){throw c.filenames.delete(e),t}finally{l.delete(f);const e=ar.shift();e?e():ir--}}async close(e=new w,n={}){const{pendingAddFileCalls:r,writer:s}=this,{writable:o}=s;for(;r.size;)await y.all(t.from(r));return await(async(e,n,r)=>{const{files:s,writer:o}=e,{diskOffset:c,writable:l}=o;let{diskNumber:u}=o,f=0,h=0,p=e.offset-c,g=s.size;for(const[,{rawFilename:e,rawExtraFieldZip64:t,rawExtraFieldAES:n,rawExtraField:r,rawComment:i,rawExtraFieldExtendedTimestamp:a,rawExtraFieldNTFS:o}]of s)h+=46+gr(e,i,t,n,a,o,r);const m=new w(h),y=pr(m);await Gt(o);let b=0;for(const[e,n]of t.from(s.values()).entries()){const{offset:t,rawFilename:i,rawExtraFieldZip64:c,rawExtraFieldAES:u,rawExtraFieldNTFS:d,rawExtraField:h,rawComment:p,versionMadeBy:g,headerArray:S,directory:k,zip64:z,zip64UncompressedSize:x,zip64CompressedSize:v,zip64DiskNumberStart:C,zip64Offset:_,msDosCompatible:D,internalFileAttribute:R,externalFileAttribute:F,extendedTimestamp:E,lastModDate:T,diskNumberStart:H,uncompressedSize:U,compressedSize:N}=n;let q;if(E){q=new w(9);const e=pr(q);fr(e,0,W),fr(e,2,gr(q)-4),ur(e,4,1),dr(e,5,a.floor(T.getTime()/1e3))}else q=new w;const L=gr(c,u,q,d,h);dr(y,f,O),fr(y,f+4,g);const M=pr(S);x||dr(M,18,U),v||dr(M,14,N),hr(m,S,f+6),fr(y,f+30,L),fr(y,f+32,gr(p)),fr(y,f+34,z&&C?A:H),fr(y,f+36,R),F?dr(y,f+38,F):k&&D&&ur(y,f+38,16),dr(y,f+42,z&&_?I:t),hr(m,i,f+46),hr(m,c,f+46+gr(i)),hr(m,u,f+46+gr(i,c)),hr(m,q,f+46+gr(i,c,u)),hr(m,d,f+46+gr(i,c,u,q)),hr(m,h,f+46+gr(i,c,u,q,d)),hr(m,p,f+46+gr(i)+L);const P=46+gr(i,p)+L;if(f-b>o.availableSize&&(o.availableSize=0,await or(l,m.slice(b,f)),b=f),f+=P,r.onprogress)try{await r.onprogress(e+1,s.size,new kn(n))}catch(e){}}await or(l,b?m.slice(b):m);let S=o.diskNumber;const{availableSize:k}=o;22>k&&S++;let z=lr(e,r,"zip64");if(!(I>p&&I>h&&A>g&&A>S)){if(!1===z)throw new d(rr);z=!0}const x=new w(z?98:22),v=pr(x);f=0,z&&(dr(v,0,U),wr(v,4,i(44)),fr(v,12,45),fr(v,14,45),dr(v,16,S),dr(v,20,u),wr(v,24,i(g)),wr(v,32,i(g)),wr(v,40,i(h)),wr(v,48,i(p)),dr(v,56,N),wr(v,64,i(p)+i(h)),dr(v,72,S+1),lr(e,r,"supportZip64SplitFile",!0)&&(S=A,u=A),g=A,p=I,h=I,f+=76),dr(v,f,101010256),fr(v,f+4,S),fr(v,f+6,u),fr(v,f+8,g),fr(v,f+10,g),dr(v,f+12,h),dr(v,f+16,p);const C=gr(n);if(C){if(C>A)throw new d(jn);fr(v,f+20,C)}await or(l,x),C&&await or(l,n)})(this,e,n),lr(this,n,"preventClose")||await o.close(),s.getData?s.getData():o}},e.configure=Q,e.getMimeType=()=>"application/octet-stream",e.initReader=jt,e.initShimAsyncCodec=(e,t={},n)=>({Deflate:ee(e.Deflate,t.deflate,n),Inflate:ee(e.Inflate,t.inflate,n)}),e.initStream=Gt,e.initWriter=Jt,e.readUint8Array=Qt,e.terminateWorkers=()=>{mt.forEach((e=>{kt(e),e.terminate()}))},n.defineProperty(e,"__esModule",{value:!0})}));
diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js
deleted file mode 100644
index 1de3973..0000000
--- a/public_included_ws_fallback/service-worker.js
+++ /dev/null
@@ -1,170 +0,0 @@
-const cacheVersion = 'v1.9.4';
-const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
-const forceFetch = false; // FOR DEVELOPMENT: Set to true to always update assets instead of using cached versions
-const urlsToCache = [
- './',
- 'index.html',
- 'manifest.json',
- 'styles.css',
- 'scripts/localization.js',
- 'scripts/network.js',
- 'scripts/NoSleep.min.js',
- 'scripts/QRCode.min.js',
- 'scripts/theme.js',
- 'scripts/ui.js',
- 'scripts/util.js',
- 'scripts/zip.min.js',
- 'sounds/blop.mp3',
- 'images/favicon-96x96.png',
- 'images/favicon-96x96-notification.png',
- 'images/android-chrome-192x192.png',
- 'images/android-chrome-192x192-maskable.png',
- 'images/android-chrome-512x512.png',
- 'images/android-chrome-512x512-maskable.png',
- 'images/apple-touch-icon.png',
- 'lang/ar.json',
- 'lang/de.json',
- 'lang/en.json',
- 'lang/es.json',
- 'lang/fr.json',
- 'lang/id.json',
- 'lang/it.json',
- 'lang/ja.json',
- 'lang/nb.json',
- 'lang/nl.json',
- 'lang/ro.json',
- 'lang/ru.json',
- 'lang/zh-CN.json'
-];
-
-self.addEventListener('install', function(event) {
- // Perform install steps
- event.waitUntil(
- caches.open(cacheTitle)
- .then(function(cache) {
- return cache.addAll(urlsToCache).then(_ => {
- console.log('All files cached.');
- });
- })
- );
-});
-
-// fetch the resource from the network
-const fromNetwork = (request, timeout) =>
- new Promise((fulfill, reject) => {
- const timeoutId = setTimeout(reject, timeout);
- fetch(request).then(response => {
- clearTimeout(timeoutId);
- fulfill(response);
- update(request).then(() => console.log("Cache successfully updated for", request.url));
- }, reject);
- });
-
-// fetch the resource from the browser cache
-const fromCache = request =>
- caches
- .open(cacheTitle)
- .then(cache =>
- cache.match(request)
- );
-
-// cache the current page to make it available for offline
-const update = request =>
- caches
- .open(cacheTitle)
- .then(cache =>
- fetch(request)
- .then(async response => {
- await cache.put(request, response);
- })
- .catch(() => console.log(`Cache could not be updated. ${request.url}`))
- );
-
-// general strategy when making a request (eg if online try to fetch it
-// from cache, if something fails fetch from network. Update cache everytime files are fetched.
-// This way files should only be fetched if cacheVersion is changed
-self.addEventListener('fetch', function(event) {
- if (event.request.method === "POST") {
- // Requests related to Web Share Target.
- event.respondWith((async () => {
- const share_url = await evaluateRequestData(event.request);
- return Response.redirect(encodeURI(share_url), 302);
- })());
- } else {
- // Regular requests not related to Web Share Target.
- if (forceFetch) {
- event.respondWith(fromNetwork(event.request, 10000));
- } else {
- event.respondWith(
- fromCache(event.request).then(rsp => {
- // if fromCache resolves to undefined fetch from network instead
- return rsp || fromNetwork(event.request, 10000);
- })
- );
- }
- }
-});
-
-
-// on activation, we clean up the previously registered service workers
-self.addEventListener('activate', evt => {
- return evt.waitUntil(
- caches.keys().then(cacheNames => {
- return Promise.all(
- cacheNames.map(cacheName => {
- if (cacheName !== cacheTitle) {
- return caches.delete(cacheName);
- }
- })
- );
- })
- )
- }
-);
-
-const evaluateRequestData = function (request) {
- return new Promise(async (resolve) => {
- const formData = await request.formData();
- const title = formData.get("title");
- const text = formData.get("text");
- const url = formData.get("url");
- const files = formData.getAll("allfiles");
-
- const pairDropUrl = request.url;
-
- if (files && files.length > 0) {
- let fileObjects = [];
- for (let i=0; i {
- const db = e.target.result;
- for (let i = 0; i < fileObjects.length; i++) {
- const transaction = db.transaction('share_target_files', 'readwrite');
- const objectStore = transaction.objectStore('share_target_files');
-
- const objectStoreRequest = objectStore.add(fileObjects[i]);
- objectStoreRequest.onsuccess = _ => {
- if (i === fileObjects.length - 1) resolve(pairDropUrl + '?share-target=files');
- }
- }
- }
- DBOpenRequest.onerror = _ => {
- resolve(pairDropUrl);
- }
- } else {
- let urlArgument = '?share-target=text';
-
- if (title) urlArgument += `&title=${title}`;
- if (text) urlArgument += `&text=${text}`;
- if (url) urlArgument += `&url=${url}`;
-
- resolve(pairDropUrl + urlArgument);
- }
- });
-}
diff --git a/public_included_ws_fallback/sounds/blop.mp3 b/public_included_ws_fallback/sounds/blop.mp3
deleted file mode 100644
index 28a6244..0000000
Binary files a/public_included_ws_fallback/sounds/blop.mp3 and /dev/null differ
diff --git a/public_included_ws_fallback/sounds/blop.ogg b/public_included_ws_fallback/sounds/blop.ogg
deleted file mode 100644
index d1ce0c2..0000000
Binary files a/public_included_ws_fallback/sounds/blop.ogg and /dev/null differ
diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css
deleted file mode 100644
index 6895f6d..0000000
--- a/public_included_ws_fallback/styles.css
+++ /dev/null
@@ -1,1619 +0,0 @@
-/* Constants */
-
-:root {
- --icon-size: 24px;
- --primary-color: #4285f4;
- --paired-device-color: #00a69c;
- --public-room-color: #db8500;
- --accent-color: var(--primary-color);
- --peer-width: 120px;
- --ws-peer-color: #ff6b6b;
- color-scheme: light dark;
-}
-
-/* Layout */
-
-html,
-body {
- margin: 0;
- display: flex;
- flex-direction: column;
- width: 100vw;
- overflow-x: hidden;
- overscroll-behavior: none;
- overflow-y: hidden;
- /* Only allow selection on message and pair key */
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- transition: color 300ms;
-}
-
-body {
- height: 100%;
- /* mobile viewport bug fix */
- min-height: -moz-available; /* WebKit-based browsers will ignore this. */
- min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
- min-height: fill-available;
-}
-
-html {
- height: 100%;
- min-height: -moz-available; /* WebKit-based browsers will ignore this. */
- min-height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
- min-height: fill-available;
-}
-
-.fw {
- width: 100%;
-}
-
-.row-reverse {
- display: flex;
- flex-direction: row-reverse;
-}
-
-.space-between {
- justify-content: space-between;
-}
-
-.row {
- display: flex;
- flex-direction: row;
-}
-
-.column {
- display: flex;
- flex-direction: column;
-}
-
-.center {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.grow {
- flex-grow: 1;
-}
-
-.full {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
-}
-
-.pointer {
- cursor: pointer;
-}
-
-header {
- position: absolute;
- align-items: baseline;
- padding: 8px 12px;
- box-sizing: border-box;
- width: 100vw;
- z-index: 2;
- top: 0;
- right: 0;
-}
-
-header > * {
- margin-left: 4px;
- margin-right: 4px;
-}
-
-header > div {
- display: flex;
- flex-direction: column;
- align-self: flex-start;
- touch-action: manipulation;
-}
-
-header > div .icon-button {
- height: 40px;
- transition: all 300ms;
-}
-
-header > div > div {
- display: flex;
- flex-direction: column;
-}
-
-header > div:not(:hover) .icon-button:not(.selected) {
- height: 0;
- opacity: 0;
-}
-
-#theme-wrapper:hover::before {
- border-radius: 20px;
- background: currentColor;
- opacity: 0.1;
- transition: opacity 300ms;
- content: '';
- position: absolute;
- width: 40px;
- top: 0;
- bottom: 0;
- margin-top: 8px;
- margin-bottom: 8px;
-}
-
-header > div:hover .icon-button.selected::before {
- opacity: 0.1;
-}
-
-@media (pointer: coarse) {
- header > div:hover .icon-button.selected:hover::before {
- opacity: 0.2;
- }
-
- header > div .icon-button:not(.selected) {
- height: 0;
- opacity: 0;
- pointer-events: none;
- }
-
- header > div > div {
- flex-direction: column-reverse;
- }
-}
-
-[hidden] {
- display: none !important;
-}
-
-
-/* Typography */
-
-body {
- font-family: -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-h1 {
- font-size: 34px;
- font-weight: 400;
- letter-spacing: -.01em;
- line-height: 40px;
- margin: 8px 0 0;
-}
-
-h2 {
- font-size: 24px;
- font-weight: 400;
- letter-spacing: -.012em;
- line-height: 32px;
- color: var(--primary-color);}
-
-h3 {
- font-size: 20px;
- font-weight: 500;
- margin: 16px 0;
- color: var(--primary-color);
-}
-
-.font-subheading {
- font-size: 14px;
- font-weight: 400;
- line-height: 18px;
- word-break: normal;
-}
-
-.text-center {
- text-align: center;
-}
-
-.font-body1,
-body {
- font-size: 14px;
- font-weight: 400;
- line-height: 20px;
-}
-
-.font-body2 {
- font-size: 12px;
- line-height: 18px;
-}
-
-a,
-.icon-button {
- text-decoration: none;
- color: currentColor;
- cursor: pointer;
-}
-
-input {
- cursor: pointer;
-}
-
-input[type="checkbox"] {
- min-width: 13px;
-}
-
-x-noscript {
- background: var(--primary-color);
- color: white;
- z-index: 2;
-}
-
-
-/* Icons */
-
-.icon {
- width: var(--icon-size);
- height: var(--icon-size);
- fill: currentColor;
-}
-
-
-
-/* Shadows */
-
-[shadow="1"] {
- box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14),
- 0 1px 8px 0 rgba(0, 0, 0, 0.12),
- 0 3px 3px -2px rgba(0, 0, 0, 0.4);
-}
-
-[shadow="2"] {
- box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14),
- 0 1px 10px 0 rgba(0, 0, 0, 0.12),
- 0 2px 4px -1px rgba(0, 0, 0, 0.4);
-}
-
-
-
-
-/* Animations */
-
-@keyframes fade-in {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
-
-#center {
- position: relative;
- display: flex;
- margin-top: 56px;
- flex-direction: column-reverse;
- flex-grow: 1;
- justify-content: space-around;
- align-items: center;
- overflow-x: hidden;
- overflow-y: scroll;
- overscroll-behavior-x: none;
-}
-
-
-/* Peers List */
-
-#x-peers-filler {
- display: flex;
- flex-grow: 1;
-}
-
-x-peers {
- position: relative;
- display: flex;
- flex-flow: row wrap;
- flex-grow: 1;
- align-items: start !important;
- justify-content: center;
-
- z-index: 2;
- transition: --bg-color 0.5s ease;
- overflow-y: scroll;
- overflow-x: hidden;
- overscroll-behavior-x: none;
- scrollbar-width: none;
-
- --peers-per-row: 6; /* default if browser does not support :has selector */
- --x-peers-width: min(100vw, calc(var(--peers-per-row) * (var(--peer-width) + 25px) - 16px));
- width: var(--x-peers-width);
- margin-right: 20px;
- margin-left: 20px;
-}
-
-x-peers.overflowing {
- background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
- linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
- /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .2), rgba(var(--text-color), 0)),
- radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .2), rgba(var(--text-color), 0)) 0 100%;
-
- background-repeat: no-repeat;
- background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
-
- /* Opera doesn't support this in the shorthand */
- background-attachment: local, local, scroll, scroll;
-}
-
-x-peers:has(> x-peer) {
- --peers-per-row: 10;
-}
-
-/* peers-per-row if height is too small for 2 rows */
-@media screen and (min-height: 538px) and (max-height: 683px) and (max-width: 402px),
-screen and (min-height: 517px) and (max-height: 664px) and (max-width: 426px),
-screen and (min-height: 501px) and (max-height: 647px) and (min-width: 426px) {
- x-peers:has(> x-peer) {
- --peers-per-row: 3;
- }
-
- x-peers:has(> x-peer:nth-of-type(7)) {
- --peers-per-row: 4;
- }
-
- x-peers:has(> x-peer:nth-of-type(10)) {
- --peers-per-row: 5;
- }
-
- x-peers:has(> x-peer:nth-of-type(13)) {
- --peers-per-row: 6;
- }
-
- x-peers:has(> x-peer:nth-of-type(16)) {
- --peers-per-row: 7;
- }
-
- x-peers:has(> x-peer:nth-of-type(19)) {
- --peers-per-row: 8;
- }
-
- x-peers:has(> x-peer:nth-of-type(22)) {
- --peers-per-row: 9;
- }
-
- x-peers:has(> x-peer:nth-of-type(25)) {
- --peers-per-row: 10;
- }
-}
-
-/* peers-per-row if height is too small for 3 rows */
-@media screen and (min-height: 683px) and (max-width: 402px),
-screen and (min-height: 664px) and (max-width: 426px),
-screen and (min-height: 647px) and (min-width: 426px) {
- x-peers:has(> x-peer) {
- --peers-per-row: 3;
- }
-
- x-peers:has(> x-peer:nth-of-type(10)) {
- --peers-per-row: 4;
- }
-
- x-peers:has(> x-peer:nth-of-type(13)) {
- --peers-per-row: 5;
- }
-
- x-peers:has(> x-peer:nth-of-type(16)) {
- --peers-per-row: 6;
- }
-
- x-peers:has(> x-peer:nth-of-type(19)) {
- --peers-per-row: 7;
- }
-
- x-peers:has(> x-peer:nth-of-type(22)) {
- --peers-per-row: 8;
- }
-
- x-peers:has(> x-peer:nth-of-type(25)) {
- --peers-per-row: 9;
- }
-
- x-peers:has(> x-peer:nth-of-type(28)) {
- --peers-per-row: 10;
- }
-}
-
-::-webkit-scrollbar {
- display: none;
-}
-
-/* Empty Peers List */
-
-x-no-peers {
- display: flex;
- flex-direction: column;
- padding: 8px;
- height: 137px;
- text-align: center;
-}
-
-x-no-peers h2,
-x-no-peers a {
- color: var(--primary-color);
- margin-bottom: 5px;
-}
-
-x-peers:not(:empty)+x-no-peers {
- display: none;
-}
-
-x-no-peers::before {
- color: var(--primary-color);
- font-size: 24px;
- font-weight: 400;
- letter-spacing: -.012em;
- line-height: 32px;
-}
-
-x-no-peers[drop-bg]::before {
- content: attr(data-drop-bg);
-}
-
-x-no-peers[drop-bg] * {
- display: none;
-}
-
-
-
-/* Peer */
-
-x-peer {
- padding: 8px;
- align-content: start;
- flex-wrap: wrap;
-}
-
-x-peer label {
- width: var(--peer-width);
- touch-action: manipulation;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
- position: relative;
-}
-
-input[type="file"] {
- visibility: hidden;
- position: absolute;
-}
-
-x-peer x-icon {
- --icon-size: 40px;
- margin-bottom: 4px;
- transition: transform 150ms;
- will-change: transform;
- display: flex;
- flex-direction: column;
-}
-
-x-peer .icon-wrapper {
- width: var(--icon-size);
- padding: 12px;
- border-radius: 50%;
- background: var(--primary-color);
- color: white;
- display: flex;
-}
-
-x-peer.type-secret .icon-wrapper {
- background: var(--paired-device-color);
-}
-
-x-peer:not(.type-ip):not(.type-secret).type-public-id .icon-wrapper {
- background: var(--public-room-color);
-}
-
-x-peer x-icon > .highlight-wrapper {
- align-self: center;
- align-items: center;
- margin: 7px auto 0;
- height: 6px;
-}
-
-x-peer x-icon > .highlight-wrapper > .highlight {
- width: 15px;
- height: 6px;
- border-radius: 4px;
- margin-left: 1px;
- margin-right: 1px;
- display: none;
-}
-
-x-peer.type-ip x-icon > .highlight-wrapper > .highlight.highlight-room-ip {
- background-color: var(--primary-color);
- display: inline;
-}
-
-x-peer.type-secret x-icon > .highlight-wrapper > .highlight.highlight-room-secret {
- background-color: var(--paired-device-color);
- display: inline;
-}
-
-x-peer.type-public-id x-icon > .highlight-wrapper > .highlight.highlight-room-public-id {
- background-color: var(--public-room-color);
- display: inline;
-}
-
-x-peer:not([status]):hover x-icon,
-x-peer:not([status]):focus x-icon {
- transform: scale(1.05);
-}
-
-x-peer[status] x-icon {
- box-shadow: none;
- opacity: 0.8;
- transform: scale(1);
-}
-
-
-x-peer.ws-peer {
- margin-top: -1.5px;
-}
-
-x-peer.ws-peer .progress {
- margin-top: 3px;
-}
-
-x-peer.ws-peer .icon-wrapper{
- border: solid 3px var(--ws-peer-color);
-}
-
-x-peer.ws-peer .highlight-wrapper {
- margin-top: 3px;
-}
-
-#websocket-fallback {
- opacity: 0.5;
-}
-
-#websocket-fallback > span:nth-of-type(2) {
- border-bottom: solid 2px var(--ws-peer-color);
-}
-
-.device-descriptor {
- width: 100%;
- text-align: center;
-}
-
-.device-descriptor > div {
- width: 100%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: center;
-}
-
-.status,
-.device-name,
-.connection-hash {
- opacity: 0.7;
-}
-
-.device-name {
- font-size: 14px;
- white-space: nowrap;
-}
-
-.connection-hash {
- font-size: 12px;
- white-space: nowrap;
-}
-
-x-peer:not([status]) .status,
-x-peer[status] .device-name {
- display: none;
-}
-
-x-peer[status] {
- pointer-events: none;
-}
-
-x-peer x-icon {
- animation: pop 600ms ease-out 1;
-}
-
-@keyframes pop {
- 0% {
- transform: scale(0.7);
- }
-
- 40% {
- transform: scale(1.2);
- }
-}
-
-x-peer[drop] x-icon {
- transform: scale(1.1);
-}
-
-
-
-/* Footer */
-
-footer {
- position: relative;
- z-index: 2;
- align-items: center;
- text-align: center;
- cursor: default;
- margin: auto 5px 5px;
-}
-
-footer .logo {
- --icon-size: 80px;
- margin-bottom: 8px;
- color: var(--primary-color);
- margin-top: -10px;
-}
-
-.discovery-wrapper {
- font-size: 12px;
- margin: 10px auto auto;
- border: 3px solid var(--border-color);
- border-radius: 0.5rem;
- padding: 2px;
- background-color: rgb(var(--bg-color));
- transition: background-color 0.5s ease;
- min-height: 24px;
-}
-
-/*You can be discovered wrapper*/
-.discovery-wrapper > div:first-of-type {
- padding-left: 4px;
- padding-right: 4px;
-}
-
-
-.discovery-wrapper .badge {
- word-break: keep-all;
- margin: 2px;
-}
-
-.badge {
- border-radius: 0.3rem/0.3rem;
- padding-right: 0.3rem;
- padding-left: 0.3em;
- background-color: var(--badge-color);
- color: white;
- transition: background-color 0.5s ease;
- white-space: nowrap;
-}
-
-.badge-room-ip {
- background-color: var(--primary-color);
- border-color: var(--primary-color);
-}
-
-.badge-room-secret {
- background-color: var(--paired-device-color);
- border-color: var(--paired-device-color);
-}
-
-.badge-room-public-id {
- background-color: var(--public-room-color);
- border-color: var(--public-room-color);
-}
-
-#display-name {
- position: relative;
- display: inline-block;
- text-align: left;
- border: none;
- outline: none;
- max-width: 15em;
- text-overflow: ellipsis;
- cursor: text;
- margin-bottom: -6px;
- padding-bottom: 0.1rem;
- border-radius: 1.3rem/30%;
- border-right: solid 1rem transparent;
- border-left: solid 1rem transparent;
- background-clip: padding-box;
- overflow: hidden;
- z-index: 1;
-}
-
-#edit-pen {
- width: 1rem;
- height: 1rem;
- margin-bottom: -2px;
- position: relative;
-}
-
-html:not([dir="rtl"]) #display-name,
-html:not([dir="rtl"]) #edit-pen {
- margin-left: -1rem;
-}
-
-html[dir="rtl"] #display-name,
-html[dir="rtl"] #edit-pen {
- margin-right: -1rem;
-}
-
-html[dir="rtl"] #edit-pen {
- transform: rotateY(180deg);
-}
-
-/* Dialog */
-
-x-dialog x-background {
- background: rgba(0, 0, 0, 0.61);
- z-index: 10;
- transition: opacity 300ms;
- will-change: opacity;
- padding: 15px;
- overflow: overlay;
-}
-
-x-dialog x-paper {
- display: flex;
- flex-direction: column;
- width: calc(100vw - 10px);
- z-index: 3;
- background: white;
- border-radius: 8px;
- max-width: 400px;
- overflow: hidden;
- box-sizing: border-box;
- transition: transform 300ms;
- will-change: transform;
-}
-
-#pair-device-dialog x-paper,
-#edit-paired-devices-dialog x-paper,
-#public-room-dialog x-paper,
-#language-select-dialog x-paper {
- position: absolute;
- top: max(50%, 350px);
- margin-top: -328.5px;
-}
-
-x-paper > .row:first-of-type {
- background-color: var(--accent-color);
- border-bottom: solid 4px var(--border-color);
- margin-bottom: 10px;
-}
-
-x-paper > .row:first-of-type h2 {
- color: white;
-}
-
-#pair-device-dialog,
-#edit-paired-devices-dialog {
- --accent-color: var(--paired-device-color);
-}
-
-#public-room-dialog {
- --accent-color: var(--public-room-color);
-}
-
-#pair-device-dialog ::-moz-selection,
-#pair-device-dialog ::selection {
- color: black;
- background: var(--paired-device-color);
-}
-
-#public-room-dialog ::-moz-selection,
-#public-room-dialog ::selection {
- color: black;
- background: var(--public-room-color);
-}
-
-x-dialog:not([show]) {
- pointer-events: none;
-}
-
-x-dialog:not([show]) x-paper {
- transform: scale(0.1);
-}
-
-x-dialog:not([show]) x-background {
- opacity: 0;
-}
-
-
-x-dialog a {
- color: var(--primary-color);
-}
-
-/* Pair Devices Dialog & Public Room Dialog */
-
-.input-key-container {
- width: 100%;
- display: flex;
- justify-content: center;
- margin-top: 10px;
-}
-
-.input-key-container > input {
- width: 45px;
- height: 45px;
- font-size: 30px;
- padding: 0;
- text-align: center;
- text-transform: uppercase;
- display: -webkit-box !important;
- display: -webkit-flex !important;
- display: -moz-flex !important;
- display: -ms-flexbox !important;
- display: flex !important;
- -webkit-justify-content: center;
- -ms-justify-content: center;
- justify-content: center;
-}
-
-.input-key-container > input {
- margin: 0 3px;
-}
-
-.input-key-container.six-chars > input:nth-of-type(4) {
- margin-left: 5%;
-}
-
-.key {
- -webkit-user-select: text;
- -moz-user-select: text;
- user-select: text;
- display: inline-block;
- font-size: 50px;
- letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 20px);
- text-indent: calc(0.5 * (11px + min(calc((100vw - 80px - 99px) / 100 * 6), 28px)));
- margin: 25px 0;
-}
-
-.key-qr-code {
- margin: 16px;
- width: fit-content;
- align-self: center;
-}
-
-.key-instructions {
- flex-direction: column;
-}
-
-x-dialog h2 {
- margin-top: 5px;
- margin-bottom: 0;
-}
-
-x-dialog hr {
- height: 3px;
- border: none;
- width: 100%;
- background-color: var(--border-color);
-}
-
-.hr-note {
- margin-top: 10px;
- margin-bottom: 20px;
-}
-
-.hr-note hr {
- margin-bottom: -2px;
-}
-
-.hr-note > div {
- height: 0;
- transform: translateY(-10px);
-}
-
-
-.hr-note > div > span {
- padding: 3px 10px;
- border-radius: 10px;
- color: rgb(var(--text-color));
- background-color: rgb(var(--bg-color));
- border: var(--border-color) solid 3px;
- text-transform: uppercase;
-}
-
-#pair-device-dialog x-background {
- padding: 16px!important;
-}
-
-/* Edit Paired Devices Dialog */
-.paired-devices-wrapper:empty:before {
- content: attr(data-empty);
-}
-
-.paired-devices-wrapper:empty {
- padding: 10px;
-}
-
-.paired-devices-wrapper {
- border-top: solid 4px var(--paired-device-color);
- border-bottom: solid 4px var(--paired-device-color);
- max-height: 65vh;
- overflow: scroll;
- background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
- linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
- /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .3), rgba(var(--text-color), 0)),
- radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .3), rgba(var(--text-color), 0)) 0 100%;
-
- background-repeat: no-repeat;
- background-size: 100% 80px, 100% 80px, 100% 24px, 100% 24px;
-
- /* Opera doesn't support this in the shorthand */
- background-attachment: local, local, scroll, scroll;
-}
-
-.paired-device {
- display: flex;
- justify-content: space-between;
- flex-direction: column;
- align-items: center;
-}
-
-.paired-device:not(:last-child) {
- border-bottom: solid 4px var(--paired-device-color);
-}
-
-.paired-device > .display-name,
-.paired-device > .device-name {
- width: 100%;
- height: 36px;
- display: flex;
- align-items: center;
- text-align: center;
- align-self: center;
- border-bottom: solid 2px rgba(128, 128, 128, 0.5);
- opacity: 1;
-}
-.paired-device span {
- width: 100%;
-}
-
-.paired-device > .button-wrapper {
- display: flex;
- height: 36px;
- justify-content: space-between;
- flex-direction: row;
- align-items: center;
- width: 100%;
-}
-
-.paired-device > .button-wrapper > label,
-.paired-device > .button-wrapper > button {
- display: flex;
- align-items: center;
- text-align: center;
- white-space: nowrap;
- justify-content: center;
- width: 50%;
- padding-left: 6px;
- padding-right: 6px;
- height: 36px;
-}
-
-.paired-device > .button-wrapper > :not(:last-child) {
- border-right: solid 1px rgba(128, 128, 128, 0.5);
-}
-
-.paired-device > .button-wrapper > :not(:first-child) {
- border-left: solid 1px rgba(128, 128, 128, 0.5);
-}
-
-.paired-device * {
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Receive Dialog */
-
-x-paper > .row {
- padding: 10px;
-}
-
-/* button row*/
-x-paper > .button-row {
- border-top: solid 3px var(--border-color);
- height: 50px;
- margin-top: 10px;
-}
-
-x-paper > .button-row > .button {
- height: 100%;
- width: 100%;
-}
-
-html:not([dir="rtl"]) x-paper > .button-row > .button:not(:first-child) {
- border-right: solid 1.5px var(--border-color);
-}
-
-html:not([dir="rtl"]) x-paper > .button-row > .button:not(:last-child) {
- border-left: solid 1.5px var(--border-color);
-}
-
-html[dir="rtl"] x-paper > .button-row > .button:not(:first-child) {
- border-left: solid 1.5px var(--border-color);
-}
-
-html[dir="rtl"] x-paper > .button-row > .button:not(:last-child) {
- border-right: solid 1.5px var(--border-color);
-}
-
-.language-buttons > button > span {
- margin: 0 0.3em;
-}
-
-.file-description {
- max-width: 100%;
-}
-
-.file-description span {
- display: inline;
- word-break: normal;
-}
-
-.file-name {
- font-style: italic;
- max-width: 100%;
- margin-top: 5px;
-}
-
-.file-stem {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- padding-right: 1px;
-}
-
-/* Send Text Dialog */
-x-dialog .dialog-subheader {
- padding-top: 16px;
- padding-bottom: 16px;
-}
-
-#send-text-dialog .display-name-wrapper {
- padding-bottom: 0;
-}
-
-#text-input {
- min-height: 200px;
- width: 100%;
-}
-
-/* Receive Text Dialog */
-
-#receive-text-dialog #text {
- width: 100%;
- word-break: break-all;
- max-height: calc(100vh - 393px);
- overflow-x: hidden;
- overflow-y: auto;
- -webkit-user-select: text;
- -moz-user-select: text;
- user-select: text;
- white-space: pre-wrap;
-}
-
-#receive-text-dialog #text a {
- cursor: pointer;
-}
-
-#receive-text-dialog #text a:hover {
- text-decoration: underline;
-}
-
-#receive-text-dialog h3 {
- /* Select the received text when double-clicking the dialog */
- user-select: none;
- pointer-events: none;
-}
-
-.row-separator {
- border-bottom: solid 2.5px var(--border-color);
- margin: auto -24px;
-}
-
-#base64-paste-btn,
-#base64-paste-dialog .textarea {
- width: 100%;
- height: 40vh;
- border: solid 12px #438cff;
- border-radius: 8px;
-}
-
-#base64-paste-dialog .textarea {
- display: flex;
- align-items: center;
- justify-content: center;
- text-align: center;
-}
-
-#base64-paste-dialog .textarea::before {
- font-size: 15px;
- letter-spacing: 0.12em;
- color: var(--primary-color);
- font-weight: 700;
- text-transform: uppercase;
- white-space: pre-wrap;
-}
-
-
-/* Button */
-
-.button {
- padding: 2px 16px 0;
- box-sizing: border-box;
- min-height: 36px;
- font-size: 14px;
- line-height: 24px;
- font-weight: 700;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- white-space: nowrap;
- cursor: pointer;
- user-select: none;
- background: inherit;
- color: var(--accent-color);
- overflow: hidden;
-}
-
-.button[disabled] {
- color: #5B5B66;
- cursor: not-allowed;
-}
-
-
-.button,
-.icon-button {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
- touch-action: manipulation;
- border: none;
- outline: none;
-}
-
-.button:before,
-.icon-button:before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: currentColor;
- opacity: 0;
- transition: opacity 300ms;
-}
-
-.button:not([disabled]):hover:before,
-.icon-button:hover:before {
- opacity: 0.1;
-}
-
-.button[selected],
-.icon-button[selected] {
- opacity: 0.1;
-}
-
-#cancel-paste-mode {
- z-index: 2;
- margin: 0;
- padding: 0;
- position: absolute;
- top: 0;
- right: 0;
- left: 0;
- width: 100vw;
- height: 56px;
- background-color: var(--primary-color);
- color: rgb(238, 238, 238);
-}
-
-.button:focus:before,
-.icon-button:focus:before {
- opacity: 0.2;
-}
-
-
-
-button::-moz-focus-inner {
- border: 0;
-}
-
-
-/* Icon Button */
-.icon-button {
- width: 40px;
- height: 40px;
-}
-
-.icon-button:before {
- border-radius: 50%;
-}
-
-/* Text Input */
-.textarea {
- box-sizing: border-box;
- border: none;
- outline: none;
- padding: 16px 24px;
- border-radius: 8px;
- font-size: 14px;
- font-family: inherit;
- background: #f1f3f4;
- display: block;
- overflow: auto;
- resize: none;
- line-height: 16px;
- max-height: calc(100vh - 254px);
- white-space: pre;
-}
-
-
-/* Info Animation */
-
-#about {
- color: white;
- z-index: 11;
- overflow: hidden;
- pointer-events: none;
- text-align: center;
-}
-
-#about header {
- z-index: 1;
-}
-
-#about:not(:target) header {
- transition-delay: 400ms;
-}
-
-#about:target header {
- transition-delay: 100ms;
-}
-
-#about > * {
- transition: opacity 300ms ease 300ms;
- will-change: opacity;
- pointer-events: all;
-}
-
-#about:not(:target) > header,
-#about:not(:target) > section {
- opacity: 0;
- pointer-events: none;
- transition-delay: 0s;
-}
-
-#about .logo {
- --icon-size: 96px;
-}
-
-#about .title-wrapper {
- display: flex;
- align-items: baseline;
-}
-
-#about .title-wrapper > div {
- margin-left: 0.5em;
-}
-
-#about x-background {
- position: absolute;
- --size: max(max(230vw, 230vh), calc(150vh + 150vw));
- --size-half: calc(var(--size)/2);
- top: calc(28px - var(--size-half));
- width: var(--size);
- height: var(--size);
- border-radius: 50%;
- background: var(--primary-color);
- transform: scale(0);
- z-index: -1;
-}
-
-html:not([dir="rtl"]) #about x-background {
- right: calc(36px - var(--size-half));
-}
-
-html[dir="rtl"] #about x-background {
- left: calc(36px - var(--size-half));
-}
-
-
-/* Hack such that initial scale(0) isn't animated */
-#about x-background {
- will-change: transform;
- transition: transform 800ms cubic-bezier(0.77, 0, 0.175, 1);
-}
-
-#about:target x-background {
- transform: scale(1);
-}
-
-#about .row a {
- margin: 8px 8px -16px;
-}
-
-#about section {
- flex-grow: 1;
-}
-
-canvas.circles {
- width: 100vw;
- position: absolute;
- z-index: -10;
- top: 0;
- left: 0;
-}
-
-/* Loading Indicator */
-
-.progress {
- width: 80px;
- height: 80px;
- position: absolute;
- top: -8px;
- clip: rect(0px, 80px, 80px, 40px);
- --progress: rotate(0deg);
- transition: transform 200ms;
-}
-
-.circle {
- width: 72px;
- height: 72px;
- border: 4px solid var(--primary-color);
- border-radius: 40px;
- position: absolute;
- clip: rect(0px, 40px, 80px, 0px);
- will-change: transform;
- transform: var(--progress);
-}
-
-.over50 {
- clip: rect(auto, auto, auto, auto);
-}
-
-.over50 .circle.right {
- transform: rotate(180deg);
-}
-
-
-/* Generic placeholder */
-[placeholder]:empty:before {
- content: attr(placeholder);
-}
-
-/* Toast */
-
-.toast-container {
- padding: 0 8px 24px;
- overflow: hidden;
- pointer-events: none;
-}
-
-x-toast {
- position: absolute;
- min-height: 48px;
- top: 50px;
- width: 100%;
- max-width: 344px;
- background-color: rgb(var(--text-color));
- color: rgb(var(--bg-color));
- align-items: center;
- box-sizing: border-box;
- padding: 8px 24px;
- z-index: 20;
- transition: opacity 200ms, transform 300ms ease-out;
- cursor: default;
- line-height: 24px;
- border-radius: 8px;
- pointer-events: all;
-}
-
-x-toast:not([show]):not(:hover) {
- opacity: 0;
- transform: translateY(-100px);
-}
-
-
-/* Instructions */
-
-x-instructions {
- position: relative;
- opacity: 0.5;
- text-align: center;
- margin-left: 10px;
- margin-right: 10px;
- display: flex;
- flex-direction: column;
- flex-grow: 1;
- justify-content: center;
-}
-
-x-instructions:not([drop-peer]):not([drop-bg]):before {
- content: attr(mobile);
-}
-
-x-instructions[drop-peer]:before {
- content: attr(data-drop-peer);
-}
-
-x-instructions[drop-bg]:not([drop-peer]):before {
- content: attr(data-drop-bg);
-}
-
-x-instructions p {
- display: none;
-}
-
-x-peers:empty~x-instructions {
- opacity: 0 !important;
-}
-
-@media (hover: none) and (pointer: coarse) {
- x-peer {
- transform: scale(0.95);
- padding: 4px;
- }
-}
-
-/* Prevent Cumulative Layout Shift */
-
-.fade-in {
- animation: fade-in 600ms;
- animation-fill-mode: backwards;
-}
-
-.no-animation-on-load {
- animation-iteration-count: 0;
-}
-
-.opacity-0 {
- opacity: 0;
-}
-
-/* Responsive Styles */
-
-@media screen and (min-height: 800px) {
- footer {
- margin-bottom: 16px;
- }
-}
-
-@media (hover: hover) and (pointer: fine) {
- x-instructions:not([drop-peer]):not([drop-bg]):before {
- content: attr(desktop);
- }
-}
-
-/*
- Color Themes
-*/
-
-/* Default colors */
-body {
- --text-color: 51,51,51;
- --bg-color: 250,250,250; /*rgb code*/
- --bg-color-test: 18,18,18;
- --bg-color-secondary: #e4e4e4;
- --border-color: rgb(169, 169, 169);
- --badge-color: #a5a5a5;
-}
-
-/* Dark theme colors */
-body.dark-theme {
- --text-color: 238,238,238;
- --bg-color: 18,18,18; /*rgb code*/
- --bg-color-secondary: #333;
- --border-color: rgb(238,238,238);
- --badge-color: #717171;
-}
-
-/* Colored Elements */
-body {
- color: rgb(var(--text-color));
- background-color: rgb(var(--bg-color));
- transition: background-color 0.5s ease;
-}
-
-x-dialog x-paper {
- background-color: rgb(var(--bg-color));
-}
-
-.textarea {
- color: rgb(var(--text-color)) !important;
- background-color: var(--bg-color-secondary) !important;
-}
-
-.textarea * {
- margin: 0 !important;
- padding: 0 !important;
- color: unset !important;
- background: unset !important;
- border: unset !important;
- opacity: unset !important;
- font-family: inherit !important;
- font-size: inherit !important;
- font-style: unset !important;
- font-weight: unset !important;
-}
-
-/* Image/Video/Audio Preview */
-.file-preview {
- margin-bottom: 15px;
-}
-
-.file-preview:empty {
- display: none;
-}
-
-.file-preview > img,
-.file-preview > audio,
-.file-preview > video {
- max-width: 100%;
- max-height: 40vh;
- margin: auto;
- display: block;
-}
-
-/* Styles for users who prefer dark mode at the OS level */
-@media (prefers-color-scheme: dark) {
-
- /* defaults to dark theme */
- body {
- --text-color: 238,238,238;
- --bg-color: 18,18,18; /*rgb code*/
- --bg-color-secondary: #333;
- --border-color: rgb(238,238,238);
- --badge-color: #717171;
- }
-
- /* Override dark mode with light mode styles if the user decides to swap */
- body.light-theme {
- --text-color: 51,51,51;
- --bg-color: 250,250,250; /*rgb code*/
- --bg-color-secondary: #e4e4e4;
- --border-color: rgb(169, 169, 169);
- --badge-color: #a5a5a5;
- }
-}
-
-
-/*
- Edge specific styles
-*/
-@supports (-ms-ime-align: auto) {
-
- html,
- body {
- overflow: hidden;
- }
-}
-
-/*
- iOS specific styles
-*/
-@supports (-webkit-overflow-scrolling: touch) {
- html {
- min-height: -webkit-fill-available;
- }
-}
-
-/* webkit scrollbar style*/
-
-::-webkit-scrollbar{
- width: 4px;
- height: 4px;
-}
-
-::-webkit-scrollbar-thumb{
- background: #bfbfbf;
- border-radius: 4px;
-}
-
-::-moz-selection,
-::selection {
- color: black;
- background: var(--primary-color);
-}
-
-/* make elements with attribute contenteditable editable on older iOS devices.
-See note here: https://developer.mozilla.org/en-US/docs/Web/CSS/user-select */
-[contenteditable] {
- -webkit-user-select: text;
- user-select: text;
-}
diff --git a/rtc_config_example.json b/rtc_config_example.json
index d7e48e8..fe34c25 100644
--- a/rtc_config_example.json
+++ b/rtc_config_example.json
@@ -2,10 +2,10 @@
"sdpSemantics": "unified-plan",
"iceServers": [
{
- "urls": "stun:stun.l.google.com:19302"
+ "urls": "stun::3478"
},
{
- "urls": "turn:example.com:3478",
+ "urls": "turns::5349",
"username": "username",
"credential": "password"
}
diff --git a/server/helper.js b/server/helper.js
new file mode 100644
index 0000000..c8746d4
--- /dev/null
+++ b/server/helper.js
@@ -0,0 +1,65 @@
+import crypto from "crypto";
+
+export const hasher = (() => {
+ let password;
+ return {
+ hashCodeSalted(salt) {
+ if (!password) {
+ // password is created on first call.
+ password = randomizer.getRandomString(128);
+ }
+
+ return crypto.createHash("sha3-512")
+ .update(password)
+ .update(crypto.createHash("sha3-512").update(salt, "utf8").digest("hex"))
+ .digest("hex");
+ }
+ }
+})()
+
+export const randomizer = (() => {
+ let charCodeLettersOnly = r => 65 <= r && r <= 90;
+ let charCodeAllPrintableChars = r => r === 45 || 47 <= r && r <= 57 || 64 <= r && r <= 90 || 97 <= r && r <= 122;
+
+ return {
+ getRandomString(length, lettersOnly = false) {
+ const charCodeCondition = lettersOnly
+ ? charCodeLettersOnly
+ : charCodeAllPrintableChars;
+
+ let string = "";
+ while (string.length < length) {
+ let arr = new Uint16Array(length);
+ crypto.webcrypto.getRandomValues(arr);
+ arr = Array.apply([], arr); /* turn into non-typed array */
+ arr = arr.map(function (r) {
+ return r % 128
+ })
+ arr = arr.filter(function (r) {
+ /* strip non-printables: if we transform into desirable range we have a probability bias, so I suppose we better skip this character */
+ return charCodeCondition(r);
+ });
+ string += String.fromCharCode.apply(String, arr);
+ }
+ return string.substring(0, length)
+ }
+ }
+})()
+
+/*
+ cyrb53 (c) 2018 bryc (github.com/bryc)
+ A fast and simple hash function with decent collision resistance.
+ Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
+ Public domain. Attribution appreciated.
+*/
+export const cyrb53 = function(str, seed = 0) {
+ let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
+ for (let i = 0, ch; i < str.length; i++) {
+ ch = str.charCodeAt(i);
+ h1 = Math.imul(h1 ^ ch, 2654435761);
+ h2 = Math.imul(h2 ^ ch, 1597334677);
+ }
+ h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
+ h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
+ return 4294967296 * (2097151 & h2) + (h1>>>0);
+};
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
new file mode 100644
index 0000000..9868b31
--- /dev/null
+++ b/server/index.js
@@ -0,0 +1,144 @@
+import {spawn} from "child_process";
+import fs from "fs";
+
+import PairDropServer from "./server.js";
+import PairDropWsServer from "./ws-server.js";
+
+// Handle SIGINT
+process.on('SIGINT', () => {
+ console.info("SIGINT Received, exiting...")
+ process.exit(0)
+})
+
+// Handle SIGTERM
+process.on('SIGTERM', () => {
+ console.info("SIGTERM Received, exiting...")
+ process.exit(0)
+})
+
+// Handle APP ERRORS
+process.on('uncaughtException', (error, origin) => {
+ console.log('----- Uncaught exception -----')
+ console.log(error)
+ console.log('----- Exception origin -----')
+ console.log(origin)
+})
+process.on('unhandledRejection', (reason, promise) => {
+ console.log('----- Unhandled Rejection at -----')
+ console.log(promise)
+ console.log('----- Reason -----')
+ console.log(reason)
+})
+
+// Evaluate arguments for deployment with Docker and Node.js
+let conf = {};
+conf.debugMode = process.env.DEBUG_MODE === "true";
+conf.port = process.env.PORT || 3000;
+conf.wsFallback = process.argv.includes('--include-ws-fallback') || process.env.WS_FALLBACK === "true";
+conf.rtcConfig = process.env.RTC_CONFIG
+ ? JSON.parse(fs.readFileSync(process.env.RTC_CONFIG, 'utf8'))
+ : {
+ "sdpSemantics": "unified-plan",
+ "iceServers": [
+ {
+ "urls": "stun:stun.l.google.com:19302"
+ }
+ ]
+ };
+
+
+conf.signalingServer = process.env.SIGNALING_SERVER || false;
+conf.ipv6Localize = parseInt(process.env.IPV6_LOCALIZE) || false;
+
+let rateLimit = false;
+if (process.argv.includes('--rate-limit') || process.env.RATE_LIMIT === "true") {
+ rateLimit = 5;
+}
+else {
+ let envRateLimit = parseInt(process.env.RATE_LIMIT);
+ if (!isNaN(envRateLimit)) {
+ rateLimit = envRateLimit;
+ }
+}
+conf.rateLimit = rateLimit;
+
+// Evaluate arguments for deployment with Node.js only
+conf.autoStart = process.argv.includes('--auto-restart');
+conf.localhostOnly = process.argv.includes('--localhost-only');
+
+// Validate configuration
+if (conf.ipv6Localize) {
+ if (!(0 < conf.ipv6Localize && conf.ipv6Localize < 8)) {
+ console.error("ipv6Localize must be an integer between 1 and 7");
+ process.exit(1);
+ }
+
+ console.log("IPv6 client IPs will be localized to",
+ conf.ipv6Localize,
+ conf.ipv6Localize === 1 ? "segment" : "segments");
+}
+
+if (conf.signalingServer) {
+ const isValidUrl = /[a-z|0-9|\-._~:\/?#\[\]@!$&'()*+,;=]+$/.test(conf.signalingServer);
+ const containsProtocol = /:\/\//.test(conf.signalingServer)
+ const endsWithSlash = /\/$/.test(conf.signalingServer)
+ if (!isValidUrl || containsProtocol) {
+ console.error("SIGNALING_SERVER must be a valid url without the protocol prefix.\n" +
+ "Examples of valid values: `pairdrop.net`, `pairdrop.example.com:3000`, `example.com/pairdrop`");
+ process.exit(1);
+ }
+
+ if (!endsWithSlash) {
+ conf.signalingServer += "/";
+ }
+
+ if (process.env.RTC_CONFIG || conf.wsFallback || conf.ipv6Localize) {
+ console.error("SIGNALING_SERVER cannot be used alongside WS_FALLBACK, RTC_CONFIG or IPV6_LOCALIZE as these " +
+ "configurations are specified by the signaling server.\n" +
+ "To use this instance as the signaling server do not set SIGNALING_SERVER");
+ process.exit(1);
+ }
+}
+
+// Logs for debugging
+if (conf.debugMode) {
+ console.log("DEBUG_MODE is active. To protect privacy, do not use in production.");
+ console.debug("\n");
+ console.debug("----DEBUG ENVIRONMENT VARIABLES----")
+ console.debug(JSON.stringify(conf, null, 4));
+ console.debug("\n");
+}
+
+// Start a new PairDrop instance when an uncaught exception occurs
+if (conf.autoStart) {
+ process.on(
+ 'uncaughtException',
+ () => {
+ process.once(
+ 'exit',
+ () => spawn(
+ process.argv.shift(),
+ process.argv,
+ {
+ cwd: process.cwd(),
+ detached: true,
+ stdio: 'inherit'
+ }
+ )
+ );
+ process.exit();
+ }
+ );
+}
+
+// Start server to serve client files
+const pairDropServer = new PairDropServer(conf);
+
+if (!conf.signalingServer) {
+ // Start websocket server if SIGNALING_SERVER is not set
+ new PairDropWsServer(pairDropServer.server, conf);
+} else {
+ console.log("This instance does not include a signaling server. Clients on this instance connect to the following signaling server:", conf.signalingServer);
+}
+
+console.log('\nPairDrop is running on port', conf.port);
diff --git a/server/peer.js b/server/peer.js
new file mode 100644
index 0000000..70e87b2
--- /dev/null
+++ b/server/peer.js
@@ -0,0 +1,205 @@
+import crypto from "crypto";
+import parser from "ua-parser-js";
+import {animals, colors, uniqueNamesGenerator} from "unique-names-generator";
+import {cyrb53, hasher} from "./helper.js";
+
+export default class Peer {
+
+ constructor(socket, request, conf) {
+ this.conf = conf
+
+ // set socket
+ this.socket = socket;
+
+ // set remote ip
+ this._setIP(request);
+
+ // set peer id
+ this._setPeerId(request);
+
+ // is WebRTC supported
+ this._setRtcSupported(request);
+
+ // set name
+ this._setName(request);
+
+ this.requestRate = 0;
+
+ this.roomSecrets = [];
+ this.pairKey = null;
+
+ this.publicRoomId = null;
+ }
+
+ rateLimitReached() {
+ // rate limit implementation: max 10 attempts every 10s
+ if (this.requestRate >= 10) {
+ return true;
+ }
+ this.requestRate += 1;
+ setTimeout(_ => this.requestRate -= 1, 10000);
+ return false;
+ }
+
+ _setIP(request) {
+ if (request.headers['cf-connecting-ip']) {
+ this.ip = request.headers['cf-connecting-ip'].split(/\s*,\s*/)[0];
+ } else if (request.headers['x-forwarded-for']) {
+ this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0];
+ } else {
+ this.ip = request.connection.remoteAddress;
+ }
+
+ // remove the prefix used for IPv4-translated addresses
+ if (this.ip.substring(0,7) === "::ffff:")
+ this.ip = this.ip.substring(7);
+
+ let ipv6_was_localized = false;
+ if (this.conf.ipv6Localize && this.ip.includes(':')) {
+ this.ip = this.ip.split(':',this.conf.ipv6Localize).join(':');
+ ipv6_was_localized = true;
+ }
+
+ if (this.conf.debugMode) {
+ console.debug("\n");
+ console.debug("----DEBUGGING-PEER-IP-START----");
+ console.debug("remoteAddress:", request.connection.remoteAddress);
+ console.debug("x-forwarded-for:", request.headers['x-forwarded-for']);
+ console.debug("cf-connecting-ip:", request.headers['cf-connecting-ip']);
+ if (ipv6_was_localized) {
+ console.debug("IPv6 client IP was localized to", this.conf.ipv6Localize, this.conf.ipv6Localize > 1 ? "segments" : "segment");
+ }
+ console.debug("PairDrop uses:", this.ip);
+ console.debug("IP is private:", this.ipIsPrivate(this.ip));
+ console.debug("if IP is private, '127.0.0.1' is used instead");
+ console.debug("----DEBUGGING-PEER-IP-END----");
+ }
+
+ // IPv4 and IPv6 use different values to refer to localhost
+ // put all peers on the same network as the server into the same room as well
+ if (this.ip === '::1' || this.ipIsPrivate(this.ip)) {
+ this.ip = '127.0.0.1';
+ }
+ }
+
+ ipIsPrivate(ip) {
+ // if ip is IPv4
+ if (!ip.includes(":")) {
+ // 10.0.0.0 - 10.255.255.255 || 172.16.0.0 - 172.31.255.255 || 192.168.0.0 - 192.168.255.255
+ return /^(10)\.(.*)\.(.*)\.(.*)$/.test(ip) || /^(172)\.(1[6-9]|2[0-9]|3[0-1])\.(.*)\.(.*)$/.test(ip) || /^(192)\.(168)\.(.*)\.(.*)$/.test(ip)
+ }
+
+ // else: ip is IPv6
+ const firstWord = ip.split(":").find(el => !!el); //get first not empty word
+
+ if (/^fe[c-f][0-f]$/.test(firstWord)) {
+ // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Range: fec0 - feff
+ return true;
+ }
+
+ // These days Unique Local Addresses (ULA) are used in place of Site Local.
+ // Range: fc00 - fcff
+ else if (/^fc[0-f]{2}$/.test(firstWord)) {
+ return true;
+ }
+
+ // Range: fd00 - fcff
+ else if (/^fd[0-f]{2}$/.test(firstWord)) {
+ return true;
+ }
+
+ // Link local addresses (prefixed with fe80) are not routable
+ else if (firstWord === "fe80") {
+ return true;
+ }
+
+ // Discard Prefix
+ else if (firstWord === "100") {
+ return true;
+ }
+
+ // Any other IP address is not Unique Local Address (ULA)
+ return false;
+ }
+
+ _setPeerId(request) {
+ const searchParams = new URL(request.url, "http://server").searchParams;
+ let peerId = searchParams.get("peer_id");
+ let peerIdHash = searchParams.get("peer_id_hash");
+ if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) {
+ this.id = peerId;
+ } else {
+ this.id = crypto.randomUUID();
+ }
+ }
+
+ _setRtcSupported(request) {
+ const searchParams = new URL(request.url, "http://server").searchParams;
+ this.rtcSupported = searchParams.get("webrtc_supported") === "true";
+ }
+
+ _setName(req) {
+ let ua = parser(req.headers['user-agent']);
+
+ let deviceName = '';
+
+ if (ua.os && ua.os.name) {
+ deviceName = ua.os.name.replace('Mac OS', 'Mac') + ' ';
+ }
+
+ if (ua.device.model) {
+ deviceName += ua.device.model;
+ } else {
+ deviceName += ua.browser.name;
+ }
+
+ if (!deviceName) {
+ deviceName = 'Unknown Device';
+ }
+
+ const displayName = uniqueNamesGenerator({
+ length: 2,
+ separator: ' ',
+ dictionaries: [colors, animals],
+ style: 'capital',
+ seed: cyrb53(this.id)
+ })
+
+ this.name = {
+ model: ua.device.model,
+ os: ua.os.name,
+ browser: ua.browser.name,
+ type: ua.device.type,
+ deviceName,
+ displayName
+ };
+ }
+
+ getInfo() {
+ return {
+ id: this.id,
+ name: this.name,
+ rtcSupported: this.rtcSupported
+ }
+ }
+
+ static isValidUuid(uuid) {
+ return /^([0-9]|[a-f]){8}-(([0-9]|[a-f]){4}-){3}([0-9]|[a-f]){12}$/.test(uuid);
+ }
+
+ isPeerIdHashValid(peerId, peerIdHash) {
+ return peerIdHash === hasher.hashCodeSalted(peerId);
+ }
+
+ addRoomSecret(roomSecret) {
+ if (!(roomSecret in this.roomSecrets)) {
+ this.roomSecrets.push(roomSecret);
+ }
+ }
+
+ removeRoomSecret(roomSecret) {
+ if (roomSecret in this.roomSecrets) {
+ delete this.roomSecrets[roomSecret];
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/server.js b/server/server.js
new file mode 100644
index 0000000..9b3d340
--- /dev/null
+++ b/server/server.js
@@ -0,0 +1,80 @@
+import express from "express";
+import RateLimit from "express-rate-limit";
+import {fileURLToPath} from "url";
+import path, {dirname} from "path";
+import http from "http";
+
+export default class PairDropServer {
+
+ constructor(conf) {
+ const app = express();
+
+ if (conf.rateLimit) {
+ const limiter = RateLimit({
+ windowMs: 5 * 60 * 1000, // 5 minutes
+ max: 1000, // Limit each IP to 1000 requests per `window` (here, per 5 minutes)
+ message: 'Too many requests from this IP Address, please try again after 5 minutes.',
+ standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
+ legacyHeaders: false, // Disable the `X-RateLimit-*` headers
+ })
+
+ app.use(limiter);
+ // ensure correct client ip and not the ip of the reverse proxy is used for rate limiting
+ // see https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues
+
+ app.set('trust proxy', conf.rateLimit);
+
+ if (!conf.debugMode) {
+ console.log("Use DEBUG_MODE=true to find correct number for RATE_LIMIT.");
+ }
+ }
+
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = dirname(__filename);
+
+ const publicPathAbs = path.join(__dirname, '../public');
+ app.use(express.static(publicPathAbs));
+
+ if (conf.debugMode && conf.rateLimit) {
+ console.debug("\n");
+ console.debug("----DEBUG RATE_LIMIT----")
+ console.debug("To find out the correct value for RATE_LIMIT go to '/ip' and ensure the returned IP-address is the IP-address of your client.")
+ console.debug("See https://github.com/express-rate-limit/express-rate-limit#troubleshooting-proxy-issues for more info")
+ app.get('/ip', (req, res) => {
+ res.send(req.ip);
+ })
+ }
+
+ // By default, clients connecting to your instance use the signaling server of your instance to connect to other devices.
+ // By using `WS_SERVER`, you can host an instance that uses another signaling server.
+ app.get('/config', (req, res) => {
+ res.send({
+ signalingServer: conf.signalingServer
+ });
+ });
+
+ app.use((req, res) => {
+ res.redirect(301, '/');
+ });
+
+ app.get('/', (req, res) => {
+ res.sendFile('index.html');
+ console.log(`Serving client files from:\n${publicPathAbs}`)
+ });
+
+ const hostname = conf.localhostOnly ? '127.0.0.1' : null;
+ const server = http.createServer(app);
+
+ server.listen(conf.port, hostname);
+
+ server.on('error', (err) => {
+ if (err.code === 'EADDRINUSE') {
+ console.error(err);
+ console.info("Error EADDRINUSE received, exiting process without restarting process...");
+ process.exit(1)
+ }
+ });
+
+ this.server = server
+ }
+}
\ No newline at end of file
diff --git a/server/ws-server.js b/server/ws-server.js
new file mode 100644
index 0000000..da24375
--- /dev/null
+++ b/server/ws-server.js
@@ -0,0 +1,486 @@
+import {WebSocketServer} from "ws";
+import crypto from "crypto"
+
+import Peer from "./peer.js";
+import {hasher, randomizer} from "./helper.js";
+
+export default class PairDropWsServer {
+
+ constructor(server, conf) {
+ this._conf = conf
+
+ this._rooms = {}; // { roomId: peers[] }
+
+ this._roomSecrets = {}; // { pairKey: roomSecret }
+ this._keepAliveTimers = {};
+
+ this._wss = new WebSocketServer({ server });
+ this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request, conf)));
+ }
+
+ _onConnection(peer) {
+ peer.socket.on('message', message => this._onMessage(peer, message));
+ peer.socket.onerror = e => console.error(e);
+
+ this._keepAlive(peer);
+
+ this._send(peer, {
+ type: 'ws-config',
+ wsConfig: {
+ rtcConfig: this._conf.rtcConfig,
+ wsFallback: this._conf.wsFallback
+ }
+ });
+
+ // send displayName
+ this._send(peer, {
+ type: 'display-name',
+ displayName: peer.name.displayName,
+ deviceName: peer.name.deviceName,
+ peerId: peer.id,
+ peerIdHash: hasher.hashCodeSalted(peer.id)
+ });
+ }
+
+ _onMessage(sender, message) {
+ // Try to parse message
+ try {
+ message = JSON.parse(message);
+ } catch (e) {
+ console.warn("WS: Received JSON is malformed");
+ return;
+ }
+
+ switch (message.type) {
+ case 'disconnect':
+ this._onDisconnect(sender);
+ break;
+ case 'pong':
+ this._setKeepAliveTimerToNow(sender);
+ break;
+ case 'join-ip-room':
+ this._joinIpRoom(sender);
+ break;
+ case 'room-secrets':
+ this._onRoomSecrets(sender, message);
+ break;
+ case 'room-secrets-deleted':
+ this._onRoomSecretsDeleted(sender, message);
+ break;
+ case 'pair-device-initiate':
+ this._onPairDeviceInitiate(sender);
+ break;
+ case 'pair-device-join':
+ this._onPairDeviceJoin(sender, message);
+ break;
+ case 'pair-device-cancel':
+ this._onPairDeviceCancel(sender);
+ break;
+ case 'regenerate-room-secret':
+ this._onRegenerateRoomSecret(sender, message);
+ break;
+ case 'create-public-room':
+ this._onCreatePublicRoom(sender);
+ break;
+ case 'join-public-room':
+ this._onJoinPublicRoom(sender, message);
+ break;
+ case 'leave-public-room':
+ this._onLeavePublicRoom(sender);
+ break;
+ case 'signal':
+ this._signalAndRelay(sender, message);
+ break;
+ case 'request':
+ case 'header':
+ case 'partition':
+ case 'partition-received':
+ case 'progress':
+ case 'files-transfer-response':
+ case 'file-transfer-complete':
+ case 'message-transfer-complete':
+ case 'text':
+ case 'display-name-changed':
+ case 'ws-chunk':
+ // relay ws-fallback
+ if (this._conf.wsFallback) {
+ this._signalAndRelay(sender, message);
+ }
+ else {
+ console.log("Websocket fallback is not activated on this instance.")
+ }
+ }
+ }
+
+ _signalAndRelay(sender, message) {
+ const room = message.roomType === 'ip'
+ ? sender.ip
+ : message.roomId;
+
+ // relay message to recipient
+ if (message.to && Peer.isValidUuid(message.to) && this._rooms[room]) {
+ const recipient = this._rooms[room][message.to];
+ delete message.to;
+ // add sender
+ message.sender = {
+ id: sender.id,
+ rtcSupported: sender.rtcSupported
+ };
+ this._send(recipient, message);
+ }
+ }
+
+ _onDisconnect(sender) {
+ this._disconnect(sender);
+ }
+
+ _disconnect(sender) {
+ this._removePairKey(sender.pairKey);
+ sender.pairKey = null;
+
+ this._cancelKeepAlive(sender);
+ delete this._keepAliveTimers[sender.id];
+
+ this._leaveIpRoom(sender, true);
+ this._leaveAllSecretRooms(sender, true);
+ this._leavePublicRoom(sender, true);
+
+ sender.socket.terminate();
+ }
+
+ _onRoomSecrets(sender, message) {
+ if (!message.roomSecrets) return;
+
+ const roomSecrets = message.roomSecrets.filter(roomSecret => {
+ return /^[\x00-\x7F]{64,256}$/.test(roomSecret);
+ })
+
+ if (!roomSecrets) return;
+
+ this._joinSecretRooms(sender, roomSecrets);
+ }
+
+ _onRoomSecretsDeleted(sender, message) {
+ for (let i = 0; i 5 * timeout) {
+ // Disconnect peer if unresponsive for 10s
+ this._disconnect(peer);
+ return;
+ }
+
+ this._send(peer, { type: 'ping' });
+
+ this._keepAliveTimers[peer.id].timer = setTimeout(() => this._keepAlive(peer), timeout);
+ }
+
+ _cancelKeepAlive(peer) {
+ if (this._keepAliveTimers[peer.id]?.timer) {
+ clearTimeout(this._keepAliveTimers[peer.id].timer);
+ }
+ }
+
+ _setKeepAliveTimerToNow(peer) {
+ if (this._keepAliveTimers[peer.id]?.lastBeat) {
+ this._keepAliveTimers[peer.id].lastBeat = Date.now();
+ }
+ }
+}
+
diff --git a/turnserver_example.conf b/turnserver_example.conf
index 09e7986..04bc82d 100644
--- a/turnserver_example.conf
+++ b/turnserver_example.conf
@@ -6,11 +6,16 @@ server-name=pairdrop
listening-ip=0.0.0.0
# External IP-Address of the TURN server
-external-ip=
+# only needed, if coturn is behind a NAT
+# external-ip=
-# Main listening port
+# Main listening port for STUN and TURN
listening-port=3478
+# Main listening port for TURN over TLS (TURNS)
+# Use port 443 to bypass some firewalls
+tls-listening-port=5349
+
# Further ports that are open for communication
min-port=10000
max-port=20000
@@ -18,21 +23,34 @@ max-port=20000
# Use fingerprint in TURN message
fingerprint
-# Log file path
-log-file=/var/log/turnserver.log
-
# Enable verbose logging
-verbose
+# verbose
+
+# Log file path
+# - is logging to STDOUT, so it's visible in docker-compose logs
+log-file=-
# Specify the user for the TURN authentification
-user=user:password
+user=username:password
# Enable long-term credential mechanism
lt-cred-mech
# SSL certificates
-cert=/etc/letsencrypt/live//cert.pem
-pkey=/etc/letsencrypt/live//privkey.pem
+cert=/etc/coturn/ssl/cert.crt
+pkey=/etc/coturn/ssl/pkey.pem
+dh-file=/etc/coturn/ssl/dhparam.pem
-# 443 for TURN over TLS, which can bypass firewalls
-tls-listening-port=443
+# For security-reasons disable old ssl and tls-protocols
+# and other recommended options: see https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf
+no-sslv3
+no-tlsv1
+no-tlsv1_1
+no-tlsv1_2
+no-rfc5780
+no-stun-backward-compatibility
+response-origin-only-with-rfc5780
+no-cli
+no-multicast-peers
+no-software-attribute
+check-origin-consistency
\ No newline at end of file