UNPKG

@sendbird/uikit-react

Version:

Sendbird UIKit for React: A feature-rich and customizable chat UI kit with messaging, channel management, and user authentication.

481 lines (474 loc) 29 kB
'use strict'; var _tslib = require('./bundle-jAsAzWpU.js'); var React = require('react'); var useTypingLifecycle = require('./bundle-BjZvm-U5.js'); var utils = require('./bundle-DwLWArJq.js'); var LocalizationContext = require('./bundle-ClT0IexP.js'); var hooks_useModal = require('../hooks/useModal.js'); var GroupChannel_components_SuggestedMentionList = require('../GroupChannel/components/SuggestedMentionList.js'); var Message_hooks_useDirtyGetMentions = require('../Message/hooks/useDirtyGetMentions.js'); var ui_QuoteMessageInput = require('../ui/QuoteMessageInput.js'); var VoicePlayer_useVoicePlayer = require('../VoicePlayer/useVoicePlayer.js'); var VoiceRecorder_useVoiceRecorder = require('../VoiceRecorder/useVoiceRecorder.js'); var ui_VoiceMessageInput = require('./bundle-bmGcb273.js'); var ui_Modal = require('./bundle-BFmC2V1o.js'); var ui_Button = require('../ui/Button.js'); var VoicePlayer_context = require('./bundle-iWa9rWFV.js'); var uuid = require('./bundle-BNgfU9I_.js'); var useSendbird = require('./bundle-on0zTbLT.js'); var ui_MessageInput = require('./bundle-CzhNQgac.js'); var useDragAndDrop = require('./bundle-gKWdBSmm.js'); var ui_MessageInput_hooks_usePaste = require('./bundle-DHOzCMYH.js'); var MediaQueryContext = require('./bundle-DDUAkmTu.js'); var _const = require('./bundle-BSEj3ItE.js'); var compressImages = require('./bundle-2FCFe95u.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefaultCompat(React); var VoiceMessageInputWrapper = function (_a) { var channel = _a.channel, onCancelClick = _a.onCancelClick, onSubmitClick = _a.onSubmitClick; var uuid$1 = React.useRef(uuid.uuidv4()).current; var _b = React.useState(null), audioFile = _b[0], setAudioFile = _b[1]; var _c = React.useState(ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_RECORD), voiceInputState = _c[0], setVoiceInputState = _c[1]; var _d = React.useState(false), isSubmitted = _d[0], setSubmit = _d[1]; var _e = React.useState(false), isDisabled = _e[0], setDisabled = _e[1]; var _f = React.useState(false), showModal = _f[0], setShowModal = _f[1]; var stringSet = LocalizationContext.useLocalization().stringSet; var state = useSendbird.useSendbird().state; var config = state.config; var _g = VoiceRecorder_useVoiceRecorder.useVoiceRecorder({ onRecordingStarted: function () { setVoiceInputState(ui_VoiceMessageInput.VoiceMessageInputStatus.RECORDING); }, onRecordingEnded: function (audioFile) { setAudioFile(audioFile); }, }), start = _g.start, stop = _g.stop, cancel = _g.cancel, recordingTime = _g.recordingTime, recordingStatus = _g.recordingStatus, recordingLimit = _g.recordingLimit; var voicePlayer = VoicePlayer_useVoicePlayer.useVoicePlayer({ channelUrl: channel === null || channel === void 0 ? void 0 : channel.url, key: uuid$1, audioFile: audioFile !== null && audioFile !== void 0 ? audioFile : undefined, }); var play = voicePlayer.play, pause = voicePlayer.pause, playbackTime = voicePlayer.playbackTime, playingStatus = voicePlayer.playingStatus; var stopVoicePlayer = voicePlayer.stop; // disabled state: muted & frozen React.useEffect(function () { if (utils.isDisabledBecauseFrozen(channel) || utils.isDisabledBecauseMuted(channel)) { setDisabled(true); } else { setDisabled(false); } }, [channel === null || channel === void 0 ? void 0 : channel.myRole, channel === null || channel === void 0 ? void 0 : channel.isFrozen, channel === null || channel === void 0 ? void 0 : channel.myMutedState]); // call onSubmitClick when submit button is clicked and recorded audio file is created React.useEffect(function () { if (isSubmitted && audioFile) { onSubmitClick === null || onSubmitClick === void 0 ? void 0 : onSubmitClick(audioFile, recordingTime); setSubmit(false); setAudioFile(null); } }, [isSubmitted, audioFile, recordingTime]); // operate which control button should be displayed React.useEffect(function () { if (audioFile) { if (recordingTime < config.voiceRecord.minRecordingTime) { setVoiceInputState(ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_RECORD); setAudioFile(null); } else if (playingStatus === VoicePlayer_context.VOICE_PLAYER_STATUS.PLAYING) { setVoiceInputState(ui_VoiceMessageInput.VoiceMessageInputStatus.PLAYING); } else { setVoiceInputState(ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_PLAY); } } }, [audioFile, recordingTime, playingStatus]); return (React__default.default.createElement("div", { className: "sendbird-voice-message-input-wrapper" }, React__default.default.createElement(ui_VoiceMessageInput.VoiceMessageInput, { currentValue: recordingStatus === VoiceRecorder_useVoiceRecorder.VoiceRecorderStatus.COMPLETED ? playbackTime : recordingTime, maximumValue: recordingStatus === VoiceRecorder_useVoiceRecorder.VoiceRecorderStatus.COMPLETED ? recordingTime : recordingLimit, currentType: voiceInputState, onCancelClick: function () { onCancelClick === null || onCancelClick === void 0 ? void 0 : onCancelClick(); cancel(); stopVoicePlayer(); }, onSubmitClick: function () { if (isDisabled) { setShowModal(true); setVoiceInputState(ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_RECORD); } else { stop(); pause(); setSubmit(true); } }, onControlClick: function (type) { switch (type) { case ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_RECORD: { stopVoicePlayer(); start(); break; } case ui_VoiceMessageInput.VoiceMessageInputStatus.RECORDING: { if (recordingTime >= config.voiceRecord.minRecordingTime && !isDisabled) { stop(); } else if (isDisabled) { cancel(); setShowModal(true); setVoiceInputState(ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_RECORD); } else { cancel(); setVoiceInputState(ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_RECORD); } break; } case ui_VoiceMessageInput.VoiceMessageInputStatus.READY_TO_PLAY: { play(); break; } case ui_VoiceMessageInput.VoiceMessageInputStatus.PLAYING: { pause(); break; } } } }), showModal && (React__default.default.createElement(ui_Modal.Modal, { className: "sendbird-voice-message-input-wrapper-alert", titleText: utils.isDisabledBecauseMuted(channel) ? stringSet.MODAL__VOICE_MESSAGE_INPUT_DISABLED__TITLE_MUTED : stringSet.MODAL__VOICE_MESSAGE_INPUT_DISABLED__TITLE_FROZEN, hideFooter: true, isCloseOnClickOutside: true, onClose: function () { setShowModal(false); onCancelClick === null || onCancelClick === void 0 ? void 0 : onCancelClick(); } }, React__default.default.createElement("div", { className: "sendbird-voice-message-input-wrapper-alert__body" }, React__default.default.createElement(ui_Button.default, { className: "sendbird-voice-message-input-wrapper-alert__body__ok-button", type: ui_Button.ButtonTypes.PRIMARY, size: ui_Button.ButtonSizes.BIG, onClick: function () { setShowModal(false); onCancelClick === null || onCancelClick === void 0 ? void 0 : onCancelClick(); } }, stringSet.BUTTON__OK)))))); }; var MessageInputWrapperView = React__default.default.forwardRef(function (props, ref) { var _a; // Props var currentChannel = props.currentChannel, messages = props.messages, loading = props.loading, quoteMessage = props.quoteMessage, setQuoteMessage = props.setQuoteMessage, messageInputRef = props.messageInputRef, sendUserMessage = props.sendUserMessage, sendFileMessage = props.sendFileMessage, sendVoiceMessage = props.sendVoiceMessage, sendMultipleFilesMessage = props.sendMultipleFilesMessage, // render renderUserMentionItem = props.renderUserMentionItem, renderFileUploadIcon = props.renderFileUploadIcon, renderVoiceMessageIcon = props.renderVoiceMessageIcon, renderSendMessageIcon = props.renderSendMessageIcon, acceptableMimeTypes = props.acceptableMimeTypes, disabled = props.disabled; var stringSet = LocalizationContext.useLocalization().stringSet; var isMobile = MediaQueryContext.useMediaQueryContext().isMobile; var openModal = hooks_useModal.useGlobalModalContext().openModal; var state = useSendbird.useSendbird().state; var stores = state.stores, config = state.config; var isOnline = config.isOnline, userMention = config.userMention, logger = config.logger, groupChannel = config.groupChannel, imageCompression = config.imageCompression; var sdk = stores.sdkStore.sdk; var maxMentionCount = userMention.maxMentionCount, maxSuggestionCount = userMention.maxSuggestionCount; var uikitUploadSizeLimit = config.uikitUploadSizeLimit, uikitMultipleFilesMessageLimit = config.uikitMultipleFilesMessageLimit; var isBroadcast = currentChannel === null || currentChannel === void 0 ? void 0 : currentChannel.isBroadcast; var isOperator = (currentChannel === null || currentChannel === void 0 ? void 0 : currentChannel.myRole) === 'operator'; var isMultipleFilesMessageEnabled = (_a = props.isMultipleFilesMessageEnabled) !== null && _a !== void 0 ? _a : config.isMultipleFilesMessageEnabled; var isMentionEnabled = groupChannel.enableMention; var isVoiceMessageEnabled = groupChannel.enableVoiceMessage; // States var _b = React.useState(''), mentionNickname = _b[0], setMentionNickname = _b[1]; var _c = React.useState([]), mentionedUsers = _c[0], setMentionedUsers = _c[1]; var _d = React.useState([]), mentionedUserIds = _d[0], setMentionedUserIds = _d[1]; var _e = React.useState(null), selectedUser = _e[0], setSelectedUser = _e[1]; var _f = React.useState([]), mentionSuggestedUsers = _f[0], setMentionSuggestedUsers = _f[1]; var _g = React.useState(null), messageInputEvent = _g[0], setMessageInputEvent = _g[1]; var _h = React.useState(false), showVoiceMessageInput = _h[0], setShowVoiceMessageInput = _h[1]; // Conditions var isMessageInputDisabled = loading || (!currentChannel || !sdk) || (!sdk.isCacheEnabled && !isOnline) || utils.isDisabledBecauseFrozen(currentChannel) || utils.isDisabledBecauseMuted(currentChannel) || utils.isDisabledBecauseSuggestedReplies(currentChannel, config.groupChannel.enableSuggestedReplies) || utils.isDisabledBecauseMessageForm(messages, config.groupChannel.enableFormTypeMessage) || disabled; var showSuggestedMentionList = !isMessageInputDisabled && isMentionEnabled && mentionNickname.length > 0 && !isBroadcast; var mentionNodes = Message_hooks_useDirtyGetMentions.useDirtyGetMentions({ ref: (ref || messageInputRef) }, { logger: logger }); var ableMention = (mentionNodes === null || mentionNodes === void 0 ? void 0 : mentionNodes.length) < maxMentionCount; // Composer staging — file picker, drag-drop, and clipboard paste all feed // into pendingFiles. The submit handler drains them along with any text body. var allowMultipleFiles = Boolean(isMultipleFilesMessageEnabled) && Boolean(currentChannel) && ui_MessageInput_hooks_usePaste.isChannelTypeSupportsMultipleFilesMessage(currentChannel); var effectiveMultiLimit = allowMultipleFiles ? uikitMultipleFilesMessageLimit : 1; var _j = useDragAndDrop.usePendingFiles({ uikitUploadSizeLimit: uikitUploadSizeLimit, uikitMultipleFilesMessageLimit: effectiveMultiLimit, acceptableMimeTypes: acceptableMimeTypes, openModal: openModal, stringSet: stringSet, logger: logger, }), pendingFiles = _j.pendingFiles, addFiles = _j.addFiles, removeFile = _j.removeFile, clearPendingFiles = _j.clear; // Window-level drop target — files dropped anywhere in the viewport route // into this channel's composer EXCEPT when the drop lands inside an open // thread panel, which has its own composer. Disabled on mobile and when // the input itself is not accepting new files (voice recording, channel // disabled). var isFileUploadEnabled = ui_MessageInput.checkIfFileUploadEnabled({ channel: currentChannel !== null && currentChannel !== void 0 ? currentChannel : undefined, config: config }); useDragAndDrop.useDragAndDrop({ onAddFiles: addFiles, disabled: isMobile || isMessageInputDisabled || showVoiceMessageInput || !isFileUploadEnabled, shouldAccept: function (event) { var target = event.target; if (!(target instanceof Element)) return true; return !target.closest('.sendbird-thread-ui'); }, }); var stashedMentionedUsersRef = React.useRef(null); var stashedQuoteMessageRef = React.useRef(null); var prevHasPendingFilesRef = React.useRef(false); var hasPendingFilesInWrapper = pendingFiles.length > 0; React.useEffect(function () { if (hasPendingFilesInWrapper && !prevHasPendingFilesRef.current) { if (mentionedUsers.length > 0) { stashedMentionedUsersRef.current = mentionedUsers; } if (quoteMessage) { stashedQuoteMessageRef.current = quoteMessage; } setMentionNickname(''); } else if (!hasPendingFilesInWrapper && prevHasPendingFilesRef.current) { if (stashedMentionedUsersRef.current) { setMentionedUsers(stashedMentionedUsersRef.current); stashedMentionedUsersRef.current = null; } if (stashedQuoteMessageRef.current) { setQuoteMessage(stashedQuoteMessageRef.current); stashedQuoteMessageRef.current = null; } } prevHasPendingFilesRef.current = hasPendingFilesInWrapper; }, [hasPendingFilesInWrapper]); // Operate states React.useEffect(function () { setMentionNickname(''); setMentionedUsers([]); setMentionedUserIds([]); setSelectedUser(null); setMentionSuggestedUsers([]); setMessageInputEvent(null); setShowVoiceMessageInput(false); clearPendingFiles(); stashedMentionedUsersRef.current = null; stashedQuoteMessageRef.current = null; }, [currentChannel === null || currentChannel === void 0 ? void 0 : currentChannel.url]); var _k = useTypingLifecycle.useTypingLifecycle(currentChannel), startTyping = _k.startTyping, stopTyping = _k.stopTyping; React.useEffect(function () { setMentionedUsers(mentionedUsers.filter(function (_a) { var userId = _a.userId; var i = mentionedUserIds.indexOf(userId); if (i < 0) { return false; } else { mentionedUserIds.splice(i, 1); return true; } })); }, [mentionedUserIds]); var isSubmittingFilesRef = React.useRef(false); // Submit handler: drains pendingFiles XOR text body. Files and body do not // coexist in a single send anymore — when files are present, text from the // composer is suppressed at the UI level (textarea locked) and again here // for defense in depth. The caption read path remains in MessageBody to // render historical file messages that still carry a body. var handleSubmit = React.useCallback(function (_a) { return _tslib.__awaiter(void 0, [_a], void 0, function (_b) { var trimmed, parentMessageId, rawImageFiles, otherFiles, compressedImageFiles_1, tasks_1, useMFMBatch, file_1; var message = _b.message, mentionTemplate = _b.mentionTemplate, files = _b.files; return _tslib.__generator(this, function (_c) { switch (_c.label) { case 0: trimmed = message.trim(); parentMessageId = quoteMessage === null || quoteMessage === void 0 ? void 0 : quoteMessage.messageId; if (files.length === 0) { if (trimmed.length === 0) return [2 /*return*/]; sendUserMessage({ message: message, mentionedUsers: mentionedUsers, mentionedMessageTemplate: mentionTemplate, parentMessageId: parentMessageId, }); setMentionNickname(''); setMentionedUsers([]); setQuoteMessage(null); stopTyping(); return [2 /*return*/]; } if (isSubmittingFilesRef.current) return [2 /*return*/]; isSubmittingFilesRef.current = true; // Clear pending state and other composer state immediately so a rapid // second send (within the compression window) finds an empty queue and // bails out at MessageInput's sendMessage gate. setMentionNickname(''); setMentionedUsers([]); setQuoteMessage(null); stashedQuoteMessageRef.current = null; stopTyping(); clearPendingFiles(); _c.label = 1; case 1: _c.trys.push([1, , 3, 4]); rawImageFiles = files.filter(function (entry) { return entry.isImage; }).map(function (entry) { return entry.file; }); otherFiles = files.filter(function (entry) { return !entry.isImage; }).map(function (entry) { return entry.file; }); return [4 /*yield*/, compressImages.compressImages({ files: rawImageFiles, imageCompression: imageCompression, logger: logger, })]; case 2: compressedImageFiles_1 = (_c.sent()).compressedFiles; tasks_1 = []; useMFMBatch = isMultipleFilesMessageEnabled && compressedImageFiles_1.length > 1; if (useMFMBatch) { tasks_1.push(function () { return sendMultipleFilesMessage({ fileInfoList: compressedImageFiles_1.map(function (file) { return ({ file: file, fileName: file.name, fileSize: file.size, mimeType: file.type, }); }), parentMessageId: parentMessageId, }); }); } else if (compressedImageFiles_1.length === 1) { file_1 = compressedImageFiles_1[0]; tasks_1.push(function () { return sendFileMessage({ file: file_1, parentMessageId: parentMessageId, }); }); } else if (compressedImageFiles_1.length > 1) { compressedImageFiles_1.forEach(function (file) { tasks_1.push(function () { return sendFileMessage({ file: file, parentMessageId: parentMessageId, }); }); }); } otherFiles.forEach(function (file) { tasks_1.push(function () { return sendFileMessage({ file: file, parentMessageId: parentMessageId, }); }); }); // Sequential dispatch with per-task error isolation: one failure must not // break the rest of the batch. Fire-and-forget so UI cleanup runs now. (function () { return _tslib.__awaiter(void 0, void 0, void 0, function () { var _i, tasks_2, task, error_1; var _a; return _tslib.__generator(this, function (_b) { switch (_b.label) { case 0: _i = 0, tasks_2 = tasks_1; _b.label = 1; case 1: if (!(_i < tasks_2.length)) return [3 /*break*/, 6]; task = tasks_2[_i]; _b.label = 2; case 2: _b.trys.push([2, 4, , 5]); return [4 /*yield*/, task()]; case 3: _b.sent(); return [3 /*break*/, 5]; case 4: error_1 = _b.sent(); (_a = logger.warning) === null || _a === void 0 ? void 0 : _a.call(logger, 'GroupChannel|composer: file send failed', error_1); return [3 /*break*/, 5]; case 5: _i++; return [3 /*break*/, 1]; case 6: return [2 /*return*/]; } }); }); })(); return [3 /*break*/, 4]; case 3: isSubmittingFilesRef.current = false; return [7 /*endfinally*/]; case 4: return [2 /*return*/]; } }); }); }, [ sendUserMessage, sendFileMessage, sendMultipleFilesMessage, mentionedUsers, quoteMessage, setQuoteMessage, currentChannel, stopTyping, clearPendingFiles, isMultipleFilesMessageEnabled, imageCompression, logger, ]); if (isBroadcast && !isOperator) { /* Only `Operator` can send messages in the Broadcast channel */ return null; } // other conditions return (React__default.default.createElement("div", { className: showVoiceMessageInput ? 'sendbird-message-input-wrapper--voice-message' : 'sendbird-message-input-wrapper' }, showSuggestedMentionList && (React__default.default.createElement(GroupChannel_components_SuggestedMentionList.SuggestedMentionList, { currentChannel: currentChannel, targetNickname: mentionNickname, inputEvent: messageInputEvent !== null && messageInputEvent !== void 0 ? messageInputEvent : undefined, renderUserMentionItem: renderUserMentionItem, onUserItemClick: function (user) { if (user) { setMentionedUsers(_tslib.__spreadArray(_tslib.__spreadArray([], mentionedUsers, true), [user], false)); } setMentionNickname(''); setSelectedUser(user); setMessageInputEvent(null); }, onFocusItemChange: function () { setMessageInputEvent(null); }, onFetchUsers: function (users) { setMentionSuggestedUsers(users); }, ableAddMention: ableMention, maxMentionCount: maxMentionCount, maxSuggestionCount: maxSuggestionCount })), quoteMessage && (React__default.default.createElement("div", { className: "sendbird-message-input-wrapper__quote-message-input" }, React__default.default.createElement(ui_QuoteMessageInput, { replyingMessage: quoteMessage, onClose: function () { setQuoteMessage(null); stashedQuoteMessageRef.current = null; } }))), showVoiceMessageInput ? (React__default.default.createElement(VoiceMessageInputWrapper, { channel: currentChannel !== null && currentChannel !== void 0 ? currentChannel : undefined, onSubmitClick: function (recordedFile, duration) { sendVoiceMessage({ file: recordedFile, parentMessageId: quoteMessage === null || quoteMessage === void 0 ? void 0 : quoteMessage.messageId }, duration); setQuoteMessage(null); setShowVoiceMessageInput(false); }, onCancelClick: function () { setShowVoiceMessageInput(false); } })) : (React__default.default.createElement(ui_MessageInput.MessageInput, { className: "sendbird-message-input-wrapper__message-input", channel: currentChannel, channelUrl: currentChannel === null || currentChannel === void 0 ? void 0 : currentChannel.url, isMobile: isMobile, acceptableMimeTypes: acceptableMimeTypes, mentionSelectedUser: selectedUser, isMentionEnabled: isMentionEnabled, isVoiceMessageEnabled: isVoiceMessageEnabled, isSelectingMultipleFilesEnabled: isMultipleFilesMessageEnabled, onVoiceMessageIconClick: function () { setShowVoiceMessageInput(true); }, setMentionedUsers: setMentionedUsers, placeholder: (quoteMessage && stringSet.MESSAGE_INPUT__QUOTE_REPLY__PLACE_HOLDER) || (utils.isDisabledBecauseFrozen(currentChannel) && stringSet.MESSAGE_INPUT__PLACE_HOLDER__FROZEN) || (utils.isDisabledBecauseMuted(currentChannel) && (isMobile ? stringSet.MESSAGE_INPUT__PLACE_HOLDER__MUTED_SHORT : stringSet.MESSAGE_INPUT__PLACE_HOLDER__MUTED)) || (utils.isDisabledBecauseSuggestedReplies(currentChannel, config.groupChannel.enableSuggestedReplies) && stringSet.MESSAGE_INPUT__PLACE_HOLDER__SUGGESTED_REPLIES) || (utils.isDisabledBecauseMessageForm(messages, config.groupChannel.enableFormTypeMessage) && stringSet.MESSAGE_INPUT__PLACE_HOLDER__MESSAGE_FORM) || (disabled && stringSet.MESSAGE_INPUT__PLACE_HOLDER__DISABLED) || undefined, ref: (ref || messageInputRef), disabled: isMessageInputDisabled, renderFileUploadIcon: renderFileUploadIcon, renderSendMessageIcon: renderSendMessageIcon, renderVoiceMessageIcon: renderVoiceMessageIcon, onStartTyping: startTyping, onStopTyping: stopTyping, pendingFiles: pendingFiles, onAddFiles: addFiles, onRemoveFile: removeFile, onSubmit: handleSubmit, onUserMentioned: function (user) { if ((selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.userId) === (user === null || user === void 0 ? void 0 : user.userId)) { setSelectedUser(null); setMentionNickname(''); } }, onMentionStringChange: function (mentionText) { setMentionNickname(mentionText); }, onMentionedUserIdsUpdated: function (userIds) { setMentionedUserIds(userIds); }, onKeyDown: function (e) { if (showSuggestedMentionList && (mentionSuggestedUsers === null || mentionSuggestedUsers === void 0 ? void 0 : mentionSuggestedUsers.length) > 0 && ((e.key === _const.MessageInputKeys.Enter && ableMention) || e.key === _const.MessageInputKeys.ArrowUp || e.key === _const.MessageInputKeys.ArrowDown)) { setMessageInputEvent(e); return true; } return false; } })))); }); exports.MessageInputWrapperView = MessageInputWrapperView; exports.VoiceMessageInputWrapper = VoiceMessageInputWrapper; //# sourceMappingURL=bundle-CsVrpeSa.js.map