PWA: Support system sharing menu (#2057)
This commit is contained in:
parent
0e4f9e5ae6
commit
08ef6130b7
28
public/share/index.html
Normal file
28
public/share/index.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="refresh" content="3;URL='..'">
|
||||||
|
<title>Telegram Web</title>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="message">
|
||||||
|
<p>An error occurred during sharing. You will be redirected back to the app in 3 seconds...</p>
|
||||||
|
<p>If it did not happen automatically, <a href="..">click here</a>.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -26,6 +26,22 @@
|
|||||||
"sizes": "1280x802",
|
"sizes": "1280x802",
|
||||||
"type": "image/jpeg"
|
"type": "image/jpeg"
|
||||||
}],
|
}],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/share/",
|
||||||
|
"method": "POST",
|
||||||
|
"enctype": "multipart/form-data",
|
||||||
|
"params": {
|
||||||
|
"title": "title",
|
||||||
|
"text": "text",
|
||||||
|
"url": "url",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "files",
|
||||||
|
"accept": "*/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"display": "standalone"
|
"display": "standalone"
|
||||||
|
|||||||
@ -26,6 +26,22 @@
|
|||||||
"sizes": "1280x802",
|
"sizes": "1280x802",
|
||||||
"type": "image/jpeg"
|
"type": "image/jpeg"
|
||||||
}],
|
}],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/share/",
|
||||||
|
"method": "POST",
|
||||||
|
"enctype": "multipart/form-data",
|
||||||
|
"params": {
|
||||||
|
"title": "title",
|
||||||
|
"text": "text",
|
||||||
|
"url": "url",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "files",
|
||||||
|
"accept": "*/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"display": "standalone"
|
"display": "standalone"
|
||||||
|
|||||||
@ -26,6 +26,22 @@
|
|||||||
"sizes": "1280x802",
|
"sizes": "1280x802",
|
||||||
"type": "image/jpeg"
|
"type": "image/jpeg"
|
||||||
}],
|
}],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/share/",
|
||||||
|
"method": "POST",
|
||||||
|
"enctype": "multipart/form-data",
|
||||||
|
"params": {
|
||||||
|
"title": "title",
|
||||||
|
"text": "text",
|
||||||
|
"url": "url",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "files",
|
||||||
|
"accept": "*/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"display": "standalone"
|
"display": "standalone"
|
||||||
|
|||||||
@ -26,6 +26,22 @@
|
|||||||
"sizes": "1280x802",
|
"sizes": "1280x802",
|
||||||
"type": "image/jpeg"
|
"type": "image/jpeg"
|
||||||
}],
|
}],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/share/",
|
||||||
|
"method": "POST",
|
||||||
|
"enctype": "multipart/form-data",
|
||||||
|
"params": {
|
||||||
|
"title": "title",
|
||||||
|
"text": "text",
|
||||||
|
"url": "url",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "files",
|
||||||
|
"accept": "*/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"display": "standalone"
|
"display": "standalone"
|
||||||
|
|||||||
@ -34,7 +34,7 @@ const DraftRecipientPicker: FC<OwnProps> = ({
|
|||||||
}, [isOpen, markIsShown]);
|
}, [isOpen, markIsShown]);
|
||||||
|
|
||||||
const handleSelectRecipient = useCallback((recipientId: string) => {
|
const handleSelectRecipient = useCallback((recipientId: string) => {
|
||||||
openChatWithDraft({ chatId: recipientId, text: requestedDraft!.text });
|
openChatWithDraft({ chatId: recipientId, text: requestedDraft!.text, files: requestedDraft!.files });
|
||||||
}, [openChatWithDraft, requestedDraft]);
|
}, [openChatWithDraft, requestedDraft]);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import type { ApiAttachment, ApiChatMember, ApiSticker } from '../../../api/types';
|
import type { ApiAttachment, ApiChatMember, ApiSticker } from '../../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CONTENT_TYPES_WITH_PREVIEW,
|
|
||||||
EDITABLE_INPUT_MODAL_ID,
|
EDITABLE_INPUT_MODAL_ID,
|
||||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
SUPPORTED_IMAGE_CONTENT_TYPES,
|
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||||
@ -16,6 +15,7 @@ import {
|
|||||||
import { getFileExtension } from '../../common/helpers/documentInfo';
|
import { getFileExtension } from '../../common/helpers/documentInfo';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
||||||
|
import { hasPreview } from '../../../util/files';
|
||||||
|
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useMentionTooltip from './hooks/useMentionTooltip';
|
import useMentionTooltip from './hooks/useMentionTooltip';
|
||||||
@ -185,11 +185,7 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const files = await getFilesFromDataTransferItems(dataTransfer.items);
|
const files = await getFilesFromDataTransferItems(dataTransfer.items);
|
||||||
if (files?.length) {
|
if (files?.length) {
|
||||||
const newFiles = isQuick
|
const newFiles = Array.from(files).filter((file) => !isQuick || hasPreview(file));
|
||||||
? Array.from(files).filter((file) => {
|
|
||||||
return file.type && CONTENT_TYPES_WITH_PREVIEW.has(file.type);
|
|
||||||
})
|
|
||||||
: Array.from(files);
|
|
||||||
|
|
||||||
onFileAppend(newFiles, isQuick);
|
onFileAppend(newFiles, isQuick);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,11 +50,12 @@ import {
|
|||||||
selectCanScheduleUntilOnline,
|
selectCanScheduleUntilOnline,
|
||||||
selectEditingScheduledDraft,
|
selectEditingScheduledDraft,
|
||||||
selectEditingDraft,
|
selectEditingDraft,
|
||||||
selectRequestedText,
|
selectRequestedDraftText,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectChatType,
|
selectChatType,
|
||||||
|
selectRequestedDraftFiles,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import {
|
import {
|
||||||
getAllowedAttachmentOptions,
|
getAllowedAttachmentOptions,
|
||||||
@ -75,6 +76,7 @@ import windowSize from '../../../util/windowSize';
|
|||||||
import { isSelectionInsideInput } from './helpers/selection';
|
import { isSelectionInsideInput } from './helpers/selection';
|
||||||
import applyIosAutoCapitalizationFix from './helpers/applyIosAutoCapitalizationFix';
|
import applyIosAutoCapitalizationFix from './helpers/applyIosAutoCapitalizationFix';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { hasPreview } from '../../../util/files';
|
||||||
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||||
import { buildCustomEmojiHtml } from './helpers/customEmoji';
|
import { buildCustomEmojiHtml } from './helpers/customEmoji';
|
||||||
import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager';
|
import { processMessageInputForCustomEmoji } from '../../../util/customEmojiManager';
|
||||||
@ -175,7 +177,8 @@ type StateProps =
|
|||||||
sendAsChat?: ApiChat;
|
sendAsChat?: ApiChat;
|
||||||
sendAsId?: string;
|
sendAsId?: string;
|
||||||
editingDraft?: ApiFormattedText;
|
editingDraft?: ApiFormattedText;
|
||||||
requestedText?: string;
|
requestedDraftText?: string;
|
||||||
|
requestedDraftFiles?: File[];
|
||||||
attachBots: GlobalState['attachMenu']['bots'];
|
attachBots: GlobalState['attachMenu']['bots'];
|
||||||
attachMenuPeerType?: ApiAttachMenuPeerType;
|
attachMenuPeerType?: ApiAttachMenuPeerType;
|
||||||
theme: ISettings['theme'];
|
theme: ISettings['theme'];
|
||||||
@ -256,7 +259,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendAsChat,
|
sendAsChat,
|
||||||
sendAsId,
|
sendAsId,
|
||||||
editingDraft,
|
editingDraft,
|
||||||
requestedText,
|
requestedDraftText,
|
||||||
|
requestedDraftFiles,
|
||||||
botMenuButton,
|
botMenuButton,
|
||||||
attachBots,
|
attachBots,
|
||||||
attachMenuPeerType,
|
attachMenuPeerType,
|
||||||
@ -795,15 +799,23 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}, [contentToBeScheduled, handleMessageSchedule, requestCalendar]);
|
}, [contentToBeScheduled, handleMessageSchedule, requestCalendar]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (requestedText) {
|
if (requestedDraftText) {
|
||||||
setHtml(requestedText);
|
setHtml(requestedDraftText);
|
||||||
resetOpenChatWithDraft();
|
resetOpenChatWithDraft();
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const messageInput = document.getElementById(EDITABLE_INPUT_ID)!;
|
const messageInput = document.getElementById(EDITABLE_INPUT_ID)!;
|
||||||
focusEditableElement(messageInput, true);
|
focusEditableElement(messageInput, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [requestedText, resetOpenChatWithDraft, setHtml]);
|
}, [requestedDraftText, resetOpenChatWithDraft, setHtml]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (requestedDraftFiles?.length) {
|
||||||
|
const isQuick = requestedDraftFiles.every((file) => hasPreview(file));
|
||||||
|
handleFileSelect(requestedDraftFiles, isQuick);
|
||||||
|
resetOpenChatWithDraft();
|
||||||
|
}
|
||||||
|
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
|
||||||
|
|
||||||
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
|
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
|
||||||
if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) {
|
if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) {
|
||||||
@ -1417,7 +1429,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
: (chat?.adminRights?.anonymous ? chat?.id : undefined);
|
: (chat?.adminRights?.anonymous ? chat?.id : undefined);
|
||||||
const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined;
|
const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined;
|
||||||
const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined;
|
const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined;
|
||||||
const requestedText = selectRequestedText(global, chatId);
|
const requestedDraftText = selectRequestedDraftText(global, chatId);
|
||||||
|
const requestedDraftFiles = selectRequestedDraftFiles(global, chatId);
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
const isForCurrentMessageList = chatId === currentMessageList?.chatId
|
const isForCurrentMessageList = chatId === currentMessageList?.chatId
|
||||||
&& threadId === currentMessageList?.threadId
|
&& threadId === currentMessageList?.threadId
|
||||||
@ -1472,7 +1485,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
sendAsChat,
|
sendAsChat,
|
||||||
sendAsId,
|
sendAsId,
|
||||||
editingDraft,
|
editingDraft,
|
||||||
requestedText,
|
requestedDraftText,
|
||||||
|
requestedDraftFiles,
|
||||||
attachBots: global.attachMenu.bots,
|
attachBots: global.attachMenu.bots,
|
||||||
attachMenuPeerType: selectChatType(global, chatId),
|
attachMenuPeerType: selectChatType(global, chatId),
|
||||||
theme: selectTheme(global),
|
theme: selectTheme(global),
|
||||||
|
|||||||
@ -71,7 +71,7 @@ addActionHandler('openChatWithInfo', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openChatWithDraft', (global, actions, payload) => {
|
addActionHandler('openChatWithDraft', (global, actions, payload) => {
|
||||||
const { chatId, text } = payload;
|
const { chatId, text, files } = payload;
|
||||||
|
|
||||||
if (chatId) {
|
if (chatId) {
|
||||||
actions.openChat({ id: chatId });
|
actions.openChat({ id: chatId });
|
||||||
@ -82,6 +82,7 @@ addActionHandler('openChatWithDraft', (global, actions, payload) => {
|
|||||||
requestedDraft: {
|
requestedDraft: {
|
||||||
chatId,
|
chatId,
|
||||||
text,
|
text,
|
||||||
|
files,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -193,9 +193,18 @@ export function selectSendAs(global: GlobalState, chatId: string) {
|
|||||||
return selectUser(global, id) || selectChat(global, id);
|
return selectUser(global, id) || selectChat(global, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectRequestedText(global: GlobalState, chatId: string) {
|
export function selectRequestedDraftText(global: GlobalState, chatId: string) {
|
||||||
if (global.requestedDraft?.chatId === chatId) {
|
const { requestedDraft } = global;
|
||||||
return global.requestedDraft.text;
|
if (requestedDraft?.chatId === chatId && !requestedDraft.files?.length) {
|
||||||
|
return requestedDraft.text;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectRequestedDraftFiles(global: GlobalState, chatId: string) {
|
||||||
|
const { requestedDraft } = global;
|
||||||
|
if (requestedDraft?.chatId === chatId) {
|
||||||
|
return requestedDraft.files;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -601,6 +601,7 @@ export type GlobalState = {
|
|||||||
requestedDraft?: {
|
requestedDraft?: {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
files?: File[];
|
||||||
};
|
};
|
||||||
|
|
||||||
pollModal: {
|
pollModal: {
|
||||||
@ -743,6 +744,7 @@ export interface ActionPayloads {
|
|||||||
openChatWithDraft: {
|
openChatWithDraft: {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
files?: File[];
|
||||||
};
|
};
|
||||||
resetOpenChatWithDraft: never;
|
resetOpenChatWithDraft: never;
|
||||||
toggleJoinToSend: {
|
toggleJoinToSend: {
|
||||||
|
|||||||
@ -340,22 +340,6 @@ function crc32(buf) {
|
|||||||
return (crc ^ (-1)) >>> 0;
|
return (crc ^ (-1)) >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a deferred object
|
|
||||||
* @return {Deferred}
|
|
||||||
*/
|
|
||||||
function createDeferred() {
|
|
||||||
let resolve;
|
|
||||||
const promise = new Promise((_resolve) => {
|
|
||||||
resolve = _resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
promise,
|
|
||||||
resolve,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
readBigIntFromBuffer,
|
readBigIntFromBuffer,
|
||||||
readBufferFromBigInt,
|
readBufferFromBigInt,
|
||||||
@ -376,5 +360,4 @@ module.exports = {
|
|||||||
toSignedLittleBuffer,
|
toSignedLittleBuffer,
|
||||||
convertToLittle,
|
convertToLittle,
|
||||||
bufferXor,
|
bufferXor,
|
||||||
createDeferred,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import BigInt from 'big-integer';
|
import BigInt from 'big-integer';
|
||||||
import Api from '../tl/api';
|
import Api from '../tl/api';
|
||||||
import type TelegramClient from './TelegramClient';
|
import type TelegramClient from './TelegramClient';
|
||||||
import { sleep, createDeferred } from '../Helpers';
|
import { sleep } from '../Helpers';
|
||||||
import { getDownloadPartSize } from '../Utils';
|
import { getDownloadPartSize } from '../Utils';
|
||||||
import errors from '../errors';
|
import errors from '../errors';
|
||||||
|
import Deferred from '../../../util/Deferred';
|
||||||
|
|
||||||
interface OnProgress {
|
interface OnProgress {
|
||||||
isCanceled?: boolean;
|
isCanceled?: boolean;
|
||||||
@ -25,11 +26,6 @@ export interface DownloadFileParams {
|
|||||||
progressCallback?: OnProgress;
|
progressCallback?: OnProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Deferred {
|
|
||||||
promise: Promise<any>;
|
|
||||||
resolve: (value?: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chunk sizes for `upload.getFile` must be multiple of the smallest size
|
// Chunk sizes for `upload.getFile` must be multiple of the smallest size
|
||||||
const MIN_CHUNK_SIZE = 4096;
|
const MIN_CHUNK_SIZE = 4096;
|
||||||
const DEFAULT_CHUNK_SIZE = 64; // kb
|
const DEFAULT_CHUNK_SIZE = 64; // kb
|
||||||
@ -51,7 +47,7 @@ class Foreman {
|
|||||||
|
|
||||||
requestWorker() {
|
requestWorker() {
|
||||||
if (this.activeWorkers === this.maxWorkers) {
|
if (this.activeWorkers === this.maxWorkers) {
|
||||||
const deferred = createDeferred();
|
const deferred = new Deferred();
|
||||||
this.deferreds.push(deferred);
|
this.deferreds.push(deferred);
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
} else {
|
} else {
|
||||||
@ -225,7 +221,7 @@ async function downloadFile2(
|
|||||||
|
|
||||||
if (deferred) await deferred.promise;
|
if (deferred) await deferred.promise;
|
||||||
|
|
||||||
if (noParallel) deferred = createDeferred();
|
if (noParallel) deferred = new Deferred();
|
||||||
|
|
||||||
if (hasEnded) {
|
if (hasEnded) {
|
||||||
foremans[senderIndex].releaseWorker();
|
foremans[senderIndex].releaseWorker();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
const { createDeferred } = require('../Helpers');
|
const { default: Deferred } = require('../../../util/Deferred');
|
||||||
|
|
||||||
class RequestState {
|
class RequestState {
|
||||||
constructor(request, after = undefined, pending = {}) {
|
constructor(request, after = undefined, pending = {}) {
|
||||||
@ -9,7 +9,7 @@ class RequestState {
|
|||||||
this.after = after;
|
this.after = after;
|
||||||
this.result = undefined;
|
this.result = undefined;
|
||||||
this.pending = pending;
|
this.pending = pending;
|
||||||
this.deferred = createDeferred();
|
this.deferred = new Deferred();
|
||||||
this.promise = new Promise((resolve, reject) => {
|
this.promise = new Promise((resolve, reject) => {
|
||||||
this.resolve = resolve;
|
this.resolve = resolve;
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
|
|||||||
@ -2,7 +2,13 @@ import { DEBUG } from './config';
|
|||||||
import { respondForProgressive } from './serviceWorker/progressive';
|
import { respondForProgressive } from './serviceWorker/progressive';
|
||||||
import { respondForDownload } from './serviceWorker/download';
|
import { respondForDownload } from './serviceWorker/download';
|
||||||
import { respondWithCache, clearAssetCache, respondWithCacheNetworkFirst } from './serviceWorker/assetCache';
|
import { respondWithCache, clearAssetCache, respondWithCacheNetworkFirst } from './serviceWorker/assetCache';
|
||||||
import { handlePush, handleNotificationClick, handleClientMessage } from './serviceWorker/pushNotification';
|
import {
|
||||||
|
handlePush,
|
||||||
|
handleNotificationClick,
|
||||||
|
handleClientMessage as handleNotificationMessage,
|
||||||
|
} from './serviceWorker/pushNotification';
|
||||||
|
import { respondForShare, handleClientMessage as handleShareMessage } from './serviceWorker/share';
|
||||||
|
|
||||||
import { pause } from './util/schedulers';
|
import { pause } from './util/schedulers';
|
||||||
|
|
||||||
declare const self: ServiceWorkerGlobalScope;
|
declare const self: ServiceWorkerGlobalScope;
|
||||||
@ -53,6 +59,10 @@ self.addEventListener('fetch', (e: FetchEvent) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (url.includes('/share/')) {
|
||||||
|
e.respondWith(respondForShare(e));
|
||||||
|
}
|
||||||
|
|
||||||
if (url.startsWith('http')) {
|
if (url.startsWith('http')) {
|
||||||
if (NETWORK_FIRST_ASSETS.has(new URL(url).pathname)) {
|
if (NETWORK_FIRST_ASSETS.has(new URL(url).pathname)) {
|
||||||
e.respondWith(respondWithCacheNetworkFirst(e));
|
e.respondWith(respondWithCacheNetworkFirst(e));
|
||||||
@ -70,4 +80,7 @@ self.addEventListener('fetch', (e: FetchEvent) => {
|
|||||||
|
|
||||||
self.addEventListener('push', handlePush);
|
self.addEventListener('push', handlePush);
|
||||||
self.addEventListener('notificationclick', handleNotificationClick);
|
self.addEventListener('notificationclick', handleNotificationClick);
|
||||||
self.addEventListener('message', handleClientMessage);
|
self.addEventListener('message', (event) => {
|
||||||
|
handleNotificationMessage(event);
|
||||||
|
handleShareMessage(event);
|
||||||
|
});
|
||||||
|
|||||||
83
src/serviceWorker/share.ts
Normal file
83
src/serviceWorker/share.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import Deferred from '../util/Deferred';
|
||||||
|
|
||||||
|
declare const self: ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
|
type ShareData = {
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
url?: string;
|
||||||
|
files?: File[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const RESOLVED_DEFERRED = new Deferred<void>();
|
||||||
|
RESOLVED_DEFERRED.resolve();
|
||||||
|
const READY_CLIENT_DEFERREDS = new Map<string, Deferred<void>>();
|
||||||
|
|
||||||
|
export async function respondForShare(e: FetchEvent) {
|
||||||
|
if (e.request.method === 'POST') {
|
||||||
|
try {
|
||||||
|
const formData = await e.request.formData();
|
||||||
|
const data = parseFormData(formData);
|
||||||
|
requestShare(data, e.resultingClientId);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[SHARE] Failed to parse input data', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect('..');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleClientMessage(e: ExtendableMessageEvent) {
|
||||||
|
const { source, data } = e;
|
||||||
|
if (!source) return;
|
||||||
|
|
||||||
|
if (data.type === 'clientReady') {
|
||||||
|
const { id } = (source as Client);
|
||||||
|
const deferred = READY_CLIENT_DEFERREDS.get(id);
|
||||||
|
if (deferred) {
|
||||||
|
deferred.resolve();
|
||||||
|
} else {
|
||||||
|
READY_CLIENT_DEFERREDS.set(id, RESOLVED_DEFERRED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestShare(data: ShareData, clientId: string) {
|
||||||
|
const client = await self.clients.get(clientId);
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await getClientReadyDeferred(clientId);
|
||||||
|
|
||||||
|
client.postMessage({
|
||||||
|
type: 'share',
|
||||||
|
payload: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClientReadyDeferred(clientId: string) {
|
||||||
|
const deferred = READY_CLIENT_DEFERREDS.get(clientId);
|
||||||
|
if (deferred) {
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDeferred = new Deferred<void>();
|
||||||
|
READY_CLIENT_DEFERREDS.set(clientId, newDeferred);
|
||||||
|
return newDeferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFormData(formData: FormData): ShareData {
|
||||||
|
const files = formData.getAll('files') as File[];
|
||||||
|
const title = formData.get('title') as string;
|
||||||
|
const text = formData.get('text') as string;
|
||||||
|
const url = formData.get('url') as string;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
url,
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
}
|
||||||
14
src/util/Deferred.ts
Normal file
14
src/util/Deferred.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export default class Deferred<T = void> {
|
||||||
|
promise: Promise<T>;
|
||||||
|
|
||||||
|
reject!: (reason?: any) => void;
|
||||||
|
|
||||||
|
resolve!: (value: T | PromiseLike<T>) => void;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.promise = new Promise((resolve, reject) => {
|
||||||
|
this.reject = reject;
|
||||||
|
this.resolve = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -132,6 +132,6 @@ export function parseChooseParameter(choose?: string) {
|
|||||||
return types.filter((type): type is ApiChatType => API_CHAT_TYPES.includes(type as ApiChatType));
|
return types.filter((type): type is ApiChatType => API_CHAT_TYPES.includes(type as ApiChatType));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatShareText(url?: string, text?: string) {
|
export function formatShareText(url?: string, text?: string, title?: string): string {
|
||||||
return [url, text].filter(Boolean).join('\n');
|
return [url, title, text].filter(Boolean).join('\n');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { CONTENT_TYPES_WITH_PREVIEW } from '../config';
|
||||||
import { pause } from './schedulers';
|
import { pause } from './schedulers';
|
||||||
|
|
||||||
// Polyfill for Safari: `File` is not available in web worker
|
// Polyfill for Safari: `File` is not available in web worker
|
||||||
@ -122,3 +123,7 @@ export function imgToCanvas(img: HTMLImageElement) {
|
|||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasPreview(file: File) {
|
||||||
|
return CONTENT_TYPES_WITH_PREVIEW.has(file.type);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { DEBUG, DEBUG_MORE, IS_TEST } from '../config';
|
import { DEBUG, DEBUG_MORE, IS_TEST } from '../config';
|
||||||
import { getActions } from '../global';
|
import { getActions } from '../global';
|
||||||
|
import { formatShareText } from './deeplink';
|
||||||
import { IS_ANDROID, IS_IOS, IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
import { IS_ANDROID, IS_IOS, IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
||||||
import { notifyClientReady, playNotifySoundDebounced } from './notifications';
|
import { notifyClientReady, playNotifySoundDebounced } from './notifications';
|
||||||
|
|
||||||
@ -26,6 +27,12 @@ function handleWorkerMessage(e: MessageEvent) {
|
|||||||
case 'playNotificationSound':
|
case 'playNotificationSound':
|
||||||
playNotifySoundDebounced(action.payload.id);
|
playNotifySoundDebounced(action.payload.id);
|
||||||
break;
|
break;
|
||||||
|
case 'share':
|
||||||
|
dispatch.openChatWithDraft({
|
||||||
|
text: formatShareText(payload.url, payload.text, payload.title),
|
||||||
|
files: payload.files,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user