= 0 && mode.indexOf(c) < 0) {\n // Permission is marked as inactive: hide unchecked permissions, disable checked permissions\n continue;\n }\n items.push(\n \n | {names[c]} | \n {skip.indexOf(c) < 0 ?\n = 0)} onChange={this.handleChange}/>\n :\n = 0)} />\n } | {this.props.compare ? \n = 0)}/>\n | : null}\n
\n );\n }\n\n return (\n \n {this.props.userTitle ?\n
: null}\n
\n
\n {this.props.compare ?\n \n | {this.props.modeTitle} | \n {this.props.compareTitle} | \n
:\n null}\n \n {items}\n
\n
\n
\n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(PermissionsEditor);\n","// Image zoom and crop widget.\n\nimport React from 'react';\n\nconst DEFAULT_MAX_ZOOM = 2.5;\n\nexport default class Cropper extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n // Coordinates of the top left corner of the image in container coordinates.\n panX: 0,\n panY: 0,\n // Origin of the zoom in image coordinates.\n originX: 0,\n originY: 0,\n // Zoom scale.\n zoom: 1,\n // Zoom min and max.\n minZoom: 0,\n maxZoom: DEFAULT_MAX_ZOOM,\n };\n\n // References to DOM elements for hooking up events and getting screen dimensions.\n // The transparent element which captures mouse moves (moves with the image).\n this.overlay = React.createRef();\n // The image cutout window or view port (static).\n this.cutout = React.createRef();\n // Scaled and translated image preview.\n this.preview = React.createRef();\n // The main container (static).\n this.boundingBox = React.createRef();\n\n // The original image width and height before any scaling.\n this.imageWidth = 0;\n this.imageHeight = 0;\n\n // Mouse position when dragging.\n this.mouseX = 0\n this.mouseY = 0;\n // Length of the previous mouse move when dragging.\n this.prevDistance = 0;\n\n // Bounding rectangles of static elements.\n this.cutoutRect = {};\n this.bBoxRect = {};\n // Center of the bounding box.\n this.originX = 0;\n this.originY = 0;\n\n this.initScaling = this.initScaling.bind(this);\n this.onZoom = this.onZoom.bind(this);\n this.handleZoom = this.handleZoom.bind(this);\n this.mouseDown = this.mouseDown.bind(this);\n this.mouseUp = this.mouseUp.bind(this);\n this.mouseMove = this.mouseMove.bind(this);\n this.mouseTouch = this.mouseTouch.bind(this);\n this.positionAll = this.positionAll.bind(this);\n this.translate = this.translate.bind(this);\n }\n\n componentDidMount() {\n this.overlay.current.addEventListener('mousedown', this.mouseDown, { passive: true });\n this.overlay.current.addEventListener('touchstart', this.mouseDown, { passive: true });\n\n // The rectangle position is in viewport coordinates.\n this.bBoxRect = this.boundingBox.current.getBoundingClientRect();\n // Center of the bounding box in container coordinates.\n this.originX = this.bBoxRect.width / 2;\n this.originY = this.bBoxRect.height / 2;\n\n // The rectangle position is in viewport coordinates.\n this.cutoutRect = this.cutout.current.getBoundingClientRect();\n }\n\n componentWillUnmount() {\n this.overlay.current.removeEventListener('mousedown', this.mouseDown);\n this.overlay.current.removeEventListener('touchstart', this.mouseDown);\n }\n\n // Position all elements.\n positionAll(panX, panY, zoom) {\n // Zoom origin in image preview coordinates.\n this.setState({\n panX: panX,\n panY: panY,\n zoom: zoom,\n originX: this.originX - panX,\n originY: this.originY - panY,\n });\n\n // Pass cut out coordinates to caller.\n const left = (this.originX - panX) * zoom - this.originX;\n const top = (this.originY - panY) * zoom - this.originY;\n this.props.onChange(\n (left + this.cutoutRect.left - this.bBoxRect.left) / zoom,\n (top + this.cutoutRect.top - this.bBoxRect.top) / zoom,\n this.cutoutRect.width / zoom,\n this.cutoutRect.height / zoom,\n zoom);\n }\n\n // Check if new location is within the limits.\n static checkBound(currPan, img, cutout, delta) {\n let nextDiff = Math.min(0, cutout[0] - img[0] - delta, img[1] - cutout[1] + delta);\n if (nextDiff == 0) {\n // Cutout is completely within the image.\n currPan += delta;\n } else if (Math.min(0, cutout[0] - img[0], img[1] - cutout[1]) < nextDiff) {\n // Cutout is outside of the range but closer to the bound.\n currPan += delta;\n }\n return currPan;\n }\n\n initScaling() {\n // Initialize components.\n const imgRect = this.preview.current.getBoundingClientRect();\n\n this.imageWidth = imgRect.width;\n this.imageHeight = imgRect.height;\n\n // Set zoom slider's min and max values. Make sure the scaled image cannot be smaller than the cutout.\n const minZoom = Math.max(this.cutoutRect.width / imgRect.width, this.cutoutRect.height / imgRect.height);\n this.setState({\n minZoom: minZoom,\n maxZoom: Math.max(DEFAULT_MAX_ZOOM, minZoom + 1)\n });\n\n // Initial zoom level fills the bounding box at the smallest image dimension and overflows the largest, i.e. \"fill\" not \"fit\".\n const zoom = Math.max(this.bBoxRect.width / imgRect.width, this.bBoxRect.height / imgRect.height);\n // Converting from viewport coordinates to container, then panning.\n const panX = this.cutoutRect.left - this.bBoxRect.left -\n (imgRect.width - this.cutoutRect.width) / 2;\n const panY = this.cutoutRect.top - this.bBoxRect.top -\n (imgRect.height - this.cutoutRect.height) / 2;\n\n this.positionAll(panX, panY, zoom);\n }\n\n onZoom(e) {\n this.handleZoom(e.target.value);\n }\n\n handleZoom(zoom) {\n let panX = this.state.panX;\n let panY = this.state.panY;\n\n // Ensure that the image at the new zoom is not outside of the cutout boundaries.\n // Calculate image coordinates at the new zoom.\n const imgLeft = this.originX - (this.originX - panX) * zoom;\n const imgRight = imgLeft + this.imageWidth * zoom;\n // Cutout in the same coordinates as the image.\n const coLeft = this.cutoutRect.left - this.bBoxRect.left;\n const coRight = coLeft + this.cutoutRect.width;\n if (coLeft < imgLeft) {\n panX -= imgLeft - coLeft;\n } else if (coRight > imgRight) {\n panX += coRight - imgRight;\n }\n const imgTop = this.originY - (this.originY - panY) * zoom;\n const imgBottom = imgTop + this.imageHeight * zoom;\n const coTop = this.cutoutRect.top - this.bBoxRect.top;\n const coBottom = coTop + this.cutoutRect.height;\n if (coTop < imgTop) {\n panY -= imgTop - coTop;\n } else if (coBottom > imgBottom) {\n panY += coBottom - imgBottom;\n }\n\n this.positionAll(panX, panY, zoom);\n }\n\n mouseDown(e) {\n if (e.touches) {\n this.mouseX = e.touches[0].pageX;\n this.mouseY = e.touches[0].pageY;\n } else {\n this.mouseX = e.pageX;\n this.mouseY = e.pageY;\n }\n\n window.addEventListener('mousemove', this.mouseMove, { passive: false });\n window.addEventListener('touchmove', this.mouseTouch, { passive: false });\n window.addEventListener('mouseup', this.mouseUp, { passive: true });\n window.addEventListener('touchend', this.mouseUp, { passive: true });\n // Disable text selection in the entire document.\n document.body.style['userSelect'] = 'none';\n }\n\n // Perform image panning.\n translate(pageX, pageY) {\n const dX = pageX - this.mouseX;\n const dY = pageY - this.mouseY;\n\n this.mouseX = pageX;\n this.mouseY = pageY;\n\n // Make sure the image is within the cutout window.\n const imgRect = this.preview.current.getBoundingClientRect();\n\n // Check if the new position is within the boundaries, and if not if it's closer to them.\n let panX = Cropper.checkBound(this.state.panX, [imgRect.left, imgRect.right],\n [this.cutoutRect.left, this.cutoutRect.right], dX);\n let panY = Cropper.checkBound(this.state.panY, [imgRect.top, imgRect.bottom],\n [this.cutoutRect.top, this.cutoutRect.bottom], dY);\n\n this.positionAll(panX, panY, this.state.zoom);\n }\n\n // Image panning.\n mouseMove(e) {\n e.preventDefault();\n this.translate(e.pageX, e.pageY);\n }\n\n mouseTouch(e) {\n e.preventDefault();\n\n if (e.touches.length == 1) {\n this.translate(e.touches[0].pageX, e.touches[0].pageY);\n return;\n }\n\n // Image zooming by pinching.\n const [touch0, touch1] = e.touches;\n const distance = Math.sqrt((touch0.pageX - touch1.pageX) * (touch0.pageX - touch1.pageX) +\n (touch0.pageY - touch1.pageY) * (touch0.pageY - touch1.pageY));\n\n if (!this.prevDistance) {\n this.prevDistance = distance / this.state.zoom;\n }\n\n let scale = (distance / this.prevDistance);\n this.handleZoom(Math.max(this.minZoom, Math.min(this.maxZoom, scale)));\n }\n\n mouseUp(e) {\n window.removeEventListener('mousemove', this.mouseMove);\n window.removeEventListener('touchmove', this.mouseTouch);\n window.removeEventListener('mouseup', this.mouseUp);\n window.removeEventListener('touchend', this.mouseUp);\n // Re-enable text selection.\n document.body.style['userSelect'] = '';\n\n this.positionAll(this.state.panX, this.state.panY, this.state.zoom);\n }\n\n render() {\n // transform3d: position and scale.\n const t3d = `translate3d(${this.state.panX}px, ${this.state.panY}px, 0) scale(${this.state.zoom})`;\n // transformOrigin: zoom origin.\n const orig = `${this.state.originX}px ${this.state.originY}px`;\n\n // Overlay position and size are exactly equal to the position and size of the transformed image\n // except it uses different coordinates.\n const overlay = {\n top: `${this.originY - this.state.originY * this.state.zoom}px`,\n left: `${this.originX - this.state.originX * this.state.zoom}px`,\n width: `${this.imageWidth * this.state.zoom}px`,\n height: `${this.imageHeight * this.state.zoom}px`\n };\n return (\n \n
\n

\n
\n
\n
\n
\n \n
\n
\n );\n }\n}\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Cropper from './cropper.jsx';\nimport { imageCrop } from '../lib/blob-helpers.js';\n\nexport default class AvatarCrop extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n top: 0,\n left: 0,\n width: 0,\n height: 0,\n scale: 1\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleChange = this.handleChange.bind(this);\n }\n\n // Cut out in image coordinates and desired scale factor.\n handleChange(left, top, width, height, scale) {\n this.setState({\n left: left,\n top: top,\n width: width,\n height: height,\n scale: scale\n });\n }\n\n handleSubmit() {\n imageCrop(this.props.mime, this.props.avatar,\n this.state.left, this.state.top, this.state.width, this.state.height,\n this.state.scale)\n .then(img => {\n this.props.onSubmit(img.mime, img.blob, img.width, img.height);\n })\n .catch(err => {\n this.props.onError(err);\n });\n }\n\n render() {\n return (\n \n
\n \n
\n
\n {this.props.onCancel ?\n \n : null}\n \n
\n
\n );\n }\n};\n","// Password with a visiblity toggle.\nimport React from 'react';\n\nexport default class VisiblePassword extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n value: this.props.value,\n visible: false\n };\n\n this.handleVisibility = this.handleVisibility.bind(this);\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value});\n if (this.props.onChange) {\n this.props.onChange(e);\n }\n }\n\n handleVisibility(e) {\n e.preventDefault();\n this.setState({visible: !this.state.visible});\n }\n\n handleKeyDown(e) {\n if (e.keyCode == 27) {\n // Escape pressed\n this.setState({value: this.props.value, visible: false});\n if (this.props.onFinished) {\n this.props.onFinished();\n }\n } else if (e.keyCode == 13) {\n // Enter pressed\n this.handleEditingFinished();\n }\n }\n\n handleEditingFinished(e) {\n if (e) {\n let currentTarget = e.currentTarget;\n setTimeout(() => {\n if (!currentTarget.contains(document.activeElement)) {\n if (this.props.onFinished) {\n this.props.onFinished(this.state.value);\n }\n }\n }, 0);\n } else if (this.props.onFinished) {\n this.props.onFinished(this.state.value.trim());\n }\n }\n\n render() {\n return (\n \n \n \n \n {this.state.visible ? 'visibility' : 'visibility_off'}\n \n \n
\n );\n }\n}\n","// In-place text editor. Shows text with an icon which toggles it into an input field.\nimport React from 'react';\n\nimport VisiblePassword from './visible-password.jsx';\n\nexport default class InPlaceEdit extends React.Component {\n constructor(props) {\n super(props);\n\n this.selfRef = React.createRef();\n\n this.state = {\n active: props.active,\n initialValue: props.value || '',\n value: props.value || ''\n };\n\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleStartEditing = this.handleStartEditing.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n this.handlePasswordFinished = this.handlePasswordFinished.bind(this);\n }\n\n componentDidUpdate(prevProps, prevState) {\n // If text has changed while in read mode, update text and discard changes.\n // Ignore update if in edit mode.\n const newValue = this.props.value || '';\n if (prevState.initialValue != newValue && !prevState.active) {\n this.setState({\n initialValue: newValue,\n value: newValue\n });\n }\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value || ''});\n }\n\n handleKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({value: this.props.value, active: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleEditingFinished(e);\n }\n }\n\n handleStartEditing() {\n if (!this.props.readOnly) {\n this.setState({active: true}, () => {\n if (this.selfRef.current) {\n this.selfRef.current.focus();\n }\n });\n }\n }\n\n handleEditingFinished(event) {\n const value = this.state.value.trim();\n if (this.props.required && (!event.target.checkValidity() || !value)) {\n // Empty input\n this.setState({value: this.props.value, active: false});\n return;\n }\n this.setState({active: false});\n if ((value || this.props.value) && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n handlePasswordFinished(value) {\n this.setState({active: false});\n if (value && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n render() {\n if (!this.state.active) {\n let spanText = this.props.type == 'password' ? '••••••••' : this.state.value;\n let spanClass = 'in-place-edit' + (this.props.readOnly ? ' disabled' : '');\n if (!spanText) {\n spanText = this.props.placeholder;\n spanClass += ' placeholder';\n }\n if (!this.props.multiline || this.props.multiline == 1) {\n spanClass += ' short';\n }\n\n return (\n {spanText}\n );\n }\n\n let element;\n const attr = {};\n if (this.props.type == 'password') {\n element = VisiblePassword;\n attr.onFinished = this.handlePasswordFinished;\n } else {\n if (this.props.multiline > 1) {\n element = 'textarea';\n attr.rows = this.props.multiline;\n attr.className = 'inplace-edit';\n } else {\n element = 'input';\n attr.type = this.props.type || 'text';\n }\n attr.value = this.state.value;\n attr.onChange = this.handeTextChange;\n attr.onKeyDown = this.handleKeyDown;\n attr.onBlur = this.handleEditingFinished;\n }\n attr.placeholder = this.props.placeholder;\n attr.required = this.props.required ? 'required' : '';\n attr.autoComplete = this.props.autoComplete;\n attr.autoFocus = true;\n attr.ref = this.selfRef;\n\n return React.createElement(element, attr, null);\n }\n};\n","// TagManager: edit topic or user tags.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport ChipInput from './chip-input.jsx';\n\nimport { MAX_TAG_COUNT, MAX_TAG_LENGTH, MIN_TAG_LENGTH } from '../config.js';\nimport { arrayEqual } from '../lib/utils.js';\n\nexport default class TagManager extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tags: this.props.tags || [],\n tagInput: '',\n activated: this.props.activated\n };\n\n this.handleTagInput = this.handleTagInput.bind(this);\n this.handleAddTag = this.handleAddTag.bind(this);\n this.handleRemoveTag = this.handleRemoveTag.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n const tags = nextProps.tags || [];\n if (!arrayEqual(tags, prevState.tags) && !prevState.activated) {\n return {tags: tags};\n }\n return null;\n }\n\n handleTagInput(text) {\n this.setState({tagInput: text});\n if (text.length > 0) {\n const last = text[text.length-1];\n if (text[0] == '\"') {\n // This is a quoted string.\n if (text.length > 1 && last == '\"') {\n this.handleAddTag(text.substring(1, text.length-1));\n }\n } else if (last == ',' || last == ' ' || last == ';' || last == '\"') {\n // User entered ',', ' ' or ';'\n this.handleAddTag(text.substring(0, text.length-1).trim());\n }\n }\n }\n\n handleAddTag(tag) {\n const maxTagCount = this.props.tinode.getServerLimit('maxTagCount', MAX_TAG_COUNT);\n\n if (tag.length > 0 && this.state.tags.length < maxTagCount) {\n const tags = this.state.tags.slice(0);\n tags.push(tag);\n\n this.setState({tags: tags, tagInput: ''});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n return tags;\n }\n return this.state.tags;\n }\n\n handleRemoveTag(tag, index) {\n const tags = this.state.tags.slice(0);\n tags.splice(index, 1);\n this.setState({tags: tags});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n }\n\n handleSubmit() {\n // Add unprocessed input to tags, submit the list.\n this.props.onSubmit(this.handleAddTag(this.state.tagInput.trim()));\n this.setState({activated: false, tags: this.props.tags || []});\n }\n\n handleCancel() {\n this.setState({activated: false, tagInput: '', tags: this.props.tags || []});\n if (this.props.onCancel) {\n this.props.onCancel();\n }\n }\n\n render() {\n const minTagLength = this.props.tinode.getServerLimit('minTagLength', MIN_TAG_LENGTH);\n const maxTagLength = this.props.tinode.getServerLimit('maxTagLength', MAX_TAG_LENGTH);\n\n let tags = [];\n if (this.state.activated) {\n this.state.tags.map((tag) => {\n tags.push({user: tag, invalid: (tag.length < minTagLength || tag.length > maxTagLength)});\n });\n } else {\n this.state.tags.map((tag) => {\n tags.push({tag});\n });\n if (tags.length == 0) {\n tags = (\n \n \n \n );\n }\n }\n return (\n \n
\n \n
\n {this.state.activated ?\n
\n
{\n (add_tags_prompt) => \n }\n {this.props.onSubmit || this.props.onCancel ?\n
\n \n \n
\n : null}\n
\n :\n
\n }\n
\n );\n }\n};\n","// Widget for editing topic description.\n\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarCrop from './avatar-crop.jsx';\nimport AvatarUpload from './avatar-upload.jsx';\nimport InPlaceEdit from './in-place-edit.jsx';\nimport TagManager from './tag-manager.jsx';\n\nimport { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE, MAX_TITLE_LENGTH,\n MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';\nimport { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, theCard } from '../lib/utils.js';\n\nexport default class TopicDescEdit extends React.Component {\n constructor(props) {\n super(props);\n\n const topic = this.props.tinode.getTopic(this.props.topic);\n const acs = topic.getAccessMode();\n this.state = {\n isMe: Tinode.isMeTopicName(this.props.topic),\n owner: acs && acs.isOwner(),\n fullName: topic.public ? topic.public.fn : undefined,\n private: topic.private ? topic.private.comment : null,\n description: topic.public ? topic.public.note : undefined,\n avatar: makeImageUrl(topic.public ? topic.public.photo : null),\n tags: topic.tags() || [],\n newAvatar: null,\n newAvatarMime: null\n };\n\n this.previousOnTags = null;\n\n this.tnNewTags = this.tnNewTags.bind(this);\n this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);\n this.handleImageUpdated = this.handleImageUpdated.bind(this);\n this.handleAvatarCropped = this.handleAvatarCropped.bind(this);\n this.handlePrivateUpdate = this.handlePrivateUpdate.bind(this);\n this.handleDescriptionUpdate = this.handleDescriptionUpdate.bind(this);\n this.uploadAvatar = this.uploadAvatar.bind(this);\n this.handleAvatarCropCancel = this.handleAvatarCropCancel.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n }\n\n componentDidMount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n this.previousOnTags = topic.onTagsUpdated;\n topic.onTagsUpdated = this.tnNewTags;\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n topic.onTagsUpdated = this.previousOnTags;\n }\n\n tnNewTags(tags) {\n this.setState({tags: tags});\n }\n\n handleFullNameUpdate(fn) {\n fn = fn.trim().substring(0, MAX_TITLE_LENGTH);\n if (fn && this.state.fullName !== fn) {\n this.setState({fullName: fn});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(fn, null));\n }\n }\n\n handlePrivateUpdate(comment) {\n comment = comment.trim().substring(0, MAX_TITLE_LENGTH);\n if (this.state.private !== comment) {\n this.setState({private: comment});\n this.props.onUpdateTopicDesc(this.props.topic, null, comment || Tinode.DEL_CHAR);\n }\n }\n\n handleDescriptionUpdate(desc) {\n desc = desc.trim().substring(0, MAX_TOPIC_DESCRIPTION_LENGTH);\n if (desc) {\n this.setState({description: desc});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, null, null, desc));\n }\n }\n\n // AvatarUpload widget has changed the avatar: received a new file or deleted the avatar.\n handleImageUpdated(mime, img) {\n // This will show the AvatarCropView is img is not null.\n this.setState({newAvatar: img, newAvatarMime: mime});\n if (!img) {\n // Deleting the avatar.\n this.setState({avatar: null});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, Tinode.DEL_CHAR));\n }\n }\n\n // AvatarCropView calls this method when the user has cropped the image.\n handleAvatarCropped(mime, blob, width, height) {\n const url = blob ? URL.createObjectURL(blob) : null;\n this.setState({avatar: url, newAvatar: null, newAvatarMime: null});\n if (blob) {\n this.uploadAvatar(mime, blob, width, height);\n }\n }\n\n // Utility method for converting cropped avatar blob to bytes for sending inband or\n // for uploading it to the server out of band.\n uploadAvatar(mime, blob, width, height) {\n const readyToUpload = (image) => {\n let {mime, blob} = image;\n if (blob.size > MAX_AVATAR_BYTES) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n\n this.setState({uploading: true});\n uploader.upload(blob)\n .then((url) => {\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, url));\n })\n .catch((err) => {\n this.props.onError(err, 'err');\n })\n .finally(() => {\n this.setState({uploading: false});\n });\n } else {\n this.setState({uploading: true});\n // Convert blob to base64-encoded bits.\n blobToBase64(blob)\n .then(b64 => {\n const du = makeImageUrl({data: b64.bits, type: mime});\n this.setState({source: du});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, du));\n this.setState({uploading: false});\n });\n }\n };\n\n if (width > AVATAR_SIZE || height > AVATAR_SIZE || width != height) {\n // Avatar is not square or too large even after cropping. Shrink it and make square.\n imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true)\n .then(scaled => readyToUpload(scaled))\n .catch(err => this.props.onError(err, 'err'));\n } else {\n readyToUpload({mime: mime, blob: blob, width: width, height: height});\n }\n }\n\n handleAvatarCropCancel(img) {\n this.setState({newAvatar: null, newAvatarMime: null});\n }\n\n handleTagsUpdated(tags) {\n // Check if tags have actually changed.\n if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n return;\n }\n this.props.onUpdateTags(tags);\n }\n\n render() {\n if (this.state.newAvatar) {\n return (\n \n );\n }\n\n const editable = this.state.isMe || this.state.owner;\n\n return (\n <>\n \n
\n \n \n {this.state.isMe ?\n
\n
\n
\n {\n (full_name_placeholder) => \n }\n
\n
\n :\n <>\n
\n
\n
\n {\n (group_name_placeholder) => \n }\n
\n
\n
\n
\n \n
\n
\n {\n (private_placeholder) => \n }\n
\n
\n >\n }\n {editable || this.state.description ?\n
\n
\n
\n {\n (private_placeholder) => \n }\n
\n
\n : null\n }\n
\n {editable ?\n <>\n \n {\n (title_tag_manager) => \n }\n >\n : null\n }\n >\n );\n }\n};\n","// View for editing topic (including 'me' topic) parameters.\n\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport TopicDescEdit from '../widgets/topic-desc-edit.jsx';\n\nimport { arrayEqual, asEmail, asPhone } from '../lib/utils.js';\n\nexport default class TopicCommon extends React.Component {\n constructor(props) {\n super(props);\n\n const topic = this.props.tinode.getTopic(this.props.topic);\n const acs = topic.getAccessMode();\n this.state = {\n isMe: this.props.topic == 'me',\n owner: acs && acs.isOwner(),\n credentials: (topic.getCredentials ? topic.getCredentials() : null) || [],\n addCredActive: false,\n addCredInvalid: false,\n newCred: '',\n tags: []\n };\n\n this.previousTagsUpdated = undefined;\n\n this.onTagsUpdated = this.onTagsUpdated.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n this.tnCredsUpdated = this.tnCredsUpdated.bind(this);\n this.handleCredChange = this.handleCredChange.bind(this);\n this.handleCredKeyDown = this.handleCredKeyDown.bind(this);\n this.handleCredEntered = this.handleCredEntered.bind(this);\n }\n\n // No need to separately handle component mount.\n componentDidUpdate(props) {\n const topic = this.props.tinode.getTopic(props.topic);\n if (!topic) {\n return;\n }\n\n topic.onCredsUpdated = this.tnCredsUpdated;\n\n if (topic.onTagsUpdated != this.onTagsUpdated) {\n if (topic.getType() == 'grp') {\n this.previousTagsUpdated = topic.onTagsUpdated;\n topic.onTagsUpdated = this.onTagsUpdated;\n } else {\n this.previousTagsUpdated = undefined;\n }\n }\n\n if (this.state.topic != props.topic) {\n this.setState({topic: props.topic});\n }\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n topic.onCredsUpdated = undefined;\n topic.onTagsUpdated = this.previousTagsUpdated;\n }\n\n tnCredsUpdated(creds) {\n this.setState({credentials: creds || []});\n }\n\n handleCredChange(e) {\n this.setState({newCred: e.target.value, addCredInvalid: false});\n }\n\n handleCredKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({newCred: '', addCredActive: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleCredEntered(e);\n }\n }\n\n handleCredEntered(e) {\n const value = this.state.newCred.trim();\n if (!value) {\n this.setState({addCredActive: false, addCredInvalid: false});\n return;\n }\n\n let val = asPhone(value);\n let method;\n if (val) {\n method = 'tel';\n } else {\n val = asEmail(value);\n if (val) {\n method = 'email';\n }\n }\n if (method) {\n this.props.onCredAdd(method, val);\n this.setState({addCredActive: false, newCred: ''});\n } else {\n this.setState({addCredInvalid: true});\n }\n }\n\n // Server informs that the tags have been updated.\n onTagsUpdated(tags) {\n this.setState({tags: tags});\n\n if (this.previousTagsUpdated && this.previousTagsUpdated != this.onTagsUpdated) {\n this.previousTagsUpdated(tags);\n }\n }\n\n // Request server to change tags.\n handleTagsUpdated(tags) {\n if (!arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n this.props.onTopicTagsUpdateRequest(this.props.topic, tags);\n }\n }\n\n render() {\n const credentials = [];\n if (this.state.isMe) {\n this.state.credentials.map((cred) => {\n credentials.push(\n \n );\n });\n }\n\n return (\n \n
\n {this.state.isMe ?\n <>\n
\n
\n
\n
\n {credentials}\n {this.state.addCredActive ?\n
\n : null}\n
\n
\n
\n >\n : null\n }\n
\n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport InPlaceEdit from './in-place-edit.jsx';\n\nimport { NO_ACCESS_MODE } from '../config.js';\n\nconst messages = defineMessages({\n clear_messages: {\n id: 'action_clear_messages',\n defaultMessage: 'Clear Messages',\n description: 'Flat button [Clear Messages] (soft-delete messages)'\n },\n clear_messages_warning: {\n id: 'clear_messages_warning',\n defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',\n description: 'Alert dialog warning when deleting all messages.'\n },\n delete_messages: {\n id: 'action_delete_messages',\n defaultMessage: 'Clear Messages for All',\n description: 'Flat button [Clear for All] (hard-delete all messages)'\n },\n delete_messages_warning: {\n id: 'delete_messages_warning',\n defaultMessage: 'Are you sure you want to delete all messages for everyone? It cannot be undone.',\n description: 'Alert dialog warning when hard-deleting all messages.'\n },\n topic_delete: {\n id: 'topic_delete',\n defaultMessage: 'Delete Conversation',\n description: 'Alert title when deleting the topic.'\n },\n topic_delete_warning: {\n id: 'topic_delete_warning',\n defaultMessage: 'Are you sure you want to delete this conversation? It cannot be undone.',\n description: 'Alert warning when deleting entire topic'\n },\n leave_chat: {\n id: 'action_leave_chat',\n defaultMessage: 'Leave Conversation',\n description: 'Flat button [Leave Conversation]'\n },\n leave_chat_warning: {\n id: 'leave_chat_warning',\n defaultMessage: 'Are you sure you want to leave this conversation?',\n description: 'Alert dialog warning when unsubscribing from a chat.'\n },\n block_contact: {\n id: 'action_block_contact',\n defaultMessage: \"Block Contact\",\n description: \"Flat button [Block Contact]\"\n },\n block_contact_warning: {\n id: 'block_contact_warning',\n defaultMessage: 'Are you sure you want to block this contact?',\n description: 'Alert dialog warning when blocking a contact.'\n },\n report_chat: {\n id: 'action_report_chat',\n defaultMessage: 'Report Conversation',\n description: 'Flat button [Report Group]'\n },\n report_chat_warning: {\n id: 'report_chat_warning',\n defaultMessage: 'Are you sure you want to block and report this conversation?',\n description: 'Alert dialog warning when reporting a conversation for abuse'\n },\n other_user: {\n id: 'label_other_user',\n defaultMessage: 'Other',\n description: 'Label for the other user when the user is unnamed'\n },\n});\n\nclass TopicSecurity extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleDeleteTopic = this.handleDeleteTopic.bind(this);\n this.handleDeleteMessages = this.handleDeleteMessages.bind(this);\n this.handleLeave = this.handleLeave.bind(this);\n this.handleBlock = this.handleBlock.bind(this);\n this.handleReport = this.handleReport.bind(this);\n }\n\n handleDeleteTopic(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.topic_delete), // title\n formatMessage(messages.topic_delete_warning), // content\n (() => { this.props.onDeleteTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleDeleteMessages(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(this.props.deleter ? messages.delete_messages : messages.clear_messages), // title\n formatMessage(this.props.deleter ? messages.delete_messages_warning : messages.clear_messages_warning), // content\n (() => { this.props.onDeleteMessages(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleLeave(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.leave_chat), // title\n formatMessage(messages.leave_chat_warning), // content\n (() => { this.props.onLeaveTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleBlock(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.block_contact), // title\n formatMessage(messages.block_contact_warning), // content\n (() => { this.props.onBlockTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleReport(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.report_chat), // title\n formatMessage(messages.report_chat_warning), // content\n (() => { this.props.onReportTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n
\n
\n
\n {this.props.groupTopic ?\n <>\n
\n {e.preventDefault(); this.props.onLaunchPermissionsEditor('want')}}>\n {this.props.access}\n \n
\n {!this.props.channel ?\n
\n
\n \n
\n
\n
Auth: {\n e.preventDefault();\n if (this.props.owner) {\n this.props.onLaunchPermissionsEditor('auth');\n }\n }}>{this.props.auth}\n
\n
Anon: {\n e.preventDefault();\n if (this.props.owner) {\n this.props.onLaunchPermissionsEditor('anon');\n }\n }}>{this.props.anon}\n
\n
\n
\n :\n null\n }\n >\n :\n
\n
\n \n
\n
\n
\n {e.preventDefault(); this.props.onLaunchPermissionsEditor('want')}}>\n {this.props.access}\n \n
\n
{this.props.fullName ? this.props.fullName : formatMessage(messages.other_user)}:\n {e.preventDefault(); this.props.onLaunchPermissionsEditor('given')}}>\n {this.props.modeGiven2}\n \n
\n
\n
\n }\n
\n
\n );\n }\n};\n\nexport default injectIntl(TopicSecurity);\n","// InfoView: panel with topic/user info.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport BadgeList from '../widgets/badge-list.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport ContactList from '../widgets/contact-list.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupManager from '../widgets/group-manager.jsx';\nimport MenuCancel from '../widgets/menu-cancel.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\nimport TopicCommon from '../widgets/topic-common.jsx';\nimport TopicSecurity from '../widgets/topic-security.jsx';\n\nimport { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH, NO_ACCESS_MODE } from '../config.js';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, theCard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n info: {\n id: 'panel_title_info',\n description: 'Title for InfoView',\n defaultMessage: 'Info'\n },\n general: {\n id: 'panel_title_general',\n description: 'Title for TopicCommon.',\n defaultMessage: 'General'\n },\n security: {\n id: 'panel_title_security',\n description: 'Title for TopicSecirity and AccSecurity.',\n defaultMessage: 'Security'\n },\n members: {\n id: 'panel_title_members',\n description: 'Title for managing group members view.',\n defaultMessage: 'Members'\n },\n crop: {\n id: 'panel_title_crop',\n description: 'Title for AvatarCropView.',\n defaultMessage: 'Drag to Adjust'\n },\n perm_want: {\n id: 'requested_permissions',\n defaultMessage: 'Requested',\n description: 'Title for permissions'\n },\n perm_given: {\n id: 'granted_permissions',\n defaultMessage: 'Granted',\n description: 'Title for permissions'\n },\n perm_auth: {\n id: 'permissions_authenticated',\n defaultMessage: 'Authenticated',\n description: 'Title for editing default authenticated permissions'\n },\n perm_anon: {\n id: 'permissions_anonymous',\n defaultMessage: 'Anonymous',\n description: 'Title for editing default anonymous permissions'\n },\n perm_user: {\n id: 'permissions_user',\n defaultMessage: 'User\\'s Permissions',\n description: 'Title for editing user\\'s permissions'\n },\n edit_permissions: {\n id: 'menu_item_edit_permissions',\n defaultMessage: 'Edit permissions',\n description: 'Menu item [Edit permissions]'\n },\n});\n\nfunction _clip(str, length) {\n return str && str.substring(0, length);\n}\n\nclass InfoView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n topic: null,\n owner: false,\n admin: false,\n sharer: false,\n deleter: false,\n muted: false,\n address: null,\n groupTopic: undefined,\n channel: undefined,\n fullName: undefined,\n description: undefined,\n avatar: null,\n private: null,\n selectedContact: null,\n access: null,\n modeGiven: null,\n modeWant: null,\n modeGiven2: null, // P2P topic, the other user mode given\n modeWant2: null, // P2P topic, the other user mode want\n auth: null,\n anon: null,\n contactList: [],\n trustedBadges: [],\n previousMetaDesc: undefined,\n previousSubsUpdated: undefined,\n };\n\n this.resetSubs = this.resetSubs.bind(this);\n this.resetDesc = this.resetDesc.bind(this);\n this.resetTags = this.resetTags.bind(this);\n this.onMetaDesc = this.onMetaDesc.bind(this);\n this.onSubsUpdated = this.onSubsUpdated.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleMuted = this.handleMuted.bind(this);\n this.handleUnarchive = this.handleUnarchive.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleShowAddMembers = this.handleShowAddMembers.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleMemberSelected = this.handleMemberSelected.bind(this);\n this.handleContextMenu = this.handleContextMenu.bind(this);\n this.handleBackNavigate = this.handleBackNavigate.bind(this);\n }\n\n // No need to separately handle component mount.\n componentDidUpdate(props) {\n const topic = this.props.tinode.getTopic(props.topic);\n if (!topic) {\n return;\n }\n\n if (this.onMetaDesc != topic.onMetaDesc) {\n this.previousMetaDesc = topic.onMetaDesc;\n topic.onMetaDesc = this.onMetaDesc;\n\n this.previousSubsUpdated = topic.onSubsUpdated;\n topic.onSubsUpdated = this.onSubsUpdated;\n }\n\n if (this.state.topic != props.topic) {\n this.setState({topic: props.topic});\n this.resetDesc(topic, props);\n this.resetSubs(topic, props);\n this.resetTags(topic);\n }\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.setState({topic: null});\n topic.onMetaDesc = this.previousMetaDesc;\n topic.onSubsUpdated = this.previousSubsUpdated;\n }\n\n resetSubs(topic, props) {\n const newState = {contactList: []};\n if (topic.getType() == 'p2p') {\n // Fetch the other party in the p2p conversation.\n // Topic may not be ready yet, so check if user is found.\n const user2 = topic.subscriber(props.topic);\n if (user2) {\n newState.modeGiven2 = user2.acs.getGiven();\n newState.modeWant2 = user2.acs.getWant();\n } else {\n newState.modeGiven2 = NO_ACCESS_MODE;\n newState.modeWant2 = NO_ACCESS_MODE;\n }\n } else {\n topic.subscribers((sub) => {\n newState.contactList.push(sub);\n }, this);\n }\n\n this.setState(newState);\n }\n\n resetDesc(topic, props) {\n const defacs = topic.getDefaultAccess() || {};\n const acs = topic.getAccessMode();\n\n const badges = [];\n if (topic.trusted) {\n for (const [key, val] of Object.entries(topic.trusted)) {\n if (val) {\n badges.push(key);\n }\n }\n }\n\n this.setState({\n owner: acs && acs.isOwner(),\n admin: acs && acs.isAdmin(),\n sharer: acs && acs.isSharer(),\n deleter: acs && acs.isDeleter(),\n muted: acs && acs.isMuted(),\n\n fullName: _clip(topic.public ? topic.public.fn : undefined, MAX_TITLE_LENGTH),\n description: _clip(topic.public ? topic.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),\n avatar: makeImageUrl(topic.public ? topic.public.photo : null),\n trustedBadges: badges,\n private: _clip(topic.private ? topic.private.comment : null, MAX_TITLE_LENGTH),\n archived: topic.isArchived(),\n address: topic.name,\n groupTopic: topic.isGroupType(),\n channel: topic.isChannelType() || topic.chan,\n access: acs ? acs.getMode() : undefined,\n modeGiven: acs ? acs.getGiven() : undefined,\n modeWant: acs ? acs.getWant() : undefined,\n auth: defacs.auth,\n anon: defacs.anon\n });\n }\n\n resetTags(topic) {\n if (topic.getType() != 'grp') {\n return;\n }\n\n const acs = topic.getAccessMode();\n if (acs && acs.isOwner()) {\n // Requesting tags: owner is editing the topic.\n topic.getMeta(topic.startMetaQuery().withTags().build());\n }\n }\n\n onMetaDesc(desc) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetDesc(topic, this.props);\n\n if (this.previousMetaDesc && this.previousMetaDesc != this.onMetaDesc) {\n this.previousMetaDesc(desc);\n }\n }\n\n onSubsUpdated(subs) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetSubs(topic, this.props);\n\n if (this.previousSubsUpdated && this.previousSubsUpdated != this.onSubsUpdated) {\n this.previousSubsUpdated(subs);\n }\n }\n\n handleImageChanged(img) {\n this.setState({avatar: img});\n this.props.onTopicDescUpdate(this.props.topic, theCard(null, img || Tinode.DEL_CHAR), null);\n }\n\n handleMuted(ignored, checked) {\n this.setState({muted: checked});\n this.props.onChangePermissions(this.props.topic, checked ? '-P' : '+P');\n }\n\n handleUnarchive(ignored, ignored2) {\n this.props.onTopicUnArchive(this.props.topic);\n }\n\n handlePermissionsChanged(which, perm) {\n switch (which) {\n case 'auth':\n this.props.onTopicDescUpdateRequest(this.props.topic, null, null, {auth: perm});\n break;\n case 'anon':\n this.props.onTopicDescUpdateRequest(this.props.topic, null, null, {anon: perm});\n break;\n case 'mode':\n case 'want':\n this.props.onChangePermissions(this.props.topic, perm);\n break;\n case 'given':\n this.props.onChangePermissions(this.props.topic, perm, this.props.topic);\n break;\n case 'user':\n this.props.onChangePermissions(this.props.topic, perm, this.state.userPermissionsEdited);\n break;\n }\n\n this.handleBackNavigate();\n }\n\n handleLaunchPermissionsEditor(which, uid) {\n const {formatMessage} = this.props.intl;\n let toEdit, toCompare, toSkip, titleEdit, titleCompare, userTitle, userAvatar;\n switch (which) {\n case 'mode':\n toEdit = this.state.access;\n break;\n case 'want':\n toEdit = this.state.modeWant;\n toCompare = this.state.modeGiven;\n if (this.state.owner) {\n // Don't allow owner to unset 'O' permission. All other permissions are OK.\n toSkip = 'O';\n } else {\n // Allow accepting any of 'ASDO' permissions but don't allow asking for them.\n toSkip = Tinode.AccessMode.encode(Tinode.AccessMode.diff('ASDO', this.state.modeGiven));\n if (this.state.channel) {\n // Channels are read-only.\n toSkip += 'W';\n }\n }\n titleEdit = formatMessage(messages.perm_want);\n titleCompare = formatMessage(messages.perm_given);\n break;\n case 'given':\n toEdit = this.state.modeGiven2;\n toCompare = this.state.modeWant2;\n toSkip = this.state.groupTopic ? (this.state.owner ? '' : 'O') : 'ASDO';\n titleEdit = formatMessage(messages.perm_given);\n titleCompare = formatMessage(messages.perm_want);\n break;\n case 'auth':\n toEdit = this.state.auth;\n toSkip = 'O';\n break;\n case 'anon':\n toEdit = this.state.anon;\n toSkip = 'O';\n break;\n case 'user': {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n const user = topic.subscriber(uid);\n if (!user || !user.acs) {\n return;\n }\n toEdit = user.acs.getGiven();\n toCompare = user.acs.getWant();\n toSkip = this.state.owner ? '' : 'O';\n titleEdit = formatMessage(messages.perm_given);\n titleCompare = formatMessage(messages.perm_want);\n if (user.public) {\n userTitle = user.public.fn;\n userAvatar = user.public.photo;\n }\n break;\n }\n default:\n console.error(\"Unknown permission editing mode '\" + which + \"'\");\n return;\n }\n this.setState({\n userPermissionsEdited: uid,\n userPermissionsTitle: userTitle,\n userPermissionsAvatar: userAvatar,\n editedPermissions: toEdit,\n immutablePermissions: toCompare,\n editedPermissionsTitle: titleEdit,\n immutablePermissionsTitle: titleCompare,\n editedPermissionsSkipped: toSkip,\n });\n this.props.onNavigate(`perm/${which}`);\n }\n\n handleShowAddMembers(e) {\n e.preventDefault();\n this.props.onInitFind();\n this.props.onNavigate('members');\n }\n\n handleMemberUpdateRequest(members, added, removed) {\n this.props.onMemberUpdateRequest(this.props.topic, added, removed);\n this.props.onNavigate('info');\n }\n\n handleMemberSelected(uid) {\n this.setState({selectedContact: uid});\n }\n\n handleBackNavigate() {\n const args = (this.props.panel || 'info').split('/');\n if (args[0] == 'info') {\n this.props.onNavigate(null);\n } else if (args[0] == 'perm') {\n if (args[1] == 'user') {\n this.props.onNavigate('info');\n } else {\n this.props.onNavigate('security');\n }\n } else {\n this.props.onNavigate('info');\n }\n }\n\n handleContextMenu(params) {\n const {formatMessage} = this.props.intl;\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n const user = topic.subscriber(params.topicName);\n if (!user || !user.acs) {\n return;\n }\n\n const isMe = this.props.tinode.isMe(params.topicName);\n const menuItems = [\n {title: formatMessage(messages.edit_permissions), handler: () => {\n this.handleLaunchPermissionsEditor(isMe ? 'want' : 'user', params.topicName);\n }}\n ];\n if (!isMe) {\n menuItems.push('member_delete');\n }\n menuItems.push(user.acs.isMuted() ? 'member_unmute' : 'member_mute');\n if (!isMe) {\n menuItems.push(user.acs.isJoiner() ? 'member_block' : 'member_unblock');\n }\n this.props.showContextMenu({\n topicName: this.props.topic,\n x: params.x,\n y: params.y,\n user: params.topicName}, menuItems);\n }\n\n render() {\n const args = (this.props.panel || 'info').split('/');\n const view = args[0];\n args.shift();\n\n const {formatMessage} = this.props.intl;\n const panelTitle = formatMessage((view == 'perm' ? messages['perm_' + args[0]] : messages[view])\n || messages['info']);\n\n return (\n \n
\n
{panelTitle}
\n
\n \n
\n
\n {this.props.displayMobile ?\n
: null}\n {view == 'members' ?\n
\n :\n view == 'perm' && args.length > 0 ?\n
this.handlePermissionsChanged(args[0], mode)}\n onCancel={this.handleBackNavigate} />\n :\n view == 'general' ?\n \n :\n view == 'security' ?\n \n :\n \n
\n
\n
\n \n \n
\n {this.state.archived ?\n
\n \n \n
\n :\n null\n }\n
\n
\n {this.state.groupTopic && this.state.sharer ?\n <>\n
\n
\n \n
\n
\n
{\n (no_members) => \n }\n >\n :\n null\n }\n
\n }\n \n );\n }\n};\n\nexport default injectIntl(InfoView);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport FileProgress from './file-progress.jsx';\nimport { bytesToHumanSize } from '../lib/strformat.js';\nimport { isUrlRelative, sanitizeUrl } from '../lib/utils.js';\n\nexport default class Attachment extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n downloader: null,\n progress: 0\n };\n\n this.downloadFile = this.downloadFile.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n downloadFile(url, filename, mimetype) {\n if (!url) {\n this.props.onError(\"Invalid download URL '\" + url + \"'\");\n return;\n }\n\n const downloader = this.props.tinode.getLargeFileHelper();\n this.setState({downloader: downloader});\n downloader.download(url, filename, mimetype,\n (loaded) => {\n this.setState({progress: loaded / this.props.size});\n },\n (err) => {\n this.props.onError(err, 'err');\n }\n ).then(() => {\n this.setState({downloader: null, progress: 0});\n }).catch((err) => {\n if (err) {\n this.props.onError(\"Error downloading file: \" + err.message, 'err');\n }\n this.setState({downloader: null, progress: 0});\n });\n }\n\n handleCancel() {\n if (this.props.uploading) {\n this.props.onCancelUpload();\n } else if (this.state.downloader) {\n this.state.downloader.cancel();\n }\n }\n\n render() {\n let filename = this.props.filename || 'file_attachment';\n if (filename.length > 36) {\n filename = filename.substr(0, 16) + '...' + filename.substr(-16);\n }\n let size = this.props.size > 0 ?\n ({bytesToHumanSize(this.props.size)}) :\n null;\n\n // Detect if the download URL is relative or absolute.\n // If the URL is relative use LargeFileHelper to attach authentication\n // credentials to the request.\n let url, helperFunc;\n if (!this.props.uploading && !this.state.downloader && isUrlRelative(this.props.downloadUrl)) {\n // Relative URL. Use download helper.\n url = '#';\n helperFunc = (e) => {\n e.preventDefault();\n this.downloadFile(this.props.downloadUrl, this.props.filename, this.props.mimetype);\n };\n } else {\n url = sanitizeUrl(this.props.downloadUrl);\n helperFunc = null;\n }\n const downloadWidget = <>file_download >;\n return (\n \n
insert_drive_file
\n
\n
{filename} {size}
\n {this.props.uploading || this.state.downloader ?\n
\n :\n
\n }\n
\n
\n );\n }\n};\n","// Received/read indicator.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { deliveryMarker } from '../lib/utils.js';\n\nconst messages = defineMessages({\n message_sending: {\n id: 'message_sending',\n defaultMessage: 'sending...',\n description: 'Message being sent, in place of time stamp'\n },\n message_sending_failed: {\n id: 'message_sending_failed',\n defaultMessage: 'failed',\n description: 'Failed to send message, in place of time stamp'\n }\n});\n\nclass ReceivedMarker extends React.PureComponent {\n render() {\n const {formatMessage} = this.props.intl;\n let timestamp;\n if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {\n timestamp = formatMessage(messages.message_sending);\n } else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n timestamp = formatMessage(messages.message_sending_failed);\n } else {\n timestamp = this.props.timestamp.toLocaleTimeString(this.props.intl.locale, { timeStyle: 'short' });\n }\n\n const icon = deliveryMarker(this.props.received);\n const marker = icon ? {icon.name} : null;\n\n return (\n \n {timestamp}{'\\u00a0'}{marker}\n \n );\n }\n};\n\nexport default injectIntl(ReceivedMarker);\n","// Single chat message bubble, sent or received.\n\nimport React from 'react';\nimport { FormattedMessage, injectIntl } from 'react-intl';\nimport { Drafty } from 'tinode-sdk';\n\nimport Attachment from './attachment.jsx';\nimport LetterTile from './letter-tile.jsx';\nimport ReceivedMarker from './received-marker.jsx'\n\nimport { fullFormatter } from '../lib/formatters.js';\nimport { sanitizeUrl } from '../lib/utils.js';\n\nclass BaseChatMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n progress: 0\n };\n\n if (props.uploader) {\n props.uploader.onProgress = this.handleProgress.bind(this);\n }\n\n this.handleImagePreview = this.handleImagePreview.bind(this);\n this.handleFormButtonClick = this.handleFormButtonClick.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleCancelUpload = this.handleCancelUpload.bind(this);\n this.handleQuoteClick = this.handleQuoteClick.bind(this);\n\n this.formatterContext = {\n formatMessage: props.intl.formatMessage.bind(props.intl),\n viewportWidth: props.viewportWidth,\n authorizeURL: props.tinode.authorizeURL.bind(props.tinode),\n onImagePreview: this.handleImagePreview,\n onFormButtonClick: this.handleFormButtonClick,\n onQuoteClick: this.handleQuoteClick\n };\n }\n\n handleImagePreview(e) {\n e.preventDefault();\n this.props.onImagePreview({\n url: e.target.src,\n filename: e.target.title,\n width: e.target.dataset.width,\n height: e.target.dataset.height,\n size: e.target.dataset.size,\n type: e.target.dataset.mime\n });\n }\n\n handleFormButtonClick(e) {\n e.preventDefault();\n const data = {\n seq: this.props.seq\n };\n data.resp = {};\n if (e.target.dataset.name) {\n data.resp[e.target.dataset.name] = e.target.dataset.val ? e.target.dataset.val :\n e.target.dataset.val === undefined ? 1 : '' + e.target.dataset.val;\n }\n if (e.target.dataset.act == 'url') {\n data.ref = sanitizeUrl(e.target.dataset.ref) || 'about:blank';\n }\n const text = e.target.dataset.title || 'unknown';\n this.props.onFormResponse(e.target.dataset.act, text, data);\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n const menuItems = [];\n if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n menuItems.push('menu_item_send_retry');\n }\n if (this.props.userIsWriter &&\n this.props.received > Tinode.MESSAGE_STATUS_FAILED &&\n this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {\n menuItems.push('menu_item_reply');\n }\n menuItems.push('menu_item_forward');\n\n this.props.showContextMenu({\n seq: this.props.seq,\n content: this.props.content,\n userFrom: this.props.userFrom,\n userName: this.props.userName,\n y: e.pageY,\n x: e.pageX,\n pickReply: this.props.pickReply\n }, menuItems);\n }\n\n handleProgress(ratio) {\n this.setState({progress: ratio});\n }\n\n handleCancelUpload() {\n this.props.onCancelUpload(this.props.seq, this.props.uploader);\n }\n\n handleQuoteClick(e) {\n e.preventDefault();\n e.stopPropagation();\n const replyToSeq = this.props.replyToSeq;\n if (replyToSeq) {\n this.props.onQuoteClick(replyToSeq);\n }\n }\n\n render() {\n const sideClass = this.props.sequence + ' ' + (this.props.response ? 'left' : 'right');\n const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';\n const avatar = this.props.userAvatar || true;\n const fullDisplay = (this.props.isGroup && this.props.response &&\n (this.props.sequence == 'single' || this.props.sequence == 'last'));\n\n let content = this.props.content;\n const attachments = [];\n if (this.props.mimeType == Drafty.getContentType() && Drafty.isValid(content)) {\n Drafty.attachments(content, (att, i) => {\n if (att.mime == 'application/json') {\n // Don't show json objects as attachments.\n // They are not meant for users.\n return;\n }\n attachments.push();\n }, this);\n const tree = Drafty.format(content, fullFormatter, this.formatterContext);\n content = React.createElement(React.Fragment, null, tree);\n } else if (typeof content != 'string') {\n content = <>warning_amber \n >\n }\n\n return (\n \n {this.props.isGroup && this.props.response ?\n \n {fullDisplay ?\n :\n null}\n
:\n null}\n \n
\n
\n
\n {content}\n {attachments}\n
\n {this.props.timestamp ?\n
\n : null}\n
\n {this.props.showContextMenu ?\n
\n \n expand_more\n \n : null\n }\n
\n {fullDisplay ?\n
\n {this.props.userName ||\n \n }\n
\n : null\n }\n
\n \n );\n }\n};\n\nconst IntlChatMessage = injectIntl(BaseChatMessage);\nconst ChatMessage = React.forwardRef((props, ref) => );\n\nexport default ChatMessage;\n","// Send message form.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\nimport { Drafty } from 'tinode-sdk';\n\nimport { KEYPRESS_DELAY } from '../config.js';\nimport { filePasted } from '../lib/blob-helpers.js';\nimport { replyFormatter } from '../lib/formatters.js';\n\nconst messages = defineMessages({\n messaging_disabled: {\n id: 'messaging_disabled_prompt',\n defaultMessage: 'Messaging disabled',\n description: 'Prompt in SendMessage in read-only topic'\n },\n type_new_message: {\n id: 'new_message_prompt',\n defaultMessage: 'New message',\n description: 'Prompt in send message field'\n },\n add_image_caption: {\n id: 'image_caption_prompt',\n defaultMessage: 'Image caption',\n description: 'Prompt in SendMessage for attached image'\n },\n file_attachment_too_large: {\n id: 'file_attachment_too_large',\n defaultMessage: 'The file size {size} exceeds the {limit} limit.',\n description: 'Error message when attachment is too large'\n },\n cannot_initiate_upload: {\n id: 'cannot_initiate_file_upload',\n defaultMessage: 'Cannot initiate file upload.',\n description: 'Generic error messagewhen attachment fails'\n },\n});\n\nclass SendMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n quote: null,\n message: '',\n // Make initial keypress time as if it happened 5001 milliseconds in the past.\n keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1\n };\n\n this.handlePasteEvent = this.handlePasteEvent.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleSend = this.handleSend.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleMessageTyping = this.handleMessageTyping.bind(this);\n\n this.handleQuoteClick = this.handleQuoteClick.bind(this);\n\n this.formatReply = this.formatReply.bind(this);\n }\n\n componentDidMount() {\n if (this.messageEditArea) {\n this.messageEditArea.addEventListener('paste', this.handlePasteEvent, false);\n }\n\n this.setState({quote: this.formatReply()});\n }\n\n componentWillUnmount() {\n if (this.messageEditArea) {\n this.messageEditArea.removeEventListener('paste', this.handlePasteEvent, false);\n }\n }\n\n componentDidUpdate(prevProps) {\n if (this.messageEditArea) {\n this.messageEditArea.focus();\n }\n\n if (prevProps.reply != this.props.reply) {\n this.setState({quote: this.formatReply()});\n }\n }\n\n formatReply() {\n return this.props.reply ?\n Drafty.format(this.props.reply.content, replyFormatter, {\n formatMessage: this.props.intl.formatMessage.bind(this.props.intl),\n authorizeURL: this.props.tinode.authorizeURL.bind(this.props.tinode)\n }) : null;\n }\n\n handlePasteEvent(e) {\n if (this.props.disabled) {\n return;\n }\n // FIXME: handle large files too.\n if (filePasted(e,\n (bits, mime, width, height, fname) => {\n this.props.onAttachImage(mime, bits, width, height, fname);\n },\n (mime, bits, fname) => {\n this.props.onAttachFile(mime, bits, fname);\n },\n this.props.onError)) {\n\n // If a file was pasted, don't paste base64 data into input field.\n e.preventDefault();\n }\n }\n\n handleAttachImage(e) {\n if (e.target.files && e.target.files.length > 0) {\n this.props.onAttachImage(e.target.files[0]);\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleAttachFile(e) {\n const {formatMessage} = this.props.intl;\n if (e.target.files && e.target.files.length > 0) {\n this.props.onAttachFile(e.target.files[0]);\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleSend(e) {\n e.preventDefault();\n const message = this.state.message.trim();\n if (message || this.props.acceptBlank || this.props.noInput) {\n this.props.onSendMessage(message);\n this.setState({message: ''});\n }\n }\n\n /* Send on Enter key */\n handleKeyPress(e) {\n // Remove this if you don't want Enter to trigger send\n if (e.key === 'Enter') {\n // Have Shift-Enter insert a line break instead\n if (!e.shiftKey) {\n e.preventDefault();\n e.stopPropagation();\n\n this.handleSend(e);\n }\n }\n }\n\n handleMessageTyping(e) {\n const newState = {message: e.target.value};\n if (this.props.onKeyPress) {\n const now = new Date().getTime();\n if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {\n this.props.onKeyPress();\n newState.keypressTimestamp = now;\n }\n }\n this.setState(newState);\n }\n\n handleQuoteClick(e) {\n e.preventDefault();\n e.stopPropagation();\n if (this.props.reply && this.props.onQuoteClick) {\n const replyToSeq = this.props.reply.seq;\n this.props.onQuoteClick(replyToSeq);\n }\n }\n\n render() {\n const { formatMessage } = this.props.intl;\n const prompt = this.props.disabled ?\n formatMessage(messages.messaging_disabled) :\n (this.props.messagePrompt ?\n formatMessage(messages[this.props.messagePrompt]) :\n formatMessage(messages.type_new_message));\n\n const quote =\n (\n
\n {this.state.quote}\n
);\n\n return (\n \n {this.state.quote && !this.props.noInput ? quote : null}\n
\n
\n );\n }\n};\n\nexport default injectIntl(SendMessage);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { bytesToHumanSize, shortenFileName } from '../lib/strformat.js';\n\n// Get material icon name from mime type.\nfunction iconFromMime(mime) {\n // If more icons become available in material icons, add them to this mime-to-icon mapping.\n const mimeToIcon = {default: 'insert_drive_file', image: 'image', text: 'description', video: 'theaters'};\n\n return mimeToIcon[mime] || mimeToIcon[(mime || '').split('/')[0]] || mimeToIcon['default'];\n}\n\nexport default class DocPreview extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleSendDoc = this.handleSendDoc.bind(this);\n }\n\n handleSendDoc(caption) {\n this.props.onClose();\n this.props.onSendMessage(this.props.content.file);\n }\n\n render() {\n if (!this.props.content) {\n return null;\n }\n return (\n \n
\n
\n
\n
{iconFromMime(this.props.content.type)}\n
{shortenFileName(this.props.content.name, 24) || '-'}
\n
{this.props.content.type || 'application/octet-stream'}
\n
{bytesToHumanSize(this.props.content.size)}
\n
\n
\n
\n
\n );\n }\n};\n","// GroupSubs: a list of group subscribers currently online.\n// Show in the topic title bar\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport { MAX_ONLINE_IN_TOPIC } from '../config.js'\n\nimport LetterTile from './letter-tile.jsx';\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\nexport default class GroupSubs extends React.Component {\n constructor(props) {\n super(props);\n }\n\n render() {\n const usersOnline = [];\n const totalCount = (this.props.subscribers || []).length;\n const countToShow = Math.min(MAX_ONLINE_IN_TOPIC, totalCount);\n\n (this.props.subscribers || []).some((sub) => {\n usersOnline.push(\n \n \n
\n );\n return usersOnline.length == countToShow;\n });\n\n return (\n {usersOnline} {totalCount > countToShow ?\n \n \n : null}\n
\n );\n }\n};\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { REM_SIZE } from '../config.js';\nimport { fitImageSize } from '../lib/blob-helpers.js';\nimport { bytesToHumanSize, shortenFileName } from '../lib/strformat.js';\n\nexport default class ImagePreview extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n width: 0,\n height: 0\n };\n\n this.handleSendImage = this.handleSendImage.bind(this);\n }\n\n assignWidth(node) {\n if (node && !this.state.width) {\n const bounds = node.getBoundingClientRect();\n this.setState({\n width: bounds.width | 0,\n height: bounds.height | 0\n });\n }\n }\n\n handleSendImage(caption) {\n this.props.onClose();\n this.props.onSendMessage(caption, this.props.content.blob);\n }\n\n render() {\n if (!this.props.content) {\n return null;\n }\n\n const dim = fitImageSize(this.props.content.width, this.props.content.height,\n this.state.width, this.state.height, false);\n const size = dim ? { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' } :\n ((this.props.content.width > this.props.content.height) ? {width: '100%'} : {height: '100%'});\n size.maxWidth = '100%';\n size.maxHeight = '100%';\n\n // Average font aspect ratio is ~0.5; File name takes 1/3 of the viewport width.\n const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12);\n const fname = shortenFileName(this.props.content.name, maxlength) || '-';\n\n const width = this.props.content.width || '-';\n const height = this.props.content.height || '-';\n return (\n \n
\n
this.assignWidth(node)}>\n

\n
\n {this.props.onSendMessage ?\n
\n :\n }\n
\n );\n }\n};\n","// A single topic or user.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class Invitation extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleButtonAction = this.handleButtonAction.bind(this);\n }\n\n handleButtonAction(evt, data) {\n evt.preventDefault();\n this.props.onAction(data);\n }\n\n render() {\n return (\n \n
\n \n
\n
\n \n \n \n
\n
\n );\n }\n};\n","/* This is just a static page to display when no conversation is selected. */\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { APP_NAME } from '../config.js';\n\nexport default class LogoView extends React.PureComponent {\n render() {\n const version = APP_NAME + ' (' + Tinode.getLibrary() + ')';\n return (\n \n
\n
\n
\n Tinode Web
\n \n
{version}
\n
{this.props.serverVersion} ({this.props.serverAddress})
\n
\n
\n );\n }\n};\n","// Message bubble with non-payload content, such as an indicator of deleted content or a date stamp.\n\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class MetaMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n }\n\n render() {\n let content = null;\n let bubbleClass = 'bubble';\n if (this.props.deleted) {\n // Message represents a range of deleted messages.\n content = <>block \n \n >\n bubbleClass += ' deleted';\n } else if (this.props.date) {\n // A date badge.\n content = <>{this.props.date}>\n bubbleClass += ' date';\n }\n\n if (!content) {\n return <>{null}>\n }\n\n return (\n \n \n \n );\n }\n};\n","// Utility class for hash navigation.\n\n// Parse hash as in http://www.example.com/path#hash as if it were\n// path and arguments.\nexport default class HashNavigation {\n static parseUrlHash(hash) {\n // Split path from args, path -> parts[0], args->path[1]\n const parts = hash.split('?', 2);\n const params = {};\n let path = [];\n if (parts[0]) {\n path = parts[0].replace('#', '').split('/');\n }\n if (parts[1]) {\n parts[1].split('&').forEach(function(part) {\n const item = part.split('=');\n if (item[0]) {\n params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);\n }\n });\n }\n return {path: path, params: params};\n }\n\n static navigateTo(url) {\n window.location.hash = url;\n }\n\n static composeUrlHash(path, params) {\n let url = path.join('/');\n const args = [];\n for (const key in params) {\n if (params.hasOwnProperty(key)) {\n args.push(key + '=' + params[key]);\n }\n }\n if (args.length > 0) {\n url += '?' + args.join('&');\n }\n return url;\n }\n\n static addUrlParam(hash, key, value) {\n const parsed = this.parseUrlHash(hash);\n parsed.params[key] = value;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static removeUrlParam(hash, key) {\n const parsed = this.parseUrlHash(hash);\n delete parsed.params[key];\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlSidePanel(hash, sidepanel) {\n const parsed = this.parseUrlHash(hash);\n parsed.path[0] = sidepanel;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlInfoPanel(hash, infopanel) {\n const parsed = this.parseUrlHash(hash);\n if (infopanel) {\n parsed.params.info = infopanel;\n } else {\n delete parsed.params.info;\n }\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlTopic(hash, topic) {\n const parsed = this.parseUrlHash(hash);\n parsed.path[1] = topic;\n // Close InfoView on topic change.\n delete parsed.params.info;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n}\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\nconst Drafty = Tinode.Drafty;\n\nimport ChatMessage from '../widgets/chat-message.jsx';\nimport ContactBadges from '../widgets/contact-badges.jsx';\nimport DocPreview from '../widgets/doc-preview.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupSubs from '../widgets/group-subs.jsx';\nimport ImagePreview from '../widgets/image-preview.jsx';\nimport Invitation from '../widgets/invitation.jsx';\nimport LetterTile from '../widgets/letter-tile.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport LogoView from './logo-view.jsx';\nimport MetaMessage from '../widgets/meta-message.jsx';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { DEFAULT_P2P_ACCESS_MODE, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY,\n MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE,\n READ_DELAY, QUOTED_REPLY_LENGTH } from '../config.js';\nimport { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';\nimport HashNavigation from '../lib/navigation.js';\nimport { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';\n\n// Run timer with this frequency (ms) for checking notification queue.\nconst NOTIFICATION_EXEC_INTERVAL = 300;\n// Scroll distance before [go to latest message] button is shown.\nconst SHOW_GO_TO_LAST_DIST = 100;\n\nconst messages = defineMessages({\n online_now: {\n id: 'online_now',\n defaultMessage: 'online now',\n description: 'Indicator that the user or topic is currently online',\n },\n last_seen: {\n id: 'last_seen_timestamp',\n defaultMessage: 'Last seen',\n description: 'Label for the timestamp of when the user or topic was last online'\n },\n not_found: {\n id: 'title_not_found',\n defaultMessage: 'Not found',\n description: 'Title shown when topic is not found'\n },\n channel: {\n id: 'channel',\n defaultMessage: 'channel',\n description: 'Subtitle shown for channels in MessagesView instead of last seen'\n },\n file_attachment_too_large: {\n id: 'file_attachment_too_large',\n defaultMessage: 'The file size {size} exceeds the {limit} limit.',\n description: 'Error message when attachment is too large'\n },\n invalid_content: {\n id: 'invalid_content',\n defaultMessage: 'invalid content',\n description: 'Shown when the message is unreadable'\n },\n});\n\n// Checks if the access permissions are granted but not yet accepted.\nfunction isUnconfirmed(acs) {\n if (acs) {\n const ex = acs.getExcessive() || '';\n return acs.isJoiner('given') && (ex.includes('R') || ex.includes('W'));\n }\n return false;\n}\n\nfunction isPeerRestricted(acs) {\n if (acs) {\n const ms = acs.getMissing() || '';\n return acs.isJoiner('want') && (ms.includes('R') || ms.includes('W'));\n }\n return false;\n}\n\nclass MessagesView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = MessagesView.getDerivedStateFromProps(props, {});\n\n this.leave = this.leave.bind(this);\n this.sendMessage = this.sendMessage.bind(this);\n this.retrySend = this.retrySend.bind(this);\n this.sendImageAttachment = this.sendImageAttachment.bind(this);\n this.sendFileAttachment = this.sendFileAttachment.bind(this);\n this.sendKeyPress = this.sendKeyPress.bind(this);\n this.subscribe = this.subscribe.bind(this);\n this.handleScrollReference = this.handleScrollReference.bind(this);\n this.handleScrollEvent = this.handleScrollEvent.bind(this);\n this.handleDescChange = this.handleDescChange.bind(this);\n this.handleSubsUpdated = this.handleSubsUpdated.bind(this);\n this.handleMessageUpdate = this.handleMessageUpdate.bind(this);\n this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);\n this.handleInfoReceipt = this.handleInfoReceipt.bind(this);\n this.handleImagePostview = this.handleImagePostview.bind(this);\n this.handleClosePreview = this.handleClosePreview.bind(this);\n this.handleFormResponse = this.handleFormResponse.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleShowMessageContextMenu = this.handleShowMessageContextMenu.bind(this);\n this.handleNewChatAcceptance = this.handleNewChatAcceptance.bind(this);\n this.handleEnablePeer = this.handleEnablePeer.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n this.handleCancelUpload = this.handleCancelUpload.bind(this);\n this.postReadNotification = this.postReadNotification.bind(this);\n this.clearNotificationQueue = this.clearNotificationQueue.bind(this);\n this.goToLatestMessage = this.goToLatestMessage.bind(this);\n\n this.handlePickReply = this.handlePickReply.bind(this);\n this.handleCancelReply = this.handleCancelReply.bind(this);\n this.handleQuoteClick = this.handleQuoteClick.bind(this);\n\n this.chatMessageRefs = {};\n this.getOrCreateMessageRef = this.getOrCreateMessageRef.bind(this);\n\n this.readNotificationQueue = [];\n this.readNotificationTimer = null;\n }\n\n getOrCreateMessageRef(seqId) {\n if (this.chatMessageRefs.hasOwnProperty(seqId)) {\n return this.chatMessageRefs[seqId];\n }\n const ref = React.createRef();\n this.chatMessageRefs[seqId] = ref;\n return ref;\n }\n\n componentDidMount() {\n if (this.messagesScroller) {\n this.messagesScroller.addEventListener('scroll', this.handleScrollEvent);\n }\n }\n\n componentWillUnmount() {\n if (this.messagesScroller) {\n this.messagesScroller.removeEventListener('scroll', this.handleScrollEvent);\n }\n\n // Flush all notifications.\n this.clearNotificationQueue();\n }\n\n // Scroll last message into view on component update e.g. on message received\n // or vertical shrinking.\n componentDidUpdate(prevProps, prevState) {\n if (this.messagesScroller &&\n (prevState.topic != this.state.topic || prevState.messageCount != this.state.messageCount)) {\n // New message.\n if (this.state.scrollPosition < SHOW_GO_TO_LAST_DIST) {\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -\n this.state.scrollPosition -\n this.messagesScroller.offsetHeight;\n }\n }\n\n const topic = this.props.tinode ? this.props.tinode.getTopic(this.state.topic) : undefined;\n if (this.state.topic != prevState.topic) {\n if (prevState.topic && !Tinode.isNewGroupTopicName(prevState.topic)) {\n this.leave(prevState.topic);\n }\n\n if (topic) {\n topic.onData = this.handleMessageUpdate;\n topic.onAllMessagesReceived = this.handleAllMessagesReceived;\n topic.onInfo = this.handleInfoReceipt;\n topic.onMetaDesc = this.handleDescChange;\n topic.onSubsUpdated = this.handleSubsUpdated;\n topic.onPres = this.handleSubsUpdated;\n }\n }\n\n if (!this.props.applicationVisible) {\n // If application is not visible, flush all unsent 'read' notifications.\n this.clearNotificationQueue();\n } else {\n // Otherwise assume there are unread messages.\n this.postReadNotification(0);\n }\n\n if ((this.state.topic != prevState.topic) || !prevProps.ready) {\n this.subscribe(topic);\n }\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n let nextState = {};\n if (!nextProps.topic) {\n // Default state: no topic.\n nextState = {\n messageCount: 0,\n latestClearId: -1,\n onlineSubs: [],\n topic: null,\n title: '',\n avatar: null,\n isVerified: false,\n isStaff: false,\n isDangerous: false,\n deleted: false,\n docPreview: null,\n imagePreview: null,\n imagePostview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false,\n peerMessagingDisabled: false,\n channel: false,\n reply: null,\n showGoToLastButton: false\n };\n } else if (nextProps.topic != prevState.topic) {\n const topic = nextProps.tinode.getTopic(nextProps.topic);\n\n let reply = null;\n if (nextProps.forwardMessage) {\n // We are forwarding a message. Show preview.\n const preview = nextProps.forwardMessage.preview;\n reply = {\n content: preview,\n seq: null\n };\n }\n\n nextState = {\n topic: nextProps.topic,\n docPreview: null,\n imagePreview: null,\n imagePostview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false,\n reply: reply,\n showGoToLastButton: false,\n deleted: topic._deleted\n };\n\n if (topic) {\n // Topic exists.\n const subs = [];\n\n if (nextProps.connected) {\n topic.subscribers((sub) => {\n if (sub.online && sub.user != nextProps.myUserId) {\n subs.push(sub);\n }\n });\n }\n\n Object.assign(nextState, {\n onlineSubs: subs\n });\n\n if (topic.public) {\n Object.assign(nextState, {\n title: topic.public.fn,\n avatar: makeImageUrl(topic.public.photo)\n });\n } else {\n Object.assign(nextState, {\n title: '',\n avatar: null\n });\n }\n\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(nextState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (prevState.peerMessagingDisabled) {\n Object.assign(nextState, {\n peerMessagingDisabled: false\n });\n }\n Object.assign(nextState, {\n messageCount: topic.messageCount(),\n latestClearId: topic.maxClearId(),\n channel: topic.isChannelType()\n });\n } else {\n // Invalid topic.\n Object.assign(nextState, {\n messageCount: 0,\n latestClearId: -1,\n onlineSubs: [],\n title: '',\n avatar: null,\n peerMessagingDisabled: false,\n channel: false\n });\n }\n }\n\n if (nextProps.acs) {\n if (nextProps.acs.isWriter() != prevState.isWriter) {\n nextState.isWriter = !prevState.isWriter;\n }\n if (nextProps.acs.isReader() != prevState.isReader) {\n nextState.isReader = !prevState.isReader;\n }\n if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {\n nextState.readingBlocked = !prevState.readingBlocked;\n }\n if (nextProps.acs.isSharer() != prevState.isSharer) {\n nextState.isSharer = !prevState.isSharer;\n }\n } else {\n if (prevState.isWriter) {\n nextState.isWriter = false;\n }\n if (prevState.isReader) {\n nextState.isReader = false;\n }\n if (!prevState.readingBlocked) {\n prevState.readingBlocked = true;\n }\n if (prevState.isSharer) {\n nextState.isSharer = false;\n }\n }\n\n if (isUnconfirmed(nextProps.acs) == !prevState.unconformed) {\n nextState.unconfirmed = !prevState.unconformed;\n }\n\n // Clear subscribers online when there is no connection.\n if (!nextProps.connected && prevState.onlineSubs && prevState.onlineSubs.length > 0) {\n nextState.onlineSubs = [];\n }\n\n return nextState;\n }\n\n subscribe(topic) {\n if (!topic || topic.isSubscribed() || !this.props.ready) {\n return;\n }\n\n // Is this a new topic?\n const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);\n\n // Don't request the tags. They are useless unless the user\n // is the owner and is editing the topic.\n let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();\n if (this.state.isReader || newTopic) {\n // Reading is either permitted or we don't know because it's a new topic. Ask for messages.\n getQuery = getQuery.withLaterData(MESSAGES_PAGE);\n if (this.state.isReader) {\n getQuery = getQuery.withLaterDel();\n }\n // And show \"loading\" spinner.\n this.setState({ fetchingMessages: true });\n }\n\n const setQuery = newTopic ? this.props.newTopicParams : undefined;\n topic.subscribe(getQuery.build(), setQuery)\n .then((ctrl) => {\n if (ctrl.code == 303) {\n // Redirect to another topic requested.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', ctrl.params.topic));\n return;\n }\n if (this.state.topic != ctrl.topic) {\n this.setState({topic: ctrl.topic});\n }\n this.props.onNewTopicCreated(this.props.topic, ctrl.topic);\n // If there are unsent messages, try sending them now.\n topic.queuedMessages((pub) => {\n if (!pub._sending && topic.isSubscribed()) {\n this.retrySend(pub);\n }\n });\n })\n .catch((err) => {\n console.error(\"Failed subscription to\", this.state.topic);\n this.props.onError(err.message, 'err');\n const blankState = MessagesView.getDerivedStateFromProps({}, {});\n blankState.title = this.props.intl.formatMessage(messages.not_found);\n this.setState(blankState);\n });\n }\n\n leave(oldTopicName) {\n if (!oldTopicName || !this.props.tinode.isTopicCached(oldTopicName)) {\n return;\n }\n const oldTopic = this.props.tinode.getTopic(oldTopicName);\n if (oldTopic && oldTopic.isSubscribed()) {\n oldTopic.leave(false)\n .catch(() => { /* do nothing here */ })\n .finally(() => {\n // We don't care if the request succeeded or failed.\n // The topic is dead regardless.\n this.setState({fetchingMessages: false});\n oldTopic.onData = undefined;\n oldTopic.onAllMessagesReceived = undefined;\n oldTopic.onInfo = undefined;\n oldTopic.onMetaDesc = undefined;\n oldTopic.onSubsUpdated = undefined;\n oldTopic.onPres = undefined;\n });\n }\n }\n\n // Don't use React.createRef as the ref.current is not available in componentDidMount in this component.\n handleScrollReference(node) {\n if (node) {\n node.addEventListener('scroll', this.handleScrollEvent);\n this.messagesScroller = node;\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;\n }\n }\n\n // Get older messages and show/hide [go to latest message] button.\n handleScrollEvent(event) {\n const pos = event.target.scrollHeight - event.target.scrollTop - event.target.offsetHeight;\n this.setState({\n scrollPosition: pos,\n showGoToLastButton: pos > SHOW_GO_TO_LAST_DIST,\n });\n\n if (this.state.fetchingMessages) {\n return;\n }\n\n if (event.target.scrollTop <= 0) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {\n this.setState({fetchingMessages: true}, () => {\n topic.getMessagesPage(MESSAGES_PAGE)\n .catch((err) => this.props.onError(err.message, 'err'))\n .finally(() => this.setState({fetchingMessages: false}));\n });\n }\n }\n }\n\n goToLatestMessage() {\n this.setState({scrollPosition: 0});\n if (this.messagesScroller) {\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight;\n }\n }\n\n handleDescChange(desc) {\n if (desc.public) {\n this.setState({\n title: desc.public.fn,\n avatar: makeImageUrl(desc.public.photo)\n });\n } else {\n this.setState({\n title: '',\n avatar: null\n });\n }\n\n if (desc.acs) {\n this.setState({\n isWriter: desc.acs.isWriter(),\n isReader: desc.acs.isReader(),\n readingBlocked: !desc.acs.isReader('given'),\n unconfirmed: isUnconfirmed(desc.acs),\n });\n }\n }\n\n postReadNotification(seq) {\n // Ignore notifications if the app is invisible.\n if (!this.props.applicationVisible) {\n return;\n }\n\n // Set up the timer if it's not running already.\n if (!this.readNotificationTimer) {\n this.readNotificationTimer = setInterval(() => {\n if (this.readNotificationQueue.length == 0) {\n // Shut down the timer if the queue is empty.\n clearInterval(this.readNotificationTimer);\n this.readNotificationTimer = null;\n return;\n }\n\n let seq = -1;\n while (this.readNotificationQueue.length > 0) {\n const n = this.readNotificationQueue[0];\n if (n.topicName != this.state.topic) {\n // Topic has changed. Drop the notification.\n this.readNotificationQueue.shift();\n continue;\n }\n\n const now = new Date();\n if (n.sendAt <= now) {\n // Remove expired notification from queue.\n this.readNotificationQueue.shift();\n seq = Math.max(seq, n.seq);\n } else {\n break;\n }\n }\n\n // Send only one notification for the whole batch of messages.\n if (seq >= 0) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic) {\n topic.noteRead(seq);\n }\n }\n }, NOTIFICATION_EXEC_INTERVAL);\n }\n\n const now = new Date();\n this.readNotificationQueue.push({\n topicName: this.state.topic,\n seq: seq,\n sendAt: now.setMilliseconds(now.getMilliseconds() + READ_DELAY)\n });\n }\n\n // Clear notification queue and timer.\n clearNotificationQueue() {\n this.readNotificationQueue = [];\n if (this.readNotificationTimer) {\n clearInterval(this.readNotificationTimer);\n this.readNotificationTimer = null;\n }\n }\n\n handleSubsUpdated() {\n if (this.state.topic) {\n const subs = [];\n const topic = this.props.tinode.getTopic(this.state.topic);\n topic.subscribers((sub) => {\n if (sub.online && sub.user != this.props.myUserId) {\n subs.push(sub);\n }\n });\n const newState = {onlineSubs: subs};\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(newState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (this.state.peerMessagingDisabled) {\n Object.assign(newState, {\n peerMessagingDisabled: false\n });\n }\n this.setState(newState);\n }\n }\n\n // The 'msg' could be false-ish if some message ranges were deleted.\n handleMessageUpdate(msg) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (!msg) {\n // msg could be null if one or more messages were deleted.\n // Updating state to force redraw.\n this.setState({latestClearId: topic.maxClearId()});\n return;\n }\n\n clearTimeout(this.keyPressTimer)\n this.setState({messageCount: topic.messageCount(), typingIndicator: false});\n\n // Scroll to the bottom if the message is added to the end of the message list.\n // TODO: This should be replaced by showing a \"scroll to bottom\" button.\n if (topic.isNewMessage(msg.seq)) {\n if (this.state.scrollPosition > SHOW_GO_TO_LAST_DIST) {\n this.setState({showGoToLastButton: true});\n } else {\n this.setState({scrollPosition: 0});\n }\n }\n\n // Aknowledge messages except own messages. They are\n // automatically assumed to be read and recived.\n const status = topic.msgStatus(msg, true);\n if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {\n this.postReadNotification(msg.seq);\n }\n // This will send \"received\" notifications right away.\n this.props.onData(msg);\n }\n\n handleAllMessagesReceived(count) {\n this.setState({fetchingMessages: false});\n if (count > 0) {\n // 0 means \"latest\".\n this.postReadNotification(0);\n }\n }\n\n handleInfoReceipt(info) {\n switch (info.what) {\n case 'kp': {\n clearTimeout(this.keyPressTimer);\n this.keyPressTimer = setTimeout(() => {\n this.setState({typingIndicator: false});\n }, KEYPRESS_DELAY + 1000);\n if (!this.state.typingIndicator) {\n this.setState({typingIndicator: true});\n }\n break;\n }\n case 'read':\n case 'recv':\n // Redraw due to changed recv/read status.\n this.forceUpdate();\n break;\n default:\n console.info(\"Other change in topic: \", info.what);\n }\n }\n\n handleImagePostview(content) {\n this.setState({ imagePostview: content });\n }\n\n handleClosePreview() {\n if (this.state.imagePreview && this.state.imagePreview.url) {\n URL.revokeObjectURL(this.state.imagePreview.url);\n }\n this.setState({ imagePostview: null, imagePreview: null, docPreview: null });\n }\n\n handleFormResponse(action, text, data) {\n if (action == 'pub') {\n this.sendMessage(Drafty.attachJSON(Drafty.parse(text), data));\n } else if (action == 'url') {\n const url = new URL(data.ref);\n const params = url.searchParams;\n for (let key in data.resp) {\n if (data.resp.hasOwnProperty(key)) {\n params.set(key, data.resp[key]);\n }\n }\n ['name', 'seq'].map((key) => {\n if (data[key]) {\n params.set(key, data[key]);\n }\n });\n params.set('uid', this.props.myUserId);\n params.set('topic', this.state.topic);\n url.search = params;\n window.open(url, '_blank');\n } else {\n console.info(\"Unknown action in form\", action);\n }\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ topicName: this.state.topic, y: e.pageY, x: e.pageX });\n }\n\n handleShowMessageContextMenu(params, messageSpecificMenuItems) {\n if (params.userFrom == 'chan') {\n params.userFrom = this.state.topic;\n params.userName = this.state.title;\n }\n params.topicName = this.state.topic;\n const menuItems = messageSpecificMenuItems || [];\n const topic = this.props.tinode.getTopic(params.topicName);\n if (topic) {\n if (!topic.isChannelType()) {\n menuItems.push('message_delete');\n }\n const acs = topic.getAccessMode();\n if (acs && acs.isDeleter()) {\n menuItems.push('message_delete_hard');\n }\n }\n this.props.showContextMenu(params, menuItems);\n }\n\n handleNewChatAcceptance(action) {\n this.props.onNewChat(this.state.topic, action);\n }\n\n handleEnablePeer(e) {\n e.preventDefault();\n this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);\n }\n\n sendKeyPress() {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic.isSubscribed()) {\n topic.noteKeyPress();\n }\n }\n\n // sendMessage sends the message with an optional subscription to topic first.\n sendMessage(msg, uploadCompletionPromise, uploader) {\n let head;\n if (this.props.forwardMessage) {\n // We are forwarding a message.\n msg = this.props.forwardMessage.msg;\n head = this.props.forwardMessage.head;\n this.handleCancelReply();\n } else if (this.state.reply && this.state.reply.content) {\n // We are replying to a message in this topic.\n head = {reply: '' + this.state.reply.seq};\n // Turn it into Drafty so we can make a quoted Drafty object later.\n if (typeof msg == 'string') {\n msg = Drafty.parse(msg);\n }\n msg = Drafty.append(Drafty.sanitizeEntities(this.state.reply.content), msg);\n this.handleCancelReply();\n }\n this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);\n }\n\n // Retry sending a message.\n retrySend(pub) {\n this.props.sendMessage(pub.content, undefined, undefined, pub.head);\n }\n\n // Send attachment as Drafty message:\n // - if file is too large, upload it and send a s link.\n // - if file is small enough, just send it in-band.\n sendFileAttachment(file) {\n // Server-provided limit reduced for base64 encoding and overhead.\n const maxInbandAttachmentSize = (this.props.tinode.getServerLimit('maxMessageSize',\n MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;\n\n if (file.size > maxInbandAttachmentSize) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n const uploadCompletionPromise = uploader.upload(file);\n const msg = Drafty.attachFile(null, {\n mime: file.type,\n filename: file.name,\n size: file.size,\n urlPromise: uploadCompletionPromise\n });\n // Pass data and the uploader to the TinodeWeb.\n this.sendMessage(msg, uploadCompletionPromise, uploader);\n } else {\n // Small enough to send inband.\n fileToBase64(file)\n .then(b64 => this.sendMessage(Drafty.attachFile(null, {mime: b64.mime, data: b64.bits, filename: b64.name})))\n .catch(err => this.props.onError(err));\n }\n }\n\n // handleAttachFile method is called when [Attach file] button is clicked.\n handleAttachFile(file) {\n const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);\n\n if (file.size > maxExternAttachmentSize) {\n // Too large.\n this.props.onError(this.props.intl.formatMessage(messages.file_attachment_too_large,\n {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(maxExternAttachmentSize)}), 'err');\n } else {\n this.setState({\n docPreview: {\n file: file,\n name: file.name,\n size: file.size,\n type: file.type\n }\n });\n }\n }\n\n // sendImageAttachment sends the image bits inband as Drafty message.\n sendImageAttachment(caption, blob) {\n const mime = this.state.imagePreview.mime;\n const width = this.state.imagePreview.width;\n const height = this.state.imagePreview.height;\n const fname = this.state.imagePreview.name;\n\n // Server-provided limit reduced for base64 encoding and overhead.\n const maxInbandAttachmentSize = (this.props.tinode.getServerLimit('maxMessageSize',\n MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;\n\n if (blob.size > maxInbandAttachmentSize) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n if (!uploader) {\n this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));\n return;\n }\n const uploadCompletionPromise = uploader.upload(blob);\n\n // Make small preview to show while uploading.\n imageScaled(blob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false)\n // Convert tiny image into base64 for serialization and previewing.\n .then(scaled => blobToBase64(scaled.blob))\n .then(b64 => {\n let msg = Drafty.insertImage(null, 0, {\n mime: mime,\n _tempPreview: b64.bits, // This preview will not be serialized.\n width: width,\n height: height,\n filename: fname,\n size: blob.size,\n urlPromise: uploadCompletionPromise\n });\n if (caption) {\n msg = Drafty.appendLineBreak(msg);\n msg = Drafty.append(msg, Drafty.parse(caption));\n }\n // Pass data and the uploader to the TinodeWeb.\n this.sendMessage(msg, uploadCompletionPromise, uploader);\n }).catch((err) => {\n this.props.onError(err, 'err');\n });\n return;\n }\n\n // Upload the image if it's too big to be send inband.\n blobToBase64(blob)\n .then(b64 => {\n let msg = Drafty.insertImage(null, 0, {\n mime: b64.mime,\n preview: b64.bits, // Serializable preview\n width: width,\n height: height,\n filename: fname,\n size: blob.size\n });\n if (caption) {\n msg = Drafty.appendLineBreak(msg);\n msg = Drafty.append(msg, Drafty.parse(caption));\n }\n this.sendMessage(msg);\n });\n }\n\n // handleAttachImage method is called when [Attach image] button is clicked.\n handleAttachImage(file) {\n const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);\n\n // Get image dimensions and size, optionally scale it down.\n imageScaled(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, maxExternAttachmentSize, false)\n .then(scaled => {\n this.setState({imagePreview: {\n url: URL.createObjectURL(scaled.blob),\n blob: scaled.blob,\n name: scaled.name,\n width: scaled.width,\n height: scaled.height,\n size: scaled.blob.size,\n mime: scaled.mime\n }});\n }).catch(err => {\n this.props.onError(err, 'err');\n });\n }\n\n handleCancelUpload(seq, uploader) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n const found = topic.findMessage(seq);\n if (found) {\n found._cancelled = true;\n }\n uploader.cancel();\n }\n\n // seq: seq ID of the source message\n // context: message content.\n // forwarded: true if the source message is also a forwarded message.\n // senderId: UID of the sender of the source message.\n // senderName: full name of the sender of the original message.\n handlePickReply(seq, content, senderId, senderName) {\n this.setState({reply: null});\n\n if (!seq || !content) {\n return;\n }\n\n content = typeof content == 'string' ? Drafty.init(content) : content;\n if (Drafty.isValid(content)) {\n content = Drafty.replyContent(content, QUOTED_REPLY_LENGTH);\n } else {\n // /!\\ invalid content.\n content = Drafty.append(Drafty.init('\\u26A0 '),\n Drafty.wrapInto(this.props.intl.formatMessage(messages.invalid_content), 'EM'));\n }\n\n this.setState({\n reply: {\n content: Drafty.quote(senderName, senderId, content),\n seq: seq\n }\n });\n this.props.onCancelForwardMessage();\n }\n\n handleCancelReply() {\n this.setState({reply: null});\n this.props.onCancelForwardMessage();\n }\n\n handleQuoteClick(replyToSeq) {\n const ref = this.getOrCreateMessageRef(replyToSeq);\n if (ref && ref.current) {\n ref.current.scrollIntoView({block: \"center\", behavior: \"smooth\"});\n ref.current.classList.add('flash');\n setTimeout(() => { ref.current.classList.remove('flash') } , 1000);\n } else {\n console.error(\"Unresolved message ref\", replyToSeq);\n }\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n\n let component;\n if (this.props.hideSelf) {\n component = null;\n } else if (!this.state.topic) {\n component = (\n \n );\n } else {\n let component2;\n if (this.state.imagePreview) {\n // Preview image before sending.\n component2 = (\n \n );\n } else if (this.state.imagePostview) {\n // Expand received image.\n component2 = (\n \n );\n } else if (this.state.docPreview) {\n // Preview attachment before sending.\n component2 = (\n \n );\n } else {\n const topic = this.props.tinode.getTopic(this.state.topic);\n const isChannel = topic.isChannelType();\n const groupTopic = topic.isGroupType() && !isChannel;\n const icon_badges = [];\n if (topic.trusted) {\n if (topic.trusted.verified) {\n icon_badges.push({icon: 'verified', color: 'badge-inv'});\n }\n if (topic.trusted.staff) {\n icon_badges.push({icon: 'staff', color: 'badge-inv'});\n }\n if (topic.trusted.danger) {\n icon_badges.push({icon: 'dangerous', color: 'badge-inv'});\n }\n }\n\n const messageNodes = [];\n let previousFrom = null;\n let chatBoxClass = null;\n const dateFmt = new Intl.DateTimeFormat(this.props.intl.locale);\n topic.messages((msg, prev, next, i) => {\n let nextFrom = next ? (next.from || 'chan') : null;\n\n let sequence = 'single';\n let thisFrom = msg.from || 'chan';\n if (thisFrom == previousFrom) {\n if (thisFrom == nextFrom) {\n sequence = 'middle';\n } else {\n sequence = 'last';\n }\n } else if (thisFrom == nextFrom) {\n sequence = 'first';\n }\n previousFrom = thisFrom;\n\n const isReply = !(thisFrom == this.props.myUserId);\n const deliveryStatus = topic.msgStatus(msg, true);\n\n let userFrom = thisFrom, userName, userAvatar;\n const user = topic.userDesc(thisFrom);\n if (user && user.public) {\n userName = user.public.fn;\n userAvatar = makeImageUrl(user.public.photo);\n }\n chatBoxClass = groupTopic ? 'chat-box group' : 'chat-box';\n\n // Ref for this chat message.\n const ref = this.getOrCreateMessageRef(msg.seq);\n let replyToSeq = msg.head ? parseInt(msg.head.reply) : null;\n if (!replyToSeq || isNaN(replyToSeq)) {\n replyToSeq = null;\n }\n\n // This message was sent on a different date than the previous.\n if (!prev || new Date(prev.ts).toDateString() != new Date(msg.ts).toDateString()) {\n messageNodes.push(\n \n );\n }\n\n if (msg.hi) {\n // Deleted message.\n messageNodes.push(\n \n );\n } else {\n messageNodes.push(\n \n );\n }\n });\n\n let lastSeen = null;\n if (isChannel) {\n lastSeen = formatMessage(messages.channel);\n } else {\n const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);\n if (cont && Tinode.isP2PTopicName(cont.topic)) {\n if (cont.online) {\n lastSeen = formatMessage(messages.online_now);\n } else if (cont.seen) {\n lastSeen = formatMessage(messages.last_seen) + \": \" +\n shortDateFormat(cont.seen.when, this.props.intl.locale);\n // TODO: also handle user agent in c.seen.ua\n }\n }\n }\n const avatar = this.state.avatar || true;\n const online = this.state.deleted ? null :\n this.props.online ? 'online' + (this.state.typingIndicator ? ' typing' : '') : 'offline';\n\n const titleClass = 'panel-title' + (this.state.deleted ? ' deleted' : '');\n\n component2 = (\n <>\n \n {this.props.displayMobile ?\n \n : null}\n \n \n
\n
\n {!this.state.isReader ?\n
\n {this.state.readingBlocked ?\n
\n \n
\n : null }\n
\n : null }\n
\n {this.state.peerMessagingDisabled && !this.state.unconfirmed ?\n : null}\n {this.state.unconfirmed ?\n \n :\n }\n >\n );\n }\n\n component = {component2}
\n }\n return component;\n }\n};\n\nexport default injectIntl(MessagesView);\n","// The <- button to be displayed in title bars.\nimport React from 'react';\n\nexport default class ButtonBack extends React.PureComponent {\n render() {\n return (\n {e.preventDefault(); this.props.onBack();}}>\n arrow_back\n \n );\n }\n}\n","import React from 'react';\n\nexport default class MenuContacts extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nexport default class MenuStart extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\nimport ButtonBack from './button-back.jsx';\nimport ContactBadges from './contact-badges.jsx';\nimport MenuContacts from './menu-contacts.jsx';\nimport MenuStart from './menu-start.jsx';\n\nimport { sanitizeImageUrl } from '../lib/utils.js';\n\nexport default class SideNavbar extends React.PureComponent {\n render() {\n const icon_badges = [];\n if (this.props.trustedBadges) {\n this.props.trustedBadges.map((b) => {\n icon_badges.push({icon: b, color: 'badge-inv'});\n });\n }\n let avatar = null;\n if (this.props.tinode) {\n avatar = this.props.tinode.authorizeURL(sanitizeImageUrl(this.props.avatar));\n }\n return (\n \n {this.props.onCancel ?
: null}\n {avatar ?\n
\n \n
\n :\n null}\n
{this.props.title}
\n {this.props.state === 'login' ?\n
:\n this.props.state === 'contacts' ?\n
:\n null}\n
\n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport BadgeList from '../widgets/badge-list.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';\n\nfunction _clip(str, length) {\n return str && str.substring(0, length);\n}\n\nexport default class AccountSettingsView extends React.Component {\n constructor(props) {\n super(props);\n\n const me = this.props.tinode.getMeTopic();\n this.state = {\n fullName: _clip(me.public ? me.public.fn : undefined, MAX_TITLE_LENGTH),\n description: _clip(me.public ? me.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),\n avatar: makeImageUrl(me.public ? me.public.photo : null)\n };\n }\n\n render() {\n return (\n \n );\n }\n};\n","/* ContactsView holds all contacts-related stuff */\nimport React from 'react';\nimport { FormattedMessage, defineMessages } from 'react-intl';\n\nimport ContactList from '../widgets/contact-list.jsx';\n\nimport { updateFavicon } from '../lib/utils.js';\n\nconst messages = defineMessages({\n archived_contacts_title: {\n id: \"archived_contacts\",\n defaultMessage: \"Archived contacts ({count})\",\n description: \"Label for archived chats\"\n }\n});\n\nexport default class ContactsView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleAction = this.handleAction.bind(this);\n\n this.state = ContactsView.deriveStateFromProps(props);\n }\n\n static deriveStateFromProps(props) {\n const contacts = [];\n let unreadThreads = 0;\n let archivedCount = 0;\n props.chatList.map((c) => {\n const blocked = c.acs && !c.acs.isJoiner();\n // Show only blocked contacts only when props.blocked == true.\n if (blocked && props.blocked) {\n contacts.push(c);\n }\n if (blocked || props.blocked) {\n return;\n }\n\n if (c.private && c.private.arch) {\n if (props.archive) {\n contacts.push(c);\n } else {\n archivedCount ++;\n }\n } else if (!props.archive) {\n contacts.push(c);\n unreadThreads += c.unread > 0 ? 1 : 0;\n }\n });\n\n contacts.sort((a, b) => {\n return (b.touched || 0) - (a.touched || 0);\n });\n\n if (archivedCount > 0) {\n contacts.push({\n action: 'archive',\n title: messages.archived_contacts_title,\n values: {count: archivedCount}\n });\n }\n\n return {\n contactList: contacts,\n unreadThreads: unreadThreads\n };\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.chatList != this.props.chatList ||\n prevProps.archive != this.props.archive ||\n prevProps.blocked != this.props.blocked) {\n const newState = ContactsView.deriveStateFromProps(this.props);\n this.setState(newState);\n if (newState.unreadThreads != prevState.unreadThreads) {\n updateFavicon(newState.unreadThreads);\n }\n }\n }\n\n handleAction(action_ignored) {\n this.props.onShowArchive();\n }\n\n render() {\n return (\n ¯∖_(ツ)_/¯\"\n description=\"HTML message shown in ContactList when no contacts are found\">{\n (no_contacts) => \n }\n );\n }\n};\n","// Helper functions for storing values in localStorage.\n// By default localStorage can store only strings, not objects or other types.\n\nexport default class LocalStorageUtil {\n // Replace old object with the new one.\n static setObject(key, value) {\n localStorage.setItem(key, JSON.stringify(value));\n }\n\n // Get stored object.\n static getObject(key) {\n const value = localStorage.getItem(key);\n return value && JSON.parse(value);\n }\n\n // Partially or wholly update stored object.\n static updateObject(key, value) {\n const oldVal = this.getObject(key);\n this.setObject(key, Object.assign(oldVal || {}, value));\n }\n\n // Just a wrapper.\n static removeItem(key) {\n localStorage.removeItem(key);\n }\n}\n","// Account registration.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport { theCard } from '../lib/utils.js';\n\nimport { MAX_TITLE_LENGTH } from '../config.js';\n\nexport default class CreateAccountView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n login: '',\n password: '',\n email: '',\n fn: '', // full/formatted name\n imageDataUrl: null,\n errorCleared: false,\n saveToken: LocalStorageUtil.getObject('keep-logged-in')\n };\n\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handleFnChange = this.handleFnChange.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(password) {\n this.setState({password: password});\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value})\n }\n\n handleFnChange(e) {\n this.setState({fn: e.target.value});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleToggleSaveToken() {\n LocalStorageUtil.setObject('keep-logged-in', !this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.setState({errorCleared: false});\n this.props.onCreateAccount(\n this.state.login.trim(),\n this.state.password.trim(),\n theCard(this.state.fn.trim().substring(0, MAX_TITLE_LENGTH), this.state.imageDataUrl),\n {'meth': 'email', 'val': this.state.email});\n }\n\n render() {\n let submitClasses = 'primary';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport CheckBox from '../widgets/checkbox.jsx';\n\nexport default class EditAccountView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleCheckboxClick = this.handleCheckboxClick.bind(this);\n }\n\n handleCheckboxClick(what, checked) {\n if (what == 'sound') {\n this.props.onToggleMessageSounds(checked);\n } else if (what == 'alert') {\n this.props.onTogglePushNotifications(checked);\n } else if (what == 'incognito') {\n this.props.onToggleIncognitoMode(checked);\n }\n }\n\n render() {\n return (\n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport InPlaceEdit from '../widgets/in-place-edit.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\n\nconst messages = defineMessages({\n delete_account: {\n id: 'delete_account',\n defaultMessage: 'Delete account',\n description: 'Title for delete account warning'\n },\n delete_account_warning: {\n id: 'delete_account_warning',\n defaultMessage: 'Are you sure you want to delete your account? It cannot be undone.',\n description: 'Warning message when deleting an account'\n }\n});\n\nclass AccSecurityView extends React.Component {\n constructor(props) {\n super(props);\n\n const me = this.props.tinode.getMeTopic();\n let blockedCount = 0;\n me.contacts((c) => {\n if (c.acs && !c.acs.isJoiner()) {\n blockedCount ++;\n }\n });\n const defacs = me.getDefaultAccess();\n this.state = {\n auth: defacs ? defacs.auth : null,\n anon: defacs ? defacs.anon : null,\n showPermissionEditorFor: undefined,\n blockedCount: blockedCount,\n };\n\n this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleDeleteAccount = this.handleDeleteAccount.bind(this);\n }\n\n handlePasswordUpdate(pwd) {\n this.setState({password: pwd});\n this.props.onUpdatePassword(pwd);\n }\n\n handleLaunchPermissionsEditor(which) {\n this.setState({\n showPermissionEditorFor: which,\n editedPermissions: this.state[which]\n });\n }\n\n handleHidePermissionsEditor() {\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handlePermissionsChanged(perm) {\n let defacs = {};\n defacs[this.state.showPermissionEditorFor] = perm;\n this.props.onUpdateAccountDesc('me', undefined, undefined, defacs);\n\n let newState = {showPermissionEditorFor: undefined};\n newState[this.state.showPermissionEditorFor] = perm;\n this.setState(newState);\n }\n\n handleDeleteAccount(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.delete_account), // title\n formatMessage(messages.delete_account_warning), // content\n (() => { this.props.onDeleteAccount(); }), // onConfirm\n null, // use default text \"OK\"\n true, // Show Reject button\n null // use default text \"Cancel\"\n );\n }\n\n render() {\n return (\n <>{this.state.showPermissionEditorFor ?\n \n :\n \n
\n
\n
\n {\n (password_unchanged) => \n }\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
Auth: {this.state.auth}
\n
Anon: {this.state.anon}
\n
\n
\n {this.state.blockedCount > 0 ?\n <>\n
\n
\n >\n : null }\n
\n }>\n );\n }\n};\n\nexport default injectIntl(AccSecurityView);\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { APP_NAME, LINK_CONTACT_US, LINK_PRIVACY_POLICY, LINK_TERMS_OF_SERVICE } from '../config.js';\n\nexport default class AccSupportView extends React.PureComponent {\n render() {\n return (\n \n
\n
\n
\n
\n \n {APP_NAME}\n
\n
\n \n {Tinode.getLibrary()}\n
\n
\n \n {this.props.serverVersion}\n
\n
\n \n {this.props.serverAddress}\n
\n
\n
\n );\n }\n};\n","// Login form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\n\nexport default class LoginView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n login: props.login,\n password: '',\n hostName: props.serverAddress,\n saveToken: props.persist\n };\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n handleToggleSaveToken() {\n this.props.onPersistenceChange(!this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onLogin(this.state.login.trim(), this.state.password.trim());\n }\n\n render() {\n let submitClasses = 'primary';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n );\n }\n};\n/* END Login */\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n invalid_id: {\n id: 'error_invalid_id',\n defaultMessage: 'Invalid ID',\n description: 'Error message'\n }\n});\n\nclass NewTopicById extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n groupId: '',\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleChange(e) {\n this.setState({groupId: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.groupId) {\n const name = this.state.groupId.trim();\n const prefix = name.substr(0, 3);\n if (name.length > 3 && ['usr', 'grp', 'chn'].includes(prefix)) {\n this.props.onSubmit(name);\n } else {\n this.props.onError(this.props.intl.formatMessage(messages.invalid_id), 'err');\n }\n }\n }\n\n render() {\n return (\n \n
\n {\n (prompt) => \n }\n
\n
\n \n
\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicById);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from './avatar-upload.jsx';\nimport CheckBox from './checkbox.jsx';\nimport TagManager from './tag-manager.jsx';\n\nimport { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';\n\nexport default class NewTopicGroup extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.fullName = React.createRef();\n\n this.state = {\n fullName: '', // full/formatted name\n private: '',\n description: '',\n imageDataUrl: null,\n tags: [],\n isChannel: false\n };\n\n this.handleFieldEdit = this.handleFieldEdit.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleTagsChanged = this.handleTagsChanged.bind(this);\n this.handleChannelToggle = this.handleChannelToggle.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n componentDidMount() {\n // this.fullName.current.focus();\n }\n\n handleFieldEdit(name, e) {\n this.setState({[name]: e.target.value || ''});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleTagsChanged(tags) {\n this.setState({tags: tags});\n }\n\n handleChannelToggle() {\n this.setState({isChannel: !this.state.isChannel});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n\n const fn = this.state.fullName.trim().substring(0, MAX_TITLE_LENGTH);\n const comment = this.state.private.trim().substring(0, MAX_TITLE_LENGTH);\n const description = this.state.description.trim().substring(0, MAX_TOPIC_DESCRIPTION_LENGTH);\n if (fn) {\n this.props.onSubmit(fn, description, this.state.imageDataUrl, comment, this.state.tags, this.state.isChannel);\n }\n }\n\n render() {\n let submitClasses = 'primary';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n return (\n \n );\n }\n};\n","// Create new topic and invite users or send an invite.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport ContactList from '../widgets/contact-list.jsx';\nimport NewTopicById from '../widgets/new-topic-by-id.jsx';\nimport NewTopicGroup from '../widgets/new-topic-group.jsx';\nimport SearchContacts from '../widgets/search-contacts.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\nimport { theCard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n search_for_contacts: {\n id: \"search_for_contacts\",\n defaultMessage: \"Use search to find contacts\",\n description: \"Text shown in contacts view when user entered no search query.\"\n },\n search_no_results: {\n id: \"search_no_results\",\n defaultMessage: \"Search returned no results\",\n description: \"Text shown in contacts view when query returned no results.\"\n },\n search_placeholder: {\n id: \"search_placeholder\",\n defaultMessage: \"List like email:alice@example.com, tel:17025550003...\",\n description: \"Placeholder in contacts search field\"\n },\n});\n\nclass NewTopicView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tabSelected: 'find',\n searchQuery: null\n };\n\n this.handleTabClick = this.handleTabClick.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleContactSelected = this.handleContactSelected.bind(this);\n this.handleNewGroupSubmit = this.handleNewGroupSubmit.bind(this);\n this.handleGroupByID = this.handleGroupByID.bind(this);\n }\n\n componentDidMount() {\n this.props.onInitFind();\n }\n\n handleTabClick(e) {\n e.preventDefault();\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'tab', e.currentTarget.dataset.id));\n this.setState({tabSelected: e.currentTarget.dataset.id});\n }\n\n handleSearchContacts(query) {\n this.props.onSearchContacts(query);\n this.setState({searchQuery: Tinode.isNullValue(query) ? null : query});\n }\n\n handleContactSelected(uid) {\n if (this.state.tabSelected == 'find') {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(uid);\n }\n }\n\n handleNewGroupSubmit(name, description, dataUrl, priv, tags, isChannel) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(undefined,\n {public: theCard(name, dataUrl, null, description), private: priv, tags: tags}, isChannel);\n }\n\n handleGroupByID(topicName) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(topicName);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const no_contacts_placeholder = formatMessage(this.state.searchQuery ?\n messages.search_no_results : messages.search_for_contacts);\n const search_placeholder = formatMessage(messages.search_placeholder);\n return (\n \n
\n {this.state.tabSelected === 'grp' ?\n
:\n this.state.tabSelected === 'byid' ?\n
:\n
\n \n \n
}\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicView);\n","// A password reset form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\n\nexport default class PasswordResetView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n email: '',\n password: '',\n sent: false\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n }\n\n componentDidMount() {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n this.setState({token: parsed.params.token, scheme: parsed.params.scheme});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.token) {\n this.props.onReset(this.state.scheme, this.state.password.trim(), this.state.token);\n } else if (this.state.sent) {\n this.props.onCancel();\n } else {\n const email = this.state.email.trim();\n this.setState({email: email});\n this.props.onRequest('email', email).then(() => {\n this.setState({sent: true});\n });\n }\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n render() {\n return (\n \n );\n }\n}\n","import React from 'react';\n\nimport { KNOWN_HOSTS } from '../config.js';\n\n/* Combobox for selecting host name */\n\nexport default class HostSelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n hostName: props.serverAddress,\n changed: false\n };\n\n this.handleHostNameChange = this.handleHostNameChange.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handleHostNameChange(e) {\n this.setState({hostName: e.target.value, changed: true});\n }\n\n handleEditingFinished() {\n if (this.state.changed) {\n this.setState({changed: false});\n this.props.onServerAddressChange(this.state.hostName.trim());\n }\n }\n\n render() {\n var hostOptions = [];\n for (let key in KNOWN_HOSTS) {\n let item = KNOWN_HOSTS[key];\n hostOptions.push(\n \n );\n }\n return (\n \n \n \n
\n );\n }\n}\n","// Tinode config panel.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport HostSelector from '../widgets/host-selector.jsx';\n\nexport default class SettingsView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n transport: props.transport || 'def',\n serverAddress: props.serverAddress,\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleTransportSelected = this.handleTransportSelected.bind(this);\n this.handleServerAddressChange = this.handleServerAddressChange.bind(this);\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onUpdate({\n transport: this.state.transport,\n serverAddress: this.state.serverAddress\n });\n }\n\n handleTransportSelected(e) {\n this.setState({transport: e.currentTarget.value});\n }\n\n handleServerAddressChange(name) {\n this.setState({serverAddress: name});\n }\n\n render() {\n const names = {def: \"default\", ws: \"websocket\", lp: \"long polling\"};\n const transportOptions = [];\n ['def', 'ws', 'lp'].map((item) => {\n const id = 'transport-' + item;\n const name = names[item];\n transportOptions.push(\n \n \n \n \n );\n });\n\n return (\n \n );\n }\n};\n","// ValidationView: panel for confirming credentials, like email or phone.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n phone: {\n id: 'phone_dative',\n defaultMessage: 'phone',\n description: \"Dative case of 'phone', i.e. 'phone' in 'by phone'\",\n },\n email: {\n id: 'email_dative',\n defaultMessage: 'email',\n description: \"Dative case of 'email', i.e. 'email' in 'by email'\",\n }\n});\n\nclass ValidationView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n code: props.credCode || ''\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleChange(e) {\n this.setState({code: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n } else if (e.key == 'Escape') {\n this.handleCancel(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.code && this.state.code.trim()) {\n this.props.onSubmit(this.props.credMethod, this.state.code.trim());\n }\n }\n\n handleCancel(e) {\n e.preventDefault();\n this.props.onCancel();\n }\n\n\n render() {\n const { formatMessage } = this.props.intl;\n const methods = {'email': formatMessage(messages.email), 'tel': formatMessage(messages.phone)};\n let method = methods[this.props.credMethod] || this.props.credMethod;\n return (\n \n
\n \n
\n
\n {\n (numbers_only) => \n }\n
\n
\n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(ValidationView);\n","import React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport SideNavbar from '../widgets/side-navbar.jsx';\nimport TopicCommon from '../widgets/topic-common.jsx';\n\nimport AccountSettingsView from './account-settings-view.jsx';\nimport ContactsView from './contacts-view.jsx';\nimport CreateAccountView from './create-account-view.jsx';\nimport AccNotificationsView from './acc-notifications-view.jsx';\nimport AccSecurityView from './acc-security-view.jsx';\nimport AccSupportView from './acc-support-view.jsx';\nimport LoginView from './login-view.jsx';\nimport NewTopicView from './new-topic-view.jsx';\nimport PasswordResetView from './password-reset-view.jsx';\nimport SettingsView from './settings-view.jsx';\nimport ValidationView from './validation-view.jsx';\n\n// Panel titles for translation.\nconst messages = defineMessages({\n login: {\n id: 'sidepanel_title_login',\n description: 'Sidepanel title for LoginView.',\n defaultMessage: 'Sign In'\n },\n register: {\n id: 'sidepanel_title_register',\n description: 'Sidepanel title for CreateAccountView.',\n defaultMessage: 'Create Account'\n },\n settings: {\n id: 'sidepanel_title_settings',\n description: 'Sidepanel title for SettingsView.',\n defaultMessage: 'Settings'\n },\n edit: {\n id: 'sidepanel_title_account_settings',\n description: 'Sidepanel title for AccountSettingsView.',\n defaultMessage: 'Account Settings'\n },\n general: {\n id: 'panel_title_general',\n description: 'Title for TopicCommon.',\n defaultMessage: 'General'\n },\n security: {\n id: 'panel_title_security',\n description: 'Title for TopicSecirity and AccSecurity.',\n defaultMessage: 'Security'\n },\n crop: {\n id: 'panel_title_crop',\n description: 'Title for AvatarCropView.',\n defaultMessage: 'Drag to Adjust'\n },\n notif: {\n id: 'sidepanel_title_acc_notifications',\n description: 'Sidepanel title for AccNotificationsView.',\n defaultMessage: 'Notifications'\n },\n support: {\n id: 'sidepanel_title_acc_support',\n description: 'Sidepanel title for AccSupportView.',\n defaultMessage: 'Support'\n },\n newtpk: {\n id: 'sidepanel_title_newtpk',\n description: 'Sidepanel title for NewTopicView.',\n defaultMessage: 'Start New Chat'\n },\n cred: {\n id: 'sidepanel_title_cred',\n description: 'Sidepanel title for ValidationView.',\n defaultMessage: 'Confirm Credentials'\n },\n reset: {\n id: 'sidepanel_title_reset',\n description: 'Sidepanel title for PasswordResetView.',\n defaultMessage: 'Reset Password'\n },\n archive: {\n id: 'sidepanel_title_archive',\n description: 'Sidepanel title for ContactsView-Archive.',\n defaultMessage: 'Archived Chats'\n },\n blocked: {\n id: 'sidepanel_title_blocked',\n description: 'Sidepanel title for ContactsView-Blocked.',\n defaultMessage: 'Blocked Chats'\n }\n});\n\nclass SidepanelView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleNewTopic = this.handleNewTopic.bind(this);\n }\n\n handleNewTopic() {\n this.props.onNavigate('newtpk');\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const view = this.props.state || (this.props.myUserId ? 'contacts' : 'login');\n\n let title, avatar, badges;\n if (view == 'contacts') {\n title = this.props.title;\n avatar = this.props.avatar ? this.props.avatar : true;\n badges = this.props.trustedBadges;\n } else {\n title = formatMessage(messages[view]);\n avatar = false;\n badges = null;\n }\n\n let onCancel;\n if (['login', 'contacts'].indexOf(view) == -1) {\n onCancel = this.props.onCancel;\n }\n\n return (\n \n
\n\n
\n\n
\n\n {view === 'login' ?\n
:\n\n view === 'register' ?\n
:\n\n view === 'settings' ?\n
:\n\n view === 'edit' ?\n
:\n\n (view === 'general' || view === 'crop') ?\n
:\n\n view === 'notif' ?\n
:\n\n view === 'security' ?\n
:\n\n view === 'support' ?\n
:\n\n (view === 'contacts' || view == 'archive' || view == 'blocked') ?\n
:\n\n view === 'newtpk' ?\n
:\n\n view === 'cred' ?\n
:\n\n view === 'reset' ?\n
:\n null}\n
\n );\n }\n};\n\nexport default injectIntl(SidepanelView);\n","import { KNOWN_HOSTS, DEFAULT_HOST } from '../config.js';\n\n// Detect server address from the URL\nexport function detectServerAddress() {\n let host = DEFAULT_HOST;\n if (typeof window.location == 'object') {\n if (window.location.protocol == 'file:' || window.location.hostname == 'localhost') {\n host = KNOWN_HOSTS.local;\n } else if (window.location.hostname) {\n host = window.location.hostname + (window.location.port ? ':' + window.location.port : '');\n }\n }\n return host;\n}\n\n// Detect if the page is served over HTTPS.\nexport function isSecureConnection() {\n if (typeof window.location == 'object') {\n return window.location.protocol == 'https:';\n }\n return false;\n}\n\nexport function isLocalHost() {\n if (typeof window.location == 'object') {\n return window.location.hostname == 'localhost';\n }\n return false;\n}\n","// The top-level class to hold all functionality together.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport * as firebase from 'firebase/app';\nimport 'firebase/messaging';\n\nimport Tinode from 'tinode-sdk';\n\nimport Alert from '../widgets/alert.jsx';\nimport ContextMenu from '../widgets/context-menu.jsx';\nimport ForwardDialog from '../widgets/forward-dialog.jsx';\n\nimport InfoView from './info-view.jsx';\nimport MessagesView from './messages-view.jsx';\nimport SidepanelView from './sidepanel-view.jsx';\n\nimport { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, FORWARDED_PREVIEW_LENGTH, LOGGING_ENABLED,\n MEDIA_BREAKPOINT, RECEIVED_DELAY } from '../config.js';\nimport { PACKAGE_VERSION } from '../version.js';\nimport { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';\nimport { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport HashNavigation from '../lib/navigation.js';\nimport { secondsToTime } from '../lib/strformat.js'\nimport { updateFavicon } from '../lib/utils.js';\n\n// Sound to play on message received.\nconst POP_SOUND = new Audio('audio/msg.mp3');\n\nconst messages = defineMessages({\n reconnect_countdown: {\n id: 'reconnect_countdown',\n defaultMessage: 'Disconnected. Reconnecting in {seconds}…',\n description: 'Message shown when an app update is available.'\n },\n reconnect_now: {\n id: 'reconnect_now',\n defaultMessage: 'Try now',\n description: 'Prompt for reconnecting now'\n },\n push_init_failed: {\n id: 'push_init_failed',\n defaultMessage: 'Failed to initialize push notifications',\n description: 'Error message when push notifications have failed to initialize.'\n },\n invalid_security_token: {\n id: 'invalid_security_token',\n defaultMessage: 'Invalid security token',\n description: 'Error message when resetting password.'\n },\n no_connection: {\n id: 'no_connection',\n defaultMessage: 'No connection',\n description: 'Warning that the user is offline.'\n },\n code_doesnot_match: {\n id: 'code_doesnot_match',\n defaultMessage: 'Code does not match',\n description: 'Error message when the credential validation code is incorrect.'\n },\n menu_item_info: {\n id: 'menu_item_info',\n defaultMessage: 'Info',\n description: 'Show extended topic information'\n }\n});\n\nclass TinodeWeb extends React.Component {\n constructor(props) {\n super(props);\n\n this.selfRef = React.createRef();\n\n this.state = this.getBlankState();\n\n this.handleResize = this.handleResize.bind(this);\n this.handleHashRoute = this.handleHashRoute.bind(this);\n this.handleOnline = this.handleOnline.bind(this);\n this.checkForAppUpdate = this.checkForAppUpdate.bind(this);\n this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);\n this.handleError = this.handleError.bind(this);\n this.handleLoginRequest = this.handleLoginRequest.bind(this);\n this.handlePersistenceChange = this.handlePersistenceChange.bind(this);\n this.handleConnected = this.handleConnected.bind(this);\n this.handleAutoreconnectIteration = this.handleAutoreconnectIteration.bind(this);\n this.doLogin = this.doLogin.bind(this);\n this.handleCredentialsRequest = this.handleCredentialsRequest.bind(this);\n this.handleLoginSuccessful = this.handleLoginSuccessful.bind(this);\n this.handleDisconnect = this.handleDisconnect.bind(this);\n this.tnMeMetaDesc = this.tnMeMetaDesc.bind(this);\n this.tnMeContactUpdate = this.tnMeContactUpdate.bind(this);\n this.tnMeSubsUpdated = this.tnMeSubsUpdated.bind(this);\n this.resetContactList = this.resetContactList.bind(this);\n this.tnData = this.tnData.bind(this);\n this.tnInitFind = this.tnInitFind.bind(this);\n this.tnFndSubsUpdated = this.tnFndSubsUpdated.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleTopicSelected = this.handleTopicSelected.bind(this);\n this.handleHideMessagesView = this.handleHideMessagesView.bind(this);\n this.handleSendMessage = this.handleSendMessage.bind(this);\n this.handleNewChatInvitation = this.handleNewChatInvitation.bind(this);\n this.handleNewAccount = this.handleNewAccount.bind(this);\n this.handleNewAccountRequest = this.handleNewAccountRequest.bind(this);\n this.handleUpdatePasswordRequest = this.handleUpdatePasswordRequest.bind(this);\n this.handleUpdateAccountTagsRequest = this.handleUpdateAccountTagsRequest.bind(this);\n this.handleToggleIncognitoMode = this.handleToggleIncognitoMode.bind(this);\n this.handleSettings = this.handleSettings.bind(this);\n this.handleGlobalSettings = this.handleGlobalSettings.bind(this);\n this.handleShowArchive = this.handleShowArchive.bind(this);\n this.handleShowBlocked = this.handleShowBlocked.bind(this);\n this.handleToggleMessageSounds = this.handleToggleMessageSounds.bind(this);\n this.handleCredAdd = this.handleCredAdd.bind(this);\n this.handleCredDelete = this.handleCredDelete.bind(this);\n this.handleCredConfirm = this.handleCredConfirm.bind(this);\n this.initFCMessaging = this.initFCMessaging.bind(this);\n this.togglePushToken = this.togglePushToken.bind(this);\n this.requestPushToken = this.requestPushToken.bind(this);\n this.handlePushMessage = this.handlePushMessage.bind(this);\n this.handleSidepanelCancel = this.handleSidepanelCancel.bind(this);\n this.handleStartTopicRequest = this.handleStartTopicRequest.bind(this);\n this.handleNewTopicCreated = this.handleNewTopicCreated.bind(this);\n this.handleTopicUpdateRequest = this.handleTopicUpdateRequest.bind(this);\n this.handleUnarchive = this.handleUnarchive.bind(this);\n this.handleChangePermissions = this.handleChangePermissions.bind(this);\n this.handleTagsUpdateRequest = this.handleTagsUpdateRequest.bind(this);\n this.handleLogout = this.handleLogout.bind(this);\n this.handleDeleteAccount = this.handleDeleteAccount.bind(this);\n this.handleDeleteTopicRequest = this.handleDeleteTopicRequest.bind(this);\n this.handleDeleteMessagesRequest = this.handleDeleteMessagesRequest.bind(this);\n this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);\n this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this);\n this.handleReportTopic = this.handleReportTopic.bind(this);\n this.handleShowContextMenu = this.handleShowContextMenu.bind(this);\n this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this);\n this.handleHideContextMenu = this.handleHideContextMenu.bind(this);\n this.handleShowAlert = this.handleShowAlert.bind(this);\n this.handleShowInfoView = this.handleShowInfoView.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleValidateCredentialsRequest = this.handleValidateCredentialsRequest.bind(this);\n this.handlePasswordResetRequest = this.handlePasswordResetRequest.bind(this);\n this.handleResetPassword = this.handleResetPassword.bind(this);\n this.handleContextMenuAction = this.handleContextMenuAction.bind(this);\n\n this.handleShowForwardDialog = this.handleShowForwardDialog.bind(this);\n this.handleHideForwardDialog = this.handleHideForwardDialog.bind(this);\n\n this.sendMessageToTopic = this.sendMessageToTopic.bind(this);\n }\n\n getBlankState() {\n const settings = LocalStorageUtil.getObject('settings') || {};\n const persist = !!LocalStorageUtil.getObject('keep-logged-in');\n\n return {\n connected: false,\n // Connected and subscribed to 'me'\n ready: false,\n // Try to re-login on new connection.\n autoLogin: false,\n transport: settings.transport || null,\n serverAddress: settings.serverAddress || detectServerAddress(),\n serverVersion: \"no connection\",\n // \"On\" is the default, so saving the \"off\" state.\n messageSounds: !settings.messageSoundsOff,\n incognitoMode: false,\n desktopAlerts: persist && !!settings.desktopAlerts,\n desktopAlertsEnabled: (isSecureConnection() || isLocalHost()) &&\n (typeof firebase != 'undefined') && (typeof navigator != 'undefined') &&\n (typeof FIREBASE_INIT != 'undefined'),\n firebaseToken: persist ? LocalStorageUtil.getObject('firebase-token') : null,\n\n applicationVisible: !document.hidden,\n\n errorText: '',\n errorLevel: null,\n errorAction: undefined,\n errorActionText: null,\n\n sidePanelSelected: 'login',\n sidePanelTitle: null,\n sidePanelAvatar: null,\n myTrustedBadges: [],\n loadSpinnerVisible: false,\n\n login: '',\n password: '',\n persist: persist,\n myUserId: null,\n liveConnection: navigator.onLine,\n topicSelected: '',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n newTopicParams: null,\n loginDisabled: false,\n displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),\n infoPanel: undefined,\n mobilePanel: 'sidepanel',\n\n contextMenuVisible: false,\n contextMenuBounds: null,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuItems: [],\n\n forwardDialogVisible: false,\n forwardMessage: null,\n\n // Modal alert dialog.\n alertVisible: false,\n alertParams: {},\n\n // Chats as shown in the ContactsView\n chatList: [],\n // Contacts returned by a search query.\n searchResults: [],\n // Merged results of a search query and p2p chats.\n searchableContacts: [],\n credMethod: undefined,\n credCode: undefined,\n // Topic to go to after login.\n requestedTopic: undefined\n };\n }\n\n componentDidMount() {\n window.addEventListener('resize', this.handleResize);\n window.addEventListener('online', (e) => { this.handleOnline(true); });\n window.addEventListener('offline', (e) => { this.handleOnline(false); });\n window.addEventListener('hashchange', this.handleHashRoute);\n\n // Process background notifications from the service worker.\n if (typeof BroadcastChannel == 'function') {\n const serviceWorkerChannel = new BroadcastChannel('tinode-sw');\n serviceWorkerChannel.addEventListener('message', this.handlePushMessage);\n } else {\n // Safari is broken by design.\n console.warn(\"Your browser does not support BroadcastChannel. Some features will not be available\");\n }\n\n // Window/tab visible or invisible for pausing timers.\n document.addEventListener('visibilitychange', this.handleVisibilityEvent);\n\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n\n new Promise((resolve, reject) => {\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport,\n this.props.intl.locale, this.state.persist, resolve);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n }).then(() => {\n // Initialize desktop alerts.\n if (this.state.desktopAlertsEnabled) {\n this.initFCMessaging().then(() => {\n if (this.state.desktopAlerts) {\n if (!this.state.firebaseToken) {\n this.togglePushToken(true);\n } else {\n this.tinode.setDeviceToken(this.state.firebaseToken);\n }\n }\n }).catch(() => {\n // do nothing: handled earlier.\n // catch needed to pervent unnecessary logging of error.\n });\n }\n\n // Parse and save the hash navigation params.\n const parsedNav = HashNavigation.parseUrlHash(window.location.hash);\n\n // Read contacts from cache.\n this.resetContactList();\n\n const token = this.state.persist ? LocalStorageUtil.getObject('auth-token') : undefined;\n if (token) {\n this.setState({autoLogin: true});\n\n // When reading from storage, date is returned as string.\n token.expires = new Date(token.expires);\n this.tinode.setAuthToken(token);\n this.tinode.connect().catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n\n // Maybe navigate to home screen.\n if (!['cred', 'reset', 'register'].includes(parsedNav.path[0])) {\n // Save possible topic name.\n this.setState({requestedTopic: parsedNav.path[1]});\n HashNavigation.navigateTo('');\n }\n\n this.readTimer = null;\n this.readTimerCallback = null;\n\n this.handleHashRoute();\n });\n }\n\n componentWillUnmount() {\n window.removeEventListener('resize', this.handleResize);\n window.removeEventListener('hashchange', this.handleHashRoute);\n document.removeEventListener('visibilitychange', this.handleVisibilityEvent);\n }\n\n // Setup transport (usually websocket) and server address. This will terminate connection with the server.\n static tnSetup(serverAddress, transport, locale, persistentCache, onSetupCompleted) {\n const tinode = new Tinode({appName: APP_NAME, host: serverAddress, apiKey: API_KEY, transport: transport,\n secure: isSecureConnection(), persist: persistentCache}, onSetupCompleted);\n tinode.setHumanLanguage(locale);\n tinode.enableLogging(LOGGING_ENABLED, true);\n return tinode;\n }\n\n // Notifiy Tinode that a push message was received from the server.\n handlePushMessage(data) {\n if (data.what == 'msg' && Tinode.isChannelTopicName(data.topic)) {\n // The last argument is a fake user Id: otherwise the update is seen as one from the current user.\n this.tinode.oobNotification(data.topic, data.seq, 'fake-uid');\n }\n }\n\n initFCMessaging() {\n const {formatMessage, locale} = this.props.intl;\n const onError = (msg, err) => {\n console.error(msg, err);\n this.handleError(formatMessage(messages.push_init_failed), 'err');\n this.setState({desktopAlertsEnabled: false});\n }\n\n try {\n this.fbPush = firebase.initializeApp(FIREBASE_INIT, APP_NAME).messaging();\n this.fbPush.usePublicVapidKey(FIREBASE_INIT.messagingVapidKey);\n\n return navigator.serviceWorker.register('/service-worker.js').then((reg) => {\n this.checkForAppUpdate(reg);\n this.fbPush.useServiceWorker(reg);\n reg.active.postMessage(JSON.stringify({locale: locale, version: PACKAGE_VERSION}));\n\n // Google could not be bothered to mention that onTokenRefresh is never called.\n this.fbPush.onTokenRefresh(() => {\n this.requestPushToken();\n });\n\n // Handhe FCM pushes\n // (a) for channels always,\n // (b) pushes when the app is in foreground but has not focus.\n this.fbPush.onMessage(payload => { this.handlePushMessage(payload.data); });\n\n return reg;\n }).catch((err) => {\n // registration failed :(\n onError(\"Failed to register service worker:\", err);\n return Promise.reject(err);\n });\n } catch (err) {\n onError(\"Failed to initialize push notifications\", err);\n return Promise.reject(err);\n }\n }\n\n handleResize() {\n const mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT;\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n if (this.state.displayMobile != mobile) {\n this.setState({displayMobile: mobile});\n }\n }\n\n // Check if a newer version of TinodeWeb app is available at the server.\n checkForAppUpdate(reg) {\n reg.onupdatefound = () => {\n const installingWorker = reg.installing;\n installingWorker.onstatechange = () => {\n if (installingWorker.state == 'installed' && navigator.serviceWorker.controller) {\n const msg = <>\n \n \n .>;\n this.handleError(msg, 'info');\n }\n }\n }\n }\n\n // Handle for hashchange event: display appropriate panels.\n handleHashRoute() {\n const hash = HashNavigation.parseUrlHash(window.location.hash);\n if (hash.path && hash.path.length > 0) {\n // Left-side panel selector.\n if (['register','settings','edit','notif','security','support','general','crop',\n 'cred','reset','newtpk','archive','blocked','contacts',''].includes(hash.path[0])) {\n this.setState({sidePanelSelected: hash.path[0]});\n } else {\n console.warn(\"Unknown sidepanel view\", hash.path[0]);\n }\n\n // Topic for MessagesView selector.\n let topicName = hash.path[1] || null;\n if (topicName != this.state.topicSelected) {\n if (!Tinode.topicType(topicName)) {\n // Clear invalid topic name.\n topicName = null;\n }\n const newState = {\n topicSelected: topicName\n };\n const acs = this.tinode.getTopicAccessMode(topicName);\n if (acs) {\n newState.topicSelectedAcs = acs;\n }\n this.setState(newState);\n }\n } else {\n // Empty hashpath\n this.setState({sidePanelSelected: '', topicSelected: null});\n }\n\n // Save validation credentials, if available.\n if (hash.params.method) {\n this.setState({ credMethod: hash.params.method });\n }\n if (hash.params.code) {\n this.setState({ credCode: hash.params.code });\n }\n\n // Additional parameters of panels.\n this.setState({\n infoPanel: hash.params.info,\n newTopicTabSelected: hash.params.tab\n });\n }\n\n handleOnline(online) {\n if (online) {\n this.handleError();\n clearInterval(this.reconnectCountdown);\n this.tinode.reconnect();\n } else {\n this.handleError(this.props.intl.formatMessage(messages.no_connection), 'warn');\n }\n this.setState({liveConnection: online});\n }\n\n handleVisibilityEvent() {\n this.setState({applicationVisible: !document.hidden});\n }\n\n handleError(err, level, action, actionText) {\n this.setState({errorText: err, errorLevel: level, errorAction: action, errorActionText: actionText});\n }\n\n // User clicked Login button in the side panel.\n handleLoginRequest(login, password) {\n this.setState({\n loginDisabled: true,\n login: login,\n password: password,\n loadSpinnerVisible: true,\n autoLogin: true\n });\n this.handleError('', null);\n\n if (this.tinode.isConnected()) {\n this.doLogin(login, password, {meth: this.state.credMethod, resp: this.state.credCode});\n } else {\n this.tinode.connect().catch((err) => {\n // Socket error\n this.setState({loginDisabled: false, autoLogin: false, loadSpinnerVisible: false});\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // Enable or disable saving the password and IndexedDB.\n handlePersistenceChange(persist) {\n if (persist) {\n this.tinode.initStorage().then(() => {\n LocalStorageUtil.setObject('keep-logged-in', true);\n this.setState({persist: true});\n });\n } else {\n this.tinode.clearStorage().then(() => {\n LocalStorageUtil.setObject('keep-logged-in', false);\n this.setState({persist: false});\n });\n }\n }\n\n // Connection succeeded.\n handleConnected() {\n // Just to be sure.\n clearInterval(this.reconnectCountdown);\n this.handleError();\n\n const params = this.tinode.getServerInfo();\n this.setState({\n serverVersion: params.ver + ' ' + (params.build ? params.build : 'none')\n });\n\n if (this.state.autoLogin) {\n this.doLogin(this.state.login, this.state.password, {meth: this.state.credMethod, resp: this.state.credCode});\n }\n }\n\n // Called for each auto-reconnect iteration.\n handleAutoreconnectIteration(sec, prom) {\n clearInterval(this.reconnectCountdown);\n\n if (sec < 0) {\n // Clear error\n this.handleError();\n return;\n }\n\n if (prom) {\n // Reconnecting now\n prom.then(() => {\n // Reconnected: clear error\n this.handleError();\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n return;\n }\n\n const {formatMessage} = this.props.intl;\n let count = sec / 1000;\n count = count | count;\n this.reconnectCountdown = setInterval(() => {\n const timeLeft = (count > 99) ? secondsToTime(count) : count;\n this.handleError(\n formatMessage(messages.reconnect_countdown, {seconds: timeLeft}),\n 'warn',\n () => {\n clearInterval(this.reconnectCountdown);\n this.tinode.reconnect();\n },\n formatMessage(messages.reconnect_now)\n );\n count -= 1;\n }, 1000);\n }\n\n // Connection lost\n handleDisconnect(err) {\n this.setState({\n connected: false,\n ready: false,\n topicSelectedOnline: false,\n errorText: err && err.message ? err.message : \"Disconnected\",\n errorLevel: err && err.message ? 'err' : 'warn',\n loginDisabled: false,\n contextMenuVisible: false,\n forwardDialogVisible: false,\n serverVersion: \"no connection\"\n });\n }\n\n doLogin(login, password, cred) {\n if (this.tinode.isAuthenticated()) {\n // Already logged in. Go to default screen.\n HashNavigation.navigateTo('');\n return;\n }\n // Sanitize and package credentail.\n cred = Tinode.credential(cred);\n // Try to login with login/password. If they are not available, try token. If no token, ask for login/password.\n let promise = null;\n let token = this.tinode.getAuthToken();\n if (login && password) {\n token = null;\n this.setState({password: null});\n promise = this.tinode.loginBasic(login, password, cred);\n } else if (token) {\n promise = this.tinode.loginToken(token.token, cred);\n }\n\n if (promise) {\n promise.then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text === 'validate credentials') {\n this.setState({loadSpinnerVisible: false});\n if (cred) {\n this.handleError(this.props.intl.formatMessage(messages.code_doesnot_match), 'warn');\n }\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful();\n }\n }).catch((err) => {\n // Login failed, report error.\n this.setState({\n loginDisabled: false,\n credMethod: undefined,\n credCode: undefined,\n loadSpinnerVisible: false,\n autoLogin: false\n });\n this.handleError(err.message, 'err');\n if (token) {\n this.handleLogout();\n }\n HashNavigation.navigateTo('');\n });\n } else {\n // No login credentials provided.\n // Make sure we are on the login page.\n HashNavigation.navigateTo('');\n this.setState({loginDisabled: false});\n }\n }\n\n handleCredentialsRequest(params) {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n parsed.path[0] = 'cred';\n parsed.params['method'] = params.cred[0];\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n }\n\n handleLoginSuccessful() {\n this.handleError();\n\n // Refresh authentication token.\n if (LocalStorageUtil.getObject('keep-logged-in')) {\n LocalStorageUtil.setObject('auth-token', this.tinode.getAuthToken());\n }\n\n const goToTopic = this.state.requestedTopic;\n // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.\n const me = this.tinode.getMeTopic();\n me.onMetaDesc = this.tnMeMetaDesc;\n me.onContactUpdate = this.tnMeContactUpdate;\n me.onSubsUpdated = this.tnMeSubsUpdated;\n this.setState({\n connected: true,\n credMethod: undefined,\n credCode: undefined,\n myUserId: this.tinode.getCurrentUserID(),\n autoLogin: true,\n requestedTopic: undefined,\n });\n // Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched.\n me.subscribe(\n me.startMetaQuery().\n withLaterSub().\n withDesc().\n withTags().\n withCred().\n build()\n ).catch((err) => {\n this.tinode.disconnect();\n localStorage.removeItem('auth-token');\n this.handleError(err.message, 'err');\n HashNavigation.navigateTo('');\n }).finally(() => {\n this.setState({loadSpinnerVisible: false});\n });\n let urlHash = HashNavigation.setUrlSidePanel(window.location.hash, 'contacts');\n if (goToTopic) {\n urlHash = HashNavigation.setUrlTopic(urlHash, goToTopic);\n }\n HashNavigation.navigateTo(urlHash);\n }\n\n tnMeMetaDesc(desc) {\n if (desc) {\n if (desc.public) {\n this.setState({\n sidePanelTitle: desc.public.fn,\n sidePanelAvatar: makeImageUrl(desc.public.photo)\n });\n }\n if (desc.trusted) {\n const badges = [];\n for (const [key, val] of Object.entries(desc.trusted)) {\n if (val) {\n badges.push(key);\n }\n }\n this.setState({\n myTrustedBadges: badges,\n });\n }\n if (desc.acs) {\n this.setState({\n incognitoMode: !desc.acs.isPresencer()\n });\n }\n }\n }\n\n // Reactions to updates to the contact list.\n tnMeContactUpdate(what, cont) {\n if (what == 'on' || what == 'off') {\n this.resetContactList();\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedOnline: (what == 'on')});\n }\n } else if (what == 'read') {\n this.resetContactList();\n } else if (what == 'msg') {\n // Check if the topic is archived. If so, don't play a sound.\n const topic = this.tinode.getTopic(cont.topic);\n const archived = topic && topic.isArchived();\n\n // New message received. Maybe the message is from the current user, then unread is 0.\n if (cont.unread > 0 && this.state.messageSounds && !archived) {\n // Skip update if the topic is currently open, otherwise the badge will annoyingly flash.\n if (document.hidden || this.state.topicSelected != cont.topic) {\n POP_SOUND.play();\n }\n }\n // Reorder contact list to use possibly updated 'touched'.\n this.resetContactList();\n } else if (what == 'recv') {\n // Explicitly ignoring \"recv\" -- it causes no visible updates to contact list.\n } else if (what == 'gone' || what == 'unsub') {\n // Topic deleted or user unsubscribed. Remove topic from view.\n // If the currently selected topic is gone, clear the selection.\n if (this.state.topicSelected == cont.topic) {\n this.handleTopicSelected(null);\n }\n // Redraw without the deleted topic.\n this.resetContactList();\n } else if (what == 'acs') {\n // Permissions changed. If it's for the currently selected topic,\n // update the views.\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedAcs: cont.acs});\n }\n } else if (what == 'del') {\n // TODO: messages deleted (hard or soft) -- update pill counter.\n } else if (what == 'upd') {\n // upd - handled by the SDK. Explicitly ignoring here.\n } else {\n // TODO(gene): handle other types of notifications:\n // * ua -- user agent changes (maybe display a pictogram for mobile/desktop).\n console.info(\"Unsupported (yet) presence update:\" + what + \" in: \" + cont.topic);\n }\n }\n\n tnMeSubsUpdated(unused) {\n this.resetContactList();\n }\n\n // Merge search results and contact list to create a single flat\n // list of known contacts for GroupManager to use.\n static prepareSearchableContacts(chatList, foundContacts) {\n const merged = {};\n\n // For chatList topics merge only p2p topics and convert them to the\n // same format as foundContacts.\n for (const c of chatList) {\n if (Tinode.isP2PTopicName(c.topic)) {\n merged[c.topic] = {\n user: c.topic,\n updated: c.updated,\n public: c.public,\n private: c.private,\n acs: c.acs\n };\n }\n }\n\n // Add all foundCountacts if they have not been added already.\n for (const c of foundContacts) {\n if (!merged[c.user]) {\n merged[c.user] = c;\n }\n }\n\n return Object.values(merged);\n }\n\n resetContactList() {\n const newState = {\n chatList: []\n };\n\n if (!this.state.ready) {\n newState.ready = true;\n }\n\n this.tinode.getMeTopic().contacts((c) => {\n if (!c.topic && !c.user) {\n // Contacts expect c.topic to be set.\n c.topic = c.name;\n }\n\n newState.chatList.push(c);\n if (this.state.topicSelected == c.topic) {\n newState.topicSelectedOnline = c.online;\n newState.topicSelectedAcs = c.acs;\n }\n });\n\n const past = new Date(0);\n newState.chatList.sort((a, b) => {\n return (a.touched || past).getTime() - (b.touched || past).getTime();\n });\n\n // Merge search results and chat list.\n newState.searchableContacts = TinodeWeb.prepareSearchableContacts(newState.chatList, this.state.searchResults);\n this.setState(newState);\n }\n\n // Sending \"received\" notifications\n tnData(data) {\n const topic = this.tinode.getTopic(data.topic);\n if (topic.msgStatus(data, true) >= Tinode.MESSAGE_STATUS_SENT && data.from != this.state.myUserId) {\n clearTimeout(this.receivedTimer);\n this.receivedTimer = setTimeout(() => {\n this.receivedTimer = undefined;\n topic.noteRecv(data.seq);\n }, RECEIVED_DELAY);\n }\n }\n\n /* Fnd topic: find contacts by tokens */\n tnInitFind() {\n const fnd = this.tinode.getFndTopic();\n fnd.onSubsUpdated = this.tnFndSubsUpdated;\n if (fnd.isSubscribed()) {\n this.tnFndSubsUpdated();\n } else {\n fnd.subscribe(fnd.startMetaQuery().withSub().build()).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n tnFndSubsUpdated() {\n const foundContacts = [];\n // Don't attempt to create P2P topics which already exist. Server will reject the duplicates.\n this.tinode.getFndTopic().contacts((s) => {\n foundContacts.push(s);\n });\n this.setState({\n searchResults: foundContacts,\n searchableContacts: TinodeWeb.prepareSearchableContacts(this.state.chatList, foundContacts)\n });\n }\n\n /** Called when the user enters a contact into the contact search field in the NewAccount panel\n @param query {Array} is an array of contacts to search for\n */\n handleSearchContacts(query) {\n const fnd = this.tinode.getFndTopic();\n fnd.setMeta({desc: {public: query}}).then((ctrl) => {\n return fnd.getMeta(fnd.startMetaQuery().withSub().build());\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User clicked on a contact in the side panel or deleted a contact.\n handleTopicSelected(topicName) {\n // Clear newTopicParams after use.\n if (this.state.newTopicParams && this.state.newTopicParams._topicName != topicName) {\n this.setState({\n newTopicParams: null\n });\n }\n\n if (topicName) {\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'topic-view',\n infoPanel: undefined\n });\n // Different contact selected.\n if (this.state.topicSelected != topicName) {\n this.setState({\n topicSelectedOnline: this.tinode.isTopicOnline(topicName),\n topicSelectedAcs: this.tinode.getTopicAccessMode(topicName),\n forwardMessage: null\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', topicName));\n }\n } else {\n // Currently selected contact deleted\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'sidepanel',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n infoPanel: undefined,\n forwardMessage: null\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));\n }\n }\n\n // In mobile view user requested to show sidepanel\n handleHideMessagesView() {\n this.setState({\n mobilePanel: 'sidepanel'\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, null));\n }\n\n // User is sending a message, either plain text or a drafty object, possibly\n // with attachments.\n // - msg - Drafty message with content\n // - promise - Promise to be resolved when the upload is completed\n // - uploader - for tracking progress\n // - head - head dictionary to be attached to the message\n handleSendMessage(msg, promise, uploader, head) {\n const topic = this.tinode.getTopic(this.state.topicSelected);\n this.sendMessageToTopic(topic, msg, promise, uploader, head);\n }\n\n sendMessageToTopic(topic, msg, promise, uploader, head) {\n msg = topic.createMessage(msg, false);\n // The uploader is used to show progress.\n msg._uploader = uploader;\n\n if (head) {\n msg.head = Object.assign(msg.head || {}, head);\n }\n\n if (!topic.isSubscribed()) {\n // Topic is not subscribed yet. Subscribe.\n if (!promise) {\n promise = Promise.resolve();\n }\n promise = promise\n .then(() => topic.subscribe())\n .then(() => {\n // If there are unsent messages, try sending them now.\n topic.queuedMessages((pub) => {\n if (!pub._sending && topic.isSubscribed()) {\n this.sendMessage(pub);\n }\n });\n });\n }\n\n if (promise) {\n promise = promise.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n topic.publishDraft(msg, promise)\n .then((ctrl) => {\n if (topic.isArchived()) {\n return topic.archive(false);\n }\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleNewChatInvitation(topicName, action) {\n const topic = this.tinode.getTopic(topicName);\n let response = null;\n switch (action) {\n case 'accept':\n // Accept given permissions.\n const mode = topic.getAccessMode().getGiven();\n response = topic.setMeta({sub: {mode: mode}});\n if (topic.isP2PType()) {\n // For P2P topics change 'given' permission of the peer too.\n // In p2p topics the other user has the same name as the topic.\n response = response.then((ctrl) => {\n topic.setMeta({sub: {user: topicName, mode: mode}});\n });\n }\n break;\n case 'delete':\n // Ignore invitation by deleting it.\n response = topic.delTopic(true);\n break;\n case 'block':\n // Ban the topic making futher invites impossible.\n // Just self-ban.\n const am = topic.getAccessMode().updateWant('-JP').getWant();\n response = topic.setMeta({sub: {mode: am}}).then((ctrl) => {\n return this.handleTopicSelected(null);\n });\n break;\n default:\n console.warn(\"Unknown invitation action\", '\"' + action + '\"\"');\n }\n\n if (response != null) {\n response.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // User chose a Sign Up menu item.\n handleNewAccount() {\n this.handleError();\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'register'));\n }\n\n // Actual registration of a new account.\n handleNewAccountRequest(login_, password_, public_, cred_, tags_) {\n // Clear old error, if any.\n this.handleError();\n\n this.tinode.connect(this.state.serverAddress)\n .then(() => {\n return this.tinode.createAccountBasic(login_, password_,\n {public: public_, tags: tags_, cred: Tinode.credential(cred_)});\n }).then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text == 'validate credentials') {\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful(this);\n }\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleToggleIncognitoMode(on) {\n // Make state undefined.\n this.setState({incognitoMode: null});\n\n const me = this.tinode.getMeTopic();\n const am = me.getAccessMode().updateWant(on ? '-P' : '+P').getWant();\n me.setMeta({sub: {mode: am}}).catch((err) => {\n // Request failed, keep existing state.\n this.setState({incognitoMode: !on});\n this.handleError(err.message, 'err');\n });\n }\n\n handleUpdateAccountTagsRequest(tags) {\n this.tinode.getMeTopic().setMeta({tags: tags})\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User chose Settings menu item.\n handleSettings() {\n this.handleError();\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'edit' : 'settings'));\n }\n\n // User updated global parameters.\n handleGlobalSettings(settings) {\n const serverAddress = settings.serverAddress || this.state.serverAddress;\n const transport = settings.transport || this.state.transport;\n if (this.tinode) {\n this.tinode.clearStorage();\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n }\n this.tinode = TinodeWeb.tnSetup(serverAddress, transport, this.props.intl.locale,\n LocalStorageUtil.getObject('keep-logged-in'));\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n\n this.setState({\n serverAddress: serverAddress,\n transport: transport\n });\n LocalStorageUtil.setObject('settings', {\n serverAddress: serverAddress,\n transport: transport\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, ''));\n }\n\n // User chose 'Archived chats'.\n handleShowArchive() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'archive' : ''));\n }\n\n // User viewes 'Blocked chats'.\n handleShowBlocked() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'blocked' : ''));\n }\n\n togglePushToken(enabled) {\n if (enabled) {\n this.setState({desktopAlerts: null});\n if (!this.state.firebaseToken) {\n const fcm = this.fbPush ?\n Promise.resolve() :\n this.initFCMessaging();\n fcm.then(() => {\n return this.fbPush.requestPermission();\n }).then(() => {\n this.requestPushToken();\n }).catch((err) => {\n console.error(\"Failed to get notification permission.\", err);\n this.handleError(err.message, 'err');\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n });\n } else {\n this.setState({desktopAlerts: true});\n if (LocalStorageUtil.getObject('keep-logged-in')) {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }\n }\n } else if (this.state.firebaseToken && this.fbPush) {\n this.fbPush.deleteToken(this.state.firebaseToken).catch((err) => {\n console.error(\"Unable to delete token.\", err);\n }).finally(() => {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n localStorage.removeItem('firebase-token');\n this.setState({desktopAlerts: false, firebaseToken: null});\n // Inform the server that the token was deleted.\n this.tinode.setDeviceToken(null);\n });\n } else {\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n }\n }\n\n requestPushToken() {\n this.fbPush.getToken().then((refreshedToken) => {\n const persist = LocalStorageUtil.getObject('keep-logged-in');\n if (refreshedToken != this.state.firebaseToken) {\n this.tinode.setDeviceToken(refreshedToken);\n if (persist) {\n LocalStorageUtil.setObject('firebase-token', refreshedToken);\n }\n }\n this.setState({firebaseToken: refreshedToken, desktopAlerts: true});\n if (persist) {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }\n }).catch((err) => {\n this.handleError(err.message, 'err');\n console.error(\"Failed to retrieve firebase token\", err);\n });\n }\n\n handleToggleMessageSounds(enabled) {\n this.setState({messageSounds: enabled});\n LocalStorageUtil.updateObject('settings', {\n messageSoundsOff: !enabled\n });\n }\n\n handleCredAdd(method, value) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: method, val: value}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredDelete(method, value) {\n const me = this.tinode.getMeTopic();\n me.delCredential(method, value).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredConfirm(method, response) {\n this.handleCredentialsRequest({cred: [method]});\n }\n\n // User clicked Cancel button in Setting or Sign Up panel.\n handleSidepanelCancel() {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n let path = '';\n if (['security','support','general','notif'].includes(parsed.path[0])) {\n path = 'edit';\n } else if ('crop' == parsed.path[0]) {\n path = 'general';\n } else if ('blocked' == parsed.path[0]) {\n path = 'security';\n } else if (this.state.myUserId) {\n path = 'contacts';\n }\n parsed.path[0] = path;\n if (parsed.params) {\n delete parsed.params.code;\n delete parsed.params.method;\n delete parsed.params.tab;\n delete parsed.params.scheme;\n delete parsed.params.token;\n }\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n this.setState({errorText: '', errorLevel: null});\n }\n\n // Sidepanel navigator. No need to bind to 'this'.\n basicNavigator(hash) {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, hash));\n }\n\n // Topic info navigator. No need to bind to 'this'.\n infoNavigator(hash) {\n HashNavigation.navigateTo(HashNavigation.setUrlInfoPanel(window.location.hash, hash));\n }\n\n // Request to start a topic, new or selected from search results, or \"by ID\".\n handleStartTopicRequest(topicName, newTopicParams, isChannel) {\n // Check if topic is indeed new. If not, launch it.\n if (topicName && this.tinode.isTopicCached(topicName)) {\n this.handleTopicSelected(topicName);\n return;\n }\n\n const params = {};\n if (Tinode.isP2PTopicName(topicName)) {\n // Because we are initialing the subscription, set 'want' to all permissions.\n params.sub = {mode: DEFAULT_P2P_ACCESS_MODE};\n // Give the other user all permissions too.\n params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};\n } else {\n topicName = topicName || this.tinode.newGroupTopicName(isChannel);\n if (newTopicParams) {\n params.desc = {public: newTopicParams.public, private: {comment: newTopicParams.private}};\n params.tags = newTopicParams.tags;\n }\n }\n params._topicName = topicName;\n this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});\n }\n\n // New topic was created, here is the new topic name.\n handleNewTopicCreated(oldName, newName) {\n if (this.state.topicSelected == oldName && oldName != newName) {\n // If the current URl contains the old topic name, replace it with new.\n // Update the name of the selected topic first so the navigator doen't clear\n // the state.\n this.setState({topicSelected: newName}, () => {\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', newName));\n });\n }\n }\n\n handleTopicUpdateRequest(topicName, pub, priv, defacs) {\n this.handleError();\n\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const params = {};\n let attachments;\n if (pub) {\n if (pub.photo) {\n if (pub.photo.ref && pub.photo.ref != Tinode.DEL_CHAR) {\n attachments = [pub.photo.ref];\n } else if (!pub.photo.data || pub.photo.data == Tinode.DEL_CHAR) {\n pub.photo = Tinode.DEL_CHAR;\n }\n }\n params.public = pub;\n }\n\n if (typeof priv == 'string') {\n params.private = (priv === Tinode.DEL_CHAR) ?\n Tinode.DEL_CHAR : {comment: priv};\n }\n if (defacs) {\n params.defacs = defacs;\n }\n topic.setMeta({desc: params, attachments: attachments}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleUnarchive(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n topic.archive(false);\n }\n }\n\n handleUpdatePasswordRequest(password) {\n this.handleError();\n\n if (password) {\n this.tinode.updateAccountBasic(null, this.tinode.getCurrentLogin(), password).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleChangePermissions(topicName, mode, uid) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const am = topic.getAccessMode();\n if (uid) {\n am.updateGiven(mode);\n mode = am.getGiven();\n } else {\n am.updateWant(mode);\n mode = am.getWant();\n }\n topic.setMeta({sub: {user: uid, mode: mode}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleTagsUpdateRequest(topicName, tags) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n topic.setMeta({tags: tags}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleLogout() {\n updateFavicon(0);\n\n // Remove stored data.\n localStorage.removeItem('auth-token');\n localStorage.removeItem('firebase-token');\n localStorage.removeItem('settings');\n if (this.state.firebaseToken) {\n this.fbPush.deleteToken(this.state.firebaseToken)\n }\n\n clearInterval(this.reconnectCountdown);\n\n let cleared;\n if (this.tinode) {\n cleared = this.tinode.clearStorage();\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n } else {\n cleared = Promose.resolve();\n }\n this.setState(this.getBlankState());\n\n cleared.then(() => {\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress,\n this.state.transport, this.props.intl.locale, LocalStorageUtil.getObject('keep-logged-in'), () => {\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n HashNavigation.navigateTo('');\n })\n });\n }\n\n handleDeleteAccount() {\n this.tinode.delCurrentUser(true).then((ctrl) => {\n this.handleLogout();\n });\n }\n\n handleDeleteTopicRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Request to hard-delete topic.\n topic.delTopic(true).then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleDeleteMessagesRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Request hard-delete all messages.\n topic.delMessagesAll(true).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleLeaveUnsubRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.leave(true).then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleBlockTopicRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleReportTopic(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Publish spam report.\n this.tinode.publish(Tinode.TOPIC_SYS, Tinode.Drafty.attachJSON(null, {\n 'action': 'report',\n 'target': topicName\n }));\n\n // Remove J and P permissions.\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleShowContextMenu(params, menuItems) {\n this.setState({\n contextMenuVisible: true,\n contextMenuClickAt: {x: params.x, y: params.y},\n contextMenuParams: params,\n contextMenuItems: menuItems || this.defaultTopicContextMenu(params.topicName),\n contextMenuBounds: this.selfRef.current.getBoundingClientRect()\n });\n }\n\n //\n handleShowForwardDialog(params) {\n if (this.state.sidePanelSelected == 'newtpk') {\n // If the Find panel is active, close it.\n this.handleSidepanelCancel();\n }\n const header = '➦ ' + params.userName;\n const content = typeof params.content == 'string' ?\n Tinode.Drafty.init(params.content) : Tinode.Drafty.forwardedContent(params.content);\n const preview = Tinode.Drafty.preview(content, FORWARDED_PREVIEW_LENGTH,\n undefined, params.forwarded != null);\n const msg = Tinode.Drafty.append(\n Tinode.Drafty.appendLineBreak(\n Tinode.Drafty.mention(header, params.userFrom)),\n content);\n\n const msgPreview = Tinode.Drafty.quote(header, params.userFrom, preview);\n const head = {\n forwarded: params.topicName + ':' + params.seq\n };\n this.setState({\n forwardDialogVisible: true,\n forwardMessage: { head: head, msg: msg, preview: msgPreview }\n });\n }\n\n defaultTopicContextMenu(topicName) {\n const topic = this.tinode.getTopic(topicName);\n\n let muted = false, blocked = false, self_blocked = false, subscribed = false, deleter = false, archived = false;\n if (topic) {\n subscribed = topic.isSubscribed();\n archived = topic.isArchived();\n\n const acs = topic.getAccessMode();\n if (acs) {\n muted = acs.isMuted();\n blocked = !acs.isJoiner();\n self_blocked = !acs.isJoiner('want');\n deleter = acs.isDeleter();\n }\n }\n\n return [\n subscribed ? {\n title: this.props.intl.formatMessage(messages.menu_item_info),\n handler: this.handleShowInfoView\n } : null,\n subscribed ? 'messages_clear' : null,\n subscribed && deleter ? 'messages_clear_hard' : null,\n muted ? (blocked ? null : 'topic_unmute') : 'topic_mute',\n self_blocked ? 'topic_unblock' : 'topic_block',\n archived ? 'topic_restore' : 'topic_archive',\n 'topic_delete'\n ];\n }\n\n handleHideContextMenu() {\n this.setState({\n contextMenuVisible: false,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuBounds: null\n });\n }\n\n handleHideForwardDialog(keepForwardedMessage) {\n this.setState({\n forwardDialogVisible: false,\n forwardMessage: keepForwardedMessage ? this.state.forwardMessage : null\n });\n }\n\n handleContextMenuAction(action, promise, params) {\n if (action == 'topic_archive') {\n if (promise && params.topicName && params.topicName == this.state.topicSelected) {\n promise.then(() => {\n this.handleTopicSelected(null);\n });\n }\n } else if (action == 'menu_item_forward') {\n this.handleShowForwardDialog(params);\n }\n }\n\n handleShowAlert(title, content, onConfirm, confirmText, onReject, rejectText) {\n this.setState({\n alertVisible: true,\n alertParams: {\n title: title,\n content: content,\n onConfirm: onConfirm,\n confirm: confirmText,\n onReject: onReject,\n reject: rejectText\n }\n });\n }\n\n handleShowInfoView() {\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'info', 'info'));\n this.setState({infoPanel: 'info'});\n }\n\n handleMemberUpdateRequest(topicName, added, removed) {\n if (!topicName) {\n return;\n }\n\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n if (added && added.length > 0) {\n added.map((uid) => {\n topic.invite(uid, null).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n\n if (removed && removed.length > 0) {\n removed.map((uid) => {\n topic.delSubscription(uid).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n }\n\n handleValidateCredentialsRequest(cred, code) {\n if (this.tinode.isAuthenticated()) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: cred, resp: code}})\n .then(() => {\n HashNavigation.navigateTo('');\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n } else {\n this.setState({credMethod: cred, credCode: code});\n this.doLogin(null, null, {meth: cred, resp: code});\n }\n }\n\n handlePasswordResetRequest(method, value) {\n // If already connected, connnect() will return a resolved promise.\n return this.tinode.connect()\n .then(() => {\n return this.tinode.requestResetAuthSecret('basic', method, value);\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n\n handleResetPassword(scheme, newPassword, token) {\n token = base64ReEncode(token);\n if (!token) {\n this.handleError(this.props.intl.formatMessage(messages.invalid_security_token), 'err');\n } else {\n this.tinode.connect()\n .then(() => {\n return this.tinode.updateAccountBasic(null, null, newPassword, {token: token});\n })\n .then(() => {\n HashNavigation.navigateTo('');\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n }\n\n render() {\n return (\n \n {this.state.contextMenuVisible ?\n
{\n if (topicName == this.state.topicSelected) {\n this.handleTopicSelected(null);\n }\n }}\n onError={this.handleError} />\n :\n null\n }\n {this.state.forwardDialogVisible ?\n \n :\n null\n }\n { this.setState({alertVisible: false}); }) : null}\n reject={this.state.alertParams.reject}\n onConfirm={() => { this.setState({alertVisible: false}); this.state.alertParams.onConfirm(); }}\n confirm={this.state.alertParams.confirm}\n />\n \n\n \n\n {this.state.infoPanel ?\n \n :\n null\n }\n \n );\n }\n};\n\nexport default injectIntl(TinodeWeb);\n","// Put all packages together.\n// Used to generate umd/index.prod.js\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { IntlProvider } from 'react-intl';\n\nimport allMessages from './messages.json';\nimport TinodeWeb from './views/tinode-web.jsx';\nimport HashNavigation from './lib/navigation.js';\n\n// Detect human language to use in the UI:\n// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.\nconst { params } = HashNavigation.parseUrlHash(window.location.hash);\nconst language = (params && params.hl) ||\n (navigator.languages && navigator.languages[0]) ||\n navigator.language ||\n navigator.userLanguage ||\n 'en';\n\n// Get the base language 'en' from a more specific 'en_GB' or 'en-US' as a partial fallback.\nconst baseLanguage = language.toLowerCase().split(/[-_]/)[0];\n\n// Try the full locale first, then the locale without the region code, fallback to 'en'.\nconst htmlLang = allMessages[language] ? langauge : allMessages[baseLanguage] ? baseLanguage : 'en';\nconst messages = allMessages[htmlLang];\n\n// Set lang attribute of the HTML element: \ndocument.getElementsByTagName('html')[0].setAttribute('lang', htmlLang);\n\nReactDOM.render(\n \n \n \n \n ,\n document.getElementById('mountPoint')\n);\n","// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// startup\n// Load entry module and return exports\n// This entry module doesn't tell about it's top-level declarations so it can't be inlined\nvar __webpack_exports__ = {};\n__webpack_modules__[353](0, __webpack_exports__, __webpack_require__);\n"],"names":["React","ReactDOM","ReactIntl","firebase","messaging","Tinode","Alert","render","this","props","visible","className","title","content","onReject","onClick","reject","FormattedMessage","id","defaultMessage","onConfirm","confirm","PACKAGE_VERSION","APP_NAME","KNOWN_HOSTS","hosted","local","DEFAULT_HOST","KEYPRESS_DELAY","DEFAULT_P2P_ACCESS_MODE","REM_SIZE","AVATAR_SIZE","MAX_INBAND_ATTACHMENT_SIZE","MAX_EXTERN_ATTACHMENT_SIZE","IMAGE_THUMBNAIL_DIM","MAX_TITLE_LENGTH","MAX_TOPIC_DESCRIPTION_LENGTH","messages","defineMessages","info","clear_messages","clear_for_all","delete","delete_for_all","send_retry","mute","unmute","reply","forward","topic_delete","topic_delete_warning","delete_messages_warning","unblock","block","topic_block_warning","member_delete","archive","unarchive","edit_permissions","clear_messages_warning","ContextMenu","constructor","super","selfRef","formatMessage","intl","handlePageClick","bind","handleEscapeKey","handleClick","MenuItems","handler","params","errorHandler","onShowAlert","deleteMessages","message","retryMessage","replyToMessage","topicPermissionSetter","then","ctrl","onTopicRemoved","topicName","topic","tinode","getTopic","delTopic","catch","err","console","log","user","delSubscription","componentDidMount","document","addEventListener","componentWillUnmount","removeEventListener","e","current","contains","target","preventDefault","stopPropagation","hide","keyCode","item","items","currentTarget","dataset","onAction","onError","error","all","hard","cancelSend","seq","delMessagesAll","delMessagesList","flushMessage","msg","createMessage","publishDraft","mode","result","updateMode","pickReply","userFrom","userName","menu","count","map","push","key","vSize","length","position","left","bounds","right","clickAt","x","top","bottom","y","style","ref","injectIntl","icon_mapping","ContactBadges","badges","b","color","icon","name","bytesToHumanSize","bytes","sizes","bucket","Math","min","floor","log2","pow","round","toFixed","shortenFileName","filename","maxLength","slice","idToColorClass","light","fg","userId","abs","value","hash","i","charCodeAt","stringToColorHash","updateFavicon","oldIcon","getElementById","head","getElementsByTagName","newIcon","createElement","type","rel","href","removeChild","appendChild","theCard","fn","imageUrl","imageMimeType","note","card","trim","mimeType","matches","exec","photo","data","substring","indexOf","arrayEqual","a","Array","isArray","sort","l","sanitizeUrl","url","allowedSchemes","replace","test","schemes","join","RegExp","sanitizeImageUrl","sanitizedUrl","deliveryMarker","received","cancelablePromise","promise","hasCanceled","Error","Promise","resolve","isCanceled","cancel","LetterTile","avatar","isGroup","iconColor","letter","charAt","deleted","authorizeURL","alt","src","onerror","UnreadBadge","LazyImage","state","Object","assign","padding","whenDone","setState","componentDidUpdate","prevProps","FileProgress","width","progress","onCancel","UploadingImage","onCancelUpload","SUPPORTED_IMAGE_FORMATS","MIME_EXTENSIONS","makeImageUrl","startsWith","fitImageSize","height","maxWidth","maxHeight","forceSquare","scale","size","dstWidth","dstHeight","srcWidth","srcHeight","xoffset","yoffset","fileNameForMime","fname","mime","idx","ext","at","lastIndexOf","imageScaled","fileOrBlob","maxSize","img","Image","crossOrigin","onload","async","URL","revokeObjectURL","dim","canvas","ctx","getContext","imageSmoothingEnabled","drawImage","includes","blob","toBlob","clearRect","createObjectURL","blobToBase64","reader","FileReader","evt","bits","split","readAsDataURL","drafty_form","drafty_attachment","drafty_image","handleImageData","el","attr","viewportWidth","minWidth","minHeight","Drafty","max","onImagePreview","loading","fullFormatter","values","stack","quoteFormatter","call","onFormButtonClick","inner","child","undefined","val","onQuoteClick","concat","previewFormatter","inlineImageAttr","quoteImage","str","bin","atob","buf","ArrayBuffer","arr","Uint8Array","Blob","base64ToBlob","fetch","ok","status","statusText","scaled","b64","replyFormatter","loadedPromise","Contact","handleContextClick","onSelected","index","showContextMenu","pageY","pageX","online","now","icon_badges","preview","isVerified","isStaff","isDangerous","acs","showMode","getMode","isMuted","isJoiner","marker","titleClass","showCheckmark","selected","showOnline","isChannel","unread","small","comment","ContactAction","action","badge_you","badge_owner","ContactList","topicSelected","contactNodes","contactsCount","contacts","c","filterFunc","filter","filterOn","private","toLowerCase","public","myUserId","isOwner","forwarded","deliveryStatus","latestMessage","_status","msgStatus","substr","showUnread","connected","onTopicSelected","trusted","verified","staff","danger","_deleted","noScroll","dangerouslySetInnerHTML","__html","emptyListMessage","SearchContacts","edited","search","handleSearchChange","handleSearch","handleClear","handleKeyDown","onSearchContacts","query","placeholder","onChange","onKeyDown","required","autoFocus","ForwardDialog","handleClose","handleSearchContacts","handleContactSelected","onInitFind","uid","searchResults","isWriter","desription","search_placeholder","not_found_placeholder","LoadSpinner","large","clear","centered","show","AvatarUpload","source","handleFileReceived","image","files","onImageUpdated","randId","random","readOnly","accept","htmlFor","uploading","badge_verified","badge_staff","badge_danger","BadgeList","trustedBadges","CheckBox","handleChange","checked","MenuCancel","ErrorPanel","level","onClearError","warn","text","whiteSpace","actionText","Chip","handleCancel","invalid","noAvatar","ChipInput","deriveStateFromProps","input","focused","handleTextInput","removeChipAt","handleChipCancel","handleFocusGained","handleFocusLost","chips","prompt","sortedChips","sortChips","staticMembers","chipIndex","indexChips","prevState","keep","normal","removed","onChipRemoved","onFocusLost","onEnter","avatarDisabled","tabIndex","onFocus","onBlur","no_contacts","contacts_not_found_short","GroupManager","members","indexMembers","keepInitialMembers","requiredMember","contactFilter","noContactsMessage","selectedContacts","handleMemberRemoved","handleContactFilter","handleSubmit","m","delta","present","keepInitial","stat","sel","splice","instance","added","keys","k","onSubmit","doContactFiltering","joiner","writer","preser","approver","sharer","deleter","owner","PermissionsEditor","names","skip","compare","userTitle","userAvatar","modeTitle","compareTitle","Cropper","panX","panY","originX","originY","zoom","minZoom","maxZoom","overlay","cutout","boundingBox","imageWidth","imageHeight","mouseX","mouseY","prevDistance","cutoutRect","bBoxRect","initScaling","onZoom","handleZoom","mouseDown","mouseUp","mouseMove","mouseTouch","positionAll","translate","passive","getBoundingClientRect","currPan","nextDiff","imgRect","imgLeft","imgRight","coLeft","coRight","imgTop","imgBottom","coTop","coBottom","touches","window","body","dX","dY","checkBound","touch0","touch1","distance","sqrt","t3d","orig","transform","transformOrigin","onLoad","step","AvatarCrop","objURL","VisiblePassword","handleVisibility","handeTextChange","handleEditingFinished","onFinished","setTimeout","activeElement","autoComplete","InPlaceEdit","active","initialValue","handleStartEditing","handlePasswordFinished","newValue","focus","event","checkValidity","spanText","spanClass","multiline","element","rows","TagManager","tags","tagInput","activated","handleTagInput","handleAddTag","handleRemoveTag","nextProps","last","tag","maxTagCount","getServerLimit","onTagsChanged","minTagLength","maxTagLength","add_tags_prompt","TopicDescEdit","getAccessMode","isMe","fullName","description","newAvatar","newAvatarMime","previousOnTags","tnNewTags","handleFullNameUpdate","handleImageUpdated","handleAvatarCropped","handlePrivateUpdate","handleDescriptionUpdate","uploadAvatar","handleAvatarCropCancel","handleTagsUpdated","onTagsUpdated","onUpdateTopicDesc","desc","readyToUpload","uploader","getLargeFileHelper","upload","finally","du","onUpdateTags","editable","full_name_placeholder","group_name_placeholder","private_placeholder","title_tag_manager","TopicCommon","credentials","getCredentials","addCredActive","addCredInvalid","newCred","previousTagsUpdated","tnCredsUpdated","handleCredChange","handleCredKeyDown","handleCredEntered","onCredsUpdated","getType","creds","method","asPhone","asEmail","onCredAdd","onTopicTagsUpdateRequest","cred","meth","done","onCredConfirm","onCredDelete","delete_messages","leave_chat","leave_chat_warning","block_contact","block_contact_warning","report_chat","report_chat_warning","other_user","TopicSecurity","handleDeleteTopic","handleDeleteMessages","handleLeave","handleBlock","handleReport","onDeleteTopic","onDeleteMessages","onLeaveTopic","onBlockTopic","onReportTopic","channel","groupTopic","onLaunchPermissionsEditor","access","auth","anon","modeGiven2","general","security","crop","perm_want","perm_given","perm_auth","perm_anon","perm_user","_clip","InfoView","admin","muted","address","selectedContact","modeGiven","modeWant","modeWant2","contactList","previousMetaDesc","previousSubsUpdated","resetSubs","resetDesc","resetTags","onMetaDesc","onSubsUpdated","handleImageChanged","handleMuted","handleUnarchive","handlePermissionsChanged","handleLaunchPermissionsEditor","handleShowAddMembers","handleMemberUpdateRequest","handleMemberSelected","handleContextMenu","handleBackNavigate","newState","user2","subscriber","getGiven","getWant","subscribers","sub","defacs","getDefaultAccess","entries","isAdmin","isSharer","isDeleter","archived","isArchived","isGroupType","isChannelType","chan","getMeta","startMetaQuery","withTags","build","subs","onTopicDescUpdate","ignored","onChangePermissions","ignored2","onTopicUnArchive","which","perm","onTopicDescUpdateRequest","userPermissionsEdited","toEdit","toCompare","toSkip","titleEdit","titleCompare","userPermissionsTitle","userPermissionsAvatar","editedPermissions","immutablePermissions","editedPermissionsTitle","immutablePermissionsTitle","editedPermissionsSkipped","onNavigate","onMemberUpdateRequest","args","panel","menuItems","view","shift","panelTitle","displayMobile","errorLevel","errorText","searchableContacts","no_members","Attachment","downloader","downloadFile","mimetype","download","loaded","helperFunc","isUrlRelative","downloadUrl","downloadWidget","message_sending","message_sending_failed","ReceivedMarker","timestamp","toLocaleTimeString","locale","timeStyle","BaseChatMessage","onProgress","handleProgress","handleImagePreview","handleFormButtonClick","handleCancelUpload","handleQuoteClick","formatterContext","resp","act","onFormResponse","MESSAGE_STATUS_FAILED","userIsWriter","MESSAGE_STATUS_DEL_RANGE","ratio","replyToSeq","sideClass","sequence","response","bubbleClass","fullDisplay","attachments","att","tree","innerRef","IntlChatMessage","messaging_disabled","type_new_message","add_image_caption","file_attachment_too_large","cannot_initiate_upload","SendMessage","quote","keypressTimestamp","Date","getTime","handlePasteEvent","handleAttachImage","handleAttachFile","handleSend","handleKeyPress","handleMessageTyping","formatReply","messageEditArea","disabled","onImageSuccess","onAttachmentSuccess","clipboardData","originalEvent","kind","file","getAsFile","filePasted","onAttachImage","onAttachFile","acceptBlank","noInput","onSendMessage","shiftKey","onKeyPress","messagePrompt","onCancelReply","attachImage","click","attachFile","display","DocPreview","handleSendDoc","caption","onClose","mimeToIcon","default","video","iconFromMime","GroupSubs","usersOnline","totalCount","countToShow","some","overflow","ImagePreview","handleSendImage","assignWidth","node","maxlength","Invitation","handleButtonAction","LogoView","version","serverVersion","serverAddress","MetaMessage","date","HashNavigation","parts","path","forEach","part","decodeURIComponent","location","hasOwnProperty","parsed","parseUrlHash","composeUrlHash","sidepanel","infopanel","online_now","last_seen","not_found","invalid_content","isUnconfirmed","ex","getExcessive","isPeerRestricted","ms","getMissing","MessagesView","getDerivedStateFromProps","leave","sendMessage","retrySend","sendImageAttachment","sendFileAttachment","sendKeyPress","subscribe","handleScrollReference","handleScrollEvent","handleDescChange","handleSubsUpdated","handleMessageUpdate","handleAllMessagesReceived","handleInfoReceipt","handleImagePostview","handleClosePreview","handleFormResponse","handleShowMessageContextMenu","handleNewChatAcceptance","handleEnablePeer","postReadNotification","clearNotificationQueue","goToLatestMessage","handlePickReply","handleCancelReply","chatMessageRefs","getOrCreateMessageRef","readNotificationQueue","readNotificationTimer","seqId","messagesScroller","messageCount","scrollPosition","scrollTop","scrollHeight","offsetHeight","onData","onAllMessagesReceived","onInfo","onPres","applicationVisible","ready","nextState","forwardMessage","docPreview","imagePreview","imagePostview","typingIndicator","fetchingMessages","showGoToLastButton","onlineSubs","peer","p2pPeerDesc","peerMessagingDisabled","latestClearId","maxClearId","isReader","readingBlocked","unconformed","unconfirmed","isSubscribed","newTopic","newTopicParams","_topicName","getQuery","withLaterDesc","withLaterSub","withLaterData","withLaterDel","setQuery","code","onNewTopicCreated","queuedMessages","pub","_sending","navigateTo","setUrlTopic","blankState","oldTopicName","isTopicCached","oldTopic","pos","msgHasMoreMessages","getMessagesPage","setInterval","clearInterval","n","sendAt","noteRead","setMilliseconds","getMilliseconds","clearTimeout","keyPressTimer","isNewMessage","from","what","forceUpdate","attachJSON","parse","searchParams","set","open","messageSpecificMenuItems","onNewChat","noteKeyPress","uploadCompletionPromise","append","sanitizeEntities","maxInbandAttachmentSize","urlPromise","fileToBase64","maxExternAttachmentSize","limit","insertImage","_tempPreview","appendLineBreak","found","findMessage","_cancelled","senderId","senderName","init","isValid","replyContent","wrapInto","onCancelForwardMessage","scrollIntoView","behavior","classList","add","remove","component","hideSelf","component2","messageNodes","previousFrom","chatBoxClass","dateFmt","Intl","DateTimeFormat","prev","next","nextFrom","thisFrom","isReply","userDesc","parseInt","isNaN","ts","toDateString","format","hi","isChan","_uploader","lastSeen","cont","getMeTopic","getContact","seen","navigator","userLanguage","language","getFullYear","getMonth","getDate","hour12","hour","minute","toLocaleDateString","month","day","year","shortDateFormat","when","onHideMessagesView","ButtonBack","onBack","MenuContacts","onNewTopic","onSettings","MenuStart","onSignUp","SideNavbar","AccountSettingsView","me","archived_contacts_title","ContactsView","handleAction","unreadThreads","archivedCount","chatList","blocked","arch","touched","action_ignored","onShowArchive","LocalStorageUtil","localStorage","setItem","JSON","stringify","getItem","oldVal","getObject","setObject","removeItem","CreateAccountView","login","password","email","imageDataUrl","errorCleared","saveToken","handleLoginChange","handlePasswordChange","handleEmailChange","handleFnChange","handleToggleSaveToken","onCreateAccount","submitClasses","login_prompt","password_prompt","onImageChanged","full_name_prompt","email_prompt","stay_logged_in","EditAccountView","handleCheckboxClick","onToggleMessageSounds","onTogglePushNotifications","onToggleIncognitoMode","messageSounds","desktopAlertsEnabled","desktopAlerts","incognitoMode","delete_account","delete_account_warning","AccSecurityView","blockedCount","showPermissionEditorFor","handlePasswordUpdate","handleHidePermissionsEditor","handleDeleteAccount","pwd","onUpdatePassword","onUpdateAccountDesc","onDeleteAccount","password_unchanged","onLogout","onShowBlocked","AccSupportView","LoginView","hostName","persist","onPersistenceChange","onLogin","autoCorrect","autoCapitalize","invalid_id","NewTopicById","groupId","prefix","NewTopicGroup","handleFieldEdit","handleTagsChanged","handleChannelToggle","search_for_contacts","search_no_results","NewTopicView","tabSelected","searchQuery","handleTabClick","handleNewGroupSubmit","handleGroupByID","addUrlParam","removeUrlParam","onCreateTopic","dataUrl","priv","no_contacts_placeholder","PasswordResetView","sent","token","scheme","onReset","onRequest","HostSelector","changed","handleHostNameChange","onServerAddressChange","hostOptions","list","SettingsView","transport","handleTransportSelected","handleServerAddressChange","onUpdate","def","ws","lp","transportOptions","phone","ValidationView","credCode","credMethod","numbers_only","register","settings","edit","notif","support","newtpk","reset","SidepanelView","handleNewTopic","errorAction","errorActionText","loadSpinnerVisible","loginDisabled","onLoginRequest","onGlobalSettings","onUpdateAccountTags","onValidateCredentials","onPasswordResetRequest","onResetPassword","detectServerAddress","host","protocol","hostname","port","isSecureConnection","POP_SOUND","Audio","reconnect_countdown","reconnect_now","push_init_failed","invalid_security_token","no_connection","code_doesnot_match","menu_item_info","TinodeWeb","getBlankState","handleResize","handleHashRoute","handleOnline","checkForAppUpdate","handleVisibilityEvent","handleError","handleLoginRequest","handlePersistenceChange","handleConnected","handleAutoreconnectIteration","doLogin","handleCredentialsRequest","handleLoginSuccessful","handleDisconnect","tnMeMetaDesc","tnMeContactUpdate","tnMeSubsUpdated","resetContactList","tnData","tnInitFind","tnFndSubsUpdated","handleTopicSelected","handleHideMessagesView","handleSendMessage","handleNewChatInvitation","handleNewAccount","handleNewAccountRequest","handleUpdatePasswordRequest","handleUpdateAccountTagsRequest","handleToggleIncognitoMode","handleSettings","handleGlobalSettings","handleShowArchive","handleShowBlocked","handleToggleMessageSounds","handleCredAdd","handleCredDelete","handleCredConfirm","initFCMessaging","togglePushToken","requestPushToken","handlePushMessage","handleSidepanelCancel","handleStartTopicRequest","handleNewTopicCreated","handleTopicUpdateRequest","handleChangePermissions","handleTagsUpdateRequest","handleLogout","handleDeleteTopicRequest","handleDeleteMessagesRequest","handleLeaveUnsubRequest","handleBlockTopicRequest","handleReportTopic","handleShowContextMenu","defaultTopicContextMenu","handleHideContextMenu","handleShowAlert","handleShowInfoView","handleValidateCredentialsRequest","handlePasswordResetRequest","handleResetPassword","handleContextMenuAction","handleShowForwardDialog","handleHideForwardDialog","sendMessageToTopic","autoLogin","messageSoundsOff","FIREBASE_INIT","firebaseToken","hidden","sidePanelSelected","sidePanelTitle","sidePanelAvatar","myTrustedBadges","liveConnection","onLine","topicSelectedOnline","topicSelectedAcs","innerWidth","infoPanel","mobilePanel","contextMenuVisible","contextMenuBounds","contextMenuClickAt","contextMenuParams","contextMenuItems","forwardDialogVisible","alertVisible","alertParams","requestedTopic","BroadcastChannel","documentElement","clientWidth","viewportHeight","clientHeight","tnSetup","onConnect","onDisconnect","onAutoreconnectIteration","setDeviceToken","parsedNav","expires","setAuthToken","connect","readTimer","readTimerCallback","persistentCache","onSetupCompleted","appName","apiKey","secure","setHumanLanguage","enableLogging","oobNotification","fbPush","usePublicVapidKey","messagingVapidKey","serviceWorker","reg","useServiceWorker","postMessage","onTokenRefresh","onMessage","payload","mobile","onupdatefound","installingWorker","installing","onstatechange","controller","getTopicAccessMode","newTopicTabSelected","tab","reconnectCountdown","reconnect","isConnected","initStorage","clearStorage","getServerInfo","ver","sec","prom","timeLeft","seconds","secondsToTime","isAuthenticated","getAuthToken","loginBasic","loginToken","goToTopic","onContactUpdate","getCurrentUserID","withDesc","withCred","disconnect","urlHash","setUrlSidePanel","isPresencer","play","unused","foundContacts","merged","updated","past","prepareSearchableContacts","receivedTimer","noteRecv","fnd","getFndTopic","withSub","s","setMeta","isTopicOnline","isP2PType","am","updateWant","login_","password_","public_","cred_","tags_","createAccountBasic","on","enabled","updateObject","requestPermission","deleteToken","getToken","refreshedToken","delCredential","basicNavigator","infoNavigator","setUrlInfoPanel","newGroupTopicName","oldName","newName","updateAccountBasic","getCurrentLogin","updateGiven","cleared","Promose","delCurrentUser","publish","header","msgPreview","self_blocked","subscribed","keepForwardedMessage","confirmText","rejectText","invite","requestResetAuthSecret","newPassword","btoa","base64ReEncode","myUserName","onAddMember","handleManageGroupMembers","hl","languages","baseLanguage","htmlLang","allMessages","langauge","setAttribute","IntlProvider","textComponent","__webpack_require__","module","getter","__esModule","d","exports","definition","o","defineProperty","enumerable","get","obj","prop","prototype","__webpack_modules__"],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"file":"index.prod.js","mappings":"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","sources":["webpack://tinode-webapp/webpack/bootstrap","webpack://tinode-webapp/webpack/runtime/compat get default export","webpack://tinode-webapp/webpack/runtime/define property getters","webpack://tinode-webapp/webpack/runtime/hasOwnProperty shorthand","webpack://tinode-webapp/external var \"React\"","webpack://tinode-webapp/external var \"ReactDOM\"","webpack://tinode-webapp/external var \"ReactIntl\"","webpack://tinode-webapp/external var \"firebase\"","webpack://tinode-webapp/external var [\"firebase\",\"messaging\"]","webpack://tinode-webapp/external var \"Tinode\"","webpack://tinode-webapp/./src/widgets/alert.jsx","webpack://tinode-webapp/./src/version.js","webpack://tinode-webapp/./src/config.js","webpack://tinode-webapp/./src/widgets/context-menu.jsx","webpack://tinode-webapp/./src/widgets/contact-badges.jsx","webpack://tinode-webapp/./src/lib/strformat.js","webpack://tinode-webapp/./src/lib/utils.js","webpack://tinode-webapp/./src/widgets/letter-tile.jsx","webpack://tinode-webapp/./src/widgets/unread-badge.jsx","webpack://tinode-webapp/./src/widgets/lazy-image.jsx","webpack://tinode-webapp/./src/widgets/file-progress.jsx","webpack://tinode-webapp/./src/widgets/uploading-image.jsx","webpack://tinode-webapp/./src/lib/blob-helpers.js","webpack://tinode-webapp/./src/lib/formatters.js","webpack://tinode-webapp/./src/widgets/contact.jsx","webpack://tinode-webapp/./src/widgets/contact-action.jsx","webpack://tinode-webapp/./src/widgets/contact-list.jsx","webpack://tinode-webapp/./src/widgets/search-contacts.jsx","webpack://tinode-webapp/./src/widgets/forward-dialog.jsx","webpack://tinode-webapp/./src/widgets/load-spinner.jsx","webpack://tinode-webapp/./src/widgets/avatar-upload.jsx","webpack://tinode-webapp/./src/widgets/badge-list.jsx","webpack://tinode-webapp/./src/widgets/checkbox.jsx","webpack://tinode-webapp/./src/widgets/menu-cancel.jsx","webpack://tinode-webapp/./src/widgets/error-panel.jsx","webpack://tinode-webapp/./src/widgets/chip.jsx","webpack://tinode-webapp/./src/widgets/chip-input.jsx","webpack://tinode-webapp/./src/widgets/group-manager.jsx","webpack://tinode-webapp/./src/widgets/permissions-editor.jsx","webpack://tinode-webapp/./src/widgets/cropper.jsx","webpack://tinode-webapp/./src/widgets/avatar-crop.jsx","webpack://tinode-webapp/./src/widgets/visible-password.jsx","webpack://tinode-webapp/./src/widgets/in-place-edit.jsx","webpack://tinode-webapp/./src/widgets/tag-manager.jsx","webpack://tinode-webapp/./src/widgets/topic-desc-edit.jsx","webpack://tinode-webapp/./src/widgets/topic-common.jsx","webpack://tinode-webapp/./src/widgets/topic-security.jsx","webpack://tinode-webapp/./src/views/info-view.jsx","webpack://tinode-webapp/./src/widgets/attachment.jsx","webpack://tinode-webapp/./src/widgets/received-marker.jsx","webpack://tinode-webapp/./src/widgets/chat-message.jsx","webpack://tinode-webapp/./src/widgets/send-message.jsx","webpack://tinode-webapp/./src/widgets/doc-preview.jsx","webpack://tinode-webapp/./src/widgets/group-subs.jsx","webpack://tinode-webapp/./src/widgets/image-preview.jsx","webpack://tinode-webapp/./src/widgets/invitation.jsx","webpack://tinode-webapp/./src/views/logo-view.jsx","webpack://tinode-webapp/./src/widgets/meta-message.jsx","webpack://tinode-webapp/./src/lib/navigation.js","webpack://tinode-webapp/./src/views/messages-view.jsx","webpack://tinode-webapp/./src/widgets/button-back.jsx","webpack://tinode-webapp/./src/widgets/menu-contacts.jsx","webpack://tinode-webapp/./src/widgets/menu-start.jsx","webpack://tinode-webapp/./src/widgets/side-navbar.jsx","webpack://tinode-webapp/./src/views/account-settings-view.jsx","webpack://tinode-webapp/./src/views/contacts-view.jsx","webpack://tinode-webapp/./src/lib/local-storage.js","webpack://tinode-webapp/./src/views/create-account-view.jsx","webpack://tinode-webapp/./src/views/acc-notifications-view.jsx","webpack://tinode-webapp/./src/views/acc-security-view.jsx","webpack://tinode-webapp/./src/views/acc-support-view.jsx","webpack://tinode-webapp/./src/views/login-view.jsx","webpack://tinode-webapp/./src/widgets/new-topic-by-id.jsx","webpack://tinode-webapp/./src/widgets/new-topic-group.jsx","webpack://tinode-webapp/./src/views/new-topic-view.jsx","webpack://tinode-webapp/./src/views/password-reset-view.jsx","webpack://tinode-webapp/./src/widgets/host-selector.jsx","webpack://tinode-webapp/./src/views/settings-view.jsx","webpack://tinode-webapp/./src/views/validation-view.jsx","webpack://tinode-webapp/./src/views/sidepanel-view.jsx","webpack://tinode-webapp/./src/lib/host-name.js","webpack://tinode-webapp/./src/views/tinode-web.jsx","webpack://tinode-webapp/./src/index.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = React;","const __WEBPACK_NAMESPACE_OBJECT__ = ReactDOM;","const __WEBPACK_NAMESPACE_OBJECT__ = ReactIntl;","const __WEBPACK_NAMESPACE_OBJECT__ = firebase;","const __WEBPACK_NAMESPACE_OBJECT__ = firebase.messaging;","const __WEBPACK_NAMESPACE_OBJECT__ = Tinode;","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class Alert extends React.PureComponent {\n render() {\n return this.props.visible ?\n \n
\n
{this.props.title}
\n
{this.props.content}
\n
\n {this.props.onReject ?\n \n :\n null\n }\n \n
\n
\n
\n :\n null;\n }\n};\n","// This is a generated file. Don't edit.\n\nexport const PACKAGE_VERSION = \"0.18.2-beta1\";\n","import { PACKAGE_VERSION } from './version.js';\n\n// Name of this application, used in the User-Agent.\nexport const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.17');\n\n// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own\nexport const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';\n\n// The array of possible hosts to connect to.\nexport const KNOWN_HOSTS = {hosted: 'web.tinode.co', local: 'localhost:6060'};\n\n// Default host name and port to connect to.\nexport const DEFAULT_HOST = KNOWN_HOSTS.hosted;\n\n// Enable console logging.\nexport const LOGGING_ENABLED = true;\n\n// Minimum time between two keypress notifications, milliseconds.\nexport const KEYPRESS_DELAY = 3*1000;\n// Delay before sending a {note} for reciving a message, milliseconds.\nexport const RECEIVED_DELAY = 500;\n// Delay before sending a read notification, milliseconds.\nexport const READ_DELAY = 1000;\n\n// The default shortest allowed tag length. Matches the value on the server.\nexport const MIN_TAG_LENGTH = 2;\n// The default greatest allowed tag length. Matches the value on the server.\nexport const MAX_TAG_LENGTH = 96;\n// The default maximum number of tags allowed. Matches the value on the server.\nexport const MAX_TAG_COUNT = 16;\n\n// Access mode for P2P subscriptions initiated by the current user.\nexport const DEFAULT_P2P_ACCESS_MODE = 'JRWPS';\n// Access mode for new group topics created by the current user.\nexport const NEW_GRP_ACCESS_MODE = 'JRWPSAO';\n// Access mode for a channel.\nexport const CHANNEL_ACCESS_MODE = 'JR';\n\n// Access mode for no access.\nexport const NO_ACCESS_MODE = 'N';\n\n// Mediaquery breakpoint between desktop and mobile, in px. Should match the value\n// in @media (max-size: 640px) in base.css\nexport const MEDIA_BREAKPOINT = 640;\n// Size of css 'rem' unit in pixels. Default 1rem = 10pt = 13px.\nexport const REM_SIZE = 13;\n\n// Size of the avatar image: when image dimensions are greater or the image is not square, it's resized to\n// a square of this size or less.\nexport const AVATAR_SIZE = 384;\n\n// Maximum size of an avatar in bytes for sending in-band. Bigger avatars will be sent out of band (as uploads).\nexport const MAX_AVATAR_BYTES = 4096;\n\n// Size of the broken_image shown in MessagesView\nexport const BROKEN_IMAGE_SIZE = 32;\n\n// Number of chat messages to fetch in one call.\nexport const MESSAGES_PAGE = 24;\n\n// Default maximum in-band (included directly into the message) attachment size which fits into\n// a message of 256K in size. Used when the server-provided value is unavailable. The actual\n// binary size of the attachment should be smaller due to base64 encoding expansion and some overhead,\n// for instance 1024 bytes.\n// Increase this limit to a greater value in production, if desired. Also increase\n// max_message_size in the server config.\nexport const MAX_INBAND_ATTACHMENT_SIZE = 262144;\n\n// Default absolute maximum attachment size to be used with the server = 8MB.\n// Used when the server-provided value is unavailable. Increase to something like 100MB in production.\nexport const MAX_EXTERN_ATTACHMENT_SIZE = 1 << 23;\n\n// Maximum allowed linear dimension (pixels) of an image sent inline. Larger images will be shrunk\n// to make the larger dimension fit under this size. You may want to adjust it to 1600 or\n// 2400 for production.\nexport const MAX_IMAGE_DIM = 1024;\n\n// Linear dimensions of image preview: shrink image under this size for previews.\nexport const IMAGE_PREVIEW_DIM = 64;\n\n// Linear dimensions of image thumbnail: shrink image under this size for thumbnails in reply quote previews.\nexport const IMAGE_THUMBNAIL_DIM = 36;\n\n// Maximum number of online users to be shown in a topic title bar. Others will be\n// hidden under \"+X more\"\nexport const MAX_ONLINE_IN_TOPIC = 4;\n\n// Maximum length of user name, topic title, and private comment.\nexport const MAX_TITLE_LENGTH = 60;\n// Maximum length of topic description.\nexport const MAX_TOPIC_DESCRIPTION_LENGTH = 360;\n\n// Length of message previews in chat list, in characters.\nexport const MESSAGE_PREVIEW_LENGTH = 80;\n\n// Length of a quote in quoted reply (for outgoing messages).\nexport const QUOTED_REPLY_LENGTH = 30;\n// Length of a preview of a forwarded message.\nexport const FORWARDED_PREVIEW_LENGTH = 84;\n\n// Link for \"Contact Us\".\nexport const LINK_CONTACT_US = 'email:support@tinode.co';\n\n// Link to Privacy Policy.\nexport const LINK_PRIVACY_POLICY = 'https://tinode.co/privacy.html';\n\n// Link to Terms of Service.\nexport const LINK_TERMS_OF_SERVICE = 'https://tinode.co/terms.html';\n","// Context Menu: popup/dropdown menu.\nimport React from 'react';\nimport { injectIntl, defineMessages } from 'react-intl';\n\nimport { REM_SIZE } from '../config.js';\n\nconst messages = defineMessages({\n info: {\n id: 'menu_item_info',\n defaultMessage: 'Info',\n description: 'Show extended topic information'\n },\n clear_messages: {\n id: 'menu_item_clear_messages',\n defaultMessage: 'Clear messages',\n description: 'Delete all messages'\n },\n clear_for_all: {\n id: 'menu_item_clear_messages_for_all',\n defaultMessage: 'Clear for All',\n description: 'Delete all message(s) for all members'\n },\n delete: {\n id: 'menu_item_delete',\n defaultMessage: 'Delete',\n description: 'Delete selected messages'\n },\n delete_for_all: {\n id: 'menu_item_delete_for_all',\n defaultMessage: 'Delete for All',\n description: 'Delete selected message(s) for all members'\n },\n send_retry: {\n id: 'menu_item_send_retry',\n defaultMessage: 'Retry',\n description: 'Retry sending message'\n },\n mute: {\n id: 'menu_item_mute',\n defaultMessage: 'Mute',\n description: 'Turn off notifications'\n },\n unmute: {\n id: 'menu_item_unmute',\n defaultMessage: 'Unmute',\n description: 'Turn notifications on'\n },\n reply: {\n id: 'menu_item_reply',\n defaultMessage: 'Reply',\n description: 'Reply to message'\n },\n forward: {\n id: 'menu_item_forward',\n defaultMessage: 'Forward',\n description: 'Forward message'\n },\n topic_delete: {\n id: 'menu_item_delete_topic',\n defaultMessage: 'Delete',\n description: 'Delete entire topic'\n },\n topic_delete_warning: {\n id: 'topic_delete_warning',\n defaultMessage: 'Are you sure you want to delete this conversation? It cannot be undone.',\n description: 'Alert warning when deleting entire topic'\n },\n delete_messages_warning: {\n id: 'delete_messages_warning',\n defaultMessage: 'Are you sure you want to delete all messages for everyone? It cannot be undone.',\n description: 'Alert dialog warning when hard-deleting all messages.'\n },\n unblock: {\n id: 'menu_item_unblock',\n defaultMessage: 'Unblock',\n description: 'Unblock topic or user'\n },\n // Generic string suitable for either topic or user.\n block: {\n id: 'menu_item_block',\n defaultMessage: 'Block',\n description: 'Block topic or user'\n },\n topic_block_warning: {\n id: 'topic_block_warning',\n defaultMessage: 'Are you sure you want to block this conversation?',\n description: 'Alert warning when blocking a topic.'\n },\n member_delete: {\n id: 'menu_item_member_delete',\n defaultMessage: 'Remove',\n description: 'Remove user from topic'\n },\n archive: {\n id: 'menu_item_archive_topic',\n defaultMessage: 'Archive',\n description: 'Move topic from the list of active chats to archive'\n },\n unarchive: {\n id: 'menu_item_restore_topic',\n defaultMessage: 'Restore',\n description: 'Restore topic from archive'\n },\n edit_permissions: {\n id: 'menu_item_edit_permissions',\n defaultMessage: 'Edit permissions',\n description: 'Menu item [Edit permissions]'\n },\n clear_messages_warning: {\n id: 'clear_messages_warning',\n defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',\n description: 'Alert dialog warning when deleting all messages.'\n },\n});\n\nclass ContextMenu extends React.Component {\n constructor(props) {\n super(props);\n\n this.selfRef = React.createRef();\n\n const {formatMessage} = props.intl;\n\n this.handlePageClick = this.handlePageClick.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this.handleClick = this.handleClick.bind(this);\n\n // Preconfigured menu items.\n this.MenuItems = {\n 'topic_info': {\n id: 'topic_info',\n title: formatMessage(messages.info),\n handler: null\n },\n 'messages_clear': {\n id: 'messages_clear',\n title: formatMessage(messages.clear_messages),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage(messages.clear_messages), // title\n formatMessage(messages.clear_messages_warning), // content\n (() => { this.deleteMessages(true, false, params, errorHandler); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'messages_clear_hard': {\n id: 'messages_clear_hard',\n title: formatMessage(messages.clear_for_all),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage(messages.clear_for_all), // title\n formatMessage(message.delete_messages_warning), // content\n (() => { return this.deleteMessages(true, true, params, errorHandler); }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'message_delete': {\n id: 'message_delete',\n title: formatMessage(messages.delete),\n handler: (params, errorHandler) => {\n return this.deleteMessages(false, false, params, errorHandler);\n }\n },\n 'message_delete_hard': {\n id: 'message_delete_hard',\n title: formatMessage(messages.delete_for_all),\n handler: (params, errorHandler) => {\n return this.deleteMessages(false, true, params, errorHandler);\n }\n },\n 'menu_item_send_retry': {\n id: 'menu_item_send_retry',\n title: formatMessage(messages.send_retry),\n handler: (params, errorHandler) => {\n return this.retryMessage(params, errorHandler);\n }\n },\n 'menu_item_reply': {\n id: 'menu_item_reply',\n title: formatMessage(messages.reply),\n handler: (params, errorHandler) => {\n return this.replyToMessage(params, errorHandler);\n }\n },\n 'menu_item_forward': {\n id: 'menu_item_forward',\n title: formatMessage(messages.forward),\n handler: () => {} /* the action is taken directly in tinode-web */\n },\n 'topic_unmute': {\n id: 'topic_unmute',\n title: formatMessage(messages.unmute),\n handler: this.topicPermissionSetter.bind(this, '+P')\n },\n 'topic_mute': {\n id: 'topic_mute',\n title: formatMessage(messages.mute),\n handler: this.topicPermissionSetter.bind(this, '-P')\n },\n 'topic_unblock': {\n id: 'topic_unblock',\n title: formatMessage(messages.unblock),\n handler: this.topicPermissionSetter.bind(this, '+JP')\n },\n 'topic_block': {\n id: 'topic_block',\n title: formatMessage(messages.block),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage(messages.block), // title\n formatMessage(messages.topic_block_warning), // content\n (() => {\n return this.topicPermissionSetter('-JP', params, errorHandler).then((ctrl) => {\n this.props.onTopicRemoved(params.topicName);\n return ctrl;\n });\n }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'topic_delete': {\n id: 'topic_delete',\n title: formatMessage(messages.topic_delete),\n handler: (params, errorHandler) => {\n return props.onShowAlert(\n formatMessage(messages.topic_delete), // title\n formatMessage(messages.topic_delete_warning), // content\n (() => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n return topic.delTopic(true).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }),\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n },\n 'topic_archive': {\n id: 'topic_archive',\n title: formatMessage(messages.archive),\n handler: (params, errorHandler) => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n return topic.archive(true).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n },\n 'topic_restore': {\n id: 'topic_restore',\n title: formatMessage(messages.unarchive),\n handler: (params, errorHandler) => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n return topic.archive(false).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n },\n // menu_item_edit_permissions is defined elsewhere.\n 'permissions': {\n id: 'permissions',\n title: formatMessage(messages.edit_permissions),\n handler: null\n },\n 'member_delete': {\n id: 'member_delete',\n title: formatMessage(messages.member_delete),\n handler: (params, errorHandler) => {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic || !params.user) {\n console.log(\"Topic or user not found: '\" + params.topicName + \"', '\" + params.user + \"'\");\n return;\n }\n return topic.delSubscription(params.user).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n },\n 'member_mute': {\n id: 'member_mute',\n title: formatMessage(messages.mute),\n handler: this.topicPermissionSetter.bind(this, '-P')\n },\n 'member_unmute': {\n id: 'member_unmute',\n title: formatMessage(messages.unmute),\n handler: this.topicPermissionSetter.bind(this, '+P')\n },\n 'member_block': {\n id: 'member_block',\n title: formatMessage(messages.block),\n handler: this.topicPermissionSetter.bind(this, '-JP')\n },\n 'member_unblock': {\n id: 'member_unblock',\n title: formatMessage(messages.unblock),\n handler: this.topicPermissionSetter.bind(this, '+JP')\n },\n };\n }\n\n componentDidMount() {\n document.addEventListener('mousedown', this.handlePageClick, false);\n document.addEventListener('keyup', this.handleEscapeKey, false);\n }\n\n componentWillUnmount() {\n document.removeEventListener('mousedown', this.handlePageClick, false);\n document.removeEventListener('keyup', this.handleEscapeKey, false);\n }\n\n handlePageClick(e) {\n if (this.selfRef.current.contains(e.target)) {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n this.props.hide();\n }\n\n handleEscapeKey(e) {\n if (e.keyCode === 27) {\n this.props.hide();\n }\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.hide();\n let item = this.props.items[e.currentTarget.dataset.id];\n if (typeof item == 'string') {\n item = this.MenuItems[item];\n }\n\n if (!item) {\n console.error(\"Invalid menu item ID\", e.currentTarget.dataset.id);\n } else {\n this.props.onAction(\n item.id,\n item.handler(this.props.params, this.props.onError),\n this.props.params);\n }\n }\n\n // Menu Actions\n\n deleteMessages(all, hard, params, errorHandler) {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found: \", params.topicName);\n return;\n }\n\n // We don't know if the message is still pending (e.g. attachment is being uploaded),\n // so try cancelling first. No harm if we can't cancel.\n // The message can be cancelled if transmission to the server has not\n // started yet or if the message send has failed.\n if (!all && topic.cancelSend(params.seq)) {\n return;\n }\n // Can't cancel. Delete instead.\n const promise = all ?\n topic.delMessagesAll(hard) :\n topic.delMessagesList([params.seq], hard);\n\n return promise.catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n\n // Retries sending failed message.\n retryMessage(params, errorHandler) {\n const topic = this.props.tinode.getTopic(params.topicName);\n // Remove the existing message entry.\n if (!topic || !topic.flushMessage(params.seq)) {\n return;\n }\n const msg = topic.createMessage(params.content, false);\n return topic.publishDraft(msg).catch((err) => {\n if (errorHandler) {\n errorHandler(err.message, 'err');\n }\n });\n }\n\n // Function is used by context menu to set permissions.\n topicPermissionSetter(mode, params, errorHandler) {\n const topic = this.props.tinode.getTopic(params.topicName);\n if (!topic) {\n console.log(\"Topic not found\", params.topicName);\n return;\n }\n\n let result = topic.updateMode(params.user, mode);\n if (errorHandler) {\n result = result.catch((err) => {\n errorHandler(err.message, 'err');\n });\n }\n return result;\n }\n\n replyToMessage(params, errorHandler) {\n params.pickReply(params.seq, params.content, params.userFrom, params.userName, errorHandler);\n }\n\n render() {\n const menu = [];\n let count = 0;\n this.props.items.map((item) => {\n if (typeof item == 'string') {\n item = this.MenuItems[item];\n }\n if (item && item.title) {\n menu.push(\n item.title == '-' ?\n \n :\n {item.title}\n );\n }\n count++;\n });\n\n // Ensure that menu is inside the app-container.\n const hSize = 12 * REM_SIZE;\n const vSize = REM_SIZE * (0.7 + menu.length * 2.5);\n const left = (this.props.bounds.right - this.props.clickAt.x < hSize) ?\n (this.props.clickAt.x - this.props.bounds.left - hSize) :\n (this.props.clickAt.x - this.props.bounds.left);\n const top = (this.props.bounds.bottom - this.props.clickAt.y < vSize) ?\n (this.props.clickAt.y - this.props.bounds.top - vSize) :\n (this.props.clickAt.y - this.props.bounds.top);\n\n const position = {\n left: left + 'px',\n top: top + 'px'\n };\n\n return (\n \n );\n }\n}\n\nexport default injectIntl(ContextMenu);\n","import React from 'react';\n\nconst icon_mapping = {'muted': 'notifications_off', 'banned': 'block', 'staff': 'verified_user'};\n\n/* Contact's labels: [you], or icons [muted], [blocked] */\nexport default class ContactBadges extends React.PureComponent {\n render() {\n let badges = null;\n if (this.props.badges && this.props.badges.length > 0) {\n badges = [];\n this.props.badges.map((b) => {\n const color = (b.color ? ' ' + b.color : '');\n if (b.icon) {\n badges.push({icon_mapping[b.icon] || b.icon});\n } else {\n badges.push({b.name});\n }\n });\n return <>{badges}>;\n }\n return null;\n }\n};\n","// Short representation of time in the past.\nexport function shortDateFormat(then, locale) {\n locale = locale || window.navigator.userLanguage || window.navigator.language;\n const now = new Date();\n if (then.getFullYear() == now.getFullYear()) {\n // Same year.\n if (then.getMonth() == now.getMonth() && then.getDate() == now.getDate()) {\n // Same month and day, show time only.\n return then.toLocaleTimeString(locale, {hour12: false, hour: '2-digit', minute: '2-digit'});\n }\n // Different month and/or day, show month day, time.\n return then.toLocaleDateString(locale,\n {hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});\n }\n // Different year: just show the date.\n return then.toLocaleDateString(locale, {year: 'numeric', month: 'short', day: 'numeric'});\n}\n\n// Convert seconds to minutes:seconds, i.e. 156 sec -> 2:36.\nexport function secondsToTime(seconds) {\n const min = Math.floor(seconds / 60);\n let sec = seconds % 60;\n sec = sec < 10 ? `0${sec}` : sec;\n return `${min}:${sec}`;\n}\n\n// Convert a number of bytes to human-readable format.\nexport function bytesToHumanSize(bytes) {\n if (!bytes || bytes == 0) {\n return '0 Bytes';\n }\n\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];\n const bucket = Math.min(Math.floor(Math.log2(bytes) / 10) | 0, sizes.length-1);\n const count = bytes / Math.pow(1024, bucket);\n const round = bucket > 0 ? (count < 3 ? 2 : (count < 30 ? 1 : 0)) : 0;\n return count.toFixed(round) + ' ' + sizes[bucket];\n}\n\n// Shorten a file name to be under maxLength by clipping out the middle.\nexport function shortenFileName(filename, maxLength) {\n if (typeof filename != 'string') {\n return filename;\n }\n return filename.length > maxLength ?\n filename.slice(0, maxLength/2 - 1) + '…' + filename.slice(1 - maxLength/2) : filename;\n}\n\n// Get 32 bit integer hash value for a string. Ideally it should produce the same value\n// as Java's String#hash().\nfunction stringToColorHash(value) {\n let hash = 0;\n value = '' + value;\n for (let i = 0; i < value.length; i++) {\n hash = ((hash << 5) - hash) + value.charCodeAt(i);\n hash = hash & hash; // Convert to 32bit integer\n }\n return hash;\n}\n\n// User avatar letter color id.\nexport function letterTileColorId(userId) {\n return Math.abs(stringToColorHash(userId)) % 16;\n}\n\n// Converts user or topic ID to a CSS color class. Ideally it should produce the same color value as Java version.\n// params:\n// - {string} id: user or topic ID\n// - {boolean} light: light or dark version of the color.\n// - {boolean} fg: foreground (text) or background color.\nexport function idToColorClass(id, light, fg) {\n return (light ? 'lt-' : 'dk-') + (fg ? 'fg-' : 'bg-') + letterTileColorId(id);\n}\n","// Odds and ends\n\nimport Tinode from 'tinode-sdk';\n\n// Make shortcut icon appear with a green dot + show unread count in title.\nexport function updateFavicon(count) {\n const oldIcon = document.getElementById('shortcut-icon');\n const head = document.head || document.getElementsByTagName('head')[0];\n const newIcon = document.createElement('link');\n newIcon.type = 'image/png';\n newIcon.id = 'shortcut-icon';\n newIcon.rel = 'shortcut icon';\n newIcon.href = 'img/logo32x32' + (count > 0 ? 'a' : '') + '.png';\n if (oldIcon) {\n head.removeChild(oldIcon);\n }\n head.appendChild(newIcon);\n\n document.title = (count > 0 ? '('+count+') ' : '') + 'Tinode';\n}\n\n// Create theCard which represents user's or topic's \"public\" info.\nexport function theCard(fn, imageUrl, imageMimeType, note) {\n let card = null;\n fn = fn && fn.trim();\n note = note && note.trim();\n\n if (fn) {\n card = {\n fn: fn\n };\n }\n\n if (typeof note == 'string') {\n card = card || {};\n card.note = note ? note : Tinode.DEL_CHAR;\n }\n\n if (imageUrl) {\n card = card || {};\n let mimeType = imageMimeType;\n // Is this a data URL \"data:[][;base64],\"?\n const matches = /^data:(image\\/[-a-z0-9+.]+)?(;base64)?,/i.exec(imageUrl);\n if (matches) {\n mimeType = matches[1];\n card.photo = {\n data: imageUrl.substring(imageUrl.indexOf(',') + 1),\n ref: Tinode.DEL_CHAR\n };\n } else {\n card.photo = {\n data: Tinode.DEL_CHAR,\n ref: imageUrl\n };\n }\n card.photo.type = (mimeType || 'image/jpeg').substring('image/'.length);\n }\n\n return card;\n}\n\n// Deep-shallow compare two arrays: shallow compare each element.\nexport function arrayEqual(a, b) {\n if (a === b) {\n return true;\n }\n\n if (!Array.isArray(a) || !Array.isArray(b)) {\n return false;\n }\n\n // Compare lengths first.\n if (a.length != b.length) {\n return false;\n }\n // Order of elements is ignored.\n a.sort();\n b.sort();\n for (let i = 0, l = a.length; i < l; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n// Checks (loosely) if the given string is a phone. If so, returns the phone number in a format\n// as close to E.164 as possible.\nexport function asPhone(val) {\n val = val.trim();\n if (/^(?:\\+?(\\d{1,3}))?[- (.]*(\\d{3})[- ).]*(\\d{3})[- .]*(\\d{2})[- .]*(\\d{2})?$/.test(val)) {\n return val.replace(/[- ().]*/, '');\n }\n return null;\n}\n\n// Checks (loosely) if the given string is an email. If so returns the email.\nexport function asEmail(val) {\n val = val.trim();\n if (/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$/.test(val)) {\n return val;\n }\n return null;\n}\n\n// Checks if URL is a relative url, i.e. has no 'scheme://', including the case of missing scheme '//'.\n// The scheme is expected to be RFC-compliant, e.g. [a-z][a-z0-9+.-]*\n// example.html - ok\n// https:example.com - not ok.\n// http:/example.com - not ok.\n// ' ↲ https://example.com' - not ok. (↲ means carriage return)\nexport function isUrlRelative(url) {\n return url && !/^\\s*([a-z][a-z0-9+.-]*:|\\/\\/)/im.test(url);\n}\n\n// Ensure URL does not present an XSS risk. Optional allowedSchemes may contain an array of\n// strings with permitted URL schemes, such as ['ftp', 'ftps']; otherwise accept http and https only.\nexport function sanitizeUrl(url, allowedSchemes) {\n if (typeof url != 'string') {\n return url;\n }\n\n // Strip control characters and whitespace. They are not valid URL characters anyway.\n url = url.replace(/[^\\x20-\\x7E]/gmi, '').trim();\n\n // Relative URLs are safe.\n // Relative URL does not start with ':', abcd123: or '//'.\n if (!/^([a-z][a-z0-9+.-]*:|\\/\\/)/i.test(url)) {\n return url;\n }\n\n // Blob URLs are safe.\n if (/^blob:http/.test(url)) {\n return url;\n }\n\n // Absolute URL. Accept only safe schemes, or no scheme.\n const schemes = Array.isArray(allowedSchemes) ? allowedSchemes.join('|') : 'http|https';\n const re = new RegExp('^((' + schemes + '):|//)', 'i');\n if (!re.test(url)) {\n return null;\n }\n\n return url;\n}\n\n// Ensure URL is suitable for
field: the URL must be a relative URL or\n// have http:, https:, blob: or data: scheme.\n// In case of data: scheme, the URL must start with a 'data:image/XXXX;base64,'.\nexport function sanitizeImageUrl(url) {\n if (!url) {\n return null;\n }\n\n const sanitizedUrl = sanitizeUrl(url);\n if (sanitizedUrl) {\n return sanitizedUrl;\n }\n\n // Is this a data: URL of an image?\n if (/data:image\\/[a-z0-9.-]+;base64,/i.test(url.trim())) {\n return url;\n }\n\n return null;\n}\n\n// Given message's received status, return name and color of a delivery indicator icon.\nexport function deliveryMarker(received) {\n switch (received) {\n case Tinode.MESSAGE_STATUS_SENDING:\n return { name: 'access_time' }; // watch face\n case Tinode.MESSAGE_STATUS_FAILED:\n return { name: 'warning', color: 'danger-color' }; // yellow icon /!\\\n case Tinode.MESSAGE_STATUS_SENT:\n return { name: 'done' }; // checkmark\n case Tinode.MESSAGE_STATUS_RECEIVED:\n return { name: 'done_all' }; // double checkmark\n case Tinode.MESSAGE_STATUS_READ:\n return { name: 'done_all', color: 'blue' }; // blue double checkmark\n }\n return null;\n}\n\n// Wraps a promise to make it cancelable.\n// The parameter can be either a promise or an error. If it's an error, the wrapped promise is\n// created in a rejected state.\nexport function cancelablePromise(promise) {\n let hasCanceled = false;\n\n const wrappedPromise = promise instanceof Error ?\n Promise.reject(promise) :\n new Promise((resolve, reject) => {\n promise.then(\n result => hasCanceled ? reject({isCanceled: true}) : resolve(result),\n error => hasCanceled ? reject({isCanceled: true}) : reject(error)\n );\n });\n\n return {\n promise: wrappedPromise,\n cancel() {\n hasCanceled = true;\n },\n };\n};\n","import React from 'react';\n\nimport Tinode from 'tinode-sdk';\n\nimport { idToColorClass } from '../lib/strformat.js';\nimport { sanitizeImageUrl } from '../lib/utils.js';\n\nexport default class LetterTile extends React.PureComponent {\n render() {\n let avatar;\n if (this.props.avatar === true) {\n const isGroup = Tinode.isGroupTopicName(this.props.topic);\n const iconColor = idToColorClass(this.props.topic, isGroup);\n if (this.props.topic && this.props.title && this.props.title.trim()) {\n const letter = this.props.title.trim().charAt(0);\n const className = 'lettertile ' + iconColor + (this.props.deleted ? ' disabled' : '');\n avatar = ()\n } else {\n const className = 'material-icons ' + iconColor + (this.props.deleted ? ' disabled' : '');\n avatar = isGroup ? group : person;\n }\n } else if (this.props.avatar) {\n const url = this.props.tinode.authorizeURL(sanitizeImageUrl(this.props.avatar));\n // If avatar image is invalid, show a placeholder.\n const className = 'avatar' + (this.props.deleted ? ' deleted' : '');\n avatar =
{e.target.onerror = null; e.target.src=\"../img/broken_image.png\"}} />;\n } else {\n avatar = null;\n }\n return avatar;\n }\n}\n","// The counter of unread messages in the topic.\nimport React from 'react';\n\nexport default class UnreadBadge extends React.PureComponent {\n render() {\n return (this.props.count > 0 ?\n {this.props.count > 9 ? \"9+\" : this.props.count}\n : null);\n }\n};\n","// Image with a placeholder which is replaced when the promise is resolved.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class LazyImage extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n src: 'img/placeholder.png',\n style: Object.assign({padding: '4px'}, this.props.style),\n className: this.props.className,\n alt: this.props.alt,\n onClick: this.props.onClick,\n };\n }\n\n componentDidMount() {\n // whenDone is a wrapper around an actual promise to be able to cancel it.\n this.props.whenDone\n .promise\n .then(data => this.setState({src: data.src, style: {...this.state.style, padding: 0}}))\n .catch(() => this.setState({src: 'img/broken_image.png'}));\n }\n\n componentWillUnmount() {\n this.props.whenDone.cancel();\n }\n\n componentDidUpdate(prevProps) {\n if (prevProps.whenDone != this.props.whenDone) {\n this.setState({src: 'img/placeholder.png', style: {...this.state.style, padding: '4px'}});\n this.props.whenDone\n .promise\n .then(data => this.setState({src: data.src, style: {...this.state.style, padding: 0}}))\n .catch(() => this.setState({src: 'img/broken_image.png'}));\n }\n }\n\n render() {\n return React.createElement('img', this.state);\n }\n};\n","// File uload/download progress indicator with a cancel inside.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class FileProgress extends React.PureComponent {\n render() {\n return (\n \n );\n }\n}\n","// Image view with progress bar and a cancel button.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport FileProgress from './file-progress.jsx';\n\nexport default class UploadingImage extends React.PureComponent {\n constructor(props) {\n super(props);\n }\n\n render() {\n return (\n \n {React.createElement('img', this.props)}\n
\n \n
\n
\n );\n }\n};\n","// File and image helper functions.\n\n// Supported image MIME types and corresponding file extensions.\nexport const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];\nexport const MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'svg', 'svg'];\n\n// Get an URL from a theCard photo: either create a data: URL or return reference URL.\nexport function makeImageUrl(photo) {\n if (photo && typeof photo == 'object') {\n if (photo.ref) {\n return photo.ref\n }\n if (photo.data && photo.type) {\n const mime = photo.type.startsWith('image/') ? photo.type : ('image/' + photo.type);\n return 'data:' + mime + ';base64,' + photo.data;\n }\n }\n return null;\n}\n\n// Calculate linear dimensions for scaling image down to fit under a certain size.\n// Returns an object which contains destination sizes, source sizes, and offsets\n// into source (when making square images).\nexport function fitImageSize(width, height, maxWidth, maxHeight, forceSquare) {\n // Sanitize input\n width = width | 0;\n height = height | 0;\n maxWidth = maxWidth | 0;\n maxHeight = maxHeight | 0;\n\n if (width <= 0 || height <= 0 || maxWidth <= 0 || maxHeight <= 0) {\n return null;\n }\n\n if (forceSquare) {\n maxWidth = maxHeight = Math.min(maxWidth, maxHeight);\n }\n\n const scale = Math.min(\n Math.min(width, maxWidth) / width,\n Math.min(height, maxHeight) / height\n );\n\n const size = {\n dstWidth: (width * scale) | 0,\n dstHeight: (height * scale) | 0,\n };\n\n if (forceSquare) {\n // Also calculate parameters for making the image square.\n size.dstWidth = size.dstHeight = Math.min(size.dstWidth, size.dstHeight);\n size.srcWidth = size.srcHeight = Math.min(width, height);\n size.xoffset = ((width - size.srcWidth) / 2) | 0;\n size.yoffset = ((height - size.srcWidth) / 2) | 0;\n } else {\n size.xoffset = size.yoffset = 0;\n size.srcWidth = width;\n size.srcHeight = height;\n }\n return size;\n}\n\n// Ensure file's extension matches mime content type\nexport function fileNameForMime(fname, mime) {\n const idx = SUPPORTED_IMAGE_FORMATS.indexOf(mime);\n if (idx < 0 || !fname) {\n // Unknown mime or empty name.\n return fname;\n }\n const ext = MIME_EXTENSIONS[idx];\n\n const at = fname.lastIndexOf('.');\n if (at >= 0) {\n fname = fname.substring(0, at);\n }\n return fname + '.' + ext;\n}\n\n// Scale uploaded image to fit under certain dimensions and byte size, optionally constraining to a square.\n// Returns a promise which is resolven on success or rejected on failure.\nexport function imageScaled(fileOrBlob, maxWidth, maxHeight, maxSize, forceSquare) {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'Anonymous';\n img.onerror = function(err) {\n reject(new Error(\"Image format unrecognized\"));\n }\n img.onload = async function() {\n // Once the image is loaded, the URL is no longer needed.\n URL.revokeObjectURL(img.src);\n\n // Calculate the desired image dimensions.\n const dim = fitImageSize(img.width, img.height, maxWidth, maxHeight, forceSquare);\n if (!dim) {\n reject(new Error(\"Invalid image\"));\n return;\n }\n let canvas = document.createElement('canvas');\n canvas.width = dim.dstWidth;\n canvas.height = dim.dstHeight;\n let ctx = canvas.getContext('2d');\n ctx.imageSmoothingEnabled = true;\n ctx.drawImage(img, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,\n 0, 0, dim.dstWidth, dim.dstHeight);\n\n const mime = SUPPORTED_IMAGE_FORMATS.includes(fileOrBlob.type) ? fileOrBlob.type : 'image/jpeg';\n // Generate blob to check size of the image.\n let blob = await new Promise(resolve => canvas.toBlob(resolve, mime));\n if (!blob) {\n reject(new Error(\"Unsupported image format\"));\n return;\n }\n // Ensure the image is not too large. Shrink the image keeping the aspect ratio.\n // Do nothing if maxsize is <= 0.\n while (maxSize > 0 && blob.length > maxSize) {\n dim.dstWidth = (dim.dstWidth * 0.70710678118) | 0;\n dim.dstHeight = (dim.dstHeight * 0.70710678118) | 0;\n canvas.width = dim.dstWidth;\n canvas.height = dim.dstHeight;\n ctx = canvas.getContext('2d');\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(img, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight,\n 0, 0, dim.dstWidth, dim.dstHeight);\n blob = await new Promise(resolve => canvas.toBlob(resolve, mime));\n }\n\n canvas = null;\n resolve({mime: mime, blob: blob, width: dim.dstWidth, height: dim.dstHeight, name: fileNameForMime(fileOrBlob.name, mime)});\n };\n img.src = URL.createObjectURL(fileOrBlob);\n });\n}\n\n// Scale and crop image according to specified dimensions.\n// The coordinates are in unscaled image pixels, i.e. cut the rectangle first then scale it.\n// returns a promise.\nexport function imageCrop(mime, objURL, left, top, width, height, scale) {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'Anonymous';\n img.onerror = (err) => {\n reject(new Error(\"Image format unrecognized\"));\n };\n img.onload = () => {\n // Once the image is loaded, the URL is no longer needed.\n URL.revokeObjectURL(img.src);\n\n let canvas = document.createElement('canvas');\n canvas.width = width * scale;\n canvas.height = height * scale;\n let ctx = canvas.getContext('2d');\n ctx.imageSmoothingEnabled = true;\n ctx.drawImage(img, left, top, width, height, 0, 0, canvas.width, canvas.height);\n\n mime = SUPPORTED_IMAGE_FORMATS.includes(mime) ? mime : 'image/jpeg';\n // Generate blob to check size of the image.\n canvas.toBlob((blob) => {\n // Allow GC.\n canvas = null;\n if (blob) {\n resolve({mime: mime, blob: blob, width: width, height: height});\n } else {\n reject(new Error(\"Unsupported image format\"));\n }\n }, mime);\n };\n img.src = objURL;\n });\n}\n\n// Convert file to base64 string.\nexport function fileToBase64(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = (evt) => {\n reject(reader.error);\n };\n reader.onload = () => {\n resolve({mime: file.type, bits: reader.result.split(',')[1], name: file.name});\n };\n reader.readAsDataURL(file);\n });\n}\n\n// Convert Blob to base64 string. Returns a promise resolved with the base64 string and mime.\nexport function blobToBase64(blob) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = (evt) => {\n reject(reader.error);\n };\n reader.onload = () => {\n resolve({mime: blob.type, bits: reader.result.split(',')[1]});\n };\n reader.readAsDataURL(blob);\n });\n}\n\n// File pasted from the clipboard. It's either an inline image or a file attachment.\nexport function filePasted(event, onImageSuccess, onAttachmentSuccess, onError) {\n const items = (event.clipboardData || event.originalEvent.clipboardData || {}).items;\n if (!Array.isArray(items)) {\n return false;\n }\n\n for (let i in items) {\n const item = items[i];\n if (item.kind === 'file') {\n const file = item.getAsFile();\n if (!file) {\n console.error(\"Failed to get file object from pasted file item\", item.kind, item.type);\n onError(\"Failed to get file object from pasted file item\");\n continue;\n }\n if (file.type && file.type.split('/')[0] == 'image') {\n onImageSuccess(file);\n } else {\n onAttachmentSuccess(file);\n }\n // Indicate that the pasted data contains a file.\n return true;\n }\n }\n // No file found.\n return false;\n}\n\n// Get mime type from data URL header.\nexport function getMimeType(header) {\n var mime = /^data:(image\\/[-+a-z0-9.]+);base64/.exec(header);\n return (mime && mime.length > 1) ? mime[1] : null;\n}\n\n// Given length of a binary object in bytes, calculate the length after\n// base64 encoding.\nexport function base64EncodedLen(n) {\n return Math.floor((n + 2) / 3) * 4;\n}\n\n// Given length of a base64-encoded object, calculate decoded size of the\n// pbject in bytes.\nexport function base64DecodedLen(n) {\n return Math.floor(n / 4) * 3;\n}\n\n// Re-encode string to standard base64 encoding with padding.\n// The string may be base64-URL encoded without the padding.\nexport function base64ReEncode(str) {\n if (str) {\n str = str.replace(/-/g, '+').replace(/_/g, '/');\n try {\n str = btoa(atob(str));\n } catch(err) {\n console.error(\"Failed to base64 re-encode string.\", err);\n str = null;\n }\n }\n return str;\n}\n\n// Convert a base64 encoded string with the provided mime type into a Blob.\nexport function base64ToBlob(str, mime) {\n if (!str) {\n return null;\n }\n\n try {\n // Make blob.\n const bin = atob(str);\n const length = bin.length;\n const buf = new ArrayBuffer(length);\n const arr = new Uint8Array(buf);\n for (let i = 0; i < length; i++) {\n arr[i] = bin.charCodeAt(i);\n }\n\n return new Blob([buf], { type: mime });\n } catch(err) {\n console.error(\"Failed to convert base64 to blob: \", err);\n }\n\n return null;\n}\n","import React from 'react';\nimport { defineMessages } from 'react-intl';\n\nimport { Drafty } from 'tinode-sdk';\n\nimport LazyImage from '../widgets/lazy-image.jsx'\nimport UploadingImage from '../widgets/uploading-image.jsx'\n\nimport { IMAGE_THUMBNAIL_DIM, BROKEN_IMAGE_SIZE, REM_SIZE } from '../config.js';\nimport { base64ToBlob, blobToBase64, fitImageSize, imageScaled } from './blob-helpers.js';\nimport { idToColorClass, shortenFileName } from './strformat.js';\nimport { cancelablePromise, sanitizeImageUrl } from './utils.js';\n\nconst messages = defineMessages({\n drafty_form: {\n id: 'drafty_form',\n defaultMessage: 'Form: ',\n description: 'Comment for form in drafty preview'\n },\n drafty_attachment: {\n id: 'drafty_attachment',\n defaultMessage: 'Attachment',\n description: 'Comment for attachment in drafty preview'\n },\n drafty_image: {\n id: 'drafty_image',\n defaultMessage: 'Picture',\n description: 'Comment for embedded images in drafty preview'\n }\n});\n\n// Size the already scaled image.\nfunction handleImageData(el, data, attr) {\n if (!data) {\n attr.src = 'img/broken_image.png';\n attr.style = {\n width: IMAGE_THUMBNAIL_DIM + 'px',\n height: IMAGE_THUMBNAIL_DIM + 'px',\n };\n return el;\n }\n\n attr.className = 'inline-image';\n const dim = fitImageSize(data.width, data.height,\n this.viewportWidth > 0 ? Math.min(this.viewportWidth - REM_SIZE * 6.5, REM_SIZE * 34.5) :\n REM_SIZE * 34.5, REM_SIZE * 24, false) ||\n {dstWidth: BROKEN_IMAGE_SIZE, dstHeight: BROKEN_IMAGE_SIZE};\n attr.style = {\n width: dim.dstWidth + 'px',\n height: dim.dstHeight + 'px',\n // Looks like a Chrome bug: broken image does not respect 'width' and 'height'.\n minWidth: dim.dstWidth + 'px',\n minHeight: dim.dstHeight + 'px'\n };\n if (!Drafty.isProcessing(data)) {\n attr.src = this.authorizeURL(sanitizeImageUrl(attr.src));\n attr.alt = data.name;\n if (attr.src) {\n if (Math.max(data.width || 0, data.height || 0) > IMAGE_THUMBNAIL_DIM) {\n // Allow previews for large enough images.\n attr.onClick = this.onImagePreview;\n attr.className += ' image-clickable';\n }\n attr.loading = 'lazy';\n } else {\n attr.src = 'img/broken_image.png';\n }\n } else {\n // Use custom element instead of
.\n el = UploadingImage;\n }\n\n return el;\n}\n\nfunction quotedImage(data) {\n let promise;\n // Get the blob from the image data.\n if (data.val) {\n const blob = base64ToBlob(data.val, data.mime);\n promise = blob ? Promise.resolve(blob) : Prmise.reject(new Error(\"Invalid image\"));\n } else {\n promise = fetch(this.authorizeURL(sanitizeImageUrl(data.ref))).then(evt => {\n if (evt.ok) {\n return evt.blob();\n } else {\n throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);\n }\n });\n }\n\n // Scale the blob.\n return promise\n .then(blob => {\n return imageScaled(blob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, true)\n }).then(scaled => {\n data.mime = scaled.mime;\n data.size = scaled.blob.size;\n data.width = scaled.width;\n data.height = scaled.height;\n delete data.ref;\n // Keeping the original file name, if provided: ex.data.name;\n\n return blobToBase64(scaled.blob);\n }).then(b64 => {\n data.val = b64.bits;\n return data;\n }).catch(err => {\n delete data.val;\n delete data.name;\n data.width = IMAGE_THUMBNAIL_DIM;\n data.height = IMAGE_THUMBNAIL_DIM;\n data.maxWidth = IMAGE_THUMBNAIL_DIM;\n data.maxHeight = IMAGE_THUMBNAIL_DIM;\n // Rethrow.\n throw err;\n });\n}\n\n// The main Drafty formatter: converts Drafty elements into React classes. 'this' is set by the caller.\n// 'this' must contain:\n// viewportWidth: this.props.viewportWidth;\n// authorizeURL: this.props.tinode.authorizeURL\n// onImagePreview: this.handleImagePreview\n// onFormButtonClick: this.handleFormButtonClick\n// onQuoteClick: this.handleQuoteClick (optional)\nexport function fullFormatter(style, data, values, key, stack) {\n if (stack.includes('QQ')) {\n return quoteFormatter.call(this, style, data, values, key);\n }\n\n if (!style) {\n // Unformatted.\n return values;\n }\n\n let el = Drafty.tagName(style);\n const attr = Drafty.attrValue(style, data) || {};\n attr.key = key;\n switch (style) {\n case 'BR':\n values = null;\n break;\n case 'HL':\n // Highlighted text. Assign class name.\n attr.className = 'highlight';\n break;\n case 'HD':\n el = null;\n values = null;\n break;\n case 'IM':\n // Additional processing for images\n el = handleImageData.call(this, el, data, attr);\n // Image element cannot have content.\n values = null;\n break;\n case 'BN':\n // Button\n attr.onClick = this.onFormButtonClick;\n let inner = React.Children.map(values, (child) => {\n return typeof child == 'string' ? child : undefined;\n });\n if (!inner || inner.length == 0) {\n inner = [attr.name]\n }\n // Get text which will be sent back when the button is clicked.\n attr['data-title'] = inner.join('');\n break;\n case 'MN':\n // Mention\n attr.className = 'mention'\n if (data) {\n attr.className += ' ' + idToColorClass(data.val, false, true);\n }\n break;\n case 'FM':\n // Form\n attr.className = 'bot-form';\n break;\n case 'RW':\n // Form element formatting is dependent on element content.\n break;\n case 'QQ':\n // Quote/citation.\n attr.className = 'reply-quote'\n attr.onClick = this.onQuoteClick;\n break;\n default:\n if (el == '_UNKN') {\n // Unknown element.\n el = React.Fragment;\n values = [extension, ' '].concat(values || []);\n }\n break;\n }\n if (!el) {\n return values;\n }\n return React.createElement(el, attr, values);\n}\n\n// Converts Drafty object into a one-line preview. 'this' is set by the caller.\n// 'this' must contain:\n// formatMessage: this.props.intl.formatMessage\n// messages: formatjs messages defined with defineMessages.\nexport function previewFormatter(style, data, values, key) {\n if (!style) {\n // Unformatted.\n return values;\n }\n\n let el = Drafty.tagName(style);\n const attr = { key: key };\n switch (style) {\n case 'BR':\n // Replace new line with a space.\n el = React.Fragment;\n values = [' '];\n break;\n case 'HL':\n // Make highlight less prominent in preview.\n attr.className = 'highlight preview';\n break;\n case 'LN':\n case 'MN':\n // Disable links in previews.\n el = 'span';\n break;\n case 'IM':\n // Replace image with '[icon] Image'.\n el = React.Fragment;\n values = [photo, this.formatMessage(messages.drafty_image)];\n break;\n case 'BN':\n el = 'span';\n attr.className = 'flat-button faux';\n break;\n case 'FM':\n el = React.Fragment;\n values = [dashboard,\n this.formatMessage(messages.drafty_form)].concat(' ', values || []);\n break;\n case 'RW':\n el = React.Fragment;\n break;\n case 'EX':\n if (data) {\n if (data.mime == 'application/json') {\n // Ignore JSON attachments: they are form response payloads.\n return null;\n }\n // Clear payload.\n delete data.val;\n delete data.ref;\n }\n el = React.Fragment;\n values = [attachment, this.formatMessage(messages.drafty_attachment)];\n break;\n case 'QQ':\n case 'HD':\n el = null;\n values = null;\n break;\n default:\n if (el == '_UNKN') {\n el = React.Fragment;\n values = [extension, ' '].concat(values || []);\n }\n break;\n }\n if (!el) {\n return values;\n }\n return React.createElement(el, attr, values);\n};\n\n// Converts Drafty object into a quoted reply. 'this' is set by the caller.\nfunction inlineImageAttr(attr, data) {\n attr.style = {\n width: IMAGE_THUMBNAIL_DIM + 'px',\n height: IMAGE_THUMBNAIL_DIM + 'px',\n maxWidth: IMAGE_THUMBNAIL_DIM + 'px',\n maxHeight: IMAGE_THUMBNAIL_DIM + 'px',\n }\n attr.className = 'inline-image';\n attr.alt = this.formatMessage(messages.drafty_image);\n if (!data) {\n attr.src = 'img/broken_image.png';\n }\n attr.title = attr.alt;\n return attr;\n}\n\n// Displays a portion of Drafty within 'QQ' quotes. 'this' is set by the caller.\n// 'this' must contain:\n// formatMessage: this.props.intl.formatMessage\n// messages: formatjs messages defined with defineMessages.\n// authorizeURL: this.props.tinode.authorizeURL\n// onQuoteClick: this.handleQuoteClick (optional)\nfunction quoteFormatter(style, data, values, key) {\n if (['BR', 'EX', 'IM', 'MN'].includes(style)) {\n let el = Drafty.tagName(style);\n let attr = Drafty.attrValue(style, data) || {};\n attr.key = key;\n switch(style) {\n case 'BR':\n values = null;\n break;\n case 'IM':\n attr = inlineImageAttr.call(this, attr, data);\n values = [React.createElement('img', attr, null), ' ', attr.alt];\n el = React.Fragment;\n // Fragment attributes.\n attr = {key: key};\n break;\n case 'MN':\n el = 'span';\n attr.className = 'mention'\n if (data) {\n attr.className += ' ' + idToColorClass(data.val, false, true);\n }\n break;\n case 'EX':\n let fname;\n if (data) {\n if (data.mime == 'application/json') {\n // Ignore JSON attachments: they are form response payloads.\n return null;\n }\n fname = data.name;\n // Clear payload.\n delete data.val;\n delete data.ref;\n }\n el = React.Fragment;\n values = [attachment,\n shortenFileName(fname, 16) || this.formatMessage(messages.drafty_attachment)];\n break;\n }\n return React.createElement(el, attr, values);\n }\n return previewFormatter.call(this, style, data, values, key);\n}\n\n// Create image thumbnail suitable for inclusion in a quote.\nfunction quoteImage(data) {\n let promise;\n // Get the blob from the image data.\n if (data.val) {\n const blob = base64ToBlob(data.val, data.mime);\n if (!blob) {\n throw new Error(\"Invalid image\");\n }\n promise = Promise.resolve(blob);\n } else if (data.ref) {\n promise = fetch(this.authorizeURL(sanitizeImageUrl(data.ref)))\n .then(evt => {\n if (evt.ok) {\n return evt.blob();\n } else {\n throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);\n }\n });\n } else {\n throw new Error(\"Missing image data\");\n }\n\n // Scale the blob.\n return promise\n .then(blob => {\n // Cut the square from the center of the image and shrink it.\n return imageScaled(blob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, true)\n }).then(scaled => {\n data.mime = scaled.mime;\n data.size = scaled.blob.size;\n data.width = scaled.width;\n data.height = scaled.height;\n delete data.ref;\n // Keeping the original file name, if provided: ex.data.name;\n\n data.src = URL.createObjectURL(scaled.blob);\n return blobToBase64(scaled.blob);\n }).then(b64 => {\n data.val = b64.bits;\n return data;\n }).catch(err => {\n delete data.val;\n delete data.src;\n data.width = IMAGE_THUMBNAIL_DIM;\n data.height = IMAGE_THUMBNAIL_DIM;\n // Rethrow.\n throw err;\n });\n}\n\n// Create a preview of a reply.\nexport function replyFormatter(style, data, values, key, stack) {\n if (style == 'IM') {\n const attr = inlineImageAttr.call(this, {key: key}, data);\n let loadedPromise;\n try {\n loadedPromise = cancelablePromise(quoteImage.call(this, data));\n } catch (error) {\n loadedPromise = cancelablePromise(error);\n }\n attr.whenDone = loadedPromise;\n values = [React.createElement(LazyImage, attr, null), ' ', attr.alt];\n return React.createElement(React.Fragment, {key: key}, values);\n } else if (style == 'QQ') {\n if (stack.includes('QQ')) {\n // Quote inside quote when forwarding a message.\n return React.createElement('span', {key: key},\n [format_quote, ' ']);\n }\n\n const attr = Drafty.attrValue('QQ', data) || {};\n attr.key = key;\n attr.className = 'reply-quote'\n return React.createElement(Drafty.tagName('QQ'), attr, values);\n }\n return quoteFormatter.call(this, style, data, values, key);\n}\n","// A single topic or user.\nimport React from 'react';\nimport { FormattedMessage, injectIntl } from 'react-intl';\n\nimport ContactBadges from './contact-badges.jsx';\nimport LetterTile from './letter-tile.jsx';\nimport UnreadBadge from './unread-badge.jsx';\n\nimport { Drafty } from 'tinode-sdk';\n\nimport { previewFormatter } from '../lib/formatters.js';\nimport { deliveryMarker } from '../lib/utils.js';\n\nclass Contact extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleClick = this.handleClick.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n if (this.props.onSelected) {\n // Index this.props.index is used by GroupManager.\n this.props.onSelected(this.props.item, this.props.index);\n }\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ topicName: this.props.item, y: e.pageY, x: e.pageX });\n }\n\n render() {\n let title = this.props.title;\n if (!title) {\n title = ;\n } else if (title.length > 30) {\n // FIXME: this is probably wrong for RTL languages.\n title = title.substring(0, 28) + '…';\n }\n const online = this.props.now ? 'online' : 'offline';\n const avatar = this.props.avatar ? this.props.avatar : true;\n const badges = this.props.badges ? this.props.badges.slice() : [];\n const icon_badges = [];\n if (this.props.isVerified) {\n icon_badges.push({icon: 'verified', color: 'verified-color'});\n }\n if (this.props.isStaff) {\n icon_badges.push({icon: 'staff', color: 'staff-color'});\n }\n if (this.props.isDangerous) {\n icon_badges.push({icon: 'dangerous', color: 'danger-color'});\n }\n if (this.props.acs) {\n if (this.props.showMode) {\n badges.push({name: this.props.acs.getMode(), key: 'mode'});\n }\n if (this.props.acs.isMuted()) {\n icon_badges.push({icon: 'muted'});\n }\n if (!this.props.acs.isJoiner()) {\n icon_badges.push({icon: 'banned'});\n }\n }\n\n // The this.props.preview contains alreay shortened Drafty or string.\n let preview;\n if (typeof this.props.preview == 'string') {\n preview = this.props.preview;\n } else if (Drafty.isValid(this.props.preview)) {\n preview = React.createElement(React.Fragment, null, Drafty.format(this.props.preview, previewFormatter,\n {formatMessage: this.props.intl.formatMessage}));\n } else if (this.props.preview) {\n preview =\n <>warning_amber \n \n >;\n }\n\n const icon = deliveryMarker(this.props.received);\n const marker = icon ? {icon.name} : null;\n const titleClass = 'contact-title' + (this.props.deleted ? ' deleted' : '');\n\n return (\n \n \n \n {this.props.deleted ? cancel :\n this.props.showOnline ? :\n (this.props.showCheckmark && this.props.selected) ?\n check_circle\n : null}\n
\n \n
{title}\n {this.props.isChannel ?

: null}\n
\n {!this.props.deleted ?
: null}\n
\n {this.props.showMode ?\n
:\n this.props.small ? null :
{marker}{preview || this.props.comment || '\\u00A0'}
}\n
\n {this.props.showContextMenu ?\n \n \n expand_more\n \n : null}\n \n );\n }\n};\n\nexport default injectIntl(Contact);\n","// A panel included into a list of contacts with an action text.\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nclass ContactAction extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleClick = this.handleClick.bind(this);\n }\n\n handleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.onAction(this.props.action);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n {formatMessage(this.props.title, this.props.values)}
\n \n );\n }\n};\n\nexport default injectIntl(ContactAction);\n","// ContactList: component for showing a list of contacts,\n// such as a list of group members in a group chat.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport { Drafty } from 'tinode-sdk';\nimport Tinode from 'tinode-sdk';\n\nimport Contact from './contact.jsx';\nimport ContactAction from './contact-action.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\nimport { MESSAGE_PREVIEW_LENGTH } from '../config.js';\n\nconst messages = defineMessages({\n badge_you: {\n id: 'badge_you',\n defaultMessage: 'you',\n description: 'Badge for indicating the current user'\n },\n badge_owner: {\n id: 'badge_owner',\n defaultMessage: 'owner',\n description: 'Badge for indicating the owner'\n }\n});\n\nclass ContactList extends React.Component {\n render() {\n const { formatMessage } = this.props.intl;\n const showCheckmark = Array.isArray(this.props.topicSelected);\n const contactNodes = [];\n let contactsCount = 0;\n if (this.props.contacts && this.props.contacts.length > 0) {\n this.props.contacts.map((c) => {\n if (c.action) {\n // Action item\n contactNodes.push(\n );\n } else {\n // Normal contact\n const key = this.props.showMode ? c.user : (c.topic || c.user);\n // If filter function is provided, filter out the items\n // which don't satisfy the condition.\n if (this.props.filterFunc && this.props.filter) {\n const filterOn = [key];\n if (c.private && c.private.comment) {\n filterOn.push(('' + c.private.comment).toLowerCase());\n }\n if (c.public && c.public.fn) {\n filterOn.push(('' + c.public.fn).toLowerCase());\n }\n if (!this.props.filterFunc(this.props.filter, filterOn)) {\n return;\n }\n }\n\n const isChannel = Tinode.isChannelTopicName(key);\n const selected = showCheckmark ?\n (this.props.topicSelected.indexOf(key) > -1) :\n (this.props.topicSelected === key);\n const badges = [];\n if (this.props.showMode) {\n if (key == this.props.myUserId) {\n badges.push({name: formatMessage(messages.badge_you), color: 'green'});\n }\n if (c.acs && c.acs.isOwner()) {\n badges.push({name: formatMessage(messages.badge_owner), color: 'blue'});\n }\n }\n\n const comment = Array.isArray(c.private) ?\n c.private.join(',') : (c.private ? c.private.comment : null);\n let preview;\n let forwarded;\n let deliveryStatus;\n if (!this.props.showMode && c.latestMessage) {\n const msg = c.latestMessage(true);\n if (msg) {\n forwarded = msg.head ? msg.head.forwarded : null;\n deliveryStatus = msg._status || c.msgStatus(msg, true);\n if (msg.content) {\n preview = typeof msg.content == 'string' ?\n msg.content.substr(0, MESSAGE_PREVIEW_LENGTH) :\n Drafty.preview(msg.content, MESSAGE_PREVIEW_LENGTH);\n }\n }\n }\n\n contactNodes.push(\n \n );\n contactsCount ++;\n }\n }, this);\n }\n\n return (\n \n {contactsCount == 0 ?\n
\n :\n null\n }\n {contactNodes.length > 0 ?\n
\n :\n null\n }\n
\n );\n }\n};\n\nexport default injectIntl(ContactList);\n","import React from 'react';\n\nimport Tinode from 'tinode-sdk';\n\nexport default class SearchContacts extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n edited: false,\n search: ''\n };\n\n this.handleSearchChange = this.handleSearchChange.bind(this);\n this.handleSearch = this.handleSearch.bind(this);\n this.handleClear = this.handleClear.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n }\n\n componentWillUnmount() {\n if (this.state.edited) {\n this.setState({search: '', edited: false});\n this.props.onSearchContacts(Tinode.DEL_CHAR);\n }\n }\n\n handleSearchChange(e) {\n this.setState({search: e.target.value});\n }\n\n handleSearch(e) {\n e.preventDefault();\n var query = this.state.search.trim();\n this.setState({edited: (query.length > 0)});\n this.props.onSearchContacts(query.length > 0 ? query : Tinode.DEL_CHAR);\n }\n\n handleClear(e) {\n e.preventDefault();\n if (this.state.edited) {\n this.props.onSearchContacts(Tinode.DEL_CHAR);\n }\n this.setState({search: '', edited: false});\n }\n\n handleKeyDown(e) {\n if (e.key === 'Enter') {\n this.handleSearch(e);\n } else if (e.key === 'Escape') {\n this.handleClear();\n }\n }\n\n render() {\n return (\n \n );\n }\n};\n","// Forward Menu: message forwarding popup/dropdown menu.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport ContactList from './contact-list.jsx';\nimport SearchContacts from './search-contacts.jsx';\n\nexport default class ForwardDialog extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n query: null\n };\n\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this.handleClose = this.handleClose.bind(this);\n\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleContactSelected = this.handleContactSelected.bind(this);\n }\n\n componentDidMount() {\n this.props.onInitFind();\n }\n\n handleEscapeKey(e) {\n if (e.keyCode === 27) {\n // Not forwarding the message.\n this.props.hide(false);\n }\n }\n\n handleClose(e) {\n e.preventDefault();\n this.props.hide(false);\n }\n\n handleSearchContacts(query) {\n this.setState({ query: Tinode.isNullValue(query) ? null : query });\n this.props.onSearchContacts(query);\n }\n\n handleContactSelected(uid) {\n this.props.onTopicSelected(uid);\n this.props.hide(true);\n }\n\n render() {\n let contacts = this.state.query != null ? this.props.searchResults : this.props.contacts;\n // Filter out contacts without a 'W' or 'J' permissions as well as the current topic.\n contacts = contacts.filter((c) => {\n return (c.name != this.props.topicSelected) && c.acs.isJoiner() && c.acs.isWriter();\n });\n\n return (\n \n
\n
\n
{\n (search_placeholder) => \n }\n
{\n (not_found_placeholder) => \n }\n
\n
\n );\n }\n}\n","import React from 'react';\n\nexport default class LoadSpinner extends React.PureComponent {\n render() {\n const className = 'load-spinner-box' + (this.props.large ? ' large' : '')\n + (this.props.clear ? ' clear' : '') + (this.props.centered ? ' centered' : '');\n return (this.props.show ?\n : null);\n }\n}\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\nimport LoadSpinner from './load-spinner.jsx';\n\nimport { sanitizeImageUrl } from '../lib/utils.js';\n\nimport { AVATAR_SIZE } from '../config.js';\n\nexport default class AvatarUpload extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n source: props.avatar\n };\n\n this.handleFileReceived = this.handleFileReceived.bind(this);\n }\n\n componentDidUpdate(prevProps) {\n if (this.props.avatar != prevProps.avatar) {\n this.setState({source: this.props.avatar});\n }\n }\n\n handleFileReceived(e) {\n const image = e.target.files[0];\n this.props.onImageUpdated(image.type, URL.createObjectURL(image), image.name);\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n render() {\n // Randomize id value in case more than one AvatarUpload is shown\n // at the same time.\n const randId = 'file-input-avatar-' + ('' + Math.random()).substring(0, 4);\n const className = 'avatar-upload' + (this.props.readOnly ? ' read-only' : '');\n return (\n \n {this.props.readOnly || !this.state.source ?\n null :\n
{e.preventDefault(); this.props.onImageUpdated();}}>\n clear\n }\n {this.state.source ?\n
)})
:\n this.props.readOnly && this.props.uid ?\n
\n \n
\n :\n
{AVATAR_SIZE}×{AVATAR_SIZE}
}\n {this.props.readOnly ? null :\n
}\n {this.props.readOnly ? null :\n
}\n
\n
\n );\n }\n};\n","import React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nconst icon_mapping = {'staff': 'verified_user'};\n\nconst messages = defineMessages({\n badge_verified: {\n id: 'badge_verified',\n defaultMessage: 'Verified/official',\n description: 'Explanation of a verified account or topic badge'\n },\n badge_staff: {\n id: 'badge_staff',\n defaultMessage: 'Staff-managed',\n description: 'Explanation of a staff-managed account or topic badge'\n },\n badge_danger: {\n id: 'badge_danger',\n defaultMessage: 'Untrustworthy',\n description: 'Suspicious or untrustworthy account or topic badge'\n },\n});\n\n\n/* Trusted badges with labels: verified, staff, danger */\nclass BadgeList extends React.PureComponent {\n render() {\n const {formatMessage} = this.props.intl;\n let badges = null;\n if (this.props.trustedBadges && this.props.trustedBadges.length > 0) {\n badges = [];\n this.props.trustedBadges.map((b) => {\n const comment = formatMessage(messages['badge_' + b]);\n const style = 'material-icons ' + b + '-color';\n badges.push({icon_mapping[b] || b} {comment}
);\n });\n return <>{badges}>;\n }\n return null;\n }\n};\n\nexport default injectIntl(BadgeList);\n","import React from 'react';\n\n/* BEGIN CheckBox: styled checkbox */\nexport default class CheckBox extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleChange = this.handleChange.bind(this);\n }\n\n handleChange() {\n this.props.onChange(this.props.name, !this.props.checked);\n }\n\n render() {\n return (\n this.props.onChange ? (\n this.props.checked === true ?\n check_box :\n this.props.checked === false ?\n check_box_outline_blank :\n indeterminate_check_box\n ) : (\n this.props.checked ?\n check_box :\n check_box_outline_blank\n )\n );\n }\n}\n/* END CheckBox */\n","// The X menu to be displayed in title bars.\nimport React from 'react';\n\nexport default class MenuCancel extends React.PureComponent {\n render() {\n return (\n {e.preventDefault(); this.props.onCancel();}}>\n close\n \n );\n }\n}\n","import React from 'react';\n\nimport MenuCancel from './menu-cancel.jsx';\n\nexport default class ErrorPanel extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n show: false,\n };\n\n this.hide = this.hide.bind(this);\n }\n\n componentDidUpdate(prevProps) {\n if (prevProps.level !== this.props.level) {\n this.setState({\n show: !(!this.props.level)\n });\n }\n }\n\n hide() {\n this.setState({show: false});\n if (this.props.onClearError) {\n this.props.onClearError();\n }\n }\n\n render() {\n const icons = {err: 'error', warn: 'warning', info: 'info'}\n const level = icons[this.props.level] || '';\n const className = 'info-box ' + level;\n return (\n \n );\n }\n};\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\n\nexport default class Chip extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleCancel(e) {\n e.preventDefault();\n this.props.onCancel(this.props.topic, this.props.index);\n }\n\n render() {\n const title = this.props.title || this.props.topic;\n const className = this.props.invalid ? 'chip invalid' : 'chip';\n return (\n \n {this.props.noAvatar ?\n
:\n
\n \n
\n }\n
{title}\n {this.props.onCancel && !this.props.required ?\n
×\n :
}\n
\n );\n }\n};\n","import React from 'react';\n\nimport Chip from './chip.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\n/* BEGIN ChipInput: group membership widget */\nexport default class ChipInput extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = ChipInput.deriveStateFromProps(props);\n this.state.input = '';\n this.state.focused = false;\n\n this.handleTextInput = this.handleTextInput.bind(this);\n this.removeChipAt = this.removeChipAt.bind(this);\n this.handleChipCancel = this.handleChipCancel.bind(this);\n this.handleFocusGained = this.handleFocusGained.bind(this);\n this.handleFocusLost = this.handleFocusLost.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n }\n\n static deriveStateFromProps(props) {\n return {\n placeholder: props.chips ? '' : props.prompt,\n sortedChips: ChipInput.sortChips(props.chips, props.staticMembers),\n chipIndex: ChipInput.indexChips(props.chips)\n };\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.chips != this.props.chips ||\n prevProps.staticMembers != this.props.staticMembers ||\n prevProps.prompt != this.props.prompt) {\n this.setState(ChipInput.deriveStateFromProps(this.props));\n }\n if (!prevState || this.props.chips.length > prevState.sortedChips.length) {\n this.setState({input: ''});\n }\n }\n\n // Map chip index to user name\n static indexChips(chips) {\n const index = {};\n let count = 0;\n chips.map((item) => {\n index[item.user] = count;\n count ++;\n });\n return index;\n }\n\n // Have non-removable chips appear before all other chips.\n static sortChips(chips, keep) {\n const required = [];\n const normal = [];\n chips.map((item) => {\n if (keep && keep.includes(item.user)) {\n required.push(item);\n } else {\n normal.push(item);\n }\n });\n return required.concat(normal);\n }\n\n handleTextInput(e) {\n this.setState({input: e.target.value});\n if (this.props.filterFunc) {\n this.props.filterFunc(e.target.value);\n }\n }\n\n removeChipAt(idx) {\n const removed = this.state.sortedChips[idx];\n this.props.onChipRemoved(removed.user, this.state.chipIndex[removed.user]);\n }\n\n handleChipCancel(item, idx) {\n this.removeChipAt(idx);\n }\n\n handleFocusGained() {\n this.setState({focused: true});\n }\n\n handleFocusLost() {\n this.setState({focused: false});\n if (this.props.onFocusLost) {\n this.props.onFocusLost(this.state.input);\n }\n }\n\n handleKeyDown(e) {\n if (e.key === 'Backspace') {\n if (this.state.input.length == 0 && this.state.sortedChips.length > 0) {\n const at = this.state.sortedChips.length - 1;\n if (this.state.sortedChips[at].user !== this.props.staticMembers) {\n this.removeChipAt(at);\n }\n }\n } else if (e.key === 'Enter') {\n if (this.props.onEnter) {\n this.props.onEnter(this.state.input);\n }\n } else if (e.key === 'Escape') {\n if (this.props.onCancel) {\n this.props.onCancel();\n }\n }\n }\n\n render() {\n const chips = [];\n let count = 0;\n const staticMembers = this.props.staticMembers || [];\n this.state.sortedChips.map((item) => {\n chips.push(\n \n );\n count++;\n });\n const className = \"chip-input\" + (this.state.focused ? ' focused' : '');\n const autoFocus = !(this.props.tabIndex > 0);\n return (\n \n {chips}\n \n
\n );\n }\n};\n","// GroupMembers: control for managing a list of group members.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport ChipInput from './chip-input.jsx';\nimport ContactList from './contact-list.jsx';\n\nconst messages = defineMessages({\n no_contacts: {\n id: 'no_contacts',\n defaultMessage: 'You have no contacts :-(',\n description: 'Shown in ContactsView when the user has no contacts'\n },\n contacts_not_found_short: {\n id: 'contacts_not_found_short',\n defaultMessage: \"No contacts match ''{query}''\",\n description: 'Shown in ContactsView when search returned no results'\n }\n});\n\nclass GroupManager extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n // Array of topic members\n members: props.members,\n index: GroupManager.indexMembers(props.members),\n staticMembers: GroupManager.staticMembers(props.members, props.keepInitialMembers, props.requiredMember),\n contactFilter: '',\n noContactsMessage: props.intl.formatMessage(messages.no_contacts),\n selectedContacts: GroupManager.selectedContacts(props.members)\n };\n\n this.handleContactSelected = this.handleContactSelected.bind(this);\n this.handleMemberRemoved = this.handleMemberRemoved.bind(this);\n this.handleContactFilter = this.handleContactFilter.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n static indexMembers(members) {\n let index = {};\n members.map((m) => {\n index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed\n });\n return index;\n }\n\n static staticMembers(members, keepInitial, requiredMember) {\n let stat = [];\n members.map((m) => {\n if (keepInitial || m.user == requiredMember) {\n stat.push(m.user);\n }\n });\n return stat;\n }\n\n static selectedContacts(members) {\n let sel = [];\n members.map((m) => {\n sel.push(m.user);\n });\n return sel;\n }\n\n handleContactSelected(userId, index) {\n let status = this.state.index[userId];\n if (status) {\n if (status.present) {\n // Prevent duplicate members\n return;\n }\n status.delta += 1;\n status.present = true;\n } else {\n status = {delta: 1, present: true};\n }\n\n let m = this.state.members.slice();\n m.push(this.props.contacts[index]);\n\n const sel = GroupManager.selectedContacts(m);\n\n const i = this.state.index;\n i[userId] = status;\n\n this.setState({members: m, index: i, selectedContacts: sel});\n }\n\n handleMemberRemoved(userId, index) {\n const status = this.state.index[userId];\n if (!status || !status.present) {\n return;\n }\n status.present = false;\n status.delta -= 1;\n\n let m = this.state.members.slice();\n m.splice(index, 1);\n\n const sel = GroupManager.selectedContacts(m);\n\n const i = this.state.index;\n i[userId] = status;\n\n this.setState({members: m, index: i, selectedContacts: sel});\n }\n\n handleContactFilter(val) {\n const {formatMessage} = this.props.intl;\n const msg = !val ?\n formatMessage(messages.no_contacts) :\n formatMessage(messages.contacts_not_found_short, {query: val});\n\n this.setState({contactFilter: val, noContactsMessage: msg});\n }\n\n static doContactFiltering(filter, values) {\n if (filter) {\n for (let i=0; i= 0) {\n return true;\n }\n }\n return false;\n }\n return true;\n }\n\n handleSubmit() {\n var instance = this;\n var members = [];\n var added = [];\n var removed = [];\n\n var keys = Object.keys(this.state.index);\n keys.map(function(k) {\n if (instance.state.index[k].present) {\n members.push(k);\n }\n\n if (instance.state.index[k].delta > 0) {\n added.push(k);\n } else if (instance.state.index[k].delta < 0) {\n removed.push(k);\n }\n });\n this.props.onSubmit(members, added, removed);\n }\n\n handleCancel() {\n this.props.onCancel();\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n
\n \n
\n
\n \n
\n
\n \n
\n
\n
\n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(GroupManager);\n","// PermissionsEditor: Component for editing permissions\n// \nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport CheckBox from './checkbox.jsx';\nimport Contact from './contact.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\n// Translatable permission names.\nconst messages = defineMessages({\n joiner: {\n id: 'permission_join',\n defaultMessage: \"Join ({val})\",\n description: 'Name of J permission'\n },\n reader: {\n id: 'permission_read',\n defaultMessage: \"Read ({val})\",\n description: 'Name of R permission'\n },\n writer: {\n id: 'permission_write',\n defaultMessage: \"Write ({val})\",\n description: 'Name of W permission'\n },\n preser: {\n id: 'permission_pres',\n defaultMessage: \"Get notified ({val})\",\n description: 'Name of P permission'\n },\n approver: {\n id: 'permission_admin',\n defaultMessage: \"Approve ({val})\",\n description: 'Name of A permission'\n },\n sharer: {\n id: 'permission_share',\n defaultMessage: \"Share ({val})\",\n description: 'Name of S permission'\n },\n deleter: {\n id: 'permission_delete',\n defaultMessage: \"Delete ({val})\",\n description: 'Name of D permission'\n },\n owner: {\n id: 'permission_owner',\n defaultMessage: \"Owner ({val})\",\n description: 'Name of O permission'\n }\n});\n\nclass PermissionsEditor extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n mode: (props.mode || '').replace('N', '')\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleChange(val) {\n let mode = this.state.mode;\n const idx = mode.indexOf(val);\n if (idx == -1) {\n mode += val;\n } else {\n mode = mode.replace(val, '');\n }\n this.setState({mode: mode});\n }\n\n handleSubmit() {\n // Normalize string, otherwise cannot check if mode has changed.\n const mode = (this.state.mode || 'N').split('').sort().join('');\n const before = (this.props.mode || 'N').split('').sort().join('')\n if (mode !== before) {\n this.props.onSubmit(mode);\n } else {\n this.props.onCancel();\n }\n }\n\n handleCancel() {\n this.props.onCancel();\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const all = 'JRWPASDO';\n const names = {\n 'J': formatMessage(messages.joiner, {val: 'J'}),\n 'R': formatMessage(messages.reader, {val: 'R'}),\n 'W': formatMessage(messages.writer, {val: 'W'}),\n 'P': formatMessage(messages.preser, {val: 'P'}),\n 'A': formatMessage(messages.approver, {val: 'A'}),\n 'S': formatMessage(messages.sharer, {val: 'S'}),\n 'D': formatMessage(messages.deleter, {val: 'D'}),\n 'O': formatMessage(messages.owner, {val: 'O'})\n };\n\n let skip = this.props.skip || '';\n let mode = this.state.mode;\n let compare = (this.props.compare || '').replace('N', '');\n let items = [];\n for (let i=0; i= 0 && mode.indexOf(c) < 0) {\n // Permission is marked as inactive: hide unchecked permissions, disable checked permissions\n continue;\n }\n items.push(\n \n | {names[c]} | \n {skip.indexOf(c) < 0 ?\n = 0)} onChange={this.handleChange}/>\n :\n = 0)} />\n } | {this.props.compare ? \n = 0)}/>\n | : null}\n
\n );\n }\n\n return (\n \n {this.props.userTitle ?\n
: null}\n
\n
\n {this.props.compare ?\n \n | {this.props.modeTitle} | \n {this.props.compareTitle} | \n
:\n null}\n \n {items}\n
\n
\n
\n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(PermissionsEditor);\n","// Image zoom and crop widget.\n\nimport React from 'react';\n\nconst DEFAULT_MAX_ZOOM = 2.5;\n\nexport default class Cropper extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n // Coordinates of the top left corner of the image in container coordinates.\n panX: 0,\n panY: 0,\n // Origin of the zoom in image coordinates.\n originX: 0,\n originY: 0,\n // Zoom scale.\n zoom: 1,\n // Zoom min and max.\n minZoom: 0,\n maxZoom: DEFAULT_MAX_ZOOM,\n };\n\n // References to DOM elements for hooking up events and getting screen dimensions.\n // The transparent element which captures mouse moves (moves with the image).\n this.overlay = React.createRef();\n // The image cutout window or view port (static).\n this.cutout = React.createRef();\n // Scaled and translated image preview.\n this.preview = React.createRef();\n // The main container (static).\n this.boundingBox = React.createRef();\n\n // The original image width and height before any scaling.\n this.imageWidth = 0;\n this.imageHeight = 0;\n\n // Mouse position when dragging.\n this.mouseX = 0\n this.mouseY = 0;\n // Length of the previous mouse move when dragging.\n this.prevDistance = 0;\n\n // Bounding rectangles of static elements.\n this.cutoutRect = {};\n this.bBoxRect = {};\n // Center of the bounding box.\n this.originX = 0;\n this.originY = 0;\n\n this.initScaling = this.initScaling.bind(this);\n this.onZoom = this.onZoom.bind(this);\n this.handleZoom = this.handleZoom.bind(this);\n this.mouseDown = this.mouseDown.bind(this);\n this.mouseUp = this.mouseUp.bind(this);\n this.mouseMove = this.mouseMove.bind(this);\n this.mouseTouch = this.mouseTouch.bind(this);\n this.positionAll = this.positionAll.bind(this);\n this.translate = this.translate.bind(this);\n }\n\n componentDidMount() {\n this.overlay.current.addEventListener('mousedown', this.mouseDown, { passive: true });\n this.overlay.current.addEventListener('touchstart', this.mouseDown, { passive: true });\n\n // The rectangle position is in viewport coordinates.\n this.bBoxRect = this.boundingBox.current.getBoundingClientRect();\n // Center of the bounding box in container coordinates.\n this.originX = this.bBoxRect.width / 2;\n this.originY = this.bBoxRect.height / 2;\n\n // The rectangle position is in viewport coordinates.\n this.cutoutRect = this.cutout.current.getBoundingClientRect();\n }\n\n componentWillUnmount() {\n this.overlay.current.removeEventListener('mousedown', this.mouseDown);\n this.overlay.current.removeEventListener('touchstart', this.mouseDown);\n }\n\n // Position all elements.\n positionAll(panX, panY, zoom) {\n // Zoom origin in image preview coordinates.\n this.setState({\n panX: panX,\n panY: panY,\n zoom: zoom,\n originX: this.originX - panX,\n originY: this.originY - panY,\n });\n\n // Pass cut out coordinates to caller.\n const left = (this.originX - panX) * zoom - this.originX;\n const top = (this.originY - panY) * zoom - this.originY;\n this.props.onChange(\n (left + this.cutoutRect.left - this.bBoxRect.left) / zoom,\n (top + this.cutoutRect.top - this.bBoxRect.top) / zoom,\n this.cutoutRect.width / zoom,\n this.cutoutRect.height / zoom,\n zoom);\n }\n\n // Check if new location is within the limits.\n static checkBound(currPan, img, cutout, delta) {\n let nextDiff = Math.min(0, cutout[0] - img[0] - delta, img[1] - cutout[1] + delta);\n if (nextDiff == 0) {\n // Cutout is completely within the image.\n currPan += delta;\n } else if (Math.min(0, cutout[0] - img[0], img[1] - cutout[1]) < nextDiff) {\n // Cutout is outside of the range but closer to the bound.\n currPan += delta;\n }\n return currPan;\n }\n\n initScaling() {\n // Initialize components.\n const imgRect = this.preview.current.getBoundingClientRect();\n\n this.imageWidth = imgRect.width;\n this.imageHeight = imgRect.height;\n\n // Set zoom slider's min and max values. Make sure the scaled image cannot be smaller than the cutout.\n const minZoom = Math.max(this.cutoutRect.width / imgRect.width, this.cutoutRect.height / imgRect.height);\n this.setState({\n minZoom: minZoom,\n maxZoom: Math.max(DEFAULT_MAX_ZOOM, minZoom + 1)\n });\n\n // Initial zoom level fills the bounding box at the smallest image dimension and overflows the largest, i.e. \"fill\" not \"fit\".\n const zoom = Math.max(this.bBoxRect.width / imgRect.width, this.bBoxRect.height / imgRect.height);\n // Converting from viewport coordinates to container, then panning.\n const panX = this.cutoutRect.left - this.bBoxRect.left -\n (imgRect.width - this.cutoutRect.width) / 2;\n const panY = this.cutoutRect.top - this.bBoxRect.top -\n (imgRect.height - this.cutoutRect.height) / 2;\n\n this.positionAll(panX, panY, zoom);\n }\n\n onZoom(e) {\n this.handleZoom(e.target.value);\n }\n\n handleZoom(zoom) {\n let panX = this.state.panX;\n let panY = this.state.panY;\n\n // Ensure that the image at the new zoom is not outside of the cutout boundaries.\n // Calculate image coordinates at the new zoom.\n const imgLeft = this.originX - (this.originX - panX) * zoom;\n const imgRight = imgLeft + this.imageWidth * zoom;\n // Cutout in the same coordinates as the image.\n const coLeft = this.cutoutRect.left - this.bBoxRect.left;\n const coRight = coLeft + this.cutoutRect.width;\n if (coLeft < imgLeft) {\n panX -= imgLeft - coLeft;\n } else if (coRight > imgRight) {\n panX += coRight - imgRight;\n }\n const imgTop = this.originY - (this.originY - panY) * zoom;\n const imgBottom = imgTop + this.imageHeight * zoom;\n const coTop = this.cutoutRect.top - this.bBoxRect.top;\n const coBottom = coTop + this.cutoutRect.height;\n if (coTop < imgTop) {\n panY -= imgTop - coTop;\n } else if (coBottom > imgBottom) {\n panY += coBottom - imgBottom;\n }\n\n this.positionAll(panX, panY, zoom);\n }\n\n mouseDown(e) {\n if (e.touches) {\n this.mouseX = e.touches[0].pageX;\n this.mouseY = e.touches[0].pageY;\n } else {\n this.mouseX = e.pageX;\n this.mouseY = e.pageY;\n }\n\n window.addEventListener('mousemove', this.mouseMove, { passive: false });\n window.addEventListener('touchmove', this.mouseTouch, { passive: false });\n window.addEventListener('mouseup', this.mouseUp, { passive: true });\n window.addEventListener('touchend', this.mouseUp, { passive: true });\n // Disable text selection in the entire document.\n document.body.style['userSelect'] = 'none';\n }\n\n // Perform image panning.\n translate(pageX, pageY) {\n const dX = pageX - this.mouseX;\n const dY = pageY - this.mouseY;\n\n this.mouseX = pageX;\n this.mouseY = pageY;\n\n // Make sure the image is within the cutout window.\n const imgRect = this.preview.current.getBoundingClientRect();\n\n // Check if the new position is within the boundaries, and if not if it's closer to them.\n let panX = Cropper.checkBound(this.state.panX, [imgRect.left, imgRect.right],\n [this.cutoutRect.left, this.cutoutRect.right], dX);\n let panY = Cropper.checkBound(this.state.panY, [imgRect.top, imgRect.bottom],\n [this.cutoutRect.top, this.cutoutRect.bottom], dY);\n\n this.positionAll(panX, panY, this.state.zoom);\n }\n\n // Image panning.\n mouseMove(e) {\n e.preventDefault();\n this.translate(e.pageX, e.pageY);\n }\n\n mouseTouch(e) {\n e.preventDefault();\n\n if (e.touches.length == 1) {\n this.translate(e.touches[0].pageX, e.touches[0].pageY);\n return;\n }\n\n // Image zooming by pinching.\n const [touch0, touch1] = e.touches;\n const distance = Math.sqrt((touch0.pageX - touch1.pageX) * (touch0.pageX - touch1.pageX) +\n (touch0.pageY - touch1.pageY) * (touch0.pageY - touch1.pageY));\n\n if (!this.prevDistance) {\n this.prevDistance = distance / this.state.zoom;\n }\n\n let scale = (distance / this.prevDistance);\n this.handleZoom(Math.max(this.minZoom, Math.min(this.maxZoom, scale)));\n }\n\n mouseUp(e) {\n window.removeEventListener('mousemove', this.mouseMove);\n window.removeEventListener('touchmove', this.mouseTouch);\n window.removeEventListener('mouseup', this.mouseUp);\n window.removeEventListener('touchend', this.mouseUp);\n // Re-enable text selection.\n document.body.style['userSelect'] = '';\n\n this.positionAll(this.state.panX, this.state.panY, this.state.zoom);\n }\n\n render() {\n // transform3d: position and scale.\n const t3d = `translate3d(${this.state.panX}px, ${this.state.panY}px, 0) scale(${this.state.zoom})`;\n // transformOrigin: zoom origin.\n const orig = `${this.state.originX}px ${this.state.originY}px`;\n\n // Overlay position and size are exactly equal to the position and size of the transformed image\n // except it uses different coordinates.\n const overlay = {\n top: `${this.originY - this.state.originY * this.state.zoom}px`,\n left: `${this.originX - this.state.originX * this.state.zoom}px`,\n width: `${this.imageWidth * this.state.zoom}px`,\n height: `${this.imageHeight * this.state.zoom}px`\n };\n return (\n \n
\n

\n
\n
\n
\n
\n \n
\n
\n );\n }\n}\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Cropper from './cropper.jsx';\nimport { imageCrop } from '../lib/blob-helpers.js';\n\nexport default class AvatarCrop extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n top: 0,\n left: 0,\n width: 0,\n height: 0,\n scale: 1\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleChange = this.handleChange.bind(this);\n }\n\n // Cut out in image coordinates and desired scale factor.\n handleChange(left, top, width, height, scale) {\n this.setState({\n left: left,\n top: top,\n width: width,\n height: height,\n scale: scale\n });\n }\n\n handleSubmit() {\n imageCrop(this.props.mime, this.props.avatar,\n this.state.left, this.state.top, this.state.width, this.state.height,\n this.state.scale)\n .then(img => {\n this.props.onSubmit(img.mime, img.blob, img.width, img.height);\n })\n .catch(err => {\n this.props.onError(err);\n });\n }\n\n render() {\n return (\n \n
\n \n
\n
\n {this.props.onCancel ?\n \n : null}\n \n
\n
\n );\n }\n};\n","// Password with a visiblity toggle.\nimport React from 'react';\n\nexport default class VisiblePassword extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n value: this.props.value,\n visible: false\n };\n\n this.handleVisibility = this.handleVisibility.bind(this);\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value});\n if (this.props.onChange) {\n this.props.onChange(e);\n }\n }\n\n handleVisibility(e) {\n e.preventDefault();\n this.setState({visible: !this.state.visible});\n }\n\n handleKeyDown(e) {\n if (e.keyCode == 27) {\n // Escape pressed\n this.setState({value: this.props.value, visible: false});\n if (this.props.onFinished) {\n this.props.onFinished();\n }\n } else if (e.keyCode == 13) {\n // Enter pressed\n this.handleEditingFinished();\n }\n }\n\n handleEditingFinished(e) {\n if (e) {\n let currentTarget = e.currentTarget;\n setTimeout(() => {\n if (!currentTarget.contains(document.activeElement)) {\n if (this.props.onFinished) {\n this.props.onFinished(this.state.value);\n }\n }\n }, 0);\n } else if (this.props.onFinished) {\n this.props.onFinished(this.state.value.trim());\n }\n }\n\n render() {\n return (\n \n \n \n \n {this.state.visible ? 'visibility' : 'visibility_off'}\n \n \n
\n );\n }\n}\n","// In-place text editor. Shows text with an icon which toggles it into an input field.\nimport React from 'react';\n\nimport VisiblePassword from './visible-password.jsx';\n\nexport default class InPlaceEdit extends React.Component {\n constructor(props) {\n super(props);\n\n this.selfRef = React.createRef();\n\n this.state = {\n active: props.active,\n initialValue: props.value || '',\n value: props.value || ''\n };\n\n this.handeTextChange = this.handeTextChange.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleStartEditing = this.handleStartEditing.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n this.handlePasswordFinished = this.handlePasswordFinished.bind(this);\n }\n\n componentDidUpdate(prevProps, prevState) {\n // If text has changed while in read mode, update text and discard changes.\n // Ignore update if in edit mode.\n const newValue = this.props.value || '';\n if (prevState.initialValue != newValue && !prevState.active) {\n this.setState({\n initialValue: newValue,\n value: newValue\n });\n }\n }\n\n handeTextChange(e) {\n this.setState({value: e.target.value || ''});\n }\n\n handleKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({value: this.props.value, active: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleEditingFinished(e);\n }\n }\n\n handleStartEditing() {\n if (!this.props.readOnly) {\n this.setState({active: true}, () => {\n if (this.selfRef.current) {\n this.selfRef.current.focus();\n }\n });\n }\n }\n\n handleEditingFinished(event) {\n const value = this.state.value.trim();\n if (this.props.required && (!event.target.checkValidity() || !value)) {\n // Empty input\n this.setState({value: this.props.value, active: false});\n return;\n }\n this.setState({active: false});\n if ((value || this.props.value) && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n handlePasswordFinished(value) {\n this.setState({active: false});\n if (value && (value !== this.props.value)) {\n this.props.onFinished(value);\n }\n }\n\n render() {\n if (!this.state.active) {\n let spanText = this.props.type == 'password' ? '••••••••' : this.state.value;\n let spanClass = 'in-place-edit' + (this.props.readOnly ? ' disabled' : '');\n if (!spanText) {\n spanText = this.props.placeholder;\n spanClass += ' placeholder';\n }\n if (!this.props.multiline || this.props.multiline == 1) {\n spanClass += ' short';\n }\n\n return (\n {spanText}\n );\n }\n\n let element;\n const attr = {};\n if (this.props.type == 'password') {\n element = VisiblePassword;\n attr.onFinished = this.handlePasswordFinished;\n } else {\n if (this.props.multiline > 1) {\n element = 'textarea';\n attr.rows = this.props.multiline;\n attr.className = 'inplace-edit';\n } else {\n element = 'input';\n attr.type = this.props.type || 'text';\n }\n attr.value = this.state.value;\n attr.onChange = this.handeTextChange;\n attr.onKeyDown = this.handleKeyDown;\n attr.onBlur = this.handleEditingFinished;\n }\n attr.placeholder = this.props.placeholder;\n attr.required = this.props.required ? 'required' : '';\n attr.autoComplete = this.props.autoComplete;\n attr.autoFocus = true;\n attr.ref = this.selfRef;\n\n return React.createElement(element, attr, null);\n }\n};\n","// TagManager: edit topic or user tags.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport ChipInput from './chip-input.jsx';\n\nimport { MAX_TAG_COUNT, MAX_TAG_LENGTH, MIN_TAG_LENGTH } from '../config.js';\nimport { arrayEqual } from '../lib/utils.js';\n\nexport default class TagManager extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tags: this.props.tags || [],\n tagInput: '',\n activated: this.props.activated\n };\n\n this.handleTagInput = this.handleTagInput.bind(this);\n this.handleAddTag = this.handleAddTag.bind(this);\n this.handleRemoveTag = this.handleRemoveTag.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n const tags = nextProps.tags || [];\n if (!arrayEqual(tags, prevState.tags) && !prevState.activated) {\n return {tags: tags};\n }\n return null;\n }\n\n handleTagInput(text) {\n this.setState({tagInput: text});\n if (text.length > 0) {\n const last = text[text.length-1];\n if (text[0] == '\"') {\n // This is a quoted string.\n if (text.length > 1 && last == '\"') {\n this.handleAddTag(text.substring(1, text.length-1));\n }\n } else if (last == ',' || last == ' ' || last == ';' || last == '\"') {\n // User entered ',', ' ' or ';'\n this.handleAddTag(text.substring(0, text.length-1).trim());\n }\n }\n }\n\n handleAddTag(tag) {\n const maxTagCount = this.props.tinode.getServerLimit('maxTagCount', MAX_TAG_COUNT);\n\n if (tag.length > 0 && this.state.tags.length < maxTagCount) {\n const tags = this.state.tags.slice(0);\n tags.push(tag);\n\n this.setState({tags: tags, tagInput: ''});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n return tags;\n }\n return this.state.tags;\n }\n\n handleRemoveTag(tag, index) {\n const tags = this.state.tags.slice(0);\n tags.splice(index, 1);\n this.setState({tags: tags});\n if (this.props.onTagsChanged) {\n this.props.onTagsChanged(tags);\n }\n }\n\n handleSubmit() {\n // Add unprocessed input to tags, submit the list.\n this.props.onSubmit(this.handleAddTag(this.state.tagInput.trim()));\n this.setState({activated: false, tags: this.props.tags || []});\n }\n\n handleCancel() {\n this.setState({activated: false, tagInput: '', tags: this.props.tags || []});\n if (this.props.onCancel) {\n this.props.onCancel();\n }\n }\n\n render() {\n const minTagLength = this.props.tinode.getServerLimit('minTagLength', MIN_TAG_LENGTH);\n const maxTagLength = this.props.tinode.getServerLimit('maxTagLength', MAX_TAG_LENGTH);\n\n let tags = [];\n if (this.state.activated) {\n this.state.tags.map((tag) => {\n tags.push({user: tag, invalid: (tag.length < minTagLength || tag.length > maxTagLength)});\n });\n } else {\n this.state.tags.map((tag) => {\n tags.push({tag});\n });\n if (tags.length == 0) {\n tags = (\n \n \n \n );\n }\n }\n return (\n \n
\n \n
\n {this.state.activated ?\n
\n
{\n (add_tags_prompt) => \n }\n {this.props.onSubmit || this.props.onCancel ?\n
\n \n \n
\n : null}\n
\n :\n
\n }\n
\n );\n }\n};\n","// Widget for editing topic description.\n\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarCrop from './avatar-crop.jsx';\nimport AvatarUpload from './avatar-upload.jsx';\nimport InPlaceEdit from './in-place-edit.jsx';\nimport TagManager from './tag-manager.jsx';\n\nimport { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE, MAX_TITLE_LENGTH,\n MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';\nimport { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, theCard } from '../lib/utils.js';\n\nexport default class TopicDescEdit extends React.Component {\n constructor(props) {\n super(props);\n\n const topic = this.props.tinode.getTopic(this.props.topic);\n const acs = topic.getAccessMode();\n this.state = {\n isMe: Tinode.isMeTopicName(this.props.topic),\n owner: acs && acs.isOwner(),\n fullName: topic.public ? topic.public.fn : undefined,\n private: topic.private ? topic.private.comment : null,\n description: topic.public ? topic.public.note : undefined,\n avatar: makeImageUrl(topic.public ? topic.public.photo : null),\n tags: topic.tags() || [],\n newAvatar: null,\n newAvatarMime: null\n };\n\n this.previousOnTags = null;\n\n this.tnNewTags = this.tnNewTags.bind(this);\n this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);\n this.handleImageUpdated = this.handleImageUpdated.bind(this);\n this.handleAvatarCropped = this.handleAvatarCropped.bind(this);\n this.handlePrivateUpdate = this.handlePrivateUpdate.bind(this);\n this.handleDescriptionUpdate = this.handleDescriptionUpdate.bind(this);\n this.uploadAvatar = this.uploadAvatar.bind(this);\n this.handleAvatarCropCancel = this.handleAvatarCropCancel.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n }\n\n componentDidMount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n this.previousOnTags = topic.onTagsUpdated;\n topic.onTagsUpdated = this.tnNewTags;\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n topic.onTagsUpdated = this.previousOnTags;\n }\n\n tnNewTags(tags) {\n this.setState({tags: tags});\n }\n\n handleFullNameUpdate(fn) {\n fn = fn.trim().substring(0, MAX_TITLE_LENGTH);\n if (fn && this.state.fullName !== fn) {\n this.setState({fullName: fn});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(fn, null));\n }\n }\n\n handlePrivateUpdate(comment) {\n comment = comment.trim().substring(0, MAX_TITLE_LENGTH);\n if (this.state.private !== comment) {\n this.setState({private: comment});\n this.props.onUpdateTopicDesc(this.props.topic, null, comment || Tinode.DEL_CHAR);\n }\n }\n\n handleDescriptionUpdate(desc) {\n desc = desc.trim().substring(0, MAX_TOPIC_DESCRIPTION_LENGTH);\n if (desc) {\n this.setState({description: desc});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, null, null, desc));\n }\n }\n\n // AvatarUpload widget has changed the avatar: received a new file or deleted the avatar.\n handleImageUpdated(mime, img) {\n // This will show the AvatarCropView is img is not null.\n this.setState({newAvatar: img, newAvatarMime: mime});\n if (!img) {\n // Deleting the avatar.\n this.setState({avatar: null});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, Tinode.DEL_CHAR));\n }\n }\n\n // AvatarCropView calls this method when the user has cropped the image.\n handleAvatarCropped(mime, blob, width, height) {\n const url = blob ? URL.createObjectURL(blob) : null;\n this.setState({avatar: url, newAvatar: null, newAvatarMime: null});\n if (blob) {\n this.uploadAvatar(mime, blob, width, height);\n }\n }\n\n // Utility method for converting cropped avatar blob to bytes for sending inband or\n // for uploading it to the server out of band.\n uploadAvatar(mime, blob, width, height) {\n const readyToUpload = (image) => {\n let {mime, blob} = image;\n if (blob.size > MAX_AVATAR_BYTES) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n\n this.setState({uploading: true});\n uploader.upload(blob)\n .then((url) => {\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, url));\n })\n .catch((err) => {\n this.props.onError(err, 'err');\n })\n .finally(() => {\n this.setState({uploading: false});\n });\n } else {\n this.setState({uploading: true});\n // Convert blob to base64-encoded bits.\n blobToBase64(blob)\n .then(b64 => {\n const du = makeImageUrl({data: b64.bits, type: mime});\n this.setState({source: du});\n this.props.onUpdateTopicDesc(this.props.topic, theCard(null, du));\n this.setState({uploading: false});\n });\n }\n };\n\n if (width > AVATAR_SIZE || height > AVATAR_SIZE || width != height) {\n // Avatar is not square or too large even after cropping. Shrink it and make square.\n imageScaled(blob, AVATAR_SIZE, AVATAR_SIZE, MAX_EXTERN_ATTACHMENT_SIZE, true)\n .then(scaled => readyToUpload(scaled))\n .catch(err => this.props.onError(err, 'err'));\n } else {\n readyToUpload({mime: mime, blob: blob, width: width, height: height});\n }\n }\n\n handleAvatarCropCancel(img) {\n this.setState({newAvatar: null, newAvatarMime: null});\n }\n\n handleTagsUpdated(tags) {\n // Check if tags have actually changed.\n if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n return;\n }\n this.props.onUpdateTags(tags);\n }\n\n render() {\n if (this.state.newAvatar) {\n return (\n \n );\n }\n\n const editable = this.state.isMe || this.state.owner;\n\n return (\n <>\n \n
\n \n \n {this.state.isMe ?\n
\n
\n
\n {\n (full_name_placeholder) => \n }\n
\n
\n :\n <>\n
\n
\n
\n {\n (group_name_placeholder) => \n }\n
\n
\n
\n
\n \n
\n
\n {\n (private_placeholder) => \n }\n
\n
\n >\n }\n {editable || this.state.description ?\n
\n
\n
\n {\n (private_placeholder) => \n }\n
\n
\n : null\n }\n
\n {editable ?\n <>\n \n {\n (title_tag_manager) => \n }\n >\n : null\n }\n >\n );\n }\n};\n","// View for editing topic (including 'me' topic) parameters.\n\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport TopicDescEdit from '../widgets/topic-desc-edit.jsx';\n\nimport { arrayEqual, asEmail, asPhone } from '../lib/utils.js';\n\nexport default class TopicCommon extends React.Component {\n constructor(props) {\n super(props);\n\n const topic = this.props.tinode.getTopic(this.props.topic);\n const acs = topic.getAccessMode();\n this.state = {\n isMe: this.props.topic == 'me',\n owner: acs && acs.isOwner(),\n credentials: (topic.getCredentials ? topic.getCredentials() : null) || [],\n addCredActive: false,\n addCredInvalid: false,\n newCred: '',\n tags: []\n };\n\n this.previousTagsUpdated = undefined;\n\n this.onTagsUpdated = this.onTagsUpdated.bind(this);\n this.handleTagsUpdated = this.handleTagsUpdated.bind(this);\n this.tnCredsUpdated = this.tnCredsUpdated.bind(this);\n this.handleCredChange = this.handleCredChange.bind(this);\n this.handleCredKeyDown = this.handleCredKeyDown.bind(this);\n this.handleCredEntered = this.handleCredEntered.bind(this);\n }\n\n // No need to separately handle component mount.\n componentDidUpdate(props) {\n const topic = this.props.tinode.getTopic(props.topic);\n if (!topic) {\n return;\n }\n\n topic.onCredsUpdated = this.tnCredsUpdated;\n\n if (topic.onTagsUpdated != this.onTagsUpdated) {\n if (topic.getType() == 'grp') {\n this.previousTagsUpdated = topic.onTagsUpdated;\n topic.onTagsUpdated = this.onTagsUpdated;\n } else {\n this.previousTagsUpdated = undefined;\n }\n }\n\n if (this.state.topic != props.topic) {\n this.setState({topic: props.topic});\n }\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n topic.onCredsUpdated = undefined;\n topic.onTagsUpdated = this.previousTagsUpdated;\n }\n\n tnCredsUpdated(creds) {\n this.setState({credentials: creds || []});\n }\n\n handleCredChange(e) {\n this.setState({newCred: e.target.value, addCredInvalid: false});\n }\n\n handleCredKeyDown(e) {\n if (e.keyCode === 27) {\n // Escape pressed\n this.setState({newCred: '', addCredActive: false});\n } else if (e.keyCode === 13) {\n // Enter pressed\n this.handleCredEntered(e);\n }\n }\n\n handleCredEntered(e) {\n const value = this.state.newCred.trim();\n if (!value) {\n this.setState({addCredActive: false, addCredInvalid: false});\n return;\n }\n\n let val = asPhone(value);\n let method;\n if (val) {\n method = 'tel';\n } else {\n val = asEmail(value);\n if (val) {\n method = 'email';\n }\n }\n if (method) {\n this.props.onCredAdd(method, val);\n this.setState({addCredActive: false, newCred: ''});\n } else {\n this.setState({addCredInvalid: true});\n }\n }\n\n // Server informs that the tags have been updated.\n onTagsUpdated(tags) {\n this.setState({tags: tags});\n\n if (this.previousTagsUpdated && this.previousTagsUpdated != this.onTagsUpdated) {\n this.previousTagsUpdated(tags);\n }\n }\n\n // Request server to change tags.\n handleTagsUpdated(tags) {\n if (!arrayEqual(this.state.tags.slice(0), tags.slice(0))) {\n this.props.onTopicTagsUpdateRequest(this.props.topic, tags);\n }\n }\n\n render() {\n const credentials = [];\n if (this.state.isMe) {\n this.state.credentials.map((cred) => {\n credentials.push(\n \n );\n });\n }\n\n return (\n \n
\n {this.state.isMe ?\n <>\n
\n
\n
\n
\n {credentials}\n {this.state.addCredActive ?\n
\n : null}\n
\n
\n
\n >\n : null\n }\n
\n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport InPlaceEdit from './in-place-edit.jsx';\n\nimport { NO_ACCESS_MODE } from '../config.js';\n\nconst messages = defineMessages({\n clear_messages: {\n id: 'action_clear_messages',\n defaultMessage: 'Clear Messages',\n description: 'Flat button [Clear Messages] (soft-delete messages)'\n },\n clear_messages_warning: {\n id: 'clear_messages_warning',\n defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',\n description: 'Alert dialog warning when deleting all messages.'\n },\n delete_messages: {\n id: 'action_delete_messages',\n defaultMessage: 'Clear Messages for All',\n description: 'Flat button [Clear for All] (hard-delete all messages)'\n },\n delete_messages_warning: {\n id: 'delete_messages_warning',\n defaultMessage: 'Are you sure you want to delete all messages for everyone? It cannot be undone.',\n description: 'Alert dialog warning when hard-deleting all messages.'\n },\n topic_delete: {\n id: 'topic_delete',\n defaultMessage: 'Delete Conversation',\n description: 'Alert title when deleting the topic.'\n },\n topic_delete_warning: {\n id: 'topic_delete_warning',\n defaultMessage: 'Are you sure you want to delete this conversation? It cannot be undone.',\n description: 'Alert warning when deleting entire topic'\n },\n leave_chat: {\n id: 'action_leave_chat',\n defaultMessage: 'Leave Conversation',\n description: 'Flat button [Leave Conversation]'\n },\n leave_chat_warning: {\n id: 'leave_chat_warning',\n defaultMessage: 'Are you sure you want to leave this conversation?',\n description: 'Alert dialog warning when unsubscribing from a chat.'\n },\n block_contact: {\n id: 'action_block_contact',\n defaultMessage: \"Block Contact\",\n description: \"Flat button [Block Contact]\"\n },\n block_contact_warning: {\n id: 'block_contact_warning',\n defaultMessage: 'Are you sure you want to block this contact?',\n description: 'Alert dialog warning when blocking a contact.'\n },\n report_chat: {\n id: 'action_report_chat',\n defaultMessage: 'Report Conversation',\n description: 'Flat button [Report Group]'\n },\n report_chat_warning: {\n id: 'report_chat_warning',\n defaultMessage: 'Are you sure you want to block and report this conversation?',\n description: 'Alert dialog warning when reporting a conversation for abuse'\n },\n other_user: {\n id: 'label_other_user',\n defaultMessage: 'Other',\n description: 'Label for the other user when the user is unnamed'\n },\n});\n\nclass TopicSecurity extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleDeleteTopic = this.handleDeleteTopic.bind(this);\n this.handleDeleteMessages = this.handleDeleteMessages.bind(this);\n this.handleLeave = this.handleLeave.bind(this);\n this.handleBlock = this.handleBlock.bind(this);\n this.handleReport = this.handleReport.bind(this);\n }\n\n handleDeleteTopic(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.topic_delete), // title\n formatMessage(messages.topic_delete_warning), // content\n (() => { this.props.onDeleteTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleDeleteMessages(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(this.props.deleter ? messages.delete_messages : messages.clear_messages), // title\n formatMessage(this.props.deleter ? messages.delete_messages_warning : messages.clear_messages_warning), // content\n (() => { this.props.onDeleteMessages(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleLeave(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.leave_chat), // title\n formatMessage(messages.leave_chat_warning), // content\n (() => { this.props.onLeaveTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleBlock(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.block_contact), // title\n formatMessage(messages.block_contact_warning), // content\n (() => { this.props.onBlockTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n handleReport(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.report_chat), // title\n formatMessage(messages.report_chat_warning), // content\n (() => { this.props.onReportTopic(this.props.topic); }), // onConfirm\n null, // \"OK\"\n true, // Show Reject button\n null // \"Cancel\"\n );\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n return (\n \n
\n
\n
\n {this.props.groupTopic ?\n <>\n
\n {e.preventDefault(); this.props.onLaunchPermissionsEditor('want')}}>\n {this.props.access}\n \n
\n {!this.props.channel ?\n
\n
\n \n
\n
\n
Auth: {\n e.preventDefault();\n if (this.props.owner) {\n this.props.onLaunchPermissionsEditor('auth');\n }\n }}>{this.props.auth}\n
\n
Anon: {\n e.preventDefault();\n if (this.props.owner) {\n this.props.onLaunchPermissionsEditor('anon');\n }\n }}>{this.props.anon}\n
\n
\n
\n :\n null\n }\n >\n :\n
\n
\n \n
\n
\n
\n {e.preventDefault(); this.props.onLaunchPermissionsEditor('want')}}>\n {this.props.access}\n \n
\n
{this.props.fullName ? this.props.fullName : formatMessage(messages.other_user)}:\n {e.preventDefault(); this.props.onLaunchPermissionsEditor('given')}}>\n {this.props.modeGiven2}\n \n
\n
\n
\n }\n
\n
\n );\n }\n};\n\nexport default injectIntl(TopicSecurity);\n","// InfoView: panel with topic/user info.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport BadgeList from '../widgets/badge-list.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport ContactList from '../widgets/contact-list.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupManager from '../widgets/group-manager.jsx';\nimport MenuCancel from '../widgets/menu-cancel.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\nimport TopicCommon from '../widgets/topic-common.jsx';\nimport TopicSecurity from '../widgets/topic-security.jsx';\n\nimport { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH, NO_ACCESS_MODE } from '../config.js';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { arrayEqual, theCard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n info: {\n id: 'panel_title_info',\n description: 'Title for InfoView',\n defaultMessage: 'Info'\n },\n general: {\n id: 'panel_title_general',\n description: 'Title for TopicCommon.',\n defaultMessage: 'General'\n },\n security: {\n id: 'panel_title_security',\n description: 'Title for TopicSecirity and AccSecurity.',\n defaultMessage: 'Security'\n },\n members: {\n id: 'panel_title_members',\n description: 'Title for managing group members view.',\n defaultMessage: 'Members'\n },\n crop: {\n id: 'panel_title_crop',\n description: 'Title for AvatarCropView.',\n defaultMessage: 'Drag to Adjust'\n },\n perm_want: {\n id: 'requested_permissions',\n defaultMessage: 'Requested',\n description: 'Title for permissions'\n },\n perm_given: {\n id: 'granted_permissions',\n defaultMessage: 'Granted',\n description: 'Title for permissions'\n },\n perm_auth: {\n id: 'permissions_authenticated',\n defaultMessage: 'Authenticated',\n description: 'Title for editing default authenticated permissions'\n },\n perm_anon: {\n id: 'permissions_anonymous',\n defaultMessage: 'Anonymous',\n description: 'Title for editing default anonymous permissions'\n },\n perm_user: {\n id: 'permissions_user',\n defaultMessage: 'User\\'s Permissions',\n description: 'Title for editing user\\'s permissions'\n },\n edit_permissions: {\n id: 'menu_item_edit_permissions',\n defaultMessage: 'Edit permissions',\n description: 'Menu item [Edit permissions]'\n },\n});\n\nfunction _clip(str, length) {\n return str && str.substring(0, length);\n}\n\nclass InfoView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n topic: null,\n owner: false,\n admin: false,\n sharer: false,\n deleter: false,\n muted: false,\n address: null,\n groupTopic: undefined,\n channel: undefined,\n fullName: undefined,\n description: undefined,\n avatar: null,\n private: null,\n selectedContact: null,\n access: null,\n modeGiven: null,\n modeWant: null,\n modeGiven2: null, // P2P topic, the other user mode given\n modeWant2: null, // P2P topic, the other user mode want\n auth: null,\n anon: null,\n contactList: [],\n trustedBadges: [],\n previousMetaDesc: undefined,\n previousSubsUpdated: undefined,\n };\n\n this.resetSubs = this.resetSubs.bind(this);\n this.resetDesc = this.resetDesc.bind(this);\n this.resetTags = this.resetTags.bind(this);\n this.onMetaDesc = this.onMetaDesc.bind(this);\n this.onSubsUpdated = this.onSubsUpdated.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleMuted = this.handleMuted.bind(this);\n this.handleUnarchive = this.handleUnarchive.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleShowAddMembers = this.handleShowAddMembers.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleMemberSelected = this.handleMemberSelected.bind(this);\n this.handleContextMenu = this.handleContextMenu.bind(this);\n this.handleBackNavigate = this.handleBackNavigate.bind(this);\n }\n\n // No need to separately handle component mount.\n componentDidUpdate(props) {\n const topic = this.props.tinode.getTopic(props.topic);\n if (!topic) {\n return;\n }\n\n if (this.onMetaDesc != topic.onMetaDesc) {\n this.previousMetaDesc = topic.onMetaDesc;\n topic.onMetaDesc = this.onMetaDesc;\n\n this.previousSubsUpdated = topic.onSubsUpdated;\n topic.onSubsUpdated = this.onSubsUpdated;\n }\n\n if (this.state.topic != props.topic) {\n this.setState({topic: props.topic});\n this.resetDesc(topic, props);\n this.resetSubs(topic, props);\n this.resetTags(topic);\n }\n }\n\n componentWillUnmount() {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.setState({topic: null});\n topic.onMetaDesc = this.previousMetaDesc;\n topic.onSubsUpdated = this.previousSubsUpdated;\n }\n\n resetSubs(topic, props) {\n const newState = {contactList: []};\n if (topic.getType() == 'p2p') {\n // Fetch the other party in the p2p conversation.\n // Topic may not be ready yet, so check if user is found.\n const user2 = topic.subscriber(props.topic);\n if (user2) {\n newState.modeGiven2 = user2.acs.getGiven();\n newState.modeWant2 = user2.acs.getWant();\n } else {\n newState.modeGiven2 = NO_ACCESS_MODE;\n newState.modeWant2 = NO_ACCESS_MODE;\n }\n } else {\n topic.subscribers((sub) => {\n newState.contactList.push(sub);\n }, this);\n }\n\n this.setState(newState);\n }\n\n resetDesc(topic, props) {\n const defacs = topic.getDefaultAccess() || {};\n const acs = topic.getAccessMode();\n\n const badges = [];\n if (topic.trusted) {\n for (const [key, val] of Object.entries(topic.trusted)) {\n if (val) {\n badges.push(key);\n }\n }\n }\n\n this.setState({\n owner: acs && acs.isOwner(),\n admin: acs && acs.isAdmin(),\n sharer: acs && acs.isSharer(),\n deleter: acs && acs.isDeleter(),\n muted: acs && acs.isMuted(),\n\n fullName: _clip(topic.public ? topic.public.fn : undefined, MAX_TITLE_LENGTH),\n description: _clip(topic.public ? topic.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),\n avatar: makeImageUrl(topic.public ? topic.public.photo : null),\n trustedBadges: badges,\n private: _clip(topic.private ? topic.private.comment : null, MAX_TITLE_LENGTH),\n archived: topic.isArchived(),\n address: topic.name,\n groupTopic: topic.isGroupType(),\n channel: topic.isChannelType() || topic.chan,\n access: acs ? acs.getMode() : undefined,\n modeGiven: acs ? acs.getGiven() : undefined,\n modeWant: acs ? acs.getWant() : undefined,\n auth: defacs.auth,\n anon: defacs.anon\n });\n }\n\n resetTags(topic) {\n if (topic.getType() != 'grp') {\n return;\n }\n\n const acs = topic.getAccessMode();\n if (acs && acs.isOwner()) {\n // Requesting tags: owner is editing the topic.\n topic.getMeta(topic.startMetaQuery().withTags().build());\n }\n }\n\n onMetaDesc(desc) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetDesc(topic, this.props);\n\n if (this.previousMetaDesc && this.previousMetaDesc != this.onMetaDesc) {\n this.previousMetaDesc(desc);\n }\n }\n\n onSubsUpdated(subs) {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n this.resetSubs(topic, this.props);\n\n if (this.previousSubsUpdated && this.previousSubsUpdated != this.onSubsUpdated) {\n this.previousSubsUpdated(subs);\n }\n }\n\n handleImageChanged(img) {\n this.setState({avatar: img});\n this.props.onTopicDescUpdate(this.props.topic, theCard(null, img || Tinode.DEL_CHAR), null);\n }\n\n handleMuted(ignored, checked) {\n this.setState({muted: checked});\n this.props.onChangePermissions(this.props.topic, checked ? '-P' : '+P');\n }\n\n handleUnarchive(ignored, ignored2) {\n this.props.onTopicUnArchive(this.props.topic);\n }\n\n handlePermissionsChanged(which, perm) {\n switch (which) {\n case 'auth':\n this.props.onTopicDescUpdateRequest(this.props.topic, null, null, {auth: perm});\n break;\n case 'anon':\n this.props.onTopicDescUpdateRequest(this.props.topic, null, null, {anon: perm});\n break;\n case 'mode':\n case 'want':\n this.props.onChangePermissions(this.props.topic, perm);\n break;\n case 'given':\n this.props.onChangePermissions(this.props.topic, perm, this.props.topic);\n break;\n case 'user':\n this.props.onChangePermissions(this.props.topic, perm, this.state.userPermissionsEdited);\n break;\n }\n\n this.handleBackNavigate();\n }\n\n handleLaunchPermissionsEditor(which, uid) {\n const {formatMessage} = this.props.intl;\n let toEdit, toCompare, toSkip, titleEdit, titleCompare, userTitle, userAvatar;\n switch (which) {\n case 'mode':\n toEdit = this.state.access;\n break;\n case 'want':\n toEdit = this.state.modeWant;\n toCompare = this.state.modeGiven;\n if (this.state.owner) {\n // Don't allow owner to unset 'O' permission. All other permissions are OK.\n toSkip = 'O';\n } else {\n // Allow accepting any of 'ASDO' permissions but don't allow asking for them.\n toSkip = Tinode.AccessMode.encode(Tinode.AccessMode.diff('ASDO', this.state.modeGiven));\n if (this.state.channel) {\n // Channels are read-only.\n toSkip += 'W';\n }\n }\n titleEdit = formatMessage(messages.perm_want);\n titleCompare = formatMessage(messages.perm_given);\n break;\n case 'given':\n toEdit = this.state.modeGiven2;\n toCompare = this.state.modeWant2;\n toSkip = this.state.groupTopic ? (this.state.owner ? '' : 'O') : 'ASDO';\n titleEdit = formatMessage(messages.perm_given);\n titleCompare = formatMessage(messages.perm_want);\n break;\n case 'auth':\n toEdit = this.state.auth;\n toSkip = 'O';\n break;\n case 'anon':\n toEdit = this.state.anon;\n toSkip = 'O';\n break;\n case 'user': {\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n const user = topic.subscriber(uid);\n if (!user || !user.acs) {\n return;\n }\n toEdit = user.acs.getGiven();\n toCompare = user.acs.getWant();\n toSkip = this.state.owner ? '' : 'O';\n titleEdit = formatMessage(messages.perm_given);\n titleCompare = formatMessage(messages.perm_want);\n if (user.public) {\n userTitle = user.public.fn;\n userAvatar = user.public.photo;\n }\n break;\n }\n default:\n console.error(\"Unknown permission editing mode '\" + which + \"'\");\n return;\n }\n this.setState({\n userPermissionsEdited: uid,\n userPermissionsTitle: userTitle,\n userPermissionsAvatar: userAvatar,\n editedPermissions: toEdit,\n immutablePermissions: toCompare,\n editedPermissionsTitle: titleEdit,\n immutablePermissionsTitle: titleCompare,\n editedPermissionsSkipped: toSkip,\n });\n this.props.onNavigate(`perm/${which}`);\n }\n\n handleShowAddMembers(e) {\n e.preventDefault();\n this.props.onInitFind();\n this.props.onNavigate('members');\n }\n\n handleMemberUpdateRequest(members, added, removed) {\n this.props.onMemberUpdateRequest(this.props.topic, added, removed);\n this.props.onNavigate('info');\n }\n\n handleMemberSelected(uid) {\n this.setState({selectedContact: uid});\n }\n\n handleBackNavigate() {\n const args = (this.props.panel || 'info').split('/');\n if (args[0] == 'info') {\n this.props.onNavigate(null);\n } else if (args[0] == 'perm') {\n if (args[1] == 'user') {\n this.props.onNavigate('info');\n } else {\n this.props.onNavigate('security');\n }\n } else {\n this.props.onNavigate('info');\n }\n }\n\n handleContextMenu(params) {\n const {formatMessage} = this.props.intl;\n const topic = this.props.tinode.getTopic(this.props.topic);\n if (!topic) {\n return;\n }\n const user = topic.subscriber(params.topicName);\n if (!user || !user.acs) {\n return;\n }\n\n const isMe = this.props.tinode.isMe(params.topicName);\n const menuItems = [\n {title: formatMessage(messages.edit_permissions), handler: () => {\n this.handleLaunchPermissionsEditor(isMe ? 'want' : 'user', params.topicName);\n }}\n ];\n if (!isMe) {\n menuItems.push('member_delete');\n }\n menuItems.push(user.acs.isMuted() ? 'member_unmute' : 'member_mute');\n if (!isMe) {\n menuItems.push(user.acs.isJoiner() ? 'member_block' : 'member_unblock');\n }\n this.props.showContextMenu({\n topicName: this.props.topic,\n x: params.x,\n y: params.y,\n user: params.topicName}, menuItems);\n }\n\n render() {\n const args = (this.props.panel || 'info').split('/');\n const view = args[0];\n args.shift();\n\n const {formatMessage} = this.props.intl;\n const panelTitle = formatMessage((view == 'perm' ? messages['perm_' + args[0]] : messages[view])\n || messages['info']);\n\n return (\n \n
\n
{panelTitle}
\n
\n \n
\n
\n {this.props.displayMobile ?\n
: null}\n {view == 'members' ?\n
\n :\n view == 'perm' && args.length > 0 ?\n
this.handlePermissionsChanged(args[0], mode)}\n onCancel={this.handleBackNavigate} />\n :\n view == 'general' ?\n \n :\n view == 'security' ?\n \n :\n \n
\n
\n
\n \n \n
\n {this.state.archived ?\n
\n \n \n
\n :\n null\n }\n
\n
\n {this.state.groupTopic && this.state.sharer ?\n <>\n
\n
\n \n
\n
\n
{\n (no_members) => \n }\n >\n :\n null\n }\n
\n }\n \n );\n }\n};\n\nexport default injectIntl(InfoView);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport FileProgress from './file-progress.jsx';\nimport { bytesToHumanSize } from '../lib/strformat.js';\nimport { isUrlRelative, sanitizeUrl } from '../lib/utils.js';\n\nexport default class Attachment extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n downloader: null,\n progress: 0\n };\n\n this.downloadFile = this.downloadFile.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n downloadFile(url, filename, mimetype) {\n if (!url) {\n this.props.onError(\"Invalid download URL '\" + url + \"'\");\n return;\n }\n\n const downloader = this.props.tinode.getLargeFileHelper();\n this.setState({downloader: downloader});\n downloader.download(url, filename, mimetype,\n (loaded) => {\n this.setState({progress: loaded / this.props.size});\n },\n (err) => {\n this.props.onError(err, 'err');\n }\n ).then(() => {\n this.setState({downloader: null, progress: 0});\n }).catch((err) => {\n if (err) {\n this.props.onError(\"Error downloading file: \" + err.message, 'err');\n }\n this.setState({downloader: null, progress: 0});\n });\n }\n\n handleCancel() {\n if (this.props.uploading) {\n this.props.onCancelUpload();\n } else if (this.state.downloader) {\n this.state.downloader.cancel();\n }\n }\n\n render() {\n let filename = this.props.filename || 'file_attachment';\n if (filename.length > 36) {\n filename = filename.substr(0, 16) + '...' + filename.substr(-16);\n }\n let size = this.props.size > 0 ?\n ({bytesToHumanSize(this.props.size)}) :\n null;\n\n // Detect if the download URL is relative or absolute.\n // If the URL is relative use LargeFileHelper to attach authentication\n // credentials to the request.\n let url, helperFunc;\n if (!this.props.uploading && !this.state.downloader && isUrlRelative(this.props.downloadUrl)) {\n // Relative URL. Use download helper.\n url = '#';\n helperFunc = (e) => {\n e.preventDefault();\n this.downloadFile(this.props.downloadUrl, this.props.filename, this.props.mimetype);\n };\n } else {\n url = sanitizeUrl(this.props.downloadUrl);\n helperFunc = null;\n }\n const downloadWidget = <>file_download >;\n return (\n \n
insert_drive_file
\n
\n
{filename} {size}
\n {this.props.uploading || this.state.downloader ?\n
\n :\n
\n }\n
\n
\n );\n }\n};\n","// Received/read indicator.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { deliveryMarker } from '../lib/utils.js';\n\nconst messages = defineMessages({\n message_sending: {\n id: 'message_sending',\n defaultMessage: 'sending...',\n description: 'Message being sent, in place of time stamp'\n },\n message_sending_failed: {\n id: 'message_sending_failed',\n defaultMessage: 'failed',\n description: 'Failed to send message, in place of time stamp'\n }\n});\n\nclass ReceivedMarker extends React.PureComponent {\n render() {\n const {formatMessage} = this.props.intl;\n let timestamp;\n if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {\n timestamp = formatMessage(messages.message_sending);\n } else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n timestamp = formatMessage(messages.message_sending_failed);\n } else {\n timestamp = this.props.timestamp.toLocaleTimeString(this.props.intl.locale, { timeStyle: 'short' });\n }\n\n const icon = deliveryMarker(this.props.received);\n const marker = icon ? {icon.name} : null;\n\n return (\n \n {timestamp}{'\\u00a0'}{marker}\n \n );\n }\n};\n\nexport default injectIntl(ReceivedMarker);\n","// Single chat message bubble, sent or received.\n\nimport React from 'react';\nimport { FormattedMessage, injectIntl } from 'react-intl';\nimport { Drafty } from 'tinode-sdk';\n\nimport Attachment from './attachment.jsx';\nimport LetterTile from './letter-tile.jsx';\nimport ReceivedMarker from './received-marker.jsx'\n\nimport { fullFormatter } from '../lib/formatters.js';\nimport { sanitizeUrl } from '../lib/utils.js';\n\nclass BaseChatMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n progress: 0\n };\n\n if (props.uploader) {\n props.uploader.onProgress = this.handleProgress.bind(this);\n }\n\n this.handleImagePreview = this.handleImagePreview.bind(this);\n this.handleFormButtonClick = this.handleFormButtonClick.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleCancelUpload = this.handleCancelUpload.bind(this);\n this.handleQuoteClick = this.handleQuoteClick.bind(this);\n\n this.formatterContext = {\n formatMessage: props.intl.formatMessage.bind(props.intl),\n viewportWidth: props.viewportWidth,\n authorizeURL: props.tinode.authorizeURL.bind(props.tinode),\n onImagePreview: this.handleImagePreview,\n onFormButtonClick: this.handleFormButtonClick,\n onQuoteClick: this.handleQuoteClick\n };\n }\n\n handleImagePreview(e) {\n e.preventDefault();\n this.props.onImagePreview({\n url: e.target.src,\n filename: e.target.title,\n width: e.target.dataset.width,\n height: e.target.dataset.height,\n size: e.target.dataset.size,\n type: e.target.dataset.mime\n });\n }\n\n handleFormButtonClick(e) {\n e.preventDefault();\n const data = {\n seq: this.props.seq\n };\n data.resp = {};\n if (e.target.dataset.name) {\n data.resp[e.target.dataset.name] = e.target.dataset.val ? e.target.dataset.val :\n e.target.dataset.val === undefined ? 1 : '' + e.target.dataset.val;\n }\n if (e.target.dataset.act == 'url') {\n data.ref = sanitizeUrl(e.target.dataset.ref) || 'about:blank';\n }\n const text = e.target.dataset.title || 'unknown';\n this.props.onFormResponse(e.target.dataset.act, text, data);\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n const menuItems = [];\n if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {\n menuItems.push('menu_item_send_retry');\n }\n if (this.props.userIsWriter &&\n this.props.received > Tinode.MESSAGE_STATUS_FAILED &&\n this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {\n menuItems.push('menu_item_reply');\n }\n menuItems.push('menu_item_forward');\n\n this.props.showContextMenu({\n seq: this.props.seq,\n content: this.props.content,\n userFrom: this.props.userFrom,\n userName: this.props.userName,\n y: e.pageY,\n x: e.pageX,\n pickReply: this.props.pickReply\n }, menuItems);\n }\n\n handleProgress(ratio) {\n this.setState({progress: ratio});\n }\n\n handleCancelUpload() {\n this.props.onCancelUpload(this.props.seq, this.props.uploader);\n }\n\n handleQuoteClick(e) {\n e.preventDefault();\n e.stopPropagation();\n const replyToSeq = this.props.replyToSeq;\n if (replyToSeq) {\n this.props.onQuoteClick(replyToSeq);\n }\n }\n\n render() {\n const sideClass = this.props.sequence + ' ' + (this.props.response ? 'left' : 'right');\n const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';\n const avatar = this.props.userAvatar || true;\n const fullDisplay = (this.props.isGroup && this.props.response &&\n (this.props.sequence == 'single' || this.props.sequence == 'last'));\n\n let content = this.props.content;\n const attachments = [];\n if (this.props.mimeType == Drafty.getContentType() && Drafty.isValid(content)) {\n Drafty.attachments(content, (att, i) => {\n if (att.mime == 'application/json') {\n // Don't show json objects as attachments.\n // They are not meant for users.\n return;\n }\n attachments.push();\n }, this);\n const tree = Drafty.format(content, fullFormatter, this.formatterContext);\n content = React.createElement(React.Fragment, null, tree);\n } else if (typeof content != 'string') {\n content = <>warning_amber \n >\n }\n\n return (\n \n {this.props.isGroup && this.props.response ?\n \n {fullDisplay ?\n :\n null}\n
:\n null}\n \n
\n
\n
\n {content}\n {attachments}\n
\n {this.props.timestamp ?\n
\n : null}\n
\n {this.props.showContextMenu ?\n
\n \n expand_more\n \n : null\n }\n
\n {fullDisplay ?\n
\n {this.props.userName ||\n \n }\n
\n : null\n }\n
\n \n );\n }\n};\n\nconst IntlChatMessage = injectIntl(BaseChatMessage);\nconst ChatMessage = React.forwardRef((props, ref) => );\n\nexport default ChatMessage;\n","// Send message form.\nimport React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\nimport { Drafty } from 'tinode-sdk';\n\nimport { KEYPRESS_DELAY } from '../config.js';\nimport { filePasted } from '../lib/blob-helpers.js';\nimport { replyFormatter } from '../lib/formatters.js';\n\nconst messages = defineMessages({\n messaging_disabled: {\n id: 'messaging_disabled_prompt',\n defaultMessage: 'Messaging disabled',\n description: 'Prompt in SendMessage in read-only topic'\n },\n type_new_message: {\n id: 'new_message_prompt',\n defaultMessage: 'New message',\n description: 'Prompt in send message field'\n },\n add_image_caption: {\n id: 'image_caption_prompt',\n defaultMessage: 'Image caption',\n description: 'Prompt in SendMessage for attached image'\n },\n file_attachment_too_large: {\n id: 'file_attachment_too_large',\n defaultMessage: 'The file size {size} exceeds the {limit} limit.',\n description: 'Error message when attachment is too large'\n },\n cannot_initiate_upload: {\n id: 'cannot_initiate_file_upload',\n defaultMessage: 'Cannot initiate file upload.',\n description: 'Generic error messagewhen attachment fails'\n },\n});\n\nclass SendMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n quote: null,\n message: '',\n // Make initial keypress time as if it happened 5001 milliseconds in the past.\n keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1\n };\n\n this.handlePasteEvent = this.handlePasteEvent.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleSend = this.handleSend.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleMessageTyping = this.handleMessageTyping.bind(this);\n\n this.handleQuoteClick = this.handleQuoteClick.bind(this);\n\n this.formatReply = this.formatReply.bind(this);\n }\n\n componentDidMount() {\n if (this.messageEditArea) {\n this.messageEditArea.addEventListener('paste', this.handlePasteEvent, false);\n }\n\n this.setState({quote: this.formatReply()});\n }\n\n componentWillUnmount() {\n if (this.messageEditArea) {\n this.messageEditArea.removeEventListener('paste', this.handlePasteEvent, false);\n }\n }\n\n componentDidUpdate(prevProps) {\n if (this.messageEditArea) {\n this.messageEditArea.focus();\n }\n\n if (prevProps.reply != this.props.reply) {\n this.setState({quote: this.formatReply()});\n }\n }\n\n formatReply() {\n return this.props.reply ?\n Drafty.format(this.props.reply.content, replyFormatter, {\n formatMessage: this.props.intl.formatMessage.bind(this.props.intl),\n authorizeURL: this.props.tinode.authorizeURL.bind(this.props.tinode)\n }) : null;\n }\n\n handlePasteEvent(e) {\n if (this.props.disabled) {\n return;\n }\n // FIXME: handle large files too.\n if (filePasted(e,\n (bits, mime, width, height, fname) => {\n this.props.onAttachImage(mime, bits, width, height, fname);\n },\n (mime, bits, fname) => {\n this.props.onAttachFile(mime, bits, fname);\n },\n this.props.onError)) {\n\n // If a file was pasted, don't paste base64 data into input field.\n e.preventDefault();\n }\n }\n\n handleAttachImage(e) {\n if (e.target.files && e.target.files.length > 0) {\n this.props.onAttachImage(e.target.files[0]);\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleAttachFile(e) {\n const {formatMessage} = this.props.intl;\n if (e.target.files && e.target.files.length > 0) {\n this.props.onAttachFile(e.target.files[0]);\n }\n // Clear the value so the same file can be uploaded again.\n e.target.value = '';\n }\n\n handleSend(e) {\n e.preventDefault();\n const message = this.state.message.trim();\n if (message || this.props.acceptBlank || this.props.noInput) {\n this.props.onSendMessage(message);\n this.setState({message: ''});\n }\n }\n\n /* Send on Enter key */\n handleKeyPress(e) {\n // Remove this if you don't want Enter to trigger send\n if (e.key === 'Enter') {\n // Have Shift-Enter insert a line break instead\n if (!e.shiftKey) {\n e.preventDefault();\n e.stopPropagation();\n\n this.handleSend(e);\n }\n }\n }\n\n handleMessageTyping(e) {\n const newState = {message: e.target.value};\n if (this.props.onKeyPress) {\n const now = new Date().getTime();\n if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {\n this.props.onKeyPress();\n newState.keypressTimestamp = now;\n }\n }\n this.setState(newState);\n }\n\n handleQuoteClick(e) {\n e.preventDefault();\n e.stopPropagation();\n if (this.props.reply && this.props.onQuoteClick) {\n const replyToSeq = this.props.reply.seq;\n this.props.onQuoteClick(replyToSeq);\n }\n }\n\n render() {\n const { formatMessage } = this.props.intl;\n const prompt = this.props.disabled ?\n formatMessage(messages.messaging_disabled) :\n (this.props.messagePrompt ?\n formatMessage(messages[this.props.messagePrompt]) :\n formatMessage(messages.type_new_message));\n\n const quote =\n (\n
\n {this.state.quote}\n
);\n\n return (\n \n {this.state.quote && !this.props.noInput ? quote : null}\n
\n
\n );\n }\n};\n\nexport default injectIntl(SendMessage);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { bytesToHumanSize, shortenFileName } from '../lib/strformat.js';\n\n// Get material icon name from mime type.\nfunction iconFromMime(mime) {\n // If more icons become available in material icons, add them to this mime-to-icon mapping.\n const mimeToIcon = {default: 'insert_drive_file', image: 'image', text: 'description', video: 'theaters'};\n\n return mimeToIcon[mime] || mimeToIcon[(mime || '').split('/')[0]] || mimeToIcon['default'];\n}\n\nexport default class DocPreview extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleSendDoc = this.handleSendDoc.bind(this);\n }\n\n handleSendDoc(caption) {\n this.props.onClose();\n this.props.onSendMessage(this.props.content.file);\n }\n\n render() {\n if (!this.props.content) {\n return null;\n }\n return (\n \n
\n
\n
\n
{iconFromMime(this.props.content.type)}\n
{shortenFileName(this.props.content.name, 24) || '-'}
\n
{this.props.content.type || 'application/octet-stream'}
\n
{bytesToHumanSize(this.props.content.size)}
\n
\n
\n
\n
\n );\n }\n};\n","// GroupSubs: a list of group subscribers currently online.\n// Show in the topic title bar\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport { MAX_ONLINE_IN_TOPIC } from '../config.js'\n\nimport LetterTile from './letter-tile.jsx';\nimport { makeImageUrl } from '../lib/blob-helpers.js';\n\nexport default class GroupSubs extends React.Component {\n constructor(props) {\n super(props);\n }\n\n render() {\n const usersOnline = [];\n const totalCount = (this.props.subscribers || []).length;\n const countToShow = Math.min(MAX_ONLINE_IN_TOPIC, totalCount);\n\n (this.props.subscribers || []).some((sub) => {\n usersOnline.push(\n \n \n
\n );\n return usersOnline.length == countToShow;\n });\n\n return (\n {usersOnline} {totalCount > countToShow ?\n \n \n : null}\n
\n );\n }\n};\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { REM_SIZE } from '../config.js';\nimport { fitImageSize } from '../lib/blob-helpers.js';\nimport { bytesToHumanSize, shortenFileName } from '../lib/strformat.js';\n\nexport default class ImagePreview extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n width: 0,\n height: 0\n };\n\n this.handleSendImage = this.handleSendImage.bind(this);\n }\n\n assignWidth(node) {\n if (node && !this.state.width) {\n const bounds = node.getBoundingClientRect();\n this.setState({\n width: bounds.width | 0,\n height: bounds.height | 0\n });\n }\n }\n\n handleSendImage(caption) {\n this.props.onClose();\n this.props.onSendMessage(caption, this.props.content.blob);\n }\n\n render() {\n if (!this.props.content) {\n return null;\n }\n\n const dim = fitImageSize(this.props.content.width, this.props.content.height,\n this.state.width, this.state.height, false);\n const size = dim ? { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' } :\n ((this.props.content.width > this.props.content.height) ? {width: '100%'} : {height: '100%'});\n size.maxWidth = '100%';\n size.maxHeight = '100%';\n\n // Average font aspect ratio is ~0.5; File name takes 1/3 of the viewport width.\n const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12);\n const fname = shortenFileName(this.props.content.name, maxlength) || '-';\n\n const width = this.props.content.width || '-';\n const height = this.props.content.height || '-';\n return (\n \n
\n
this.assignWidth(node)}>\n

\n
\n {this.props.onSendMessage ?\n
\n :\n }\n
\n );\n }\n};\n","// A single topic or user.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class Invitation extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleButtonAction = this.handleButtonAction.bind(this);\n }\n\n handleButtonAction(evt, data) {\n evt.preventDefault();\n this.props.onAction(data);\n }\n\n render() {\n return (\n \n
\n \n
\n
\n \n \n \n
\n
\n );\n }\n};\n","/* This is just a static page to display when no conversation is selected. */\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { APP_NAME } from '../config.js';\n\nexport default class LogoView extends React.PureComponent {\n render() {\n const version = APP_NAME + ' (' + Tinode.getLibrary() + ')';\n return (\n \n
\n
\n
\n Tinode Web
\n \n
{version}
\n
{this.props.serverVersion} ({this.props.serverAddress})
\n
\n
\n );\n }\n};\n","// Message bubble with non-payload content, such as an indicator of deleted content or a date stamp.\n\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nexport default class MetaMessage extends React.PureComponent {\n constructor(props) {\n super(props);\n }\n\n render() {\n let content = null;\n let bubbleClass = 'bubble';\n if (this.props.deleted) {\n // Message represents a range of deleted messages.\n content = <>block \n \n >\n bubbleClass += ' deleted';\n } else if (this.props.date) {\n // A date badge.\n content = <>{this.props.date}>\n bubbleClass += ' date';\n }\n\n if (!content) {\n return <>{null}>\n }\n\n return (\n \n \n \n );\n }\n};\n","// Utility class for hash navigation.\n\n// Parse hash as in http://www.example.com/path#hash as if it were\n// path and arguments.\nexport default class HashNavigation {\n static parseUrlHash(hash) {\n // Split path from args, path -> parts[0], args->path[1]\n const parts = hash.split('?', 2);\n const params = {};\n let path = [];\n if (parts[0]) {\n path = parts[0].replace('#', '').split('/');\n }\n if (parts[1]) {\n parts[1].split('&').forEach(function(part) {\n const item = part.split('=');\n if (item[0]) {\n params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);\n }\n });\n }\n return {path: path, params: params};\n }\n\n static navigateTo(url) {\n window.location.hash = url;\n }\n\n static composeUrlHash(path, params) {\n let url = path.join('/');\n const args = [];\n for (const key in params) {\n if (params.hasOwnProperty(key)) {\n args.push(key + '=' + params[key]);\n }\n }\n if (args.length > 0) {\n url += '?' + args.join('&');\n }\n return url;\n }\n\n static addUrlParam(hash, key, value) {\n const parsed = this.parseUrlHash(hash);\n parsed.params[key] = value;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static removeUrlParam(hash, key) {\n const parsed = this.parseUrlHash(hash);\n delete parsed.params[key];\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlSidePanel(hash, sidepanel) {\n const parsed = this.parseUrlHash(hash);\n parsed.path[0] = sidepanel;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlInfoPanel(hash, infopanel) {\n const parsed = this.parseUrlHash(hash);\n if (infopanel) {\n parsed.params.info = infopanel;\n } else {\n delete parsed.params.info;\n }\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n\n static setUrlTopic(hash, topic) {\n const parsed = this.parseUrlHash(hash);\n parsed.path[1] = topic;\n // Close InfoView on topic change.\n delete parsed.params.info;\n return this.composeUrlHash(parsed.path, parsed.params);\n }\n}\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\nconst Drafty = Tinode.Drafty;\n\nimport ChatMessage from '../widgets/chat-message.jsx';\nimport ContactBadges from '../widgets/contact-badges.jsx';\nimport DocPreview from '../widgets/doc-preview.jsx';\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport GroupSubs from '../widgets/group-subs.jsx';\nimport ImagePreview from '../widgets/image-preview.jsx';\nimport Invitation from '../widgets/invitation.jsx';\nimport LetterTile from '../widgets/letter-tile.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport LogoView from './logo-view.jsx';\nimport MetaMessage from '../widgets/meta-message.jsx';\nimport SendMessage from '../widgets/send-message.jsx';\n\nimport { DEFAULT_P2P_ACCESS_MODE, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY,\n MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE,\n READ_DELAY, QUOTED_REPLY_LENGTH } from '../config.js';\nimport { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';\nimport HashNavigation from '../lib/navigation.js';\nimport { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';\n\n// Run timer with this frequency (ms) for checking notification queue.\nconst NOTIFICATION_EXEC_INTERVAL = 300;\n// Scroll distance before [go to latest message] button is shown.\nconst SHOW_GO_TO_LAST_DIST = 100;\n\nconst messages = defineMessages({\n online_now: {\n id: 'online_now',\n defaultMessage: 'online now',\n description: 'Indicator that the user or topic is currently online',\n },\n last_seen: {\n id: 'last_seen_timestamp',\n defaultMessage: 'Last seen',\n description: 'Label for the timestamp of when the user or topic was last online'\n },\n not_found: {\n id: 'title_not_found',\n defaultMessage: 'Not found',\n description: 'Title shown when topic is not found'\n },\n channel: {\n id: 'channel',\n defaultMessage: 'channel',\n description: 'Subtitle shown for channels in MessagesView instead of last seen'\n },\n file_attachment_too_large: {\n id: 'file_attachment_too_large',\n defaultMessage: 'The file size {size} exceeds the {limit} limit.',\n description: 'Error message when attachment is too large'\n },\n invalid_content: {\n id: 'invalid_content',\n defaultMessage: 'invalid content',\n description: 'Shown when the message is unreadable'\n },\n});\n\n// Checks if the access permissions are granted but not yet accepted.\nfunction isUnconfirmed(acs) {\n if (acs) {\n const ex = acs.getExcessive() || '';\n return acs.isJoiner('given') && (ex.includes('R') || ex.includes('W'));\n }\n return false;\n}\n\nfunction isPeerRestricted(acs) {\n if (acs) {\n const ms = acs.getMissing() || '';\n return acs.isJoiner('want') && (ms.includes('R') || ms.includes('W'));\n }\n return false;\n}\n\nclass MessagesView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = MessagesView.getDerivedStateFromProps(props, {});\n\n this.leave = this.leave.bind(this);\n this.sendMessage = this.sendMessage.bind(this);\n this.retrySend = this.retrySend.bind(this);\n this.sendImageAttachment = this.sendImageAttachment.bind(this);\n this.sendFileAttachment = this.sendFileAttachment.bind(this);\n this.sendKeyPress = this.sendKeyPress.bind(this);\n this.subscribe = this.subscribe.bind(this);\n this.handleScrollReference = this.handleScrollReference.bind(this);\n this.handleScrollEvent = this.handleScrollEvent.bind(this);\n this.handleDescChange = this.handleDescChange.bind(this);\n this.handleSubsUpdated = this.handleSubsUpdated.bind(this);\n this.handleMessageUpdate = this.handleMessageUpdate.bind(this);\n this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);\n this.handleInfoReceipt = this.handleInfoReceipt.bind(this);\n this.handleImagePostview = this.handleImagePostview.bind(this);\n this.handleClosePreview = this.handleClosePreview.bind(this);\n this.handleFormResponse = this.handleFormResponse.bind(this);\n this.handleContextClick = this.handleContextClick.bind(this);\n this.handleShowMessageContextMenu = this.handleShowMessageContextMenu.bind(this);\n this.handleNewChatAcceptance = this.handleNewChatAcceptance.bind(this);\n this.handleEnablePeer = this.handleEnablePeer.bind(this);\n this.handleAttachFile = this.handleAttachFile.bind(this);\n this.handleAttachImage = this.handleAttachImage.bind(this);\n this.handleCancelUpload = this.handleCancelUpload.bind(this);\n this.postReadNotification = this.postReadNotification.bind(this);\n this.clearNotificationQueue = this.clearNotificationQueue.bind(this);\n this.goToLatestMessage = this.goToLatestMessage.bind(this);\n\n this.handlePickReply = this.handlePickReply.bind(this);\n this.handleCancelReply = this.handleCancelReply.bind(this);\n this.handleQuoteClick = this.handleQuoteClick.bind(this);\n\n this.chatMessageRefs = {};\n this.getOrCreateMessageRef = this.getOrCreateMessageRef.bind(this);\n\n this.readNotificationQueue = [];\n this.readNotificationTimer = null;\n }\n\n getOrCreateMessageRef(seqId) {\n if (this.chatMessageRefs.hasOwnProperty(seqId)) {\n return this.chatMessageRefs[seqId];\n }\n const ref = React.createRef();\n this.chatMessageRefs[seqId] = ref;\n return ref;\n }\n\n componentDidMount() {\n if (this.messagesScroller) {\n this.messagesScroller.addEventListener('scroll', this.handleScrollEvent);\n }\n }\n\n componentWillUnmount() {\n if (this.messagesScroller) {\n this.messagesScroller.removeEventListener('scroll', this.handleScrollEvent);\n }\n\n // Flush all notifications.\n this.clearNotificationQueue();\n }\n\n // Scroll last message into view on component update e.g. on message received\n // or vertical shrinking.\n componentDidUpdate(prevProps, prevState) {\n if (this.messagesScroller &&\n (prevState.topic != this.state.topic || prevState.messageCount != this.state.messageCount)) {\n // New message.\n if (this.state.scrollPosition < SHOW_GO_TO_LAST_DIST) {\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -\n this.state.scrollPosition -\n this.messagesScroller.offsetHeight;\n }\n }\n\n const topic = this.props.tinode ? this.props.tinode.getTopic(this.state.topic) : undefined;\n if (this.state.topic != prevState.topic) {\n if (prevState.topic && !Tinode.isNewGroupTopicName(prevState.topic)) {\n this.leave(prevState.topic);\n }\n\n if (topic) {\n topic.onData = this.handleMessageUpdate;\n topic.onAllMessagesReceived = this.handleAllMessagesReceived;\n topic.onInfo = this.handleInfoReceipt;\n topic.onMetaDesc = this.handleDescChange;\n topic.onSubsUpdated = this.handleSubsUpdated;\n topic.onPres = this.handleSubsUpdated;\n }\n }\n\n if (!this.props.applicationVisible) {\n // If application is not visible, flush all unsent 'read' notifications.\n this.clearNotificationQueue();\n } else {\n // Otherwise assume there are unread messages.\n this.postReadNotification(0);\n }\n\n if ((this.state.topic != prevState.topic) || !prevProps.ready) {\n this.subscribe(topic);\n }\n }\n\n static getDerivedStateFromProps(nextProps, prevState) {\n let nextState = {};\n if (!nextProps.topic) {\n // Default state: no topic.\n nextState = {\n messageCount: 0,\n latestClearId: -1,\n onlineSubs: [],\n topic: null,\n title: '',\n avatar: null,\n isVerified: false,\n isStaff: false,\n isDangerous: false,\n deleted: false,\n docPreview: null,\n imagePreview: null,\n imagePostview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false,\n peerMessagingDisabled: false,\n channel: false,\n reply: null,\n showGoToLastButton: false\n };\n } else if (nextProps.topic != prevState.topic) {\n const topic = nextProps.tinode.getTopic(nextProps.topic);\n\n let reply = null;\n if (nextProps.forwardMessage) {\n // We are forwarding a message. Show preview.\n const preview = nextProps.forwardMessage.preview;\n reply = {\n content: preview,\n seq: null\n };\n }\n\n nextState = {\n topic: nextProps.topic,\n docPreview: null,\n imagePreview: null,\n imagePostview: null,\n typingIndicator: false,\n scrollPosition: 0,\n fetchingMessages: false,\n reply: reply,\n showGoToLastButton: false,\n deleted: topic._deleted\n };\n\n if (topic) {\n // Topic exists.\n const subs = [];\n\n if (nextProps.connected) {\n topic.subscribers((sub) => {\n if (sub.online && sub.user != nextProps.myUserId) {\n subs.push(sub);\n }\n });\n }\n\n Object.assign(nextState, {\n onlineSubs: subs\n });\n\n if (topic.public) {\n Object.assign(nextState, {\n title: topic.public.fn,\n avatar: makeImageUrl(topic.public.photo)\n });\n } else {\n Object.assign(nextState, {\n title: '',\n avatar: null\n });\n }\n\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(nextState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (prevState.peerMessagingDisabled) {\n Object.assign(nextState, {\n peerMessagingDisabled: false\n });\n }\n Object.assign(nextState, {\n messageCount: topic.messageCount(),\n latestClearId: topic.maxClearId(),\n channel: topic.isChannelType()\n });\n } else {\n // Invalid topic.\n Object.assign(nextState, {\n messageCount: 0,\n latestClearId: -1,\n onlineSubs: [],\n title: '',\n avatar: null,\n peerMessagingDisabled: false,\n channel: false\n });\n }\n }\n\n if (nextProps.acs) {\n if (nextProps.acs.isWriter() != prevState.isWriter) {\n nextState.isWriter = !prevState.isWriter;\n }\n if (nextProps.acs.isReader() != prevState.isReader) {\n nextState.isReader = !prevState.isReader;\n }\n if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {\n nextState.readingBlocked = !prevState.readingBlocked;\n }\n if (nextProps.acs.isSharer() != prevState.isSharer) {\n nextState.isSharer = !prevState.isSharer;\n }\n } else {\n if (prevState.isWriter) {\n nextState.isWriter = false;\n }\n if (prevState.isReader) {\n nextState.isReader = false;\n }\n if (!prevState.readingBlocked) {\n prevState.readingBlocked = true;\n }\n if (prevState.isSharer) {\n nextState.isSharer = false;\n }\n }\n\n if (isUnconfirmed(nextProps.acs) == !prevState.unconformed) {\n nextState.unconfirmed = !prevState.unconformed;\n }\n\n // Clear subscribers online when there is no connection.\n if (!nextProps.connected && prevState.onlineSubs && prevState.onlineSubs.length > 0) {\n nextState.onlineSubs = [];\n }\n\n return nextState;\n }\n\n subscribe(topic) {\n if (!topic || topic.isSubscribed() || !this.props.ready) {\n return;\n }\n\n // Is this a new topic?\n const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);\n\n // Don't request the tags. They are useless unless the user\n // is the owner and is editing the topic.\n let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();\n if (this.state.isReader || newTopic) {\n // Reading is either permitted or we don't know because it's a new topic. Ask for messages.\n getQuery = getQuery.withLaterData(MESSAGES_PAGE);\n if (this.state.isReader) {\n getQuery = getQuery.withLaterDel();\n }\n // And show \"loading\" spinner.\n this.setState({ fetchingMessages: true });\n }\n\n const setQuery = newTopic ? this.props.newTopicParams : undefined;\n topic.subscribe(getQuery.build(), setQuery)\n .then((ctrl) => {\n if (ctrl.code == 303) {\n // Redirect to another topic requested.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', ctrl.params.topic));\n return;\n }\n if (this.state.topic != ctrl.topic) {\n this.setState({topic: ctrl.topic});\n }\n this.props.onNewTopicCreated(this.props.topic, ctrl.topic);\n // If there are unsent messages, try sending them now.\n topic.queuedMessages((pub) => {\n if (!pub._sending && topic.isSubscribed()) {\n this.retrySend(pub);\n }\n });\n })\n .catch((err) => {\n console.error(\"Failed subscription to\", this.state.topic);\n this.props.onError(err.message, 'err');\n const blankState = MessagesView.getDerivedStateFromProps({}, {});\n blankState.title = this.props.intl.formatMessage(messages.not_found);\n this.setState(blankState);\n });\n }\n\n leave(oldTopicName) {\n if (!oldTopicName || !this.props.tinode.isTopicCached(oldTopicName)) {\n return;\n }\n const oldTopic = this.props.tinode.getTopic(oldTopicName);\n if (oldTopic && oldTopic.isSubscribed()) {\n oldTopic.leave(false)\n .catch(() => { /* do nothing here */ })\n .finally(() => {\n // We don't care if the request succeeded or failed.\n // The topic is dead regardless.\n this.setState({fetchingMessages: false});\n oldTopic.onData = undefined;\n oldTopic.onAllMessagesReceived = undefined;\n oldTopic.onInfo = undefined;\n oldTopic.onMetaDesc = undefined;\n oldTopic.onSubsUpdated = undefined;\n oldTopic.onPres = undefined;\n });\n }\n }\n\n // Don't use React.createRef as the ref.current is not available in componentDidMount in this component.\n handleScrollReference(node) {\n if (node) {\n node.addEventListener('scroll', this.handleScrollEvent);\n this.messagesScroller = node;\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;\n }\n }\n\n // Get older messages and show/hide [go to latest message] button.\n handleScrollEvent(event) {\n const pos = event.target.scrollHeight - event.target.scrollTop - event.target.offsetHeight;\n this.setState({\n scrollPosition: pos,\n showGoToLastButton: pos > SHOW_GO_TO_LAST_DIST,\n });\n\n if (this.state.fetchingMessages) {\n return;\n }\n\n if (event.target.scrollTop <= 0) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {\n this.setState({fetchingMessages: true}, () => {\n topic.getMessagesPage(MESSAGES_PAGE)\n .catch((err) => this.props.onError(err.message, 'err'))\n .finally(() => this.setState({fetchingMessages: false}));\n });\n }\n }\n }\n\n goToLatestMessage() {\n this.setState({scrollPosition: 0});\n if (this.messagesScroller) {\n this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight;\n }\n }\n\n handleDescChange(desc) {\n if (desc.public) {\n this.setState({\n title: desc.public.fn,\n avatar: makeImageUrl(desc.public.photo)\n });\n } else {\n this.setState({\n title: '',\n avatar: null\n });\n }\n\n if (desc.acs) {\n this.setState({\n isWriter: desc.acs.isWriter(),\n isReader: desc.acs.isReader(),\n readingBlocked: !desc.acs.isReader('given'),\n unconfirmed: isUnconfirmed(desc.acs),\n });\n }\n }\n\n postReadNotification(seq) {\n // Ignore notifications if the app is invisible.\n if (!this.props.applicationVisible) {\n return;\n }\n\n // Set up the timer if it's not running already.\n if (!this.readNotificationTimer) {\n this.readNotificationTimer = setInterval(() => {\n if (this.readNotificationQueue.length == 0) {\n // Shut down the timer if the queue is empty.\n clearInterval(this.readNotificationTimer);\n this.readNotificationTimer = null;\n return;\n }\n\n let seq = -1;\n while (this.readNotificationQueue.length > 0) {\n const n = this.readNotificationQueue[0];\n if (n.topicName != this.state.topic) {\n // Topic has changed. Drop the notification.\n this.readNotificationQueue.shift();\n continue;\n }\n\n const now = new Date();\n if (n.sendAt <= now) {\n // Remove expired notification from queue.\n this.readNotificationQueue.shift();\n seq = Math.max(seq, n.seq);\n } else {\n break;\n }\n }\n\n // Send only one notification for the whole batch of messages.\n if (seq >= 0) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic) {\n topic.noteRead(seq);\n }\n }\n }, NOTIFICATION_EXEC_INTERVAL);\n }\n\n const now = new Date();\n this.readNotificationQueue.push({\n topicName: this.state.topic,\n seq: seq,\n sendAt: now.setMilliseconds(now.getMilliseconds() + READ_DELAY)\n });\n }\n\n // Clear notification queue and timer.\n clearNotificationQueue() {\n this.readNotificationQueue = [];\n if (this.readNotificationTimer) {\n clearInterval(this.readNotificationTimer);\n this.readNotificationTimer = null;\n }\n }\n\n handleSubsUpdated() {\n if (this.state.topic) {\n const subs = [];\n const topic = this.props.tinode.getTopic(this.state.topic);\n topic.subscribers((sub) => {\n if (sub.online && sub.user != this.props.myUserId) {\n subs.push(sub);\n }\n });\n const newState = {onlineSubs: subs};\n const peer = topic.p2pPeerDesc();\n if (peer) {\n Object.assign(newState, {\n peerMessagingDisabled: isPeerRestricted(peer.acs)\n });\n } else if (this.state.peerMessagingDisabled) {\n Object.assign(newState, {\n peerMessagingDisabled: false\n });\n }\n this.setState(newState);\n }\n }\n\n // The 'msg' could be false-ish if some message ranges were deleted.\n handleMessageUpdate(msg) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (!msg) {\n // msg could be null if one or more messages were deleted.\n // Updating state to force redraw.\n this.setState({latestClearId: topic.maxClearId()});\n return;\n }\n\n clearTimeout(this.keyPressTimer)\n this.setState({messageCount: topic.messageCount(), typingIndicator: false});\n\n // Scroll to the bottom if the message is added to the end of the message list.\n // TODO: This should be replaced by showing a \"scroll to bottom\" button.\n if (topic.isNewMessage(msg.seq)) {\n if (this.state.scrollPosition > SHOW_GO_TO_LAST_DIST) {\n this.setState({showGoToLastButton: true});\n } else {\n this.setState({scrollPosition: 0});\n }\n }\n\n // Aknowledge messages except own messages. They are\n // automatically assumed to be read and recived.\n const status = topic.msgStatus(msg, true);\n if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {\n this.postReadNotification(msg.seq);\n }\n // This will send \"received\" notifications right away.\n this.props.onData(msg);\n }\n\n handleAllMessagesReceived(count) {\n this.setState({fetchingMessages: false});\n if (count > 0) {\n // 0 means \"latest\".\n this.postReadNotification(0);\n }\n }\n\n handleInfoReceipt(info) {\n switch (info.what) {\n case 'kp': {\n clearTimeout(this.keyPressTimer);\n this.keyPressTimer = setTimeout(() => {\n this.setState({typingIndicator: false});\n }, KEYPRESS_DELAY + 1000);\n if (!this.state.typingIndicator) {\n this.setState({typingIndicator: true});\n }\n break;\n }\n case 'read':\n case 'recv':\n // Redraw due to changed recv/read status.\n this.forceUpdate();\n break;\n default:\n console.info(\"Other change in topic: \", info.what);\n }\n }\n\n handleImagePostview(content) {\n this.setState({ imagePostview: content });\n }\n\n handleClosePreview() {\n if (this.state.imagePreview && this.state.imagePreview.url) {\n URL.revokeObjectURL(this.state.imagePreview.url);\n }\n this.setState({ imagePostview: null, imagePreview: null, docPreview: null });\n }\n\n handleFormResponse(action, text, data) {\n if (action == 'pub') {\n this.sendMessage(Drafty.attachJSON(Drafty.parse(text), data));\n } else if (action == 'url') {\n const url = new URL(data.ref);\n const params = url.searchParams;\n for (let key in data.resp) {\n if (data.resp.hasOwnProperty(key)) {\n params.set(key, data.resp[key]);\n }\n }\n ['name', 'seq'].map((key) => {\n if (data[key]) {\n params.set(key, data[key]);\n }\n });\n params.set('uid', this.props.myUserId);\n params.set('topic', this.state.topic);\n url.search = params;\n window.open(url, '_blank');\n } else {\n console.info(\"Unknown action in form\", action);\n }\n }\n\n handleContextClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.props.showContextMenu({ topicName: this.state.topic, y: e.pageY, x: e.pageX });\n }\n\n handleShowMessageContextMenu(params, messageSpecificMenuItems) {\n if (params.userFrom == 'chan') {\n params.userFrom = this.state.topic;\n params.userName = this.state.title;\n }\n params.topicName = this.state.topic;\n const menuItems = messageSpecificMenuItems || [];\n const topic = this.props.tinode.getTopic(params.topicName);\n if (topic) {\n if (!topic.isChannelType()) {\n menuItems.push('message_delete');\n }\n const acs = topic.getAccessMode();\n if (acs && acs.isDeleter()) {\n menuItems.push('message_delete_hard');\n }\n }\n this.props.showContextMenu(params, menuItems);\n }\n\n handleNewChatAcceptance(action) {\n this.props.onNewChat(this.state.topic, action);\n }\n\n handleEnablePeer(e) {\n e.preventDefault();\n this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);\n }\n\n sendKeyPress() {\n const topic = this.props.tinode.getTopic(this.state.topic);\n if (topic.isSubscribed()) {\n topic.noteKeyPress();\n }\n }\n\n // sendMessage sends the message with an optional subscription to topic first.\n sendMessage(msg, uploadCompletionPromise, uploader) {\n let head;\n if (this.props.forwardMessage) {\n // We are forwarding a message.\n msg = this.props.forwardMessage.msg;\n head = this.props.forwardMessage.head;\n this.handleCancelReply();\n } else if (this.state.reply && this.state.reply.content) {\n // We are replying to a message in this topic.\n head = {reply: '' + this.state.reply.seq};\n // Turn it into Drafty so we can make a quoted Drafty object later.\n if (typeof msg == 'string') {\n msg = Drafty.parse(msg);\n }\n msg = Drafty.append(Drafty.sanitizeEntities(this.state.reply.content), msg);\n this.handleCancelReply();\n }\n this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);\n }\n\n // Retry sending a message.\n retrySend(pub) {\n this.props.sendMessage(pub.content, undefined, undefined, pub.head);\n }\n\n // Send attachment as Drafty message:\n // - if file is too large, upload it and send a s link.\n // - if file is small enough, just send it in-band.\n sendFileAttachment(file) {\n // Server-provided limit reduced for base64 encoding and overhead.\n const maxInbandAttachmentSize = (this.props.tinode.getServerLimit('maxMessageSize',\n MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;\n\n if (file.size > maxInbandAttachmentSize) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n const uploadCompletionPromise = uploader.upload(file);\n const msg = Drafty.attachFile(null, {\n mime: file.type,\n filename: file.name,\n size: file.size,\n urlPromise: uploadCompletionPromise\n });\n // Pass data and the uploader to the TinodeWeb.\n this.sendMessage(msg, uploadCompletionPromise, uploader);\n } else {\n // Small enough to send inband.\n fileToBase64(file)\n .then(b64 => this.sendMessage(Drafty.attachFile(null, {mime: b64.mime, data: b64.bits, filename: b64.name})))\n .catch(err => this.props.onError(err));\n }\n }\n\n // handleAttachFile method is called when [Attach file] button is clicked.\n handleAttachFile(file) {\n const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);\n\n if (file.size > maxExternAttachmentSize) {\n // Too large.\n this.props.onError(this.props.intl.formatMessage(messages.file_attachment_too_large,\n {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(maxExternAttachmentSize)}), 'err');\n } else {\n this.setState({\n docPreview: {\n file: file,\n name: file.name,\n size: file.size,\n type: file.type\n }\n });\n }\n }\n\n // sendImageAttachment sends the image bits inband as Drafty message.\n sendImageAttachment(caption, blob) {\n const mime = this.state.imagePreview.mime;\n const width = this.state.imagePreview.width;\n const height = this.state.imagePreview.height;\n const fname = this.state.imagePreview.name;\n\n // Server-provided limit reduced for base64 encoding and overhead.\n const maxInbandAttachmentSize = (this.props.tinode.getServerLimit('maxMessageSize',\n MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;\n\n if (blob.size > maxInbandAttachmentSize) {\n // Too large to send inband - uploading out of band and sending as a link.\n const uploader = this.props.tinode.getLargeFileHelper();\n if (!uploader) {\n this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));\n return;\n }\n const uploadCompletionPromise = uploader.upload(blob);\n\n // Make small preview to show while uploading.\n imageScaled(blob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false)\n // Convert tiny image into base64 for serialization and previewing.\n .then(scaled => blobToBase64(scaled.blob))\n .then(b64 => {\n let msg = Drafty.insertImage(null, 0, {\n mime: mime,\n _tempPreview: b64.bits, // This preview will not be serialized.\n width: width,\n height: height,\n filename: fname,\n size: blob.size,\n urlPromise: uploadCompletionPromise\n });\n if (caption) {\n msg = Drafty.appendLineBreak(msg);\n msg = Drafty.append(msg, Drafty.parse(caption));\n }\n // Pass data and the uploader to the TinodeWeb.\n this.sendMessage(msg, uploadCompletionPromise, uploader);\n }).catch((err) => {\n this.props.onError(err, 'err');\n });\n return;\n }\n\n // Upload the image if it's too big to be send inband.\n blobToBase64(blob)\n .then(b64 => {\n let msg = Drafty.insertImage(null, 0, {\n mime: b64.mime,\n preview: b64.bits, // Serializable preview\n width: width,\n height: height,\n filename: fname,\n size: blob.size\n });\n if (caption) {\n msg = Drafty.appendLineBreak(msg);\n msg = Drafty.append(msg, Drafty.parse(caption));\n }\n this.sendMessage(msg);\n });\n }\n\n // handleAttachImage method is called when [Attach image] button is clicked.\n handleAttachImage(file) {\n const maxExternAttachmentSize = this.props.tinode.getServerLimit('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);\n\n // Get image dimensions and size, optionally scale it down.\n imageScaled(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, maxExternAttachmentSize, false)\n .then(scaled => {\n this.setState({imagePreview: {\n url: URL.createObjectURL(scaled.blob),\n blob: scaled.blob,\n name: scaled.name,\n width: scaled.width,\n height: scaled.height,\n size: scaled.blob.size,\n mime: scaled.mime\n }});\n }).catch(err => {\n this.props.onError(err, 'err');\n });\n }\n\n handleCancelUpload(seq, uploader) {\n const topic = this.props.tinode.getTopic(this.state.topic);\n const found = topic.findMessage(seq);\n if (found) {\n found._cancelled = true;\n }\n uploader.cancel();\n }\n\n // seq: seq ID of the source message\n // context: message content.\n // forwarded: true if the source message is also a forwarded message.\n // senderId: UID of the sender of the source message.\n // senderName: full name of the sender of the original message.\n handlePickReply(seq, content, senderId, senderName) {\n this.setState({reply: null});\n\n if (!seq || !content) {\n return;\n }\n\n content = typeof content == 'string' ? Drafty.init(content) : content;\n if (Drafty.isValid(content)) {\n content = Drafty.replyContent(content, QUOTED_REPLY_LENGTH);\n } else {\n // /!\\ invalid content.\n content = Drafty.append(Drafty.init('\\u26A0 '),\n Drafty.wrapInto(this.props.intl.formatMessage(messages.invalid_content), 'EM'));\n }\n\n this.setState({\n reply: {\n content: Drafty.quote(senderName, senderId, content),\n seq: seq\n }\n });\n this.props.onCancelForwardMessage();\n }\n\n handleCancelReply() {\n this.setState({reply: null});\n this.props.onCancelForwardMessage();\n }\n\n handleQuoteClick(replyToSeq) {\n const ref = this.getOrCreateMessageRef(replyToSeq);\n if (ref && ref.current) {\n ref.current.scrollIntoView({block: \"center\", behavior: \"smooth\"});\n ref.current.classList.add('flash');\n setTimeout(() => { ref.current.classList.remove('flash') } , 1000);\n } else {\n console.error(\"Unresolved message ref\", replyToSeq);\n }\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n\n let component;\n if (this.props.hideSelf) {\n component = null;\n } else if (!this.state.topic) {\n component = (\n \n );\n } else {\n let component2;\n if (this.state.imagePreview) {\n // Preview image before sending.\n component2 = (\n \n );\n } else if (this.state.imagePostview) {\n // Expand received image.\n component2 = (\n \n );\n } else if (this.state.docPreview) {\n // Preview attachment before sending.\n component2 = (\n \n );\n } else {\n const topic = this.props.tinode.getTopic(this.state.topic);\n const isChannel = topic.isChannelType();\n const groupTopic = topic.isGroupType() && !isChannel;\n const icon_badges = [];\n if (topic.trusted) {\n if (topic.trusted.verified) {\n icon_badges.push({icon: 'verified', color: 'badge-inv'});\n }\n if (topic.trusted.staff) {\n icon_badges.push({icon: 'staff', color: 'badge-inv'});\n }\n if (topic.trusted.danger) {\n icon_badges.push({icon: 'dangerous', color: 'badge-inv'});\n }\n }\n\n const messageNodes = [];\n let previousFrom = null;\n let chatBoxClass = null;\n const dateFmt = new Intl.DateTimeFormat(this.props.intl.locale);\n topic.messages((msg, prev, next, i) => {\n let nextFrom = next ? (next.from || 'chan') : null;\n\n let sequence = 'single';\n let thisFrom = msg.from || 'chan';\n if (thisFrom == previousFrom) {\n if (thisFrom == nextFrom) {\n sequence = 'middle';\n } else {\n sequence = 'last';\n }\n } else if (thisFrom == nextFrom) {\n sequence = 'first';\n }\n previousFrom = thisFrom;\n\n const isReply = !(thisFrom == this.props.myUserId);\n const deliveryStatus = topic.msgStatus(msg, true);\n\n let userFrom = thisFrom, userName, userAvatar;\n const user = topic.userDesc(thisFrom);\n if (user && user.public) {\n userName = user.public.fn;\n userAvatar = makeImageUrl(user.public.photo);\n }\n chatBoxClass = groupTopic ? 'chat-box group' : 'chat-box';\n\n // Ref for this chat message.\n const ref = this.getOrCreateMessageRef(msg.seq);\n let replyToSeq = msg.head ? parseInt(msg.head.reply) : null;\n if (!replyToSeq || isNaN(replyToSeq)) {\n replyToSeq = null;\n }\n\n // This message was sent on a different date than the previous.\n if (!prev || new Date(prev.ts).toDateString() != new Date(msg.ts).toDateString()) {\n messageNodes.push(\n \n );\n }\n\n if (msg.hi) {\n // Deleted message.\n messageNodes.push(\n \n );\n } else {\n messageNodes.push(\n \n );\n }\n });\n\n let lastSeen = null;\n if (isChannel) {\n lastSeen = formatMessage(messages.channel);\n } else {\n const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);\n if (cont && Tinode.isP2PTopicName(cont.topic)) {\n if (cont.online) {\n lastSeen = formatMessage(messages.online_now);\n } else if (cont.seen) {\n lastSeen = formatMessage(messages.last_seen) + \": \" +\n shortDateFormat(cont.seen.when, this.props.intl.locale);\n // TODO: also handle user agent in c.seen.ua\n }\n }\n }\n const avatar = this.state.avatar || true;\n const online = this.state.deleted ? null :\n this.props.online ? 'online' + (this.state.typingIndicator ? ' typing' : '') : 'offline';\n\n const titleClass = 'panel-title' + (this.state.deleted ? ' deleted' : '');\n\n component2 = (\n <>\n \n {this.props.displayMobile ?\n \n : null}\n \n \n
\n
\n {!this.state.isReader ?\n
\n {this.state.readingBlocked ?\n
\n \n
\n : null }\n
\n : null }\n
\n {this.state.peerMessagingDisabled && !this.state.unconfirmed ?\n : null}\n {this.state.unconfirmed ?\n \n :\n }\n >\n );\n }\n\n component = {component2}
\n }\n return component;\n }\n};\n\nexport default injectIntl(MessagesView);\n","// The <- button to be displayed in title bars.\nimport React from 'react';\n\nexport default class ButtonBack extends React.PureComponent {\n render() {\n return (\n {e.preventDefault(); this.props.onBack();}}>\n arrow_back\n \n );\n }\n}\n","import React from 'react';\n\nexport default class MenuContacts extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nexport default class MenuStart extends React.PureComponent {\n render() {\n return (\n \n );\n }\n};\n","import React from 'react';\n\nimport LetterTile from './letter-tile.jsx';\nimport ButtonBack from './button-back.jsx';\nimport ContactBadges from './contact-badges.jsx';\nimport MenuContacts from './menu-contacts.jsx';\nimport MenuStart from './menu-start.jsx';\n\nimport { sanitizeImageUrl } from '../lib/utils.js';\n\nexport default class SideNavbar extends React.PureComponent {\n render() {\n const icon_badges = [];\n if (this.props.trustedBadges) {\n this.props.trustedBadges.map((b) => {\n icon_badges.push({icon: b, color: 'badge-inv'});\n });\n }\n let avatar = null;\n if (this.props.tinode) {\n avatar = this.props.tinode.authorizeURL(sanitizeImageUrl(this.props.avatar));\n }\n return (\n \n {this.props.onCancel ?
: null}\n {avatar ?\n
\n \n
\n :\n null}\n
{this.props.title}
\n {this.props.state === 'login' ?\n
:\n this.props.state === 'contacts' ?\n
:\n null}\n
\n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport BadgeList from '../widgets/badge-list.jsx';\n\nimport { makeImageUrl } from '../lib/blob-helpers.js';\nimport { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';\n\nfunction _clip(str, length) {\n return str && str.substring(0, length);\n}\n\nexport default class AccountSettingsView extends React.Component {\n constructor(props) {\n super(props);\n\n const me = this.props.tinode.getMeTopic();\n this.state = {\n fullName: _clip(me.public ? me.public.fn : undefined, MAX_TITLE_LENGTH),\n description: _clip(me.public ? me.public.note : undefined, MAX_TOPIC_DESCRIPTION_LENGTH),\n avatar: makeImageUrl(me.public ? me.public.photo : null)\n };\n }\n\n render() {\n return (\n \n );\n }\n};\n","/* ContactsView holds all contacts-related stuff */\nimport React from 'react';\nimport { FormattedMessage, defineMessages } from 'react-intl';\n\nimport ContactList from '../widgets/contact-list.jsx';\n\nimport { updateFavicon } from '../lib/utils.js';\n\nconst messages = defineMessages({\n archived_contacts_title: {\n id: \"archived_contacts\",\n defaultMessage: \"Archived contacts ({count})\",\n description: \"Label for archived chats\"\n }\n});\n\nexport default class ContactsView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleAction = this.handleAction.bind(this);\n\n this.state = ContactsView.deriveStateFromProps(props);\n }\n\n static deriveStateFromProps(props) {\n const contacts = [];\n let unreadThreads = 0;\n let archivedCount = 0;\n props.chatList.map((c) => {\n const blocked = c.acs && !c.acs.isJoiner();\n // Show only blocked contacts only when props.blocked == true.\n if (blocked && props.blocked) {\n contacts.push(c);\n }\n if (blocked || props.blocked) {\n return;\n }\n\n if (c.private && c.private.arch) {\n if (props.archive) {\n contacts.push(c);\n } else {\n archivedCount ++;\n }\n } else if (!props.archive) {\n contacts.push(c);\n unreadThreads += c.unread > 0 ? 1 : 0;\n }\n });\n\n contacts.sort((a, b) => {\n return (b.touched || 0) - (a.touched || 0);\n });\n\n if (archivedCount > 0) {\n contacts.push({\n action: 'archive',\n title: messages.archived_contacts_title,\n values: {count: archivedCount}\n });\n }\n\n return {\n contactList: contacts,\n unreadThreads: unreadThreads\n };\n }\n\n componentDidUpdate(prevProps, prevState) {\n if (prevProps.chatList != this.props.chatList ||\n prevProps.archive != this.props.archive ||\n prevProps.blocked != this.props.blocked) {\n const newState = ContactsView.deriveStateFromProps(this.props);\n this.setState(newState);\n if (newState.unreadThreads != prevState.unreadThreads) {\n updateFavicon(newState.unreadThreads);\n }\n }\n }\n\n handleAction(action_ignored) {\n this.props.onShowArchive();\n }\n\n render() {\n return (\n ¯∖_(ツ)_/¯\"\n description=\"HTML message shown in ContactList when no contacts are found\">{\n (no_contacts) => \n }\n );\n }\n};\n","// Helper functions for storing values in localStorage.\n// By default localStorage can store only strings, not objects or other types.\n\nexport default class LocalStorageUtil {\n // Replace old object with the new one.\n static setObject(key, value) {\n localStorage.setItem(key, JSON.stringify(value));\n }\n\n // Get stored object.\n static getObject(key) {\n const value = localStorage.getItem(key);\n return value && JSON.parse(value);\n }\n\n // Partially or wholly update stored object.\n static updateObject(key, value) {\n const oldVal = this.getObject(key);\n this.setObject(key, Object.assign(oldVal || {}, value));\n }\n\n // Just a wrapper.\n static removeItem(key) {\n localStorage.removeItem(key);\n }\n}\n","// Account registration.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from '../widgets/avatar-upload.jsx';\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport { theCard } from '../lib/utils.js';\n\nimport { MAX_TITLE_LENGTH } from '../config.js';\n\nexport default class CreateAccountView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n login: '',\n password: '',\n email: '',\n fn: '', // full/formatted name\n imageDataUrl: null,\n errorCleared: false,\n saveToken: LocalStorageUtil.getObject('keep-logged-in')\n };\n\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handleFnChange = this.handleFnChange.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(password) {\n this.setState({password: password});\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value})\n }\n\n handleFnChange(e) {\n this.setState({fn: e.target.value});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleToggleSaveToken() {\n LocalStorageUtil.setObject('keep-logged-in', !this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.setState({errorCleared: false});\n this.props.onCreateAccount(\n this.state.login.trim(),\n this.state.password.trim(),\n theCard(this.state.fn.trim().substring(0, MAX_TITLE_LENGTH), this.state.imageDataUrl),\n {'meth': 'email', 'val': this.state.email});\n }\n\n render() {\n let submitClasses = 'primary';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport CheckBox from '../widgets/checkbox.jsx';\n\nexport default class EditAccountView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.handleCheckboxClick = this.handleCheckboxClick.bind(this);\n }\n\n handleCheckboxClick(what, checked) {\n if (what == 'sound') {\n this.props.onToggleMessageSounds(checked);\n } else if (what == 'alert') {\n this.props.onTogglePushNotifications(checked);\n } else if (what == 'incognito') {\n this.props.onToggleIncognitoMode(checked);\n }\n }\n\n render() {\n return (\n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n );\n }\n};\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport InPlaceEdit from '../widgets/in-place-edit.jsx';\nimport PermissionsEditor from '../widgets/permissions-editor.jsx';\n\nconst messages = defineMessages({\n delete_account: {\n id: 'delete_account',\n defaultMessage: 'Delete account',\n description: 'Title for delete account warning'\n },\n delete_account_warning: {\n id: 'delete_account_warning',\n defaultMessage: 'Are you sure you want to delete your account? It cannot be undone.',\n description: 'Warning message when deleting an account'\n }\n});\n\nclass AccSecurityView extends React.Component {\n constructor(props) {\n super(props);\n\n const me = this.props.tinode.getMeTopic();\n let blockedCount = 0;\n me.contacts((c) => {\n if (c.acs && !c.acs.isJoiner()) {\n blockedCount ++;\n }\n });\n const defacs = me.getDefaultAccess();\n this.state = {\n auth: defacs ? defacs.auth : null,\n anon: defacs ? defacs.anon : null,\n showPermissionEditorFor: undefined,\n blockedCount: blockedCount,\n };\n\n this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);\n this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);\n this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);\n this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);\n this.handleDeleteAccount = this.handleDeleteAccount.bind(this);\n }\n\n handlePasswordUpdate(pwd) {\n this.setState({password: pwd});\n this.props.onUpdatePassword(pwd);\n }\n\n handleLaunchPermissionsEditor(which) {\n this.setState({\n showPermissionEditorFor: which,\n editedPermissions: this.state[which]\n });\n }\n\n handleHidePermissionsEditor() {\n this.setState({showPermissionEditorFor: undefined});\n }\n\n handlePermissionsChanged(perm) {\n let defacs = {};\n defacs[this.state.showPermissionEditorFor] = perm;\n this.props.onUpdateAccountDesc('me', undefined, undefined, defacs);\n\n let newState = {showPermissionEditorFor: undefined};\n newState[this.state.showPermissionEditorFor] = perm;\n this.setState(newState);\n }\n\n handleDeleteAccount(e) {\n e.preventDefault();\n const {formatMessage} = this.props.intl;\n this.props.onShowAlert(\n formatMessage(messages.delete_account), // title\n formatMessage(messages.delete_account_warning), // content\n (() => { this.props.onDeleteAccount(); }), // onConfirm\n null, // use default text \"OK\"\n true, // Show Reject button\n null // use default text \"Cancel\"\n );\n }\n\n render() {\n return (\n <>{this.state.showPermissionEditorFor ?\n \n :\n \n
\n
\n
\n {\n (password_unchanged) => \n }\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
Auth: {this.state.auth}
\n
Anon: {this.state.anon}
\n
\n
\n {this.state.blockedCount > 0 ?\n <>\n
\n
\n >\n : null }\n
\n }>\n );\n }\n};\n\nexport default injectIntl(AccSecurityView);\n","// Edit account parameters.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport { APP_NAME, LINK_CONTACT_US, LINK_PRIVACY_POLICY, LINK_TERMS_OF_SERVICE } from '../config.js';\n\nexport default class AccSupportView extends React.PureComponent {\n render() {\n return (\n \n
\n
\n
\n
\n \n {APP_NAME}\n
\n
\n \n {Tinode.getLibrary()}\n
\n
\n \n {this.props.serverVersion}\n
\n
\n \n {this.props.serverAddress}\n
\n
\n
\n );\n }\n};\n","// Login form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport CheckBox from '../widgets/checkbox.jsx';\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport LocalStorageUtil from '../lib/local-storage.js';\n\nexport default class LoginView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n login: props.login,\n password: '',\n hostName: props.serverAddress,\n saveToken: props.persist\n };\n this.handleLoginChange = this.handleLoginChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n this.handleToggleSaveToken = this.handleToggleSaveToken.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleLoginChange(e) {\n this.setState({login: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n handleToggleSaveToken() {\n this.props.onPersistenceChange(!this.state.saveToken);\n this.setState({saveToken: !this.state.saveToken});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onLogin(this.state.login.trim(), this.state.password.trim());\n }\n\n render() {\n let submitClasses = 'primary';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n\n return (\n \n );\n }\n};\n/* END Login */\n","import React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n invalid_id: {\n id: 'error_invalid_id',\n defaultMessage: 'Invalid ID',\n description: 'Error message'\n }\n});\n\nclass NewTopicById extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n groupId: '',\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n handleChange(e) {\n this.setState({groupId: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.groupId) {\n const name = this.state.groupId.trim();\n const prefix = name.substr(0, 3);\n if (name.length > 3 && ['usr', 'grp', 'chn'].includes(prefix)) {\n this.props.onSubmit(name);\n } else {\n this.props.onError(this.props.intl.formatMessage(messages.invalid_id), 'err');\n }\n }\n }\n\n render() {\n return (\n \n
\n {\n (prompt) => \n }\n
\n
\n \n
\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicById);\n","import React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport AvatarUpload from './avatar-upload.jsx';\nimport CheckBox from './checkbox.jsx';\nimport TagManager from './tag-manager.jsx';\n\nimport { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';\n\nexport default class NewTopicGroup extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.fullName = React.createRef();\n\n this.state = {\n fullName: '', // full/formatted name\n private: '',\n description: '',\n imageDataUrl: null,\n tags: [],\n isChannel: false\n };\n\n this.handleFieldEdit = this.handleFieldEdit.bind(this);\n this.handleImageChanged = this.handleImageChanged.bind(this);\n this.handleTagsChanged = this.handleTagsChanged.bind(this);\n this.handleChannelToggle = this.handleChannelToggle.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n componentDidMount() {\n // this.fullName.current.focus();\n }\n\n handleFieldEdit(name, e) {\n this.setState({[name]: e.target.value || ''});\n }\n\n handleImageChanged(img) {\n this.setState({imageDataUrl: img});\n }\n\n handleTagsChanged(tags) {\n this.setState({tags: tags});\n }\n\n handleChannelToggle() {\n this.setState({isChannel: !this.state.isChannel});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n\n const fn = this.state.fullName.trim().substring(0, MAX_TITLE_LENGTH);\n const comment = this.state.private.trim().substring(0, MAX_TITLE_LENGTH);\n const description = this.state.description.trim().substring(0, MAX_TOPIC_DESCRIPTION_LENGTH);\n if (fn) {\n this.props.onSubmit(fn, description, this.state.imageDataUrl, comment, this.state.tags, this.state.isChannel);\n }\n }\n\n render() {\n let submitClasses = 'primary';\n if (this.props.disabled) {\n submitClasses += ' disabled';\n }\n return (\n \n );\n }\n};\n","// Create new topic and invite users or send an invite.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport Tinode from 'tinode-sdk';\n\nimport ContactList from '../widgets/contact-list.jsx';\nimport NewTopicById from '../widgets/new-topic-by-id.jsx';\nimport NewTopicGroup from '../widgets/new-topic-group.jsx';\nimport SearchContacts from '../widgets/search-contacts.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\nimport { theCard } from '../lib/utils.js';\n\nconst messages = defineMessages({\n search_for_contacts: {\n id: \"search_for_contacts\",\n defaultMessage: \"Use search to find contacts\",\n description: \"Text shown in contacts view when user entered no search query.\"\n },\n search_no_results: {\n id: \"search_no_results\",\n defaultMessage: \"Search returned no results\",\n description: \"Text shown in contacts view when query returned no results.\"\n },\n search_placeholder: {\n id: \"search_placeholder\",\n defaultMessage: \"List like email:alice@example.com, tel:17025550003...\",\n description: \"Placeholder in contacts search field\"\n },\n});\n\nclass NewTopicView extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n tabSelected: 'find',\n searchQuery: null\n };\n\n this.handleTabClick = this.handleTabClick.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleContactSelected = this.handleContactSelected.bind(this);\n this.handleNewGroupSubmit = this.handleNewGroupSubmit.bind(this);\n this.handleGroupByID = this.handleGroupByID.bind(this);\n }\n\n componentDidMount() {\n this.props.onInitFind();\n }\n\n handleTabClick(e) {\n e.preventDefault();\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'tab', e.currentTarget.dataset.id));\n this.setState({tabSelected: e.currentTarget.dataset.id});\n }\n\n handleSearchContacts(query) {\n this.props.onSearchContacts(query);\n this.setState({searchQuery: Tinode.isNullValue(query) ? null : query});\n }\n\n handleContactSelected(uid) {\n if (this.state.tabSelected == 'find') {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(uid);\n }\n }\n\n handleNewGroupSubmit(name, description, dataUrl, priv, tags, isChannel) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(undefined,\n {public: theCard(name, dataUrl, null, description), private: priv, tags: tags}, isChannel);\n }\n\n handleGroupByID(topicName) {\n HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));\n this.props.onCreateTopic(topicName);\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const no_contacts_placeholder = formatMessage(this.state.searchQuery ?\n messages.search_no_results : messages.search_for_contacts);\n const search_placeholder = formatMessage(messages.search_placeholder);\n return (\n \n
\n {this.state.tabSelected === 'grp' ?\n
:\n this.state.tabSelected === 'byid' ?\n
:\n
\n \n \n
}\n
\n );\n }\n};\n\nexport default injectIntl(NewTopicView);\n","// A password reset form.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport VisiblePassword from '../widgets/visible-password.jsx';\n\nimport HashNavigation from '../lib/navigation.js';\n\nexport default class PasswordResetView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n email: '',\n password: '',\n sent: false\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleEmailChange = this.handleEmailChange.bind(this);\n this.handlePasswordChange = this.handlePasswordChange.bind(this);\n }\n\n componentDidMount() {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n this.setState({token: parsed.params.token, scheme: parsed.params.scheme});\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.token) {\n this.props.onReset(this.state.scheme, this.state.password.trim(), this.state.token);\n } else if (this.state.sent) {\n this.props.onCancel();\n } else {\n const email = this.state.email.trim();\n this.setState({email: email});\n this.props.onRequest('email', email).then(() => {\n this.setState({sent: true});\n });\n }\n }\n\n handleEmailChange(e) {\n this.setState({email: e.target.value});\n }\n\n handlePasswordChange(e) {\n this.setState({password: e.target.value});\n }\n\n render() {\n return (\n \n );\n }\n}\n","import React from 'react';\n\nimport { KNOWN_HOSTS } from '../config.js';\n\n/* Combobox for selecting host name */\n\nexport default class HostSelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n hostName: props.serverAddress,\n changed: false\n };\n\n this.handleHostNameChange = this.handleHostNameChange.bind(this);\n this.handleEditingFinished = this.handleEditingFinished.bind(this);\n }\n\n handleHostNameChange(e) {\n this.setState({hostName: e.target.value, changed: true});\n }\n\n handleEditingFinished() {\n if (this.state.changed) {\n this.setState({changed: false});\n this.props.onServerAddressChange(this.state.hostName.trim());\n }\n }\n\n render() {\n var hostOptions = [];\n for (let key in KNOWN_HOSTS) {\n let item = KNOWN_HOSTS[key];\n hostOptions.push(\n \n );\n }\n return (\n \n \n \n
\n );\n }\n}\n","// Tinode config panel.\nimport React from 'react';\nimport { FormattedMessage } from 'react-intl';\n\nimport HostSelector from '../widgets/host-selector.jsx';\n\nexport default class SettingsView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n transport: props.transport || 'def',\n serverAddress: props.serverAddress,\n };\n\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleTransportSelected = this.handleTransportSelected.bind(this);\n this.handleServerAddressChange = this.handleServerAddressChange.bind(this);\n }\n\n handleSubmit(e) {\n e.preventDefault();\n this.props.onUpdate({\n transport: this.state.transport,\n serverAddress: this.state.serverAddress\n });\n }\n\n handleTransportSelected(e) {\n this.setState({transport: e.currentTarget.value});\n }\n\n handleServerAddressChange(name) {\n this.setState({serverAddress: name});\n }\n\n render() {\n const names = {def: \"default\", ws: \"websocket\", lp: \"long polling\"};\n const transportOptions = [];\n ['def', 'ws', 'lp'].map((item) => {\n const id = 'transport-' + item;\n const name = names[item];\n transportOptions.push(\n \n \n \n \n );\n });\n\n return (\n \n );\n }\n};\n","// ValidationView: panel for confirming credentials, like email or phone.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nconst messages = defineMessages({\n phone: {\n id: 'phone_dative',\n defaultMessage: 'phone',\n description: \"Dative case of 'phone', i.e. 'phone' in 'by phone'\",\n },\n email: {\n id: 'email_dative',\n defaultMessage: 'email',\n description: \"Dative case of 'email', i.e. 'email' in 'by email'\",\n }\n});\n\nclass ValidationView extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.state = {\n code: props.credCode || ''\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleKeyPress = this.handleKeyPress.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleCancel = this.handleCancel.bind(this);\n }\n\n handleChange(e) {\n this.setState({code: e.target.value});\n }\n\n handleKeyPress(e) {\n if (e.key === 'Enter') {\n this.handleSubmit(e);\n } else if (e.key == 'Escape') {\n this.handleCancel(e);\n }\n }\n\n handleSubmit(e) {\n e.preventDefault();\n if (this.state.code && this.state.code.trim()) {\n this.props.onSubmit(this.props.credMethod, this.state.code.trim());\n }\n }\n\n handleCancel(e) {\n e.preventDefault();\n this.props.onCancel();\n }\n\n\n render() {\n const { formatMessage } = this.props.intl;\n const methods = {'email': formatMessage(messages.email), 'tel': formatMessage(messages.phone)};\n let method = methods[this.props.credMethod] || this.props.credMethod;\n return (\n \n
\n \n
\n
\n {\n (numbers_only) => \n }\n
\n
\n \n \n
\n
\n );\n }\n};\n\nexport default injectIntl(ValidationView);\n","import React from 'react';\nimport { defineMessages, injectIntl } from 'react-intl';\n\nimport ErrorPanel from '../widgets/error-panel.jsx';\nimport LoadSpinner from '../widgets/load-spinner.jsx';\nimport SideNavbar from '../widgets/side-navbar.jsx';\nimport TopicCommon from '../widgets/topic-common.jsx';\n\nimport AccountSettingsView from './account-settings-view.jsx';\nimport ContactsView from './contacts-view.jsx';\nimport CreateAccountView from './create-account-view.jsx';\nimport AccNotificationsView from './acc-notifications-view.jsx';\nimport AccSecurityView from './acc-security-view.jsx';\nimport AccSupportView from './acc-support-view.jsx';\nimport LoginView from './login-view.jsx';\nimport NewTopicView from './new-topic-view.jsx';\nimport PasswordResetView from './password-reset-view.jsx';\nimport SettingsView from './settings-view.jsx';\nimport ValidationView from './validation-view.jsx';\n\n// Panel titles for translation.\nconst messages = defineMessages({\n login: {\n id: 'sidepanel_title_login',\n description: 'Sidepanel title for LoginView.',\n defaultMessage: 'Sign In'\n },\n register: {\n id: 'sidepanel_title_register',\n description: 'Sidepanel title for CreateAccountView.',\n defaultMessage: 'Create Account'\n },\n settings: {\n id: 'sidepanel_title_settings',\n description: 'Sidepanel title for SettingsView.',\n defaultMessage: 'Settings'\n },\n edit: {\n id: 'sidepanel_title_account_settings',\n description: 'Sidepanel title for AccountSettingsView.',\n defaultMessage: 'Account Settings'\n },\n general: {\n id: 'panel_title_general',\n description: 'Title for TopicCommon.',\n defaultMessage: 'General'\n },\n security: {\n id: 'panel_title_security',\n description: 'Title for TopicSecirity and AccSecurity.',\n defaultMessage: 'Security'\n },\n crop: {\n id: 'panel_title_crop',\n description: 'Title for AvatarCropView.',\n defaultMessage: 'Drag to Adjust'\n },\n notif: {\n id: 'sidepanel_title_acc_notifications',\n description: 'Sidepanel title for AccNotificationsView.',\n defaultMessage: 'Notifications'\n },\n support: {\n id: 'sidepanel_title_acc_support',\n description: 'Sidepanel title for AccSupportView.',\n defaultMessage: 'Support'\n },\n newtpk: {\n id: 'sidepanel_title_newtpk',\n description: 'Sidepanel title for NewTopicView.',\n defaultMessage: 'Start New Chat'\n },\n cred: {\n id: 'sidepanel_title_cred',\n description: 'Sidepanel title for ValidationView.',\n defaultMessage: 'Confirm Credentials'\n },\n reset: {\n id: 'sidepanel_title_reset',\n description: 'Sidepanel title for PasswordResetView.',\n defaultMessage: 'Reset Password'\n },\n archive: {\n id: 'sidepanel_title_archive',\n description: 'Sidepanel title for ContactsView-Archive.',\n defaultMessage: 'Archived Chats'\n },\n blocked: {\n id: 'sidepanel_title_blocked',\n description: 'Sidepanel title for ContactsView-Blocked.',\n defaultMessage: 'Blocked Chats'\n }\n});\n\nclass SidepanelView extends React.Component {\n constructor(props) {\n super(props);\n\n this.handleNewTopic = this.handleNewTopic.bind(this);\n }\n\n handleNewTopic() {\n this.props.onNavigate('newtpk');\n }\n\n render() {\n const {formatMessage} = this.props.intl;\n const view = this.props.state || (this.props.myUserId ? 'contacts' : 'login');\n\n let title, avatar, badges;\n if (view == 'contacts') {\n title = this.props.title;\n avatar = this.props.avatar ? this.props.avatar : true;\n badges = this.props.trustedBadges;\n } else {\n title = formatMessage(messages[view]);\n avatar = false;\n badges = null;\n }\n\n let onCancel;\n if (['login', 'contacts'].indexOf(view) == -1) {\n onCancel = this.props.onCancel;\n }\n\n return (\n \n
\n\n
\n\n
\n\n {view === 'login' ?\n
:\n\n view === 'register' ?\n
:\n\n view === 'settings' ?\n
:\n\n view === 'edit' ?\n
:\n\n (view === 'general' || view === 'crop') ?\n
:\n\n view === 'notif' ?\n
:\n\n view === 'security' ?\n
:\n\n view === 'support' ?\n
:\n\n (view === 'contacts' || view == 'archive' || view == 'blocked') ?\n
:\n\n view === 'newtpk' ?\n
:\n\n view === 'cred' ?\n
:\n\n view === 'reset' ?\n
:\n null}\n
\n );\n }\n};\n\nexport default injectIntl(SidepanelView);\n","import { KNOWN_HOSTS, DEFAULT_HOST } from '../config.js';\n\n// Detect server address from the URL\nexport function detectServerAddress() {\n let host = DEFAULT_HOST;\n if (typeof window.location == 'object') {\n if (window.location.protocol == 'file:' || window.location.hostname == 'localhost') {\n host = KNOWN_HOSTS.local;\n } else if (window.location.hostname) {\n host = window.location.hostname + (window.location.port ? ':' + window.location.port : '');\n }\n }\n return host;\n}\n\n// Detect if the page is served over HTTPS.\nexport function isSecureConnection() {\n if (typeof window.location == 'object') {\n return window.location.protocol == 'https:';\n }\n return false;\n}\n\nexport function isLocalHost() {\n if (typeof window.location == 'object') {\n return window.location.hostname == 'localhost';\n }\n return false;\n}\n","// The top-level class to hold all functionality together.\nimport React from 'react';\nimport { FormattedMessage, defineMessages, injectIntl } from 'react-intl';\n\nimport { initializeApp as firebaseInitApp } from 'firebase/app';\nimport { getMessaging as firebaseGetMessaging, getToken as firebaseGetToken,\n deleteToken as firebaseDelToken, onMessage as firebaseOnMessage } from 'firebase/messaging';\n\nimport Tinode from 'tinode-sdk';\n\nimport Alert from '../widgets/alert.jsx';\nimport ContextMenu from '../widgets/context-menu.jsx';\nimport ForwardDialog from '../widgets/forward-dialog.jsx';\n\nimport InfoView from './info-view.jsx';\nimport MessagesView from './messages-view.jsx';\nimport SidepanelView from './sidepanel-view.jsx';\n\nimport { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, FORWARDED_PREVIEW_LENGTH, LOGGING_ENABLED,\n MEDIA_BREAKPOINT, RECEIVED_DELAY } from '../config.js';\nimport { PACKAGE_VERSION } from '../version.js';\nimport { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';\nimport { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';\nimport LocalStorageUtil from '../lib/local-storage.js';\nimport HashNavigation from '../lib/navigation.js';\nimport { secondsToTime } from '../lib/strformat.js'\nimport { updateFavicon } from '../lib/utils.js';\n\n// Sound to play on message received.\nconst POP_SOUND = new Audio('audio/msg.mp3');\n\nconst messages = defineMessages({\n reconnect_countdown: {\n id: 'reconnect_countdown',\n defaultMessage: 'Disconnected. Reconnecting in {seconds}…',\n description: 'Message shown when an app update is available.'\n },\n reconnect_now: {\n id: 'reconnect_now',\n defaultMessage: 'Try now',\n description: 'Prompt for reconnecting now'\n },\n push_init_failed: {\n id: 'push_init_failed',\n defaultMessage: 'Failed to initialize push notifications',\n description: 'Error message when push notifications have failed to initialize.'\n },\n invalid_security_token: {\n id: 'invalid_security_token',\n defaultMessage: 'Invalid security token',\n description: 'Error message when resetting password.'\n },\n no_connection: {\n id: 'no_connection',\n defaultMessage: 'No connection',\n description: 'Warning that the user is offline.'\n },\n code_doesnot_match: {\n id: 'code_doesnot_match',\n defaultMessage: 'Code does not match',\n description: 'Error message when the credential validation code is incorrect.'\n },\n menu_item_info: {\n id: 'menu_item_info',\n defaultMessage: 'Info',\n description: 'Show extended topic information'\n }\n});\n\nclass TinodeWeb extends React.Component {\n constructor(props) {\n super(props);\n\n this.selfRef = React.createRef();\n\n this.state = this.getBlankState();\n\n this.handleResize = this.handleResize.bind(this);\n this.handleHashRoute = this.handleHashRoute.bind(this);\n this.handleOnline = this.handleOnline.bind(this);\n this.checkForAppUpdate = this.checkForAppUpdate.bind(this);\n this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);\n this.handleError = this.handleError.bind(this);\n this.handleLoginRequest = this.handleLoginRequest.bind(this);\n this.handlePersistenceChange = this.handlePersistenceChange.bind(this);\n this.handleConnected = this.handleConnected.bind(this);\n this.handleAutoreconnectIteration = this.handleAutoreconnectIteration.bind(this);\n this.doLogin = this.doLogin.bind(this);\n this.handleCredentialsRequest = this.handleCredentialsRequest.bind(this);\n this.handleLoginSuccessful = this.handleLoginSuccessful.bind(this);\n this.handleDisconnect = this.handleDisconnect.bind(this);\n this.tnMeMetaDesc = this.tnMeMetaDesc.bind(this);\n this.tnMeContactUpdate = this.tnMeContactUpdate.bind(this);\n this.tnMeSubsUpdated = this.tnMeSubsUpdated.bind(this);\n this.resetContactList = this.resetContactList.bind(this);\n this.tnData = this.tnData.bind(this);\n this.tnInitFind = this.tnInitFind.bind(this);\n this.tnFndSubsUpdated = this.tnFndSubsUpdated.bind(this);\n this.handleSearchContacts = this.handleSearchContacts.bind(this);\n this.handleTopicSelected = this.handleTopicSelected.bind(this);\n this.handleHideMessagesView = this.handleHideMessagesView.bind(this);\n this.handleSendMessage = this.handleSendMessage.bind(this);\n this.handleNewChatInvitation = this.handleNewChatInvitation.bind(this);\n this.handleNewAccount = this.handleNewAccount.bind(this);\n this.handleNewAccountRequest = this.handleNewAccountRequest.bind(this);\n this.handleUpdatePasswordRequest = this.handleUpdatePasswordRequest.bind(this);\n this.handleUpdateAccountTagsRequest = this.handleUpdateAccountTagsRequest.bind(this);\n this.handleToggleIncognitoMode = this.handleToggleIncognitoMode.bind(this);\n this.handleSettings = this.handleSettings.bind(this);\n this.handleGlobalSettings = this.handleGlobalSettings.bind(this);\n this.handleShowArchive = this.handleShowArchive.bind(this);\n this.handleShowBlocked = this.handleShowBlocked.bind(this);\n this.handleToggleMessageSounds = this.handleToggleMessageSounds.bind(this);\n this.handleCredAdd = this.handleCredAdd.bind(this);\n this.handleCredDelete = this.handleCredDelete.bind(this);\n this.handleCredConfirm = this.handleCredConfirm.bind(this);\n this.initFCMessaging = this.initFCMessaging.bind(this);\n this.toggleFCMToken = this.toggleFCMToken.bind(this);\n this.handlePushMessage = this.handlePushMessage.bind(this);\n this.handleSidepanelCancel = this.handleSidepanelCancel.bind(this);\n this.handleStartTopicRequest = this.handleStartTopicRequest.bind(this);\n this.handleNewTopicCreated = this.handleNewTopicCreated.bind(this);\n this.handleTopicUpdateRequest = this.handleTopicUpdateRequest.bind(this);\n this.handleUnarchive = this.handleUnarchive.bind(this);\n this.handleChangePermissions = this.handleChangePermissions.bind(this);\n this.handleTagsUpdateRequest = this.handleTagsUpdateRequest.bind(this);\n this.handleLogout = this.handleLogout.bind(this);\n this.handleDeleteAccount = this.handleDeleteAccount.bind(this);\n this.handleDeleteTopicRequest = this.handleDeleteTopicRequest.bind(this);\n this.handleDeleteMessagesRequest = this.handleDeleteMessagesRequest.bind(this);\n this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);\n this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this);\n this.handleReportTopic = this.handleReportTopic.bind(this);\n this.handleShowContextMenu = this.handleShowContextMenu.bind(this);\n this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this);\n this.handleHideContextMenu = this.handleHideContextMenu.bind(this);\n this.handleShowAlert = this.handleShowAlert.bind(this);\n this.handleShowInfoView = this.handleShowInfoView.bind(this);\n this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);\n this.handleValidateCredentialsRequest = this.handleValidateCredentialsRequest.bind(this);\n this.handlePasswordResetRequest = this.handlePasswordResetRequest.bind(this);\n this.handleResetPassword = this.handleResetPassword.bind(this);\n this.handleContextMenuAction = this.handleContextMenuAction.bind(this);\n\n this.handleShowForwardDialog = this.handleShowForwardDialog.bind(this);\n this.handleHideForwardDialog = this.handleHideForwardDialog.bind(this);\n\n this.sendMessageToTopic = this.sendMessageToTopic.bind(this);\n }\n\n getBlankState() {\n const settings = LocalStorageUtil.getObject('settings') || {};\n const persist = !!LocalStorageUtil.getObject('keep-logged-in');\n\n return {\n connected: false,\n // Connected and subscribed to 'me'\n ready: false,\n // Try to re-login on new connection.\n autoLogin: false,\n transport: settings.transport || null,\n serverAddress: settings.serverAddress || detectServerAddress(),\n serverVersion: \"no connection\",\n // \"On\" is the default, so saving the \"off\" state.\n messageSounds: !settings.messageSoundsOff,\n incognitoMode: false,\n desktopAlerts: persist && !!settings.desktopAlerts,\n desktopAlertsEnabled: (isSecureConnection() || isLocalHost()) &&\n (typeof firebase != 'undefined') && (typeof navigator != 'undefined') &&\n (typeof FIREBASE_INIT != 'undefined'),\n firebaseToken: persist ? LocalStorageUtil.getObject('firebase-token') : null,\n\n applicationVisible: !document.hidden,\n\n errorText: '',\n errorLevel: null,\n errorAction: undefined,\n errorActionText: null,\n\n sidePanelSelected: 'login',\n sidePanelTitle: null,\n sidePanelAvatar: null,\n myTrustedBadges: [],\n loadSpinnerVisible: false,\n\n login: '',\n password: '',\n persist: persist,\n myUserId: null,\n liveConnection: navigator.onLine,\n topicSelected: '',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n newTopicParams: null,\n loginDisabled: false,\n displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),\n infoPanel: undefined,\n mobilePanel: 'sidepanel',\n\n contextMenuVisible: false,\n contextMenuBounds: null,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuItems: [],\n\n forwardDialogVisible: false,\n forwardMessage: null,\n\n // Modal alert dialog.\n alertVisible: false,\n alertParams: {},\n\n // Chats as shown in the ContactsView\n chatList: [],\n // Contacts returned by a search query.\n searchResults: [],\n // Merged results of a search query and p2p chats.\n searchableContacts: [],\n credMethod: undefined,\n credCode: undefined,\n // Topic to go to after login.\n requestedTopic: undefined\n };\n }\n\n componentDidMount() {\n window.addEventListener('resize', this.handleResize);\n window.addEventListener('online', (e) => { this.handleOnline(true); });\n window.addEventListener('offline', (e) => { this.handleOnline(false); });\n window.addEventListener('hashchange', this.handleHashRoute);\n\n // Process background notifications from the service worker.\n if (typeof BroadcastChannel == 'function') {\n const serviceWorkerChannel = new BroadcastChannel('tinode-sw');\n serviceWorkerChannel.addEventListener('message', this.handlePushMessage);\n } else {\n // Safari is broken by design.\n console.warn(\"Your browser does not support BroadcastChannel. Some features will not be available\");\n }\n\n // Window/tab visible or invisible for pausing timers.\n document.addEventListener('visibilitychange', this.handleVisibilityEvent);\n\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n\n new Promise((resolve, reject) => {\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport,\n this.props.intl.locale, this.state.persist, resolve);\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n }).then(() => {\n // Initialize desktop alerts.\n if (this.state.desktopAlertsEnabled) {\n this.initFCMessaging().then(() => {\n if (this.state.desktopAlerts) {\n if (!this.state.firebaseToken) {\n this.toggleFCMToken(true);\n } else {\n this.tinode.setDeviceToken(this.state.firebaseToken);\n }\n }\n }).catch(() => {\n // do nothing: handled earlier.\n // catch needed to pervent unnecessary logging of error.\n });\n }\n\n // Parse and save the hash navigation params.\n const parsedNav = HashNavigation.parseUrlHash(window.location.hash);\n\n // Read contacts from cache.\n this.resetContactList();\n\n const token = this.state.persist ? LocalStorageUtil.getObject('auth-token') : undefined;\n if (token) {\n this.setState({autoLogin: true});\n\n // When reading from storage, date is returned as string.\n token.expires = new Date(token.expires);\n this.tinode.setAuthToken(token);\n this.tinode.connect().catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n\n // Maybe navigate to home screen.\n if (!['cred', 'reset', 'register'].includes(parsedNav.path[0])) {\n // Save possible topic name.\n this.setState({requestedTopic: parsedNav.path[1]});\n HashNavigation.navigateTo('');\n }\n\n this.readTimer = null;\n this.readTimerCallback = null;\n\n this.handleHashRoute();\n });\n }\n\n componentWillUnmount() {\n window.removeEventListener('resize', this.handleResize);\n window.removeEventListener('hashchange', this.handleHashRoute);\n document.removeEventListener('visibilitychange', this.handleVisibilityEvent);\n }\n\n // Setup transport (usually websocket) and server address. This will terminate connection with the server.\n static tnSetup(serverAddress, transport, locale, persistentCache, onSetupCompleted) {\n const tinode = new Tinode({appName: APP_NAME, host: serverAddress, apiKey: API_KEY, transport: transport,\n secure: isSecureConnection(), persist: persistentCache}, onSetupCompleted);\n tinode.setHumanLanguage(locale);\n tinode.enableLogging(LOGGING_ENABLED, true);\n return tinode;\n }\n\n // Notifiy Tinode that a push message was received from the server.\n handlePushMessage(data) {\n if (data.what == 'msg' && Tinode.isChannelTopicName(data.topic)) {\n // The last argument is a fake user Id: otherwise the update is seen as one from the current user.\n this.tinode.oobNotification(data.topic, data.seq, 'fake-uid');\n }\n }\n\n initFCMessaging() {\n const {formatMessage, locale} = this.props.intl;\n const onError = (msg, err) => {\n console.error(msg, err);\n this.handleError(formatMessage(messages.push_init_failed), 'err');\n this.setState({desktopAlertsEnabled: false});\n }\n\n try {\n this.fcm = firebaseGetMessaging(firebaseInitApp(FIREBASE_INIT, APP_NAME));\n return navigator.serviceWorker.register('/service-worker.js').then((reg) => {\n this.checkForAppUpdate(reg);\n reg.active.postMessage(JSON.stringify({locale: locale, version: PACKAGE_VERSION}));\n return reg;\n }).then((reg) => {\n // Request token.\n return TinodeWeb.requestFCMToken(this.fcm, reg);\n }).then((token) => {\n const persist = LocalStorageUtil.getObject('keep-logged-in');\n if (token != this.state.firebaseToken) {\n this.tinode.setDeviceToken(token);\n if (persist) {\n LocalStorageUtil.setObject('firebase-token', token);\n }\n }\n this.setState({firebaseToken: token, desktopAlerts: true});\n if (persist) {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }\n\n // Handhe FCM pushes\n // (a) for channels always,\n // (b) pushes when the app is in foreground but has no focus.\n firebaseOnMessage(this.fcm, (payload) => { this.handlePushMessage(payload.data); });\n }).catch((err) => {\n // SW registration or FCM has failed :(\n onError(err);\n throw err;\n });\n } catch (err) {\n onError(err);\n return Promise.reject(err);\n }\n }\n\n // Google's FCM API is idiotic.\n static requestFCMToken(fcm, sw) {\n return firebaseGetToken(fcm, {\n serviceWorkerRegistration: sw,\n vapidKey: FIREBASE_INIT.messagingVapidKey\n }).then((token) => {\n if (token) {\n return token;\n } else {\n // Try to request permissions.\n return Notification.requestPermission().then((permission) => {\n if (permission === 'granted') {\n return firebaseGetToken(fcm, {\n serviceWorkerRegistration: reg,\n vapidKey: FIREBASE_INIT.messagingVapidKey\n }).then((token) => {\n if (token) {\n return token;\n } else {\n throw new Error(\"Failed to initialize notifications\");\n }\n });\n } else {\n throw new Error(\"No permission to send notifications: \" + permission);\n }\n });\n }\n });\n }\n\n handleResize() {\n const mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT;\n this.setState({\n viewportWidth: document.documentElement.clientWidth,\n viewportHeight: document.documentElement.clientHeight\n });\n if (this.state.displayMobile != mobile) {\n this.setState({displayMobile: mobile});\n }\n }\n\n // Check if a newer version of TinodeWeb app is available at the server.\n checkForAppUpdate(reg) {\n reg.onupdatefound = () => {\n const installingWorker = reg.installing;\n installingWorker.onstatechange = () => {\n if (installingWorker.state == 'installed' && navigator.serviceWorker.controller) {\n const msg = <>\n \n \n .>;\n this.handleError(msg, 'info');\n }\n }\n }\n }\n\n // Handle for hashchange event: display appropriate panels.\n handleHashRoute() {\n const hash = HashNavigation.parseUrlHash(window.location.hash);\n if (hash.path && hash.path.length > 0) {\n // Left-side panel selector.\n if (['register','settings','edit','notif','security','support','general','crop',\n 'cred','reset','newtpk','archive','blocked','contacts',''].includes(hash.path[0])) {\n this.setState({sidePanelSelected: hash.path[0]});\n } else {\n console.warn(\"Unknown sidepanel view\", hash.path[0]);\n }\n\n // Topic for MessagesView selector.\n let topicName = hash.path[1] || null;\n if (topicName != this.state.topicSelected) {\n if (!Tinode.topicType(topicName)) {\n // Clear invalid topic name.\n topicName = null;\n }\n const newState = {\n topicSelected: topicName\n };\n const acs = this.tinode.getTopicAccessMode(topicName);\n if (acs) {\n newState.topicSelectedAcs = acs;\n }\n this.setState(newState);\n }\n } else {\n // Empty hashpath\n this.setState({sidePanelSelected: '', topicSelected: null});\n }\n\n // Save validation credentials, if available.\n if (hash.params.method) {\n this.setState({ credMethod: hash.params.method });\n }\n if (hash.params.code) {\n this.setState({ credCode: hash.params.code });\n }\n\n // Additional parameters of panels.\n this.setState({\n infoPanel: hash.params.info,\n newTopicTabSelected: hash.params.tab\n });\n }\n\n handleOnline(online) {\n if (online) {\n this.handleError();\n clearInterval(this.reconnectCountdown);\n this.tinode.reconnect();\n } else {\n this.handleError(this.props.intl.formatMessage(messages.no_connection), 'warn');\n }\n this.setState({liveConnection: online});\n }\n\n handleVisibilityEvent() {\n this.setState({applicationVisible: !document.hidden});\n }\n\n handleError(err, level, action, actionText) {\n this.setState({errorText: err, errorLevel: level, errorAction: action, errorActionText: actionText});\n }\n\n // User clicked Login button in the side panel.\n handleLoginRequest(login, password) {\n this.setState({\n loginDisabled: true,\n login: login,\n password: password,\n loadSpinnerVisible: true,\n autoLogin: true\n });\n this.handleError('', null);\n\n if (this.tinode.isConnected()) {\n this.doLogin(login, password, {meth: this.state.credMethod, resp: this.state.credCode});\n } else {\n this.tinode.connect().catch((err) => {\n // Socket error\n this.setState({loginDisabled: false, autoLogin: false, loadSpinnerVisible: false});\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // Enable or disable saving the password and IndexedDB.\n handlePersistenceChange(persist) {\n if (persist) {\n this.tinode.initStorage().then(() => {\n LocalStorageUtil.setObject('keep-logged-in', true);\n this.setState({persist: true});\n });\n } else {\n this.tinode.clearStorage().then(() => {\n LocalStorageUtil.setObject('keep-logged-in', false);\n this.setState({persist: false});\n });\n }\n }\n\n // Connection succeeded.\n handleConnected() {\n // Just to be sure.\n clearInterval(this.reconnectCountdown);\n this.handleError();\n\n const params = this.tinode.getServerInfo();\n this.setState({\n serverVersion: params.ver + ' ' + (params.build ? params.build : 'none')\n });\n\n if (this.state.autoLogin) {\n this.doLogin(this.state.login, this.state.password, {meth: this.state.credMethod, resp: this.state.credCode});\n }\n }\n\n // Called for each auto-reconnect iteration.\n handleAutoreconnectIteration(sec, prom) {\n clearInterval(this.reconnectCountdown);\n\n if (sec < 0) {\n // Clear error\n this.handleError();\n return;\n }\n\n if (prom) {\n // Reconnecting now\n prom.then(() => {\n // Reconnected: clear error\n this.handleError();\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n return;\n }\n\n const {formatMessage} = this.props.intl;\n let count = sec / 1000;\n count = count | count;\n this.reconnectCountdown = setInterval(() => {\n const timeLeft = (count > 99) ? secondsToTime(count) : count;\n this.handleError(\n formatMessage(messages.reconnect_countdown, {seconds: timeLeft}),\n 'warn',\n () => {\n clearInterval(this.reconnectCountdown);\n this.tinode.reconnect();\n },\n formatMessage(messages.reconnect_now)\n );\n count -= 1;\n }, 1000);\n }\n\n // Connection lost\n handleDisconnect(err) {\n this.setState({\n connected: false,\n ready: false,\n topicSelectedOnline: false,\n errorText: err && err.message ? err.message : \"Disconnected\",\n errorLevel: err && err.message ? 'err' : 'warn',\n loginDisabled: false,\n contextMenuVisible: false,\n forwardDialogVisible: false,\n serverVersion: \"no connection\"\n });\n }\n\n doLogin(login, password, cred) {\n if (this.tinode.isAuthenticated()) {\n // Already logged in. Go to default screen.\n HashNavigation.navigateTo('');\n return;\n }\n // Sanitize and package credentail.\n cred = Tinode.credential(cred);\n // Try to login with login/password. If they are not available, try token. If no token, ask for login/password.\n let promise = null;\n let token = this.tinode.getAuthToken();\n if (login && password) {\n token = null;\n this.setState({password: null});\n promise = this.tinode.loginBasic(login, password, cred);\n } else if (token) {\n promise = this.tinode.loginToken(token.token, cred);\n }\n\n if (promise) {\n promise.then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text === 'validate credentials') {\n this.setState({loadSpinnerVisible: false});\n if (cred) {\n this.handleError(this.props.intl.formatMessage(messages.code_doesnot_match), 'warn');\n }\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful();\n }\n }).catch((err) => {\n // Login failed, report error.\n this.setState({\n loginDisabled: false,\n credMethod: undefined,\n credCode: undefined,\n loadSpinnerVisible: false,\n autoLogin: false\n });\n this.handleError(err.message, 'err');\n if (token) {\n this.handleLogout();\n }\n HashNavigation.navigateTo('');\n });\n } else {\n // No login credentials provided.\n // Make sure we are on the login page.\n HashNavigation.navigateTo('');\n this.setState({loginDisabled: false});\n }\n }\n\n handleCredentialsRequest(params) {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n parsed.path[0] = 'cred';\n parsed.params['method'] = params.cred[0];\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n }\n\n handleLoginSuccessful() {\n this.handleError();\n\n // Refresh authentication token.\n if (LocalStorageUtil.getObject('keep-logged-in')) {\n LocalStorageUtil.setObject('auth-token', this.tinode.getAuthToken());\n }\n\n const goToTopic = this.state.requestedTopic;\n // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.\n const me = this.tinode.getMeTopic();\n me.onMetaDesc = this.tnMeMetaDesc;\n me.onContactUpdate = this.tnMeContactUpdate;\n me.onSubsUpdated = this.tnMeSubsUpdated;\n this.setState({\n connected: true,\n credMethod: undefined,\n credCode: undefined,\n myUserId: this.tinode.getCurrentUserID(),\n autoLogin: true,\n requestedTopic: undefined,\n });\n // Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched.\n me.subscribe(\n me.startMetaQuery().\n withLaterSub().\n withDesc().\n withTags().\n withCred().\n build()\n ).catch((err) => {\n this.tinode.disconnect();\n localStorage.removeItem('auth-token');\n this.handleError(err.message, 'err');\n HashNavigation.navigateTo('');\n }).finally(() => {\n this.setState({loadSpinnerVisible: false});\n });\n let urlHash = HashNavigation.setUrlSidePanel(window.location.hash, 'contacts');\n if (goToTopic) {\n urlHash = HashNavigation.setUrlTopic(urlHash, goToTopic);\n }\n HashNavigation.navigateTo(urlHash);\n }\n\n tnMeMetaDesc(desc) {\n if (desc) {\n if (desc.public) {\n this.setState({\n sidePanelTitle: desc.public.fn,\n sidePanelAvatar: makeImageUrl(desc.public.photo)\n });\n }\n if (desc.trusted) {\n const badges = [];\n for (const [key, val] of Object.entries(desc.trusted)) {\n if (val) {\n badges.push(key);\n }\n }\n this.setState({\n myTrustedBadges: badges,\n });\n }\n if (desc.acs) {\n this.setState({\n incognitoMode: !desc.acs.isPresencer()\n });\n }\n }\n }\n\n // Reactions to updates to the contact list.\n tnMeContactUpdate(what, cont) {\n if (what == 'on' || what == 'off') {\n this.resetContactList();\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedOnline: (what == 'on')});\n }\n } else if (what == 'read') {\n this.resetContactList();\n } else if (what == 'msg') {\n // Check if the topic is archived. If so, don't play a sound.\n const topic = this.tinode.getTopic(cont.topic);\n const archived = topic && topic.isArchived();\n\n // New message received. Maybe the message is from the current user, then unread is 0.\n if (cont.unread > 0 && this.state.messageSounds && !archived) {\n // Skip update if the topic is currently open, otherwise the badge will annoyingly flash.\n if (document.hidden || this.state.topicSelected != cont.topic) {\n POP_SOUND.play();\n }\n }\n // Reorder contact list to use possibly updated 'touched'.\n this.resetContactList();\n } else if (what == 'recv') {\n // Explicitly ignoring \"recv\" -- it causes no visible updates to contact list.\n } else if (what == 'gone' || what == 'unsub') {\n // Topic deleted or user unsubscribed. Remove topic from view.\n // If the currently selected topic is gone, clear the selection.\n if (this.state.topicSelected == cont.topic) {\n this.handleTopicSelected(null);\n }\n // Redraw without the deleted topic.\n this.resetContactList();\n } else if (what == 'acs') {\n // Permissions changed. If it's for the currently selected topic,\n // update the views.\n if (this.state.topicSelected == cont.topic) {\n this.setState({topicSelectedAcs: cont.acs});\n }\n } else if (what == 'del') {\n // TODO: messages deleted (hard or soft) -- update pill counter.\n } else if (what == 'upd') {\n // upd - handled by the SDK. Explicitly ignoring here.\n } else {\n // TODO(gene): handle other types of notifications:\n // * ua -- user agent changes (maybe display a pictogram for mobile/desktop).\n console.info(\"Unsupported (yet) presence update:\" + what + \" in: \" + cont.topic);\n }\n }\n\n tnMeSubsUpdated(unused) {\n this.resetContactList();\n }\n\n // Merge search results and contact list to create a single flat\n // list of known contacts for GroupManager to use.\n static prepareSearchableContacts(chatList, foundContacts) {\n const merged = {};\n\n // For chatList topics merge only p2p topics and convert them to the\n // same format as foundContacts.\n for (const c of chatList) {\n if (Tinode.isP2PTopicName(c.topic)) {\n merged[c.topic] = {\n user: c.topic,\n updated: c.updated,\n public: c.public,\n private: c.private,\n acs: c.acs\n };\n }\n }\n\n // Add all foundCountacts if they have not been added already.\n for (const c of foundContacts) {\n if (!merged[c.user]) {\n merged[c.user] = c;\n }\n }\n\n return Object.values(merged);\n }\n\n resetContactList() {\n const newState = {\n chatList: []\n };\n\n if (!this.state.ready) {\n newState.ready = true;\n }\n\n this.tinode.getMeTopic().contacts((c) => {\n if (!c.topic && !c.user) {\n // Contacts expect c.topic to be set.\n c.topic = c.name;\n }\n\n newState.chatList.push(c);\n if (this.state.topicSelected == c.topic) {\n newState.topicSelectedOnline = c.online;\n newState.topicSelectedAcs = c.acs;\n }\n });\n\n const past = new Date(0);\n newState.chatList.sort((a, b) => {\n return (a.touched || past).getTime() - (b.touched || past).getTime();\n });\n\n // Merge search results and chat list.\n newState.searchableContacts = TinodeWeb.prepareSearchableContacts(newState.chatList, this.state.searchResults);\n this.setState(newState);\n }\n\n // Sending \"received\" notifications\n tnData(data) {\n const topic = this.tinode.getTopic(data.topic);\n if (topic.msgStatus(data, true) >= Tinode.MESSAGE_STATUS_SENT && data.from != this.state.myUserId) {\n clearTimeout(this.receivedTimer);\n this.receivedTimer = setTimeout(() => {\n this.receivedTimer = undefined;\n topic.noteRecv(data.seq);\n }, RECEIVED_DELAY);\n }\n }\n\n /* Fnd topic: find contacts by tokens */\n tnInitFind() {\n const fnd = this.tinode.getFndTopic();\n fnd.onSubsUpdated = this.tnFndSubsUpdated;\n if (fnd.isSubscribed()) {\n this.tnFndSubsUpdated();\n } else {\n fnd.subscribe(fnd.startMetaQuery().withSub().build()).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n tnFndSubsUpdated() {\n const foundContacts = [];\n // Don't attempt to create P2P topics which already exist. Server will reject the duplicates.\n this.tinode.getFndTopic().contacts((s) => {\n foundContacts.push(s);\n });\n this.setState({\n searchResults: foundContacts,\n searchableContacts: TinodeWeb.prepareSearchableContacts(this.state.chatList, foundContacts)\n });\n }\n\n /** Called when the user enters a contact into the contact search field in the NewAccount panel\n @param query {Array} is an array of contacts to search for\n */\n handleSearchContacts(query) {\n const fnd = this.tinode.getFndTopic();\n fnd.setMeta({desc: {public: query}}).then((ctrl) => {\n return fnd.getMeta(fnd.startMetaQuery().withSub().build());\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User clicked on a contact in the side panel or deleted a contact.\n handleTopicSelected(topicName) {\n // Clear newTopicParams after use.\n if (this.state.newTopicParams && this.state.newTopicParams._topicName != topicName) {\n this.setState({\n newTopicParams: null\n });\n }\n\n if (topicName) {\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'topic-view',\n infoPanel: undefined\n });\n // Different contact selected.\n if (this.state.topicSelected != topicName) {\n this.setState({\n topicSelectedOnline: this.tinode.isTopicOnline(topicName),\n topicSelectedAcs: this.tinode.getTopicAccessMode(topicName),\n forwardMessage: null\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', topicName));\n }\n } else {\n // Currently selected contact deleted\n this.setState({\n errorText: '',\n errorLevel: null,\n mobilePanel: 'sidepanel',\n topicSelectedOnline: false,\n topicSelectedAcs: null,\n infoPanel: undefined,\n forwardMessage: null\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));\n }\n }\n\n // In mobile view user requested to show sidepanel\n handleHideMessagesView() {\n this.setState({\n mobilePanel: 'sidepanel'\n });\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, null));\n }\n\n // User is sending a message, either plain text or a drafty object, possibly\n // with attachments.\n // - msg - Drafty message with content\n // - promise - Promise to be resolved when the upload is completed\n // - uploader - for tracking progress\n // - head - head dictionary to be attached to the message\n handleSendMessage(msg, promise, uploader, head) {\n const topic = this.tinode.getTopic(this.state.topicSelected);\n this.sendMessageToTopic(topic, msg, promise, uploader, head);\n }\n\n sendMessageToTopic(topic, msg, promise, uploader, head) {\n msg = topic.createMessage(msg, false);\n // The uploader is used to show progress.\n msg._uploader = uploader;\n\n if (head) {\n msg.head = Object.assign(msg.head || {}, head);\n }\n\n if (!topic.isSubscribed()) {\n // Topic is not subscribed yet. Subscribe.\n if (!promise) {\n promise = Promise.resolve();\n }\n promise = promise\n .then(() => topic.subscribe())\n .then(() => {\n // If there are unsent messages, try sending them now.\n topic.queuedMessages((pub) => {\n if (!pub._sending && topic.isSubscribed()) {\n this.sendMessage(pub);\n }\n });\n });\n }\n\n if (promise) {\n promise = promise.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n topic.publishDraft(msg, promise)\n .then((ctrl) => {\n if (topic.isArchived()) {\n return topic.archive(false);\n }\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleNewChatInvitation(topicName, action) {\n const topic = this.tinode.getTopic(topicName);\n let response = null;\n switch (action) {\n case 'accept':\n // Accept given permissions.\n const mode = topic.getAccessMode().getGiven();\n response = topic.setMeta({sub: {mode: mode}});\n if (topic.isP2PType()) {\n // For P2P topics change 'given' permission of the peer too.\n // In p2p topics the other user has the same name as the topic.\n response = response.then((ctrl) => {\n topic.setMeta({sub: {user: topicName, mode: mode}});\n });\n }\n break;\n case 'delete':\n // Ignore invitation by deleting it.\n response = topic.delTopic(true);\n break;\n case 'block':\n // Ban the topic making futher invites impossible.\n // Just self-ban.\n const am = topic.getAccessMode().updateWant('-JP').getWant();\n response = topic.setMeta({sub: {mode: am}}).then((ctrl) => {\n return this.handleTopicSelected(null);\n });\n break;\n default:\n console.warn(\"Unknown invitation action\", '\"' + action + '\"\"');\n }\n\n if (response != null) {\n response.catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n // User chose a Sign Up menu item.\n handleNewAccount() {\n this.handleError();\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, 'register'));\n }\n\n // Actual registration of a new account.\n handleNewAccountRequest(login_, password_, public_, cred_, tags_) {\n // Clear old error, if any.\n this.handleError();\n\n this.tinode.connect(this.state.serverAddress)\n .then(() => {\n return this.tinode.createAccountBasic(login_, password_,\n {public: public_, tags: tags_, cred: Tinode.credential(cred_)});\n }).then((ctrl) => {\n if (ctrl.code >= 300 && ctrl.text == 'validate credentials') {\n this.handleCredentialsRequest(ctrl.params);\n } else {\n this.handleLoginSuccessful(this);\n }\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleToggleIncognitoMode(on) {\n // Make state undefined.\n this.setState({incognitoMode: null});\n\n const me = this.tinode.getMeTopic();\n const am = me.getAccessMode().updateWant(on ? '-P' : '+P').getWant();\n me.setMeta({sub: {mode: am}}).catch((err) => {\n // Request failed, keep existing state.\n this.setState({incognitoMode: !on});\n this.handleError(err.message, 'err');\n });\n }\n\n handleUpdateAccountTagsRequest(tags) {\n this.tinode.getMeTopic().setMeta({tags: tags})\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n // User chose Settings menu item.\n handleSettings() {\n this.handleError();\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'edit' : 'settings'));\n }\n\n // User updated global parameters.\n handleGlobalSettings(settings) {\n const serverAddress = settings.serverAddress || this.state.serverAddress;\n const transport = settings.transport || this.state.transport;\n if (this.tinode) {\n this.tinode.clearStorage();\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n }\n this.tinode = TinodeWeb.tnSetup(serverAddress, transport, this.props.intl.locale,\n LocalStorageUtil.getObject('keep-logged-in'));\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n\n this.setState({\n serverAddress: serverAddress,\n transport: transport\n });\n LocalStorageUtil.setObject('settings', {\n serverAddress: serverAddress,\n transport: transport\n });\n\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, ''));\n }\n\n // User chose 'Archived chats'.\n handleShowArchive() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'archive' : ''));\n }\n\n // User viewes 'Blocked chats'.\n handleShowBlocked() {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash,\n this.state.myUserId ? 'blocked' : ''));\n }\n\n toggleFCMToken(enabled) {\n if (enabled) {\n this.setState({desktopAlerts: null});\n if (!this.state.firebaseToken) {\n this.initFCMessaging().catch(() => {\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n });\n } else {\n this.setState({desktopAlerts: true});\n if (LocalStorageUtil.getObject('keep-logged-in')) {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: true});\n }\n }\n } else if (this.state.firebaseToken && this.fcm) {\n firebaseDelToken(this.fcm).catch((err) => {\n console.error(\"Unable to delete token.\", err);\n }).finally(() => {\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n localStorage.removeItem('firebase-token');\n this.setState({desktopAlerts: false, firebaseToken: null});\n // Inform the server that the token was deleted.\n this.tinode.setDeviceToken(null);\n });\n } else {\n this.setState({desktopAlerts: false, firebaseToken: null});\n LocalStorageUtil.updateObject('settings', {desktopAlerts: false});\n }\n }\n\n handleToggleMessageSounds(enabled) {\n this.setState({messageSounds: enabled});\n LocalStorageUtil.updateObject('settings', {\n messageSoundsOff: !enabled\n });\n }\n\n handleCredAdd(method, value) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: method, val: value}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredDelete(method, value) {\n const me = this.tinode.getMeTopic();\n me.delCredential(method, value).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleCredConfirm(method, response) {\n this.handleCredentialsRequest({cred: [method]});\n }\n\n // User clicked Cancel button in Setting or Sign Up panel.\n handleSidepanelCancel() {\n const parsed = HashNavigation.parseUrlHash(window.location.hash);\n let path = '';\n if (['security','support','general','notif'].includes(parsed.path[0])) {\n path = 'edit';\n } else if ('crop' == parsed.path[0]) {\n path = 'general';\n } else if ('blocked' == parsed.path[0]) {\n path = 'security';\n } else if (this.state.myUserId) {\n path = 'contacts';\n }\n parsed.path[0] = path;\n if (parsed.params) {\n delete parsed.params.code;\n delete parsed.params.method;\n delete parsed.params.tab;\n delete parsed.params.scheme;\n delete parsed.params.token;\n }\n HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));\n this.setState({errorText: '', errorLevel: null});\n }\n\n // Sidepanel navigator. No need to bind to 'this'.\n basicNavigator(hash) {\n HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, hash));\n }\n\n // Topic info navigator. No need to bind to 'this'.\n infoNavigator(hash) {\n HashNavigation.navigateTo(HashNavigation.setUrlInfoPanel(window.location.hash, hash));\n }\n\n // Request to start a topic, new or selected from search results, or \"by ID\".\n handleStartTopicRequest(topicName, newTopicParams, isChannel) {\n // Check if topic is indeed new. If not, launch it.\n if (topicName && this.tinode.isTopicCached(topicName)) {\n this.handleTopicSelected(topicName);\n return;\n }\n\n const params = {};\n if (Tinode.isP2PTopicName(topicName)) {\n // Because we are initialing the subscription, set 'want' to all permissions.\n params.sub = {mode: DEFAULT_P2P_ACCESS_MODE};\n // Give the other user all permissions too.\n params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};\n } else {\n topicName = topicName || this.tinode.newGroupTopicName(isChannel);\n if (newTopicParams) {\n params.desc = {public: newTopicParams.public, private: {comment: newTopicParams.private}};\n params.tags = newTopicParams.tags;\n }\n }\n params._topicName = topicName;\n this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});\n }\n\n // New topic was created, here is the new topic name.\n handleNewTopicCreated(oldName, newName) {\n if (this.state.topicSelected == oldName && oldName != newName) {\n // If the current URl contains the old topic name, replace it with new.\n // Update the name of the selected topic first so the navigator doen't clear\n // the state.\n this.setState({topicSelected: newName}, () => {\n HashNavigation.navigateTo(HashNavigation.setUrlTopic('', newName));\n });\n }\n }\n\n handleTopicUpdateRequest(topicName, pub, priv, defacs) {\n this.handleError();\n\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const params = {};\n let attachments;\n if (pub) {\n if (pub.photo) {\n if (pub.photo.ref && pub.photo.ref != Tinode.DEL_CHAR) {\n attachments = [pub.photo.ref];\n } else if (!pub.photo.data || pub.photo.data == Tinode.DEL_CHAR) {\n pub.photo = Tinode.DEL_CHAR;\n }\n }\n params.public = pub;\n }\n\n if (typeof priv == 'string') {\n params.private = (priv === Tinode.DEL_CHAR) ?\n Tinode.DEL_CHAR : {comment: priv};\n }\n if (defacs) {\n params.defacs = defacs;\n }\n topic.setMeta({desc: params, attachments: attachments}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleUnarchive(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n topic.archive(false);\n }\n }\n\n handleUpdatePasswordRequest(password) {\n this.handleError();\n\n if (password) {\n this.tinode.updateAccountBasic(null, this.tinode.getCurrentLogin(), password).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleChangePermissions(topicName, mode, uid) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n const am = topic.getAccessMode();\n if (uid) {\n am.updateGiven(mode);\n mode = am.getGiven();\n } else {\n am.updateWant(mode);\n mode = am.getWant();\n }\n topic.setMeta({sub: {user: uid, mode: mode}}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleTagsUpdateRequest(topicName, tags) {\n const topic = this.tinode.getTopic(topicName);\n if (topic) {\n topic.setMeta({tags: tags}).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n }\n\n handleLogout() {\n updateFavicon(0);\n\n // Remove stored data.\n localStorage.removeItem('auth-token');\n localStorage.removeItem('firebase-token');\n localStorage.removeItem('settings');\n if (this.state.firebaseToken) {\n firebaseDelToken(this.fcm);\n }\n\n clearInterval(this.reconnectCountdown);\n\n let cleared;\n if (this.tinode) {\n cleared = this.tinode.clearStorage();\n this.tinode.onDisconnect = undefined;\n this.tinode.disconnect();\n } else {\n cleared = Promose.resolve();\n }\n this.setState(this.getBlankState());\n\n cleared.then(() => {\n this.tinode = TinodeWeb.tnSetup(this.state.serverAddress,\n this.state.transport, this.props.intl.locale, LocalStorageUtil.getObject('keep-logged-in'), () => {\n this.tinode.onConnect = this.handleConnected;\n this.tinode.onDisconnect = this.handleDisconnect;\n this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;\n HashNavigation.navigateTo('');\n })\n });\n }\n\n handleDeleteAccount() {\n this.tinode.delCurrentUser(true).then((ctrl) => {\n this.handleLogout();\n });\n }\n\n handleDeleteTopicRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Request to hard-delete topic.\n topic.delTopic(true).then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleDeleteMessagesRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Request hard-delete all messages.\n topic.delMessagesAll(true).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleLeaveUnsubRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.leave(true).then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleBlockTopicRequest(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleReportTopic(topicName) {\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n // Publish spam report.\n this.tinode.publish(Tinode.TOPIC_SYS, Tinode.Drafty.attachJSON(null, {\n 'action': 'report',\n 'target': topicName\n }));\n\n // Remove J and P permissions.\n topic.updateMode(null, '-JP').then((ctrl) => {\n // Hide MessagesView and InfoView panels.\n HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));\n }).catch((err) => {\n this.handleError(err.message, 'err');\n });\n }\n\n handleShowContextMenu(params, menuItems) {\n this.setState({\n contextMenuVisible: true,\n contextMenuClickAt: {x: params.x, y: params.y},\n contextMenuParams: params,\n contextMenuItems: menuItems || this.defaultTopicContextMenu(params.topicName),\n contextMenuBounds: this.selfRef.current.getBoundingClientRect()\n });\n }\n\n //\n handleShowForwardDialog(params) {\n if (this.state.sidePanelSelected == 'newtpk') {\n // If the Find panel is active, close it.\n this.handleSidepanelCancel();\n }\n const header = '➦ ' + params.userName;\n const content = typeof params.content == 'string' ?\n Tinode.Drafty.init(params.content) : Tinode.Drafty.forwardedContent(params.content);\n const preview = Tinode.Drafty.preview(content, FORWARDED_PREVIEW_LENGTH,\n undefined, params.forwarded != null);\n const msg = Tinode.Drafty.append(\n Tinode.Drafty.appendLineBreak(\n Tinode.Drafty.mention(header, params.userFrom)),\n content);\n\n const msgPreview = Tinode.Drafty.quote(header, params.userFrom, preview);\n const head = {\n forwarded: params.topicName + ':' + params.seq\n };\n this.setState({\n forwardDialogVisible: true,\n forwardMessage: { head: head, msg: msg, preview: msgPreview }\n });\n }\n\n defaultTopicContextMenu(topicName) {\n const topic = this.tinode.getTopic(topicName);\n\n let muted = false, blocked = false, self_blocked = false, subscribed = false, deleter = false, archived = false;\n if (topic) {\n subscribed = topic.isSubscribed();\n archived = topic.isArchived();\n\n const acs = topic.getAccessMode();\n if (acs) {\n muted = acs.isMuted();\n blocked = !acs.isJoiner();\n self_blocked = !acs.isJoiner('want');\n deleter = acs.isDeleter();\n }\n }\n\n return [\n subscribed ? {\n title: this.props.intl.formatMessage(messages.menu_item_info),\n handler: this.handleShowInfoView\n } : null,\n subscribed ? 'messages_clear' : null,\n subscribed && deleter ? 'messages_clear_hard' : null,\n muted ? (blocked ? null : 'topic_unmute') : 'topic_mute',\n self_blocked ? 'topic_unblock' : 'topic_block',\n archived ? 'topic_restore' : 'topic_archive',\n 'topic_delete'\n ];\n }\n\n handleHideContextMenu() {\n this.setState({\n contextMenuVisible: false,\n contextMenuClickAt: null,\n contextMenuParams: null,\n contextMenuBounds: null\n });\n }\n\n handleHideForwardDialog(keepForwardedMessage) {\n this.setState({\n forwardDialogVisible: false,\n forwardMessage: keepForwardedMessage ? this.state.forwardMessage : null\n });\n }\n\n handleContextMenuAction(action, promise, params) {\n if (action == 'topic_archive') {\n if (promise && params.topicName && params.topicName == this.state.topicSelected) {\n promise.then(() => {\n this.handleTopicSelected(null);\n });\n }\n } else if (action == 'menu_item_forward') {\n this.handleShowForwardDialog(params);\n }\n }\n\n handleShowAlert(title, content, onConfirm, confirmText, onReject, rejectText) {\n this.setState({\n alertVisible: true,\n alertParams: {\n title: title,\n content: content,\n onConfirm: onConfirm,\n confirm: confirmText,\n onReject: onReject,\n reject: rejectText\n }\n });\n }\n\n handleShowInfoView() {\n HashNavigation.navigateTo(HashNavigation.addUrlParam(window.location.hash, 'info', 'info'));\n this.setState({infoPanel: 'info'});\n }\n\n handleMemberUpdateRequest(topicName, added, removed) {\n if (!topicName) {\n return;\n }\n\n const topic = this.tinode.getTopic(topicName);\n if (!topic) {\n return;\n }\n\n if (added && added.length > 0) {\n added.map((uid) => {\n topic.invite(uid, null).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n\n if (removed && removed.length > 0) {\n removed.map((uid) => {\n topic.delSubscription(uid).catch((err) => {\n this.handleError(err.message, 'err');\n });\n });\n }\n }\n\n handleValidateCredentialsRequest(cred, code) {\n if (this.tinode.isAuthenticated()) {\n const me = this.tinode.getMeTopic();\n me.setMeta({cred: {meth: cred, resp: code}})\n .then(() => {\n HashNavigation.navigateTo('');\n })\n .catch((err) => {\n this.handleError(err.message, 'err');\n });\n } else {\n this.setState({credMethod: cred, credCode: code});\n this.doLogin(null, null, {meth: cred, resp: code});\n }\n }\n\n handlePasswordResetRequest(method, value) {\n // If already connected, connnect() will return a resolved promise.\n return this.tinode.connect()\n .then(() => {\n return this.tinode.requestResetAuthSecret('basic', method, value);\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n\n handleResetPassword(scheme, newPassword, token) {\n token = base64ReEncode(token);\n if (!token) {\n this.handleError(this.props.intl.formatMessage(messages.invalid_security_token), 'err');\n } else {\n this.tinode.connect()\n .then(() => {\n return this.tinode.updateAccountBasic(null, null, newPassword, {token: token});\n })\n .then(() => {\n HashNavigation.navigateTo('');\n })\n .catch((err) => {\n // Socket error\n this.handleError(err.message, 'err');\n });\n }\n }\n\n render() {\n return (\n \n {this.state.contextMenuVisible ?\n
{\n if (topicName == this.state.topicSelected) {\n this.handleTopicSelected(null);\n }\n }}\n onError={this.handleError} />\n :\n null\n }\n {this.state.forwardDialogVisible ?\n \n :\n null\n }\n { this.setState({alertVisible: false}); }) : null}\n reject={this.state.alertParams.reject}\n onConfirm={() => { this.setState({alertVisible: false}); this.state.alertParams.onConfirm(); }}\n confirm={this.state.alertParams.confirm}\n />\n \n\n \n\n {this.state.infoPanel ?\n \n :\n null\n }\n \n );\n }\n};\n\nexport default injectIntl(TinodeWeb);\n","// Put all packages together.\n// Used to generate umd/index.prod.js\n\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nimport { IntlProvider } from 'react-intl';\n\nimport allMessages from './messages.json';\nimport TinodeWeb from './views/tinode-web.jsx';\nimport HashNavigation from './lib/navigation.js';\n\n// Detect human language to use in the UI:\n// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.\nconst { params } = HashNavigation.parseUrlHash(window.location.hash);\nconst language = (params && params.hl) ||\n (navigator.languages && navigator.languages[0]) ||\n navigator.language ||\n navigator.userLanguage ||\n 'en';\n\n// Get the base language 'en' from a more specific 'en_GB' or 'en-US' as a partial fallback.\nconst baseLanguage = language.toLowerCase().split(/[-_]/)[0];\n\n// Try the full locale first, then the locale without the region code, fallback to 'en'.\nconst htmlLang = allMessages[language] ? langauge : allMessages[baseLanguage] ? baseLanguage : 'en';\nconst messages = allMessages[htmlLang];\n\n// Set lang attribute of the HTML element: \ndocument.getElementsByTagName('html')[0].setAttribute('lang', htmlLang);\n\nReactDOM.render(\n \n \n \n \n ,\n document.getElementById('mountPoint')\n);\n"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","React","ReactDOM","ReactIntl","firebase","messaging","Tinode","Alert","render","this","props","visible","className","title","content","onReject","onClick","reject","FormattedMessage","id","defaultMessage","onConfirm","confirm","PACKAGE_VERSION","APP_NAME","KNOWN_HOSTS","hosted","local","DEFAULT_HOST","KEYPRESS_DELAY","DEFAULT_P2P_ACCESS_MODE","REM_SIZE","AVATAR_SIZE","MAX_INBAND_ATTACHMENT_SIZE","MAX_EXTERN_ATTACHMENT_SIZE","IMAGE_THUMBNAIL_DIM","MAX_TITLE_LENGTH","MAX_TOPIC_DESCRIPTION_LENGTH","messages","defineMessages","info","clear_messages","clear_for_all","delete","delete_for_all","send_retry","mute","unmute","reply","forward","topic_delete","topic_delete_warning","delete_messages_warning","unblock","block","topic_block_warning","member_delete","archive","unarchive","edit_permissions","clear_messages_warning","ContextMenu","constructor","super","selfRef","formatMessage","intl","handlePageClick","bind","handleEscapeKey","handleClick","MenuItems","handler","params","errorHandler","onShowAlert","deleteMessages","message","retryMessage","replyToMessage","topicPermissionSetter","then","ctrl","onTopicRemoved","topicName","topic","tinode","getTopic","delTopic","catch","err","console","log","user","delSubscription","componentDidMount","document","addEventListener","componentWillUnmount","removeEventListener","e","current","contains","target","preventDefault","stopPropagation","hide","keyCode","item","items","currentTarget","dataset","onAction","onError","error","all","hard","cancelSend","seq","delMessagesAll","delMessagesList","flushMessage","msg","createMessage","publishDraft","mode","result","updateMode","pickReply","userFrom","userName","menu","count","map","push","vSize","length","position","left","bounds","right","clickAt","x","top","bottom","y","style","ref","injectIntl","icon_mapping","ContactBadges","badges","b","color","icon","name","bytesToHumanSize","bytes","sizes","bucket","Math","min","floor","log2","pow","round","toFixed","shortenFileName","filename","maxLength","slice","idToColorClass","light","fg","userId","abs","value","hash","i","charCodeAt","stringToColorHash","updateFavicon","oldIcon","getElementById","head","getElementsByTagName","newIcon","createElement","type","rel","href","removeChild","appendChild","theCard","fn","imageUrl","imageMimeType","note","card","trim","mimeType","matches","exec","photo","data","substring","indexOf","arrayEqual","Array","isArray","sort","l","sanitizeUrl","url","allowedSchemes","replace","test","schemes","join","RegExp","sanitizeImageUrl","sanitizedUrl","deliveryMarker","received","cancelablePromise","promise","hasCanceled","Error","Promise","resolve","isCanceled","cancel","LetterTile","avatar","isGroup","iconColor","letter","charAt","deleted","authorizeURL","alt","src","onerror","UnreadBadge","LazyImage","state","assign","padding","whenDone","setState","componentDidUpdate","prevProps","FileProgress","width","progress","onCancel","UploadingImage","onCancelUpload","SUPPORTED_IMAGE_FORMATS","MIME_EXTENSIONS","makeImageUrl","startsWith","fitImageSize","height","maxWidth","maxHeight","forceSquare","scale","size","dstWidth","dstHeight","srcWidth","srcHeight","xoffset","yoffset","fileNameForMime","fname","mime","idx","ext","at","lastIndexOf","imageScaled","fileOrBlob","maxSize","img","Image","crossOrigin","onload","async","URL","revokeObjectURL","dim","canvas","ctx","getContext","imageSmoothingEnabled","drawImage","includes","blob","toBlob","clearRect","createObjectURL","blobToBase64","reader","FileReader","evt","bits","split","readAsDataURL","drafty_form","drafty_attachment","drafty_image","handleImageData","el","attr","viewportWidth","minWidth","minHeight","Drafty","max","onImagePreview","loading","fullFormatter","values","stack","quoteFormatter","onFormButtonClick","inner","child","undefined","val","onQuoteClick","concat","previewFormatter","inlineImageAttr","quoteImage","str","bin","atob","buf","ArrayBuffer","arr","Uint8Array","Blob","base64ToBlob","fetch","ok","status","statusText","scaled","b64","replyFormatter","loadedPromise","Contact","handleContextClick","onSelected","index","showContextMenu","pageY","pageX","online","now","icon_badges","preview","isVerified","isStaff","isDangerous","acs","showMode","getMode","isMuted","isJoiner","marker","titleClass","showCheckmark","selected","showOnline","isChannel","unread","small","comment","ContactAction","action","badge_you","badge_owner","ContactList","topicSelected","contactNodes","contactsCount","contacts","c","filterFunc","filter","filterOn","private","toLowerCase","public","myUserId","isOwner","forwarded","deliveryStatus","latestMessage","_status","msgStatus","substr","showUnread","connected","onTopicSelected","trusted","verified","staff","danger","_deleted","noScroll","dangerouslySetInnerHTML","__html","emptyListMessage","SearchContacts","edited","search","handleSearchChange","handleSearch","handleClear","handleKeyDown","onSearchContacts","query","placeholder","onChange","onKeyDown","required","autoFocus","ForwardDialog","handleClose","handleSearchContacts","handleContactSelected","onInitFind","uid","searchResults","isWriter","desription","search_placeholder","not_found_placeholder","LoadSpinner","large","clear","centered","show","AvatarUpload","source","handleFileReceived","image","files","onImageUpdated","randId","random","readOnly","accept","htmlFor","uploading","badge_verified","badge_staff","badge_danger","BadgeList","trustedBadges","CheckBox","handleChange","checked","MenuCancel","ErrorPanel","level","onClearError","warn","text","whiteSpace","actionText","Chip","handleCancel","invalid","noAvatar","ChipInput","deriveStateFromProps","input","focused","handleTextInput","removeChipAt","handleChipCancel","handleFocusGained","handleFocusLost","chips","prompt","sortedChips","sortChips","staticMembers","chipIndex","indexChips","prevState","keep","normal","removed","onChipRemoved","onFocusLost","onEnter","avatarDisabled","tabIndex","onFocus","onBlur","no_contacts","contacts_not_found_short","GroupManager","members","indexMembers","keepInitialMembers","requiredMember","contactFilter","noContactsMessage","selectedContacts","handleMemberRemoved","handleContactFilter","handleSubmit","m","delta","present","keepInitial","stat","sel","splice","instance","added","keys","k","onSubmit","doContactFiltering","joiner","writer","preser","approver","sharer","deleter","owner","PermissionsEditor","names","skip","compare","userTitle","userAvatar","modeTitle","compareTitle","Cropper","panX","panY","originX","originY","zoom","minZoom","maxZoom","overlay","cutout","boundingBox","imageWidth","imageHeight","mouseX","mouseY","prevDistance","cutoutRect","bBoxRect","initScaling","onZoom","handleZoom","mouseDown","mouseUp","mouseMove","mouseTouch","positionAll","translate","passive","getBoundingClientRect","currPan","nextDiff","imgRect","imgLeft","imgRight","coLeft","coRight","imgTop","imgBottom","coTop","coBottom","touches","window","body","dX","dY","checkBound","touch0","touch1","distance","sqrt","t3d","orig","transform","transformOrigin","onLoad","step","AvatarCrop","objURL","VisiblePassword","handleVisibility","handeTextChange","handleEditingFinished","onFinished","setTimeout","activeElement","autoComplete","InPlaceEdit","active","initialValue","handleStartEditing","handlePasswordFinished","newValue","focus","event","checkValidity","spanText","spanClass","multiline","element","rows","TagManager","tags","tagInput","activated","handleTagInput","handleAddTag","handleRemoveTag","nextProps","last","tag","maxTagCount","getServerLimit","onTagsChanged","minTagLength","maxTagLength","add_tags_prompt","TopicDescEdit","getAccessMode","isMe","fullName","description","newAvatar","newAvatarMime","previousOnTags","tnNewTags","handleFullNameUpdate","handleImageUpdated","handleAvatarCropped","handlePrivateUpdate","handleDescriptionUpdate","uploadAvatar","handleAvatarCropCancel","handleTagsUpdated","onTagsUpdated","onUpdateTopicDesc","desc","readyToUpload","uploader","getLargeFileHelper","upload","finally","du","onUpdateTags","editable","full_name_placeholder","group_name_placeholder","private_placeholder","title_tag_manager","TopicCommon","credentials","getCredentials","addCredActive","addCredInvalid","newCred","previousTagsUpdated","tnCredsUpdated","handleCredChange","handleCredKeyDown","handleCredEntered","onCredsUpdated","getType","creds","method","asPhone","asEmail","onCredAdd","onTopicTagsUpdateRequest","cred","meth","done","onCredConfirm","onCredDelete","delete_messages","leave_chat","leave_chat_warning","block_contact","block_contact_warning","report_chat","report_chat_warning","other_user","TopicSecurity","handleDeleteTopic","handleDeleteMessages","handleLeave","handleBlock","handleReport","onDeleteTopic","onDeleteMessages","onLeaveTopic","onBlockTopic","onReportTopic","channel","groupTopic","onLaunchPermissionsEditor","access","auth","anon","modeGiven2","general","security","crop","perm_want","perm_given","perm_auth","perm_anon","perm_user","_clip","InfoView","admin","muted","address","selectedContact","modeGiven","modeWant","modeWant2","contactList","previousMetaDesc","previousSubsUpdated","resetSubs","resetDesc","resetTags","onMetaDesc","onSubsUpdated","handleImageChanged","handleMuted","handleUnarchive","handlePermissionsChanged","handleLaunchPermissionsEditor","handleShowAddMembers","handleMemberUpdateRequest","handleMemberSelected","handleContextMenu","handleBackNavigate","newState","user2","subscriber","getGiven","getWant","subscribers","sub","defacs","getDefaultAccess","entries","isAdmin","isSharer","isDeleter","archived","isArchived","isGroupType","isChannelType","chan","getMeta","startMetaQuery","withTags","build","subs","onTopicDescUpdate","ignored","onChangePermissions","ignored2","onTopicUnArchive","which","perm","onTopicDescUpdateRequest","userPermissionsEdited","toEdit","toCompare","toSkip","titleEdit","titleCompare","userPermissionsTitle","userPermissionsAvatar","editedPermissions","immutablePermissions","editedPermissionsTitle","immutablePermissionsTitle","editedPermissionsSkipped","onNavigate","onMemberUpdateRequest","args","panel","menuItems","view","shift","panelTitle","displayMobile","errorLevel","errorText","searchableContacts","no_members","Attachment","downloader","downloadFile","mimetype","download","loaded","helperFunc","isUrlRelative","downloadUrl","downloadWidget","message_sending","message_sending_failed","ReceivedMarker","timestamp","toLocaleTimeString","locale","timeStyle","BaseChatMessage","onProgress","handleProgress","handleImagePreview","handleFormButtonClick","handleCancelUpload","handleQuoteClick","formatterContext","resp","act","onFormResponse","MESSAGE_STATUS_FAILED","userIsWriter","MESSAGE_STATUS_DEL_RANGE","ratio","replyToSeq","sideClass","sequence","response","bubbleClass","fullDisplay","attachments","att","tree","innerRef","IntlChatMessage","messaging_disabled","type_new_message","add_image_caption","file_attachment_too_large","cannot_initiate_upload","SendMessage","quote","keypressTimestamp","Date","getTime","handlePasteEvent","handleAttachImage","handleAttachFile","handleSend","handleKeyPress","handleMessageTyping","formatReply","messageEditArea","disabled","onImageSuccess","onAttachmentSuccess","clipboardData","originalEvent","kind","file","getAsFile","filePasted","onAttachImage","onAttachFile","acceptBlank","noInput","onSendMessage","shiftKey","onKeyPress","messagePrompt","onCancelReply","attachImage","click","attachFile","display","DocPreview","handleSendDoc","caption","onClose","mimeToIcon","default","video","iconFromMime","GroupSubs","usersOnline","totalCount","countToShow","some","overflow","ImagePreview","handleSendImage","assignWidth","node","maxlength","Invitation","handleButtonAction","LogoView","version","serverVersion","serverAddress","MetaMessage","date","HashNavigation","parts","path","forEach","part","decodeURIComponent","location","parsed","parseUrlHash","composeUrlHash","sidepanel","infopanel","online_now","last_seen","not_found","invalid_content","isUnconfirmed","ex","getExcessive","isPeerRestricted","ms","getMissing","MessagesView","getDerivedStateFromProps","leave","sendMessage","retrySend","sendImageAttachment","sendFileAttachment","sendKeyPress","subscribe","handleScrollReference","handleScrollEvent","handleDescChange","handleSubsUpdated","handleMessageUpdate","handleAllMessagesReceived","handleInfoReceipt","handleImagePostview","handleClosePreview","handleFormResponse","handleShowMessageContextMenu","handleNewChatAcceptance","handleEnablePeer","postReadNotification","clearNotificationQueue","goToLatestMessage","handlePickReply","handleCancelReply","chatMessageRefs","getOrCreateMessageRef","readNotificationQueue","readNotificationTimer","seqId","messagesScroller","messageCount","scrollPosition","scrollTop","scrollHeight","offsetHeight","onData","onAllMessagesReceived","onInfo","onPres","applicationVisible","ready","nextState","forwardMessage","docPreview","imagePreview","imagePostview","typingIndicator","fetchingMessages","showGoToLastButton","onlineSubs","peer","p2pPeerDesc","peerMessagingDisabled","latestClearId","maxClearId","isReader","readingBlocked","unconformed","unconfirmed","isSubscribed","newTopic","newTopicParams","_topicName","getQuery","withLaterDesc","withLaterSub","withLaterData","withLaterDel","setQuery","code","onNewTopicCreated","queuedMessages","pub","_sending","navigateTo","setUrlTopic","blankState","oldTopicName","isTopicCached","oldTopic","pos","msgHasMoreMessages","getMessagesPage","setInterval","clearInterval","n","sendAt","noteRead","setMilliseconds","getMilliseconds","clearTimeout","keyPressTimer","isNewMessage","from","what","forceUpdate","attachJSON","parse","searchParams","set","open","messageSpecificMenuItems","onNewChat","noteKeyPress","uploadCompletionPromise","append","sanitizeEntities","maxInbandAttachmentSize","urlPromise","fileToBase64","maxExternAttachmentSize","limit","insertImage","_tempPreview","appendLineBreak","found","findMessage","_cancelled","senderId","senderName","init","isValid","replyContent","wrapInto","onCancelForwardMessage","scrollIntoView","behavior","classList","add","remove","component","hideSelf","component2","messageNodes","previousFrom","chatBoxClass","dateFmt","Intl","DateTimeFormat","prev","next","nextFrom","thisFrom","isReply","userDesc","parseInt","isNaN","ts","toDateString","format","hi","isChan","_uploader","lastSeen","cont","getMeTopic","getContact","seen","navigator","userLanguage","language","getFullYear","getMonth","getDate","hour12","hour","minute","toLocaleDateString","month","day","year","shortDateFormat","when","onHideMessagesView","ButtonBack","onBack","MenuContacts","onNewTopic","onSettings","MenuStart","onSignUp","SideNavbar","AccountSettingsView","me","archived_contacts_title","ContactsView","handleAction","unreadThreads","archivedCount","chatList","blocked","arch","touched","action_ignored","onShowArchive","LocalStorageUtil","localStorage","setItem","JSON","stringify","getItem","oldVal","getObject","setObject","removeItem","CreateAccountView","login","password","email","imageDataUrl","errorCleared","saveToken","handleLoginChange","handlePasswordChange","handleEmailChange","handleFnChange","handleToggleSaveToken","onCreateAccount","submitClasses","login_prompt","password_prompt","onImageChanged","full_name_prompt","email_prompt","stay_logged_in","EditAccountView","handleCheckboxClick","onToggleMessageSounds","onTogglePushNotifications","onToggleIncognitoMode","messageSounds","desktopAlertsEnabled","desktopAlerts","incognitoMode","delete_account","delete_account_warning","AccSecurityView","blockedCount","showPermissionEditorFor","handlePasswordUpdate","handleHidePermissionsEditor","handleDeleteAccount","pwd","onUpdatePassword","onUpdateAccountDesc","onDeleteAccount","password_unchanged","onLogout","onShowBlocked","AccSupportView","LoginView","hostName","persist","onPersistenceChange","onLogin","autoCorrect","autoCapitalize","invalid_id","NewTopicById","groupId","prefix","NewTopicGroup","handleFieldEdit","handleTagsChanged","handleChannelToggle","search_for_contacts","search_no_results","NewTopicView","tabSelected","searchQuery","handleTabClick","handleNewGroupSubmit","handleGroupByID","addUrlParam","removeUrlParam","onCreateTopic","dataUrl","priv","no_contacts_placeholder","PasswordResetView","sent","token","scheme","onReset","onRequest","HostSelector","changed","handleHostNameChange","onServerAddressChange","hostOptions","list","SettingsView","transport","handleTransportSelected","handleServerAddressChange","onUpdate","def","ws","lp","transportOptions","phone","ValidationView","credCode","credMethod","numbers_only","register","settings","edit","notif","support","newtpk","reset","SidepanelView","handleNewTopic","errorAction","errorActionText","loadSpinnerVisible","loginDisabled","onLoginRequest","onGlobalSettings","onUpdateAccountTags","onValidateCredentials","onPasswordResetRequest","onResetPassword","detectServerAddress","host","protocol","hostname","port","isSecureConnection","POP_SOUND","Audio","reconnect_countdown","reconnect_now","push_init_failed","invalid_security_token","no_connection","code_doesnot_match","menu_item_info","TinodeWeb","getBlankState","handleResize","handleHashRoute","handleOnline","checkForAppUpdate","handleVisibilityEvent","handleError","handleLoginRequest","handlePersistenceChange","handleConnected","handleAutoreconnectIteration","doLogin","handleCredentialsRequest","handleLoginSuccessful","handleDisconnect","tnMeMetaDesc","tnMeContactUpdate","tnMeSubsUpdated","resetContactList","tnData","tnInitFind","tnFndSubsUpdated","handleTopicSelected","handleHideMessagesView","handleSendMessage","handleNewChatInvitation","handleNewAccount","handleNewAccountRequest","handleUpdatePasswordRequest","handleUpdateAccountTagsRequest","handleToggleIncognitoMode","handleSettings","handleGlobalSettings","handleShowArchive","handleShowBlocked","handleToggleMessageSounds","handleCredAdd","handleCredDelete","handleCredConfirm","initFCMessaging","toggleFCMToken","handlePushMessage","handleSidepanelCancel","handleStartTopicRequest","handleNewTopicCreated","handleTopicUpdateRequest","handleChangePermissions","handleTagsUpdateRequest","handleLogout","handleDeleteTopicRequest","handleDeleteMessagesRequest","handleLeaveUnsubRequest","handleBlockTopicRequest","handleReportTopic","handleShowContextMenu","defaultTopicContextMenu","handleHideContextMenu","handleShowAlert","handleShowInfoView","handleValidateCredentialsRequest","handlePasswordResetRequest","handleResetPassword","handleContextMenuAction","handleShowForwardDialog","handleHideForwardDialog","sendMessageToTopic","autoLogin","messageSoundsOff","FIREBASE_INIT","firebaseToken","hidden","sidePanelSelected","sidePanelTitle","sidePanelAvatar","myTrustedBadges","liveConnection","onLine","topicSelectedOnline","topicSelectedAcs","innerWidth","infoPanel","mobilePanel","contextMenuVisible","contextMenuBounds","contextMenuClickAt","contextMenuParams","contextMenuItems","forwardDialogVisible","alertVisible","alertParams","requestedTopic","BroadcastChannel","documentElement","clientWidth","viewportHeight","clientHeight","tnSetup","onConnect","onDisconnect","onAutoreconnectIteration","setDeviceToken","parsedNav","expires","setAuthToken","connect","readTimer","readTimerCallback","persistentCache","onSetupCompleted","appName","apiKey","secure","setHumanLanguage","enableLogging","oobNotification","fcm","firebaseGetMessaging","firebaseInitApp","serviceWorker","reg","postMessage","requestFCMToken","updateObject","firebaseOnMessage","payload","sw","firebaseGetToken","serviceWorkerRegistration","vapidKey","messagingVapidKey","Notification","requestPermission","permission","mobile","onupdatefound","installingWorker","installing","onstatechange","controller","getTopicAccessMode","newTopicTabSelected","tab","reconnectCountdown","reconnect","isConnected","initStorage","clearStorage","getServerInfo","ver","sec","prom","timeLeft","seconds","secondsToTime","isAuthenticated","getAuthToken","loginBasic","loginToken","goToTopic","onContactUpdate","getCurrentUserID","withDesc","withCred","disconnect","urlHash","setUrlSidePanel","isPresencer","play","unused","foundContacts","merged","updated","past","prepareSearchableContacts","receivedTimer","noteRecv","fnd","getFndTopic","withSub","s","setMeta","isTopicOnline","isP2PType","am","updateWant","login_","password_","public_","cred_","tags_","createAccountBasic","on","enabled","firebaseDelToken","delCredential","basicNavigator","infoNavigator","setUrlInfoPanel","newGroupTopicName","oldName","newName","updateAccountBasic","getCurrentLogin","updateGiven","cleared","Promose","delCurrentUser","publish","header","msgPreview","self_blocked","subscribed","keepForwardedMessage","confirmText","rejectText","invite","requestResetAuthSecret","newPassword","btoa","base64ReEncode","myUserName","onAddMember","handleManageGroupMembers","hl","languages","baseLanguage","htmlLang","allMessages","langauge","setAttribute","IntlProvider","textComponent"],"sourceRoot":""}
\ No newline at end of file
diff --git a/umd/tinode.dev.js b/umd/tinode.dev.js
index 7e76f586..fa85f16f 100644
--- a/umd/tinode.dev.js
+++ b/umd/tinode.dev.js
@@ -1,255 +1,34 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Tinode = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i {
- return a === b ? 0 : a < b ? -1 : 1;
- }));
-
- _classPrivateFieldSet(this, _unique, unique_);
+AccessMode._checkFlag = function(val, side, flag) {
+ side = side || 'mode';
+ if (['given', 'want', 'mode'].includes(side)) {
+ return ((val[side] & flag) != 0);
}
-
- getAt(at) {
- return this.buffer[at];
- }
-
- getLast(at) {
- at |= 0;
- return this.buffer.length > at ? this.buffer[this.buffer.length - 1 - at] : undefined;
- }
-
- put() {
- let insert;
-
- if (arguments.length == 1 && Array.isArray(arguments[0])) {
- insert = arguments[0];
- } else {
- insert = arguments;
- }
-
- for (let idx in insert) {
- _classPrivateMethodGet(this, _insertSorted, _insertSorted2).call(this, insert[idx], this.buffer);
- }
- }
-
- delAt(at) {
- at |= 0;
- let r = this.buffer.splice(at, 1);
-
- if (r && r.length > 0) {
- return r[0];
- }
-
- return undefined;
- }
-
- delRange(since, before) {
- return this.buffer.splice(since, before - since);
- }
-
- length() {
- return this.buffer.length;
- }
-
- reset() {
- this.buffer = [];
- }
-
- forEach(callback, startIdx, beforeIdx, context) {
- startIdx = startIdx | 0;
- beforeIdx = beforeIdx || this.buffer.length;
-
- for (let i = startIdx; i < beforeIdx; i++) {
- callback.call(context, this.buffer[i], i > startIdx ? this.buffer[i - 1] : undefined, i < beforeIdx - 1 ? this.buffer[i + 1] : undefined, i);
- }
- }
-
- find(elem, nearest) {
- const {
- idx
- } = _classPrivateMethodGet(this, _findNearest, _findNearest2).call(this, elem, this.buffer, !nearest);
-
- return idx;
- }
-
- filter(callback, context) {
- let count = 0;
-
- for (let i = 0; i < this.buffer.length; i++) {
- if (callback.call(context, this.buffer[i], i)) {
- this.buffer[count] = this.buffer[i];
- count++;
- }
- }
-
- this.buffer.splice(count);
- }
-
+ throw new Error(`Invalid AccessMode component '${side}'`);
}
-exports.default = CBuffer;
+/**
+ * Parse string into an access mode value.
+ * @memberof Tinode.AccessMode
+ * @static
+ *
+ * @param {string | Number} mode - either a String representation of the access mode to parse or a set of bits to assign.
+ * @returns {number} - Access mode as a numeric value.
+ */
+AccessMode.decode = function(str) {
+ if (!str) {
+ return null;
+ } else if (typeof str == 'number') {
+ return str & AccessMode._BITMASK;
+ } else if (str === 'N' || str === 'n') {
+ return AccessMode._NONE;
+ }
-function _findNearest2(elem, arr, exact) {
- let start = 0;
- let end = arr.length - 1;
- let pivot = 0;
- let diff = 0;
- let found = false;
+ const bitmask = {
+ 'J': AccessMode._JOIN,
+ 'R': AccessMode._READ,
+ 'W': AccessMode._WRITE,
+ 'P': AccessMode._PRES,
+ 'A': AccessMode._APPROVE,
+ 'S': AccessMode._SHARE,
+ 'D': AccessMode._DELETE,
+ 'O': AccessMode._OWNER
+ };
- while (start <= end) {
- pivot = (start + end) / 2 | 0;
- diff = _classPrivateFieldGet(this, _comparator).call(this, arr[pivot], elem);
+ let m0 = AccessMode._NONE;
- if (diff < 0) {
- start = pivot + 1;
- } else if (diff > 0) {
- end = pivot - 1;
- } else {
- found = true;
- break;
+ for (let i = 0; i < str.length; i++) {
+ const bit = bitmask[str.charAt(i).toUpperCase()];
+ if (!bit) {
+ // Unrecognized bit, skip.
+ continue;
+ }
+ m0 |= bit;
+ }
+ return m0;
+};
+
+/**
+ * Convert numeric representation of the access mode into a string.
+ *
+ * @memberof Tinode.AccessMode
+ * @static
+ *
+ * @param {number} val - access mode value to convert to a string.
+ * @returns {string} - Access mode as a string.
+ */
+AccessMode.encode = function(val) {
+ if (val === null || val === AccessMode._INVALID) {
+ return null;
+ } else if (val === AccessMode._NONE) {
+ return 'N';
+ }
+
+ const bitmask = ['J', 'R', 'W', 'P', 'A', 'S', 'D', 'O'];
+ let res = '';
+ for (let i = 0; i < bitmask.length; i++) {
+ if ((val & (1 << i)) != 0) {
+ res = res + bitmask[i];
+ }
+ }
+ return res;
+};
+
+/**
+ * Update numeric representation of access mode with the new value. The value
+ * is one of the following:
+ * - a string starting with '+' or '-' then the bits to add or remove, e.g. '+R-W' or '-PS'.
+ * - a new value of access mode
+ *
+ * @memberof Tinode.AccessMode
+ * @static
+ *
+ * @param {number} val - access mode value to update.
+ * @param {string} upd - update to apply to val.
+ * @returns {number} - updated access mode.
+ */
+AccessMode.update = function(val, upd) {
+ if (!upd || typeof upd != 'string') {
+ return val;
+ }
+
+ let action = upd.charAt(0);
+ if (action == '+' || action == '-') {
+ let val0 = val;
+ // Split delta-string like '+ABC-DEF+Z' into an array of parts including + and -.
+ const parts = upd.split(/([-+])/);
+ // Starting iteration from 1 because String.split() creates an array with the first empty element.
+ // Iterating by 2 because we parse pairs +/- then data.
+ for (let i = 1; i < parts.length - 1; i += 2) {
+ action = parts[i];
+ const m0 = AccessMode.decode(parts[i + 1]);
+ if (m0 == AccessMode._INVALID) {
+ return val;
+ }
+ if (m0 == null) {
+ continue;
+ }
+ if (action === '+') {
+ val0 |= m0;
+ } else if (action === '-') {
+ val0 &= ~m0;
+ }
+ }
+ val = val0;
+ } else {
+ // The string is an explicit new value 'ABC' rather than delta.
+ const val0 = AccessMode.decode(upd);
+ if (val0 != AccessMode._INVALID) {
+ val = val0;
}
}
- if (found) {
+ return val;
+};
+
+/**
+ * Bits present in a1 but missing in a2.
+ *
+ * @static
+ * @memberof Tinode
+ *
+ * @param {number | string} a1 - access mode to subtract from.
+ * @param {number | string} a2 - access mode to subtract.
+ * @returns {number} access mode with bits present in a1 but missing in a2.
+ */
+AccessMode.diff = function(a1, a2) {
+ a1 = AccessMode.decode(a1);
+ a2 = AccessMode.decode(a2);
+
+ if (a1 == AccessMode._INVALID || a2 == AccessMode._INVALID) {
+ return AccessMode._INVALID;
+ }
+ return a1 & ~a2;
+};
+
+/**
+ * AccessMode is a class representing topic access mode.
+ *
+ * @memberof Tinode
+ * @class AccessMode
+ */
+AccessMode.prototype = {
+ /**
+ * Custom formatter
+ */
+ toString: function() {
+ return '{"mode": "' + AccessMode.encode(this.mode) +
+ '", "given": "' + AccessMode.encode(this.given) +
+ '", "want": "' + AccessMode.encode(this.want) + '"}';
+ },
+ /**
+ * Converts numeric values to strings.
+ */
+ jsonHelper: function() {
return {
- idx: pivot,
- exact: true
+ mode: AccessMode.encode(this.mode),
+ given: AccessMode.encode(this.given),
+ want: AccessMode.encode(this.want)
+ };
+ },
+ /**
+ * Assign value to 'mode'.
+ * @memberof Tinode.AccessMode
+ *
+ * @param {string | Number} m - either a string representation of the access mode or a set of bits.
+ * @returns {AccessMode} - this AccessMode.
+ */
+ setMode: function(m) {
+ this.mode = AccessMode.decode(m);
+ return this;
+ },
+ /**
+ * Update mode value.
+ * @memberof Tinode.AccessMode
+ *
+ * @param {string} u - string representation of the changes to apply to access mode.
+ * @returns {AccessMode} - this AccessMode.
+ */
+ updateMode: function(u) {
+ this.mode = AccessMode.update(this.mode, u);
+ return this;
+ },
+ /**
+ * Get mode value as a string.
+ * @memberof Tinode.AccessMode
+ *
+ * @returns {string} - mode value.
+ */
+ getMode: function() {
+ return AccessMode.encode(this.mode);
+ },
+
+ /**
+ * Assign given value.
+ * @memberof Tinode.AccessMode
+ *
+ * @param {string | Number} g - either a string representation of the access mode or a set of bits.
+ * @returns {AccessMode} - this AccessMode.
+ */
+ setGiven: function(g) {
+ this.given = AccessMode.decode(g);
+ return this;
+ },
+ /**
+ * Update 'given' value.
+ * @memberof Tinode.AccessMode
+ *
+ * @param {string} u - string representation of the changes to apply to access mode.
+ * @returns {AccessMode} - this AccessMode.
+ */
+ updateGiven: function(u) {
+ this.given = AccessMode.update(this.given, u);
+ return this;
+ },
+ /**
+ * Get 'given' value as a string.
+ * @memberof Tinode.AccessMode
+ *
+ * @returns {string} - given value.
+ */
+ getGiven: function() {
+ return AccessMode.encode(this.given);
+ },
+
+ /**
+ * Assign 'want' value.
+ * @memberof Tinode.AccessMode
+ *
+ * @param {string | Number} w - either a string representation of the access mode or a set of bits.
+ * @returns {AccessMode} - this AccessMode.
+ */
+ setWant: function(w) {
+ this.want = AccessMode.decode(w);
+ return this;
+ },
+ /**
+ * Update 'want' value.
+ * @memberof Tinode.AccessMode
+ *
+ * @param {string} u - string representation of the changes to apply to access mode.
+ * @returns {AccessMode} - this AccessMode.
+ */
+ updateWant: function(u) {
+ this.want = AccessMode.update(this.want, u);
+ return this;
+ },
+ /**
+ * Get 'want' value as a string.
+ * @memberof Tinode.AccessMode
+ *
+ * @returns {string} - want value.
+ */
+ getWant: function() {
+ return AccessMode.encode(this.want);
+ },
+
+ /**
+ * Get permissions present in 'want' but missing in 'given'.
+ * Inverse of {@link Tinode.AccessMode#getExcessive}
+ *
+ * @memberof Tinode.AccessMode
+ *
+ * @returns {string} permissions present in want but missing in given.
+ */
+ getMissing: function() {
+ return AccessMode.encode(this.want & ~this.given);
+ },
+
+ /**
+ * Get permissions present in 'given' but missing in 'want'.
+ * Inverse of {@link Tinode.AccessMode#getMissing}
+ * @memberof Tinode.AccessMode
+ *
+ * @returns {string} permissions present in given but missing in want.
+ */
+ getExcessive: function() {
+ return AccessMode.encode(this.given & ~this.want);
+ },
+
+ /**
+ * Update 'want', 'give', and 'mode' values.
+ * @memberof Tinode.AccessMode
+ *
+ * @param {AccessMode} val - new access mode value.
+ * @returns {AccessMode} - this AccessMode.
+ */
+ updateAll: function(val) {
+ if (val) {
+ this.updateGiven(val.given);
+ this.updateWant(val.want);
+ this.mode = this.given & this.want;
+ }
+ return this;
+ },
+
+ /**
+ * Check if Owner (O) flag is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isOwner: function(side) {
+ return AccessMode._checkFlag(this, side, AccessMode._OWNER);
+ },
+
+ /**
+ * Check if Presence (P) flag is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isPresencer: function(side) {
+ return AccessMode._checkFlag(this, side, AccessMode._PRES);
+ },
+
+ /**
+ * Check if Presence (P) flag is NOT set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isMuted: function(side) {
+ return !this.isPresencer(side);
+ },
+
+ /**
+ * Check if Join (J) flag is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isJoiner: function(side) {
+ return AccessMode._checkFlag(this, side, AccessMode._JOIN);
+ },
+
+ /**
+ * Check if Reader (R) flag is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isReader: function(side) {
+ return AccessMode._checkFlag(this, side, AccessMode._READ);
+ },
+
+ /**
+ * Check if Writer (W) flag is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isWriter: function(side) {
+ return AccessMode._checkFlag(this, side, AccessMode._WRITE);
+ },
+
+ /**
+ * Check if Approver (A) flag is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isApprover: function(side) {
+ return AccessMode._checkFlag(this, side, AccessMode._APPROVE);
+ },
+
+ /**
+ * Check if either one of Owner (O) or Approver (A) flags is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isAdmin: function(side) {
+ return this.isOwner(side) || this.isApprover(side);
+ },
+
+ /**
+ * Check if either one of Owner (O), Approver (A), or Sharer (S) flags is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isSharer: function(side) {
+ return this.isAdmin(side) || AccessMode._checkFlag(this, side, AccessMode._SHARE);
+ },
+
+ /**
+ * Check if Deleter (D) flag is set.
+ * @memberof Tinode.AccessMode
+ * @param {string=} side - which permission to check: given, want, mode; default: mode.
+ * @returns {boolean} - true if flag is set.
+ */
+ isDeleter: function(side) {
+ return AccessMode._checkFlag(this, side, AccessMode._DELETE);
+ }
+};
+
+if (typeof module != 'undefined') {
+ module.exports = AccessMode;
+}
+
+},{}],2:[function(require,module,exports){
+/**
+ * @file In-memory sorted cache of objects.
+ *
+ * @copyright 2015-2021 Tinode
+ * @summary Javascript bindings for Tinode.
+ * @license Apache 2.0
+ * @version 0.18
+ */
+'use strict';
+
+/**
+ * In-memory sorted cache of objects.
+ *
+ * @class CBuffer
+ * @memberof Tinode
+ * @protected
+ *
+ * @param {function} compare custom comparator of objects. Takes two parameters a and b;
+ * returns -1 if a < b, 0 if a == b, 1 otherwise.
+ * @param {boolean} unique enforce element uniqueness: when true replace existing element with a new
+ * one on conflict; when false keep both elements.
+ */
+const CBuffer = function(compare, unique) {
+ let buffer = [];
+
+ compare = compare || function(a, b) {
+ return a === b ? 0 : a < b ? -1 : 1;
+ };
+
+ function findNearest(elem, arr, exact) {
+ let start = 0;
+ let end = arr.length - 1;
+ let pivot = 0;
+ let diff = 0;
+ let found = false;
+
+ while (start <= end) {
+ pivot = (start + end) / 2 | 0;
+ diff = compare(arr[pivot], elem);
+ if (diff < 0) {
+ start = pivot + 1;
+ } else if (diff > 0) {
+ end = pivot - 1;
+ } else {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ return {
+ idx: pivot,
+ exact: true
+ };
+ }
+ if (exact) {
+ return {
+ idx: -1
+ };
+ }
+ // Not exact - insertion point
+ return {
+ idx: diff < 0 ? pivot + 1 : pivot
};
}
- if (exact) {
- return {
- idx: -1
- };
+ // Insert element into a sorted array.
+ function insertSorted(elem, arr) {
+ const found = findNearest(elem, arr, false);
+ const count = (found.exact && unique) ? 1 : 0;
+ arr.splice(found.idx, count, elem);
+ return arr;
}
return {
- idx: diff < 0 ? pivot + 1 : pivot
- };
+ /**
+ * Get an element at the given position.
+ * @memberof Tinode.CBuffer#
+ * @param {number} at - Position to fetch from.
+ * @returns {Object} Element at the given position or undefined.
+ */
+ getAt: function(at) {
+ return buffer[at];
+ },
+
+ /**
+ * Convenience method for getting the element from the end of the buffer.
+ * @memberof Tinode.CBuffer#
+ * @param {number} at - position to fetch from, counting from the end;
+ * undefined or null mean "last".
+ * @returns {Object} The last element in the buffer or undefined if buffer is empty.
+ */
+ getLast: function(at) {
+ at |= 0;
+ return buffer.length > at ? buffer[buffer.length - 1 - at] : undefined;
+ },
+
+ /**
+ * Add new element(s) to the buffer. Variadic: takes one or more arguments. If an array is passed as a single
+ * argument, its elements are inserted individually.
+ * @memberof Tinode.CBuffer#
+ *
+ * @param {...Object|Array} - One or more objects to insert.
+ */
+ put: function() {
+ let insert;
+ // inspect arguments: if array, insert its elements, if one or more non-array arguments, insert them one by one
+ if (arguments.length == 1 && Array.isArray(arguments[0])) {
+ insert = arguments[0];
+ } else {
+ insert = arguments;
+ }
+ for (let idx in insert) {
+ insertSorted(insert[idx], buffer);
+ }
+ },
+
+ /**
+ * Remove element at the given position.
+ * @memberof Tinode.CBuffer#
+ * @param {number} at - Position to delete at.
+ * @returns {Object} Element at the given position or undefined.
+ */
+ delAt: function(at) {
+ at |= 0;
+ let r = buffer.splice(at, 1);
+ if (r && r.length > 0) {
+ return r[0];
+ }
+ return undefined;
+ },
+
+ /**
+ * Remove elements between two positions.
+ * @memberof Tinode.CBuffer#
+ * @param {number} since - Position to delete from (inclusive).
+ * @param {number} before - Position to delete to (exclusive).
+ *
+ * @returns {Array} array of removed elements (could be zero length).
+ */
+ delRange: function(since, before) {
+ return buffer.splice(since, before - since);
+ },
+
+ /**
+ * Return the number of elements the buffer holds.
+ * @memberof Tinode.CBuffer#
+ * @return {number} Number of elements in the buffer.
+ */
+ length: function() {
+ return buffer.length;
+ },
+
+ /**
+ * Reset the buffer discarding all elements
+ * @memberof Tinode.CBuffer#
+ */
+ reset: function() {
+ buffer = [];
+ },
+
+ /**
+ * Callback for iterating contents of buffer. See {@link Tinode.CBuffer#forEach}.
+ * @callback ForEachCallbackType
+ * @memberof Tinode.CBuffer#
+ * @param {Object} elem - Current element of the buffer.
+ * @param {Object} prev - Previous element of the buffer.
+ * @param {Object} next - Next element of the buffer.
+ * @param {number} index - Index of the current element.
+ */
+
+ /**
+ * Apply given callback to all elements of the buffer.
+ * @memberof Tinode.CBuffer#
+ *
+ * @param {Tinode.ForEachCallbackType} callback - Function to call for each element.
+ * @param {number} startIdx - Optional index to start iterating from (inclusive).
+ * @param {number} beforeIdx - Optional index to stop iterating before (exclusive).
+ * @param {Object} context - calling context (i.e. value of this in callback)
+ */
+ forEach: function(callback, startIdx, beforeIdx, context) {
+ startIdx = startIdx | 0;
+ beforeIdx = beforeIdx || buffer.length;
+ for (let i = startIdx; i < beforeIdx; i++) {
+ callback.call(context, buffer[i],
+ (i > startIdx ? buffer[i - 1] : undefined),
+ (i < beforeIdx - 1 ? buffer[i + 1] : undefined), i);
+ }
+ },
+
+ /**
+ * Find element in buffer using buffer's comparison function.
+ * @memberof Tinode.CBuffer#
+ *
+ * @param {Object} elem - element to find.
+ * @param {boolean=} nearest - when true and exact match is not found, return the nearest element (insertion point).
+ * @returns {number} index of the element in the buffer or -1.
+ */
+ find: function(elem, nearest) {
+ const {
+ idx
+ } = findNearest(elem, buffer, !nearest);
+ return idx;
+ },
+
+ /**
+ * Callback for filtering the buffer. See {@link Tinode.CBuffer#filter}.
+ * @callback ForEachCallbackType
+ * @memberof Tinode.CBuffer#
+ * @param {Object} elem - Current element of the buffer.
+ * @param {number} index - Index of the current element.
+ * @returns {boolen} true to keep the element, false to remove.
+ */
+
+ /**
+ * Remove all elements that do not pass the test implemented by the provided callback function.
+ * @memberof Tinode.CBuffer#
+ *
+ * @param {Tinode.FilterCallbackType} callback - Function to call for each element.
+ * @param {Object} context - calling context (i.e. value of this in the callback)
+ */
+ filter: function(callback, context) {
+ let count = 0;
+ for (let i = 0; i < buffer.length; i++) {
+ if (callback.call(context, buffer[i], i)) {
+ buffer[count] = buffer[i];
+ count++;
+ }
+ }
+
+ buffer.splice(count);
+ }
+ }
}
-function _insertSorted2(elem, arr) {
- const found = _classPrivateMethodGet(this, _findNearest, _findNearest2).call(this, elem, arr, false);
-
- const count = found.exact && _classPrivateFieldGet(this, _unique) ? 1 : 0;
- arr.splice(found.idx, count, elem);
- return arr;
+if (typeof module != 'undefined') {
+ module.exports = CBuffer;
}
},{}],3:[function(require,module,exports){
+/**
+ * @file Abstraction layer for websocket and long polling connections.
+ * See https://github.com/tinode/webapp for real-life usage.
+ *
+ * @copyright 2015-2021 Tinode
+ * @summary Javascript bindings for Tinode.
+ * @license Apache 2.0
+ * @version 0.18
+ */
'use strict';
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-exports.VERSION = exports.USER_NEW = exports.TOPIC_SYS = exports.TOPIC_P2P = exports.TOPIC_NEW_CHAN = exports.TOPIC_NEW = exports.TOPIC_ME = exports.TOPIC_GRP = exports.TOPIC_FND = exports.TOPIC_CHAN = exports.PROTOCOL_VERSION = exports.MESSAGE_STATUS_TO_ME = exports.MESSAGE_STATUS_SENT = exports.MESSAGE_STATUS_SENDING = exports.MESSAGE_STATUS_RECEIVED = exports.MESSAGE_STATUS_READ = exports.MESSAGE_STATUS_QUEUED = exports.MESSAGE_STATUS_NONE = exports.MESSAGE_STATUS_FAILED = exports.MESSAGE_STATUS_DEL_RANGE = exports.LOCAL_SEQID = exports.LIBRARY = exports.EXPIRE_PROMISES_TIMEOUT = exports.EXPIRE_PROMISES_PERIOD = exports.DEL_CHAR = exports.DEFAULT_MESSAGES_PAGE = void 0;
-
-var _version = require("../version.json");
-
-const PROTOCOL_VERSION = '0';
-exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
-const VERSION = _version.version || '0.18';
-exports.VERSION = VERSION;
-const LIBRARY = 'tinodejs/' + VERSION;
-exports.LIBRARY = LIBRARY;
-const TOPIC_NEW = 'new';
-exports.TOPIC_NEW = TOPIC_NEW;
-const TOPIC_NEW_CHAN = 'nch';
-exports.TOPIC_NEW_CHAN = TOPIC_NEW_CHAN;
-const TOPIC_ME = 'me';
-exports.TOPIC_ME = TOPIC_ME;
-const TOPIC_FND = 'fnd';
-exports.TOPIC_FND = TOPIC_FND;
-const TOPIC_SYS = 'sys';
-exports.TOPIC_SYS = TOPIC_SYS;
-const TOPIC_CHAN = 'chn';
-exports.TOPIC_CHAN = TOPIC_CHAN;
-const TOPIC_GRP = 'grp;';
-exports.TOPIC_GRP = TOPIC_GRP;
-const TOPIC_P2P = 'p2p';
-exports.TOPIC_P2P = TOPIC_P2P;
-const USER_NEW = 'new';
-exports.USER_NEW = USER_NEW;
-const LOCAL_SEQID = 0xFFFFFFF;
-exports.LOCAL_SEQID = LOCAL_SEQID;
-const MESSAGE_STATUS_NONE = 0;
-exports.MESSAGE_STATUS_NONE = MESSAGE_STATUS_NONE;
-const MESSAGE_STATUS_QUEUED = 1;
-exports.MESSAGE_STATUS_QUEUED = MESSAGE_STATUS_QUEUED;
-const MESSAGE_STATUS_SENDING = 2;
-exports.MESSAGE_STATUS_SENDING = MESSAGE_STATUS_SENDING;
-const MESSAGE_STATUS_FAILED = 3;
-exports.MESSAGE_STATUS_FAILED = MESSAGE_STATUS_FAILED;
-const MESSAGE_STATUS_SENT = 4;
-exports.MESSAGE_STATUS_SENT = MESSAGE_STATUS_SENT;
-const MESSAGE_STATUS_RECEIVED = 5;
-exports.MESSAGE_STATUS_RECEIVED = MESSAGE_STATUS_RECEIVED;
-const MESSAGE_STATUS_READ = 6;
-exports.MESSAGE_STATUS_READ = MESSAGE_STATUS_READ;
-const MESSAGE_STATUS_TO_ME = 7;
-exports.MESSAGE_STATUS_TO_ME = MESSAGE_STATUS_TO_ME;
-const MESSAGE_STATUS_DEL_RANGE = 8;
-exports.MESSAGE_STATUS_DEL_RANGE = MESSAGE_STATUS_DEL_RANGE;
-const EXPIRE_PROMISES_TIMEOUT = 5000;
-exports.EXPIRE_PROMISES_TIMEOUT = EXPIRE_PROMISES_TIMEOUT;
-const EXPIRE_PROMISES_PERIOD = 1000;
-exports.EXPIRE_PROMISES_PERIOD = EXPIRE_PROMISES_PERIOD;
-const DEFAULT_MESSAGES_PAGE = 24;
-exports.DEFAULT_MESSAGES_PAGE = DEFAULT_MESSAGES_PAGE;
-const DEL_CHAR = '\u2421';
-exports.DEL_CHAR = DEL_CHAR;
-
-},{"../version.json":12}],4:[function(require,module,exports){
-'use strict';
-
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-exports.default = void 0;
-
-var _utils = require("./utils.js");
-
-function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); }
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
-function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
-
-function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
-
-function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
-
-function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
-
-function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
-
-function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
-
-function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
-
-function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
+const {
+ jsonParseHelper
+} = require('./utils.js');
let WebSocketProvider;
let XHRProvider;
+
+// Error code to return in case of a network problem.
const NETWORK_ERROR = 503;
const NETWORK_ERROR_TEXT = "Connection failed";
+
+// Error code to return when user disconnected from server.
const NETWORK_USER = 418;
const NETWORK_USER_TEXT = "Disconnected by client";
-const _BOFF_BASE = 2000;
-const _BOFF_MAX_ITER = 10;
-const _BOFF_JITTER = 0.3;
+// Helper function for creating an endpoint URL.
function makeBaseUrl(host, protocol, version, apiKey) {
let url = null;
if (['http', 'https', 'ws', 'wss'].includes(protocol)) {
- url = "".concat(protocol, "://").concat(host);
-
+ url = `${protocol}://${host}`;
if (url.charAt(url.length - 1) !== '/') {
url += '/';
}
-
url += 'v' + version + '/channels';
-
if (['http', 'https'].includes(protocol)) {
+ // Long polling endpoint ends with "lp", i.e.
+ // '/v0/channels/lp' vs just '/v0/channels' for ws
url += '/lp';
}
-
url += '?apikey=' + apiKey;
}
return url;
}
-var _boffTimer = new WeakMap();
+/**
+ * An abstraction for a websocket or a long polling connection.
+ *
+ * @class Connection
+ * @memberof Tinode
-var _boffIteration = new WeakMap();
+ * @param {Object} config - configuration parameters.
+ * @param {string} config.host - Host name and optional port number to connect to.
+ * @param {string} config.apiKey - API key generated by keygen.
+ * @param {string} config.transport - Network transport to use, either "ws"/"wss" for websocket or
+ * lp for long polling.
+ * @param {boolean} config.secure - Use Secure WebSocket if true.
+ * @param {string} version_ - Major value of the protocol version, e.g. '0' in '0.17.1'.
+ * @param {boolean} autoreconnect_ - If connection is lost, try to reconnect automatically.
+ */
+// config.host, PROTOCOL_VERSION, config.apiKey, config.transport, config.secure, true
+const Connection = function(config, version_, autoreconnect_) {
+ let host = config.host;
+ const secure = config.secure;
+ const apiKey = config.apiKey;
-var _boffClosed = new WeakMap();
+ const version = version_;
+ const autoreconnect = autoreconnect_;
-var _socket = new WeakMap();
+ // Settings for exponential backoff
+ const _BOFF_BASE = 2000; // 2000 milliseconds, minimum delay between reconnects
+ const _BOFF_MAX_ITER = 10; // Maximum delay between reconnects 2^10 * 2000 ~ 34 minutes
+ const _BOFF_JITTER = 0.3; // Add random delay
-var _log = new WeakSet();
+ let _boffTimer = null;
+ let _boffIteration = 0;
+ let _boffClosed = false; // Indicator if the socket was manually closed - don't autoreconnect if true.
-var _boffReconnect = new WeakSet();
-
-var _boffStop = new WeakSet();
-
-var _boffReset = new WeakSet();
-
-var _init_lp = new WeakSet();
-
-var _init_ws = new WeakSet();
-
-class Connection {
- constructor(config, version_, autoreconnect_) {
- _classPrivateMethodInitSpec(this, _init_ws);
-
- _classPrivateMethodInitSpec(this, _init_lp);
-
- _classPrivateMethodInitSpec(this, _boffReset);
-
- _classPrivateMethodInitSpec(this, _boffStop);
-
- _classPrivateMethodInitSpec(this, _boffReconnect);
-
- _classPrivateMethodInitSpec(this, _log);
-
- _classPrivateFieldInitSpec(this, _boffTimer, {
- writable: true,
- value: null
- });
-
- _classPrivateFieldInitSpec(this, _boffIteration, {
- writable: true,
- value: 0
- });
-
- _classPrivateFieldInitSpec(this, _boffClosed, {
- writable: true,
- value: false
- });
-
- _classPrivateFieldInitSpec(this, _socket, {
- writable: true,
- value: null
- });
-
- _defineProperty(this, "host", void 0);
-
- _defineProperty(this, "secure", void 0);
-
- _defineProperty(this, "apiKey", void 0);
-
- _defineProperty(this, "version", void 0);
-
- _defineProperty(this, "autoreconnect", void 0);
-
- _defineProperty(this, "initialized", void 0);
-
- _defineProperty(this, "onMessage", undefined);
-
- _defineProperty(this, "onDisconnect", undefined);
-
- _defineProperty(this, "onOpen", undefined);
-
- _defineProperty(this, "onAutoreconnectIteration", undefined);
-
- _defineProperty(this, "logger", undefined);
-
- this.host = config.host;
- this.secure = config.secure;
- this.apiKey = config.apiKey;
- this.version = version_;
- this.autoreconnect = autoreconnect_;
-
- if (config.transport === 'lp') {
- _classPrivateMethodGet(this, _init_lp, _init_lp2).call(this);
-
- this.initialized = 'lp';
- } else if (config.transport === 'ws') {
- _classPrivateMethodGet(this, _init_ws, _init_ws2).call(this);
-
- this.initialized = 'ws';
- }
-
- if (!this.initialized) {
- _classPrivateMethodGet(this, _log, _log2).call(this, "Unknown or invalid network transport. Running under Node? Call 'Tinode.setNetworkProviders()'.");
-
- throw new Error("Unknown or invalid network transport. Running under Node? Call 'Tinode.setNetworkProviders()'.");
+ const log = (text, ...args) => {
+ if (Connection.logger) {
+ Connection.logger(text, ...args);
}
}
- static setNetworkProviders(wsProvider, xhrProvider) {
- WebSocketProvider = wsProvider;
- XHRProvider = xhrProvider;
- }
-
- connect(host_, force) {
- return Promise.reject(null);
- }
-
- reconnect(force) {}
-
- disconnect() {}
-
- sendText(msg) {}
-
- isConnected() {
- return false;
- }
-
- transport() {
- return this.initialized;
- }
-
- probe() {
- this.sendText('1');
- }
-
- backoffReset() {
- _classPrivateMethodGet(this, _boffReset, _boffReset2).call(this);
- }
-
-}
-
-exports.default = Connection;
-
-function _log2(text) {
- if (Connection.logger) {
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
- args[_key - 1] = arguments[_key];
+ // Backoff implementation - reconnect after a timeout.
+ function boffReconnect() {
+ // Clear timer
+ clearTimeout(_boffTimer);
+ // Calculate when to fire the reconnect attempt
+ const timeout = _BOFF_BASE * (Math.pow(2, _boffIteration) * (1.0 + _BOFF_JITTER * Math.random()));
+ // Update iteration counter for future use
+ _boffIteration = (_boffIteration >= _BOFF_MAX_ITER ? _boffIteration : _boffIteration + 1);
+ if (this.onAutoreconnectIteration) {
+ this.onAutoreconnectIteration(timeout);
}
- Connection.logger(text, ...args);
- }
-}
-
-function _boffReconnect2() {
- clearTimeout(_classPrivateFieldGet(this, _boffTimer));
-
- const timeout = _BOFF_BASE * (Math.pow(2, _classPrivateFieldGet(this, _boffIteration)) * (1.0 + _BOFF_JITTER * Math.random()));
-
- _classPrivateFieldSet(this, _boffIteration, _classPrivateFieldGet(this, _boffIteration) >= _BOFF_MAX_ITER ? _classPrivateFieldGet(this, _boffIteration) : _classPrivateFieldGet(this, _boffIteration) + 1);
-
- if (this.onAutoreconnectIteration) {
- this.onAutoreconnectIteration(timeout);
- }
-
- _classPrivateFieldSet(this, _boffTimer, setTimeout(() => {
- _classPrivateMethodGet(this, _log, _log2).call(this, "Reconnecting, iter=".concat(_classPrivateFieldGet(this, _boffIteration), ", timeout=").concat(timeout));
-
- if (!_classPrivateFieldGet(this, _boffClosed)) {
- const prom = this.connect();
-
- if (this.onAutoreconnectIteration) {
- this.onAutoreconnectIteration(0, prom);
- } else {
- prom.catch(() => {});
- }
- } else if (this.onAutoreconnectIteration) {
- this.onAutoreconnectIteration(-1);
- }
- }, timeout));
-}
-
-function _boffStop2() {
- clearTimeout(_classPrivateFieldGet(this, _boffTimer));
-
- _classPrivateFieldSet(this, _boffTimer, null);
-}
-
-function _boffReset2() {
- _classPrivateFieldSet(this, _boffIteration, 0);
-}
-
-function _init_lp2() {
- const XDR_UNSENT = 0;
- const XDR_OPENED = 1;
- const XDR_HEADERS_RECEIVED = 2;
- const XDR_LOADING = 3;
- const XDR_DONE = 4;
- let _lpURL = null;
- let _poller = null;
- let _sender = null;
-
- let lp_sender = url_ => {
- const sender = new XHRProvider();
-
- sender.onreadystatechange = evt => {
- if (sender.readyState == XDR_DONE && sender.status >= 400) {
- throw new Error("LP sender failed, ".concat(sender.status));
- }
- };
-
- sender.open('POST', url_, true);
- return sender;
- };
-
- let lp_poller = (url_, resolve, reject) => {
- let poller = new XHRProvider();
- let promiseCompleted = false;
-
- poller.onreadystatechange = evt => {
- if (poller.readyState == XDR_DONE) {
- if (poller.status == 201) {
- let pkt = JSON.parse(poller.responseText, _utils.jsonParseHelper);
- _lpURL = url_ + '&sid=' + pkt.ctrl.params.sid;
- poller = lp_poller(_lpURL);
- poller.send(null);
-
- if (this.onOpen) {
- this.onOpen();
- }
-
- if (resolve) {
- promiseCompleted = true;
- resolve();
- }
-
- if (this.autoreconnect) {
- _classPrivateMethodGet(this, _boffStop, _boffStop2).call(this);
- }
- } else if (poller.status < 400) {
- if (this.onMessage) {
- this.onMessage(poller.responseText);
- }
-
- poller = lp_poller(_lpURL);
- poller.send(null);
+ _boffTimer = setTimeout(() => {
+ log(`Reconnecting, iter=${_boffIteration}, timeout=${timeout}`);
+ // Maybe the socket was closed while we waited for the timer?
+ if (!_boffClosed) {
+ const prom = this.connect();
+ if (this.onAutoreconnectIteration) {
+ this.onAutoreconnectIteration(0, prom);
} else {
- if (reject && !promiseCompleted) {
- promiseCompleted = true;
- reject(poller.responseText);
+ // Suppress error if it's not used.
+ prom.catch(() => {
+ /* do nothing */
+ });
+ }
+ } else if (this.onAutoreconnectIteration) {
+ this.onAutoreconnectIteration(-1);
+ }
+ }, timeout);
+ }
+
+ // Terminate auto-reconnect process.
+ function boffStop() {
+ clearTimeout(_boffTimer);
+ _boffTimer = null;
+ }
+
+ // Reset auto-reconnect iteration counter.
+ function boffReset() {
+ _boffIteration = 0;
+ }
+
+ // Initialization for Websocket
+ function init_ws(instance) {
+ let _socket = null;
+
+ /**
+ * Initiate a new connection
+ * @memberof Tinode.Connection#
+ * @param {string} host_ Host name to connect to; if null the old host name will be used.
+ * @param {boolean} force Force new connection even if one already exists.
+ * @return {Promise} Promise resolved/rejected when the connection call completes, resolution is called without
+ * parameters, rejection passes the {Error} as parameter.
+ */
+ instance.connect = function(host_, force) {
+ _boffClosed = false;
+
+ if (_socket) {
+ if (!force && _socket.readyState == _socket.OPEN) {
+ return Promise.resolve();
+ }
+ _socket.close();
+ _socket = null;
+ }
+
+ if (host_) {
+ host = host_;
+ }
+
+ return new Promise(function(resolve, reject) {
+ const url = makeBaseUrl(host, secure ? 'wss' : 'ws', version, apiKey);
+
+ log("Connecting to: ", url);
+
+ // It throws when the server is not accessible but the exception cannot be caught:
+ // https://stackoverflow.com/questions/31002592/javascript-doesnt-catch-error-in-websocket-instantiation/31003057
+ const conn = new WebSocketProvider(url);
+
+ conn.onerror = function(err) {
+ reject(err);
+ }
+
+ conn.onopen = function(evt) {
+ if (autoreconnect) {
+ boffStop();
}
- if (this.onMessage && poller.responseText) {
- this.onMessage(poller.responseText);
+ if (instance.onOpen) {
+ instance.onOpen();
}
- if (this.onDisconnect) {
- const code = poller.status || (_classPrivateFieldGet(this, _boffClosed) ? NETWORK_USER : NETWORK_ERROR);
- const text = poller.responseText || (_classPrivateFieldGet(this, _boffClosed) ? NETWORK_USER_TEXT : NETWORK_ERROR_TEXT);
- this.onDisconnect(new Error(text + ' (' + code + ')'), code);
+ resolve();
+ }
+
+ conn.onclose = function(evt) {
+ _socket = null;
+
+ if (instance.onDisconnect) {
+ const code = _boffClosed ? NETWORK_USER : NETWORK_ERROR;
+ instance.onDisconnect(new Error(_boffClosed ? NETWORK_USER_TEXT : NETWORK_ERROR_TEXT +
+ ' (' + code + ')'), code);
}
- poller = null;
-
- if (!_classPrivateFieldGet(this, _boffClosed) && this.autoreconnect) {
- _classPrivateMethodGet(this, _boffReconnect, _boffReconnect2).call(this);
+ if (!_boffClosed && autoreconnect) {
+ boffReconnect.call(instance);
}
}
+
+ conn.onmessage = function(evt) {
+ if (instance.onMessage) {
+ instance.onMessage(evt.data);
+ }
+ }
+ _socket = conn;
+ });
+ }
+
+ /**
+ * Try to restore a network connection, also reset backoff.
+ * @memberof Tinode.Connection#
+ *
+ * @param {boolean} force - reconnect even if there is a live connection already.
+ */
+ instance.reconnect = function(force) {
+ boffStop();
+ instance.connect(null, force);
+ }
+
+ /**
+ * Terminate the network connection
+ * @memberof Tinode.Connection#
+ */
+ instance.disconnect = function() {
+ _boffClosed = true;
+ boffStop();
+
+ if (!_socket) {
+ return;
+ }
+ _socket.close();
+ _socket = null;
+ }
+
+ /**
+ * Send a string to the server.
+ * @memberof Tinode.Connection#
+ *
+ * @param {string} msg - String to send.
+ * @throws Throws an exception if the underlying connection is not live.
+ */
+ instance.sendText = function(msg) {
+ if (_socket && (_socket.readyState == _socket.OPEN)) {
+ _socket.send(msg);
+ } else {
+ throw new Error("Websocket is not connected");
}
};
- poller.open('GET', url_, true);
- return poller;
- };
+ /**
+ * Check if socket is alive.
+ * @memberof Tinode.Connection#
+ * @returns {boolean} true if connection is live, false otherwise.
+ */
+ instance.isConnected = function() {
+ return (_socket && (_socket.readyState == _socket.OPEN));
+ }
- this.connect = (host_, force) => {
- _classPrivateFieldSet(this, _boffClosed, false);
+ /**
+ * Get the name of the current network transport.
+ * @memberof Tinode.Connection#
+ * @returns {string} name of the transport such as "ws" or "lp".
+ */
+ instance.transport = function() {
+ return 'ws';
+ }
- if (_poller) {
- if (!force) {
- return Promise.resolve();
+ /**
+ * Send network probe to check if connection is indeed live.
+ * @memberof Tinode.Connection#
+ */
+ instance.probe = function() {
+ instance.sendText('1');
+ }
+ }
+
+ // Initialization for long polling.
+ function init_lp(instance) {
+ const XDR_UNSENT = 0; // Client has been created. open() not called yet.
+ const XDR_OPENED = 1; // open() has been called.
+ const XDR_HEADERS_RECEIVED = 2; // send() has been called, and headers and status are available.
+ const XDR_LOADING = 3; // Downloading; responseText holds partial data.
+ const XDR_DONE = 4; // The operation is complete.
+ // Fully composed endpoint URL, with API key & SID
+ let _lpURL = null;
+
+ let _poller = null;
+ let _sender = null;
+
+ function lp_sender(url_) {
+ const sender = new XHRProvider();
+ sender.onreadystatechange = function(evt) {
+ if (sender.readyState == XDR_DONE && sender.status >= 400) {
+ // Some sort of error response
+ throw new Error(`LP sender failed, ${sender.status}`);
+ }
}
- _poller.onreadystatechange = undefined;
-
- _poller.abort();
-
- _poller = null;
+ sender.open('POST', url_, true);
+ return sender;
}
- if (host_) {
- this.host = host_;
+ function lp_poller(url_, resolve, reject) {
+ let poller = new XHRProvider();
+ let promiseCompleted = false;
+
+ poller.onreadystatechange = function(evt) {
+
+ if (poller.readyState == XDR_DONE) {
+ if (poller.status == 201) { // 201 == HTTP.Created, get SID
+ let pkt = JSON.parse(poller.responseText, jsonParseHelper);
+ _lpURL = url_ + '&sid=' + pkt.ctrl.params.sid
+ poller = lp_poller(_lpURL);
+ poller.send(null)
+ if (instance.onOpen) {
+ instance.onOpen();
+ }
+
+ if (resolve) {
+ promiseCompleted = true;
+ resolve();
+ }
+
+ if (autoreconnect) {
+ boffStop();
+ }
+ } else if (poller.status < 400) { // 400 = HTTP.BadRequest
+ if (instance.onMessage) {
+ instance.onMessage(poller.responseText)
+ }
+ poller = lp_poller(_lpURL);
+ poller.send(null);
+ } else {
+ // Don't throw an error here, gracefully handle server errors
+ if (reject && !promiseCompleted) {
+ promiseCompleted = true;
+ reject(poller.responseText);
+ }
+ if (instance.onMessage && poller.responseText) {
+ instance.onMessage(poller.responseText);
+ }
+ if (instance.onDisconnect) {
+ const code = poller.status || (_boffClosed ? NETWORK_USER : NETWORK_ERROR);
+ const text = poller.responseText || (_boffClosed ? NETWORK_USER_TEXT : NETWORK_ERROR_TEXT);
+ instance.onDisconnect(new Error(text + ' (' + code + ')'), code);
+ }
+
+ // Polling has stopped. Indicate it by setting poller to null.
+ poller = null;
+ if (!_boffClosed && autoreconnect) {
+ boffReconnect.call(instance);
+ }
+ }
+ }
+ }
+ poller.open('GET', url_, true);
+ return poller;
}
- return new Promise((resolve, reject) => {
- const url = makeBaseUrl(this.host, this.secure ? 'https' : 'http', this.version, this.apiKey);
+ instance.connect = function(host_, force) {
+ _boffClosed = false;
- _classPrivateMethodGet(this, _log, _log2).call(this, "Connecting to:", url);
-
- _poller = lp_poller(url, resolve, reject);
-
- _poller.send(null);
- }).catch(err => {
- _classPrivateMethodGet(this, _log, _log2).call(this, "LP connection failed:", err);
- });
- };
-
- this.reconnect = force => {
- _classPrivateMethodGet(this, _boffStop, _boffStop2).call(this);
-
- this.connect(null, force);
- };
-
- this.disconnect = () => {
- _classPrivateFieldSet(this, _boffClosed, true);
-
- _classPrivateMethodGet(this, _boffStop, _boffStop2).call(this);
-
- if (_sender) {
- _sender.onreadystatechange = undefined;
-
- _sender.abort();
-
- _sender = null;
- }
-
- if (_poller) {
- _poller.onreadystatechange = undefined;
-
- _poller.abort();
-
- _poller = null;
- }
-
- if (this.onDisconnect) {
- this.onDisconnect(new Error(NETWORK_USER_TEXT + ' (' + NETWORK_USER + ')'), NETWORK_USER);
- }
-
- _lpURL = null;
- };
-
- this.sendText = msg => {
- _sender = lp_sender(_lpURL);
-
- if (_sender && _sender.readyState == XDR_OPENED) {
- _sender.send(msg);
- } else {
- throw new Error("Long poller failed to connect");
- }
- };
-
- this.isConnected = () => {
- return _poller && true;
- };
-}
-
-function _init_ws2() {
- this.connect = (host_, force) => {
- _classPrivateFieldSet(this, _boffClosed, false);
-
- if (_classPrivateFieldGet(this, _socket)) {
- if (!force && _classPrivateFieldGet(this, _socket).readyState == _classPrivateFieldGet(this, _socket).OPEN) {
- return Promise.resolve();
+ if (_poller) {
+ if (!force) {
+ return Promise.resolve();
+ }
+ _poller.onreadystatechange = undefined;
+ _poller.abort();
+ _poller = null;
}
- _classPrivateFieldGet(this, _socket).close();
+ if (host_) {
+ host = host_;
+ }
- _classPrivateFieldSet(this, _socket, null);
+ return new Promise(function(resolve, reject) {
+ const url = makeBaseUrl(host, secure ? 'https' : 'http', version, apiKey);
+ log("Connecting to:", url);
+ _poller = lp_poller(url, resolve, reject);
+ _poller.send(null)
+ }).catch((err) => {
+ log("LP connection failed:", err);
+ });
+ };
+
+ instance.reconnect = function(force) {
+ boffStop();
+ instance.connect(null, force);
+ };
+
+ instance.disconnect = function() {
+ _boffClosed = true;
+ boffStop();
+
+ if (_sender) {
+ _sender.onreadystatechange = undefined;
+ _sender.abort();
+ _sender = null;
+ }
+ if (_poller) {
+ _poller.onreadystatechange = undefined;
+ _poller.abort();
+ _poller = null;
+ }
+
+ if (instance.onDisconnect) {
+ instance.onDisconnect(new Error(NETWORK_USER_TEXT + ' (' + NETWORK_USER + ')'), NETWORK_USER);
+ }
+ // Ensure it's reconstructed
+ _lpURL = null;
}
- if (host_) {
- this.host = host_;
+ instance.sendText = function(msg) {
+ _sender = lp_sender(_lpURL);
+ if (_sender && (_sender.readyState == 1)) { // 1 == OPENED
+ _sender.send(msg);
+ } else {
+ throw new Error("Long poller failed to connect");
+ }
+ };
+
+ instance.isConnected = function() {
+ return (_poller && true);
}
- return new Promise((resolve, reject) => {
- const url = makeBaseUrl(this.host, this.secure ? 'wss' : 'ws', this.version, this.apiKey);
-
- _classPrivateMethodGet(this, _log, _log2).call(this, "Connecting to: ", url);
-
- const conn = new WebSocketProvider(url);
-
- conn.onerror = err => {
- reject(err);
- };
-
- conn.onopen = evt => {
- if (this.autoreconnect) {
- _classPrivateMethodGet(this, _boffStop, _boffStop2).call(this);
- }
-
- if (this.onOpen) {
- this.onOpen();
- }
-
- resolve();
- };
-
- conn.onclose = evt => {
- _classPrivateFieldSet(this, _socket, null);
-
- if (this.onDisconnect) {
- const code = _classPrivateFieldGet(this, _boffClosed) ? NETWORK_USER : NETWORK_ERROR;
- this.onDisconnect(new Error(_classPrivateFieldGet(this, _boffClosed) ? NETWORK_USER_TEXT : NETWORK_ERROR_TEXT + ' (' + code + ')'), code);
- }
-
- if (!_classPrivateFieldGet(this, _boffClosed) && this.autoreconnect) {
- _classPrivateMethodGet(this, _boffReconnect, _boffReconnect2).call(this);
- }
- };
-
- conn.onmessage = evt => {
- if (this.onMessage) {
- this.onMessage(evt.data);
- }
- };
-
- _classPrivateFieldSet(this, _socket, conn);
- });
- };
-
- this.reconnect = force => {
- _classPrivateMethodGet(this, _boffStop, _boffStop2).call(this);
-
- this.connect(null, force);
- };
-
- this.disconnect = () => {
- _classPrivateFieldSet(this, _boffClosed, true);
-
- _classPrivateMethodGet(this, _boffStop, _boffStop2).call(this);
-
- if (!_classPrivateFieldGet(this, _socket)) {
- return;
+ instance.transport = function() {
+ return 'lp';
}
- _classPrivateFieldGet(this, _socket).close();
-
- _classPrivateFieldSet(this, _socket, null);
- };
-
- this.sendText = msg => {
- if (_classPrivateFieldGet(this, _socket) && _classPrivateFieldGet(this, _socket).readyState == _classPrivateFieldGet(this, _socket).OPEN) {
- _classPrivateFieldGet(this, _socket).send(msg);
- } else {
- throw new Error("Websocket is not connected");
+ instance.probe = function() {
+ instance.sendText('1');
}
- };
+ }
- this.isConnected = () => {
- return _classPrivateFieldGet(this, _socket) && _classPrivateFieldGet(this, _socket).readyState == _classPrivateFieldGet(this, _socket).OPEN;
- };
-}
+ let initialized = false;
+ if (config.transport === 'lp') {
+ // explicit request to use long polling
+ init_lp(this);
+ initialized = true;
+ } else if (config.transport === 'ws') {
+ // explicit request to use web socket
+ // if websockets are not available, horrible things will happen
+ init_ws(this);
+ initialized = true;
+ }
+
+ if (!initialized) {
+ // Invalid or undefined network transport.
+ log("Unknown or invalid network transport. Running under Node? Call 'Tinode.setNetworkProviders()'.");
+ throw new Error("Unknown or invalid network transport. Running under Node? Call 'Tinode.setNetworkProviders()'.");
+ }
+
+ /**
+ * Reset autoreconnect counter to zero.
+ * @memberof Tinode.Connection#
+ */
+ this.backoffReset = function() {
+ boffReset();
+ }
+
+ // Callbacks:
+ /**
+ * A callback to pass incoming messages to. See {@link Tinode.Connection#onMessage}.
+ * @callback Tinode.Connection.OnMessage
+ * @memberof Tinode.Connection
+ * @param {string} message - Message to process.
+ */
+ /**
+ * A callback to pass incoming messages to.
+ * @type {Tinode.Connection.OnMessage}
+ * @memberof Tinode.Connection#
+ */
+ this.onMessage = undefined;
+
+ /**
+ * A callback for reporting a dropped connection.
+ * @type {function}
+ * @memberof Tinode.Connection#
+ */
+ this.onDisconnect = undefined;
+
+ /**
+ * A callback called when the connection is ready to be used for sending. For websockets it's socket open,
+ * for long polling it's readyState=1 (OPENED)
+ * @type {function}
+ * @memberof Tinode.Connection#
+ */
+ this.onOpen = undefined;
+
+ /**
+ * A callback to notify of reconnection attempts. See {@link Tinode.Connection#onAutoreconnectIteration}.
+ * @memberof Tinode.Connection
+ * @callback AutoreconnectIterationType
+ * @param {string} timeout - time till the next reconnect attempt in milliseconds. -1 means reconnect was skipped.
+ * @param {Promise} promise resolved or rejected when the reconnect attemp completes.
+ *
+ */
+ /**
+ * A callback to inform when the next attampt to reconnect will happen and to receive connection promise.
+ * @memberof Tinode.Connection#
+ * @type {Tinode.Connection.AutoreconnectIterationType}
+ */
+ this.onAutoreconnectIteration = undefined;
+
+ /**
+ * A callback to log events from Connection. See {@link Tinode.Connection#logger}.
+ * @memberof Tinode.Connection
+ * @callback LoggerCallbackType
+ * @param {string} event - Event to log.
+ */
+ /**
+ * A callback to report logging events.
+ * @memberof Tinode.Connection#
+ * @type {Tinode.Connection.LoggerCallbackType}
+ */
+ this.logger = undefined;
+};
+
+/**
+ * To use Connection in a non browser context, supply WebSocket and XMLHttpRequest providers.
+ * @static
+ * @memberof Connection
+ * @param wsProvider WebSocket provider, e.g. for nodeJS , require('ws').
+ * @param xhrProvider XMLHttpRequest provider, e.g. for node require('xhr').
+ */
+Connection.setNetworkProviders = function(wsProvider, xhrProvider) {
+ WebSocketProvider = wsProvider;
+ XHRProvider = xhrProvider;
+};
Connection.NETWORK_ERROR = NETWORK_ERROR;
Connection.NETWORK_ERROR_TEXT = NETWORK_ERROR_TEXT;
Connection.NETWORK_USER = NETWORK_USER;
Connection.NETWORK_USER_TEXT = NETWORK_USER_TEXT;
-},{"./utils.js":11}],5:[function(require,module,exports){
+if (typeof module != 'undefined') {
+ module.exports = Connection;
+}
+
+},{"./utils.js":9}],4:[function(require,module,exports){
+/**
+ * @file Helper methods for dealing with IndexedDB cache of messages, users, and topics.
+ * See https://github.com/tinode/webapp for real-life usage.
+ *
+ * @copyright 2015-2021 Tinode
+ * @summary Javascript bindings for Tinode.
+ * @license Apache 2.0
+ * @version 0.18
+ */
'use strict';
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-exports.default = void 0;
-
-function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); }
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
-function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
-
-function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
-
-function _classStaticPrivateFieldSpecGet(receiver, classConstructor, descriptor) { _classCheckPrivateStaticAccess(receiver, classConstructor); _classCheckPrivateStaticFieldDescriptor(descriptor, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
-
-function _classCheckPrivateStaticFieldDescriptor(descriptor, action) { if (descriptor === undefined) { throw new TypeError("attempted to " + action + " private static field before its declaration"); } }
-
-function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
-
-function _classStaticPrivateMethodGet(receiver, classConstructor, method) { _classCheckPrivateStaticAccess(receiver, classConstructor); return method; }
-
-function _classCheckPrivateStaticAccess(receiver, classConstructor) { if (receiver !== classConstructor) { throw new TypeError("Private static access of wrong provenance"); } }
-
-function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
-
-function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
-
-function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
-
-function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
-
-function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
+// NOTE TO DEVELOPERS:
+// Localizable strings should be double quoted "строка на другом языке",
+// non-localizable strings should be single quoted 'non-localized'.
const DB_VERSION = 1;
const DB_NAME = 'tinode-web';
+
let IDBProvider;
-var _onError = new WeakMap();
+const DB = function(onError, logger) {
+ onError = onError || function() {}
+ logger = logger || function() {}
-var _logger = new WeakMap();
+ // Instance of IndexDB.
+ let db = null;
+ // Indicator that the cache is disabled.
+ let disabled = false;
-var _mapObjects = new WeakSet();
+ // Serializable topic fields.
+ const topic_fields = ['created', 'updated', 'deleted', 'read', 'recv', 'seq', 'clear', 'defacs',
+ 'creds', 'public', 'trusted', 'private', 'touched'
+ ];
-class DB {
- constructor(onError, logger) {
- _classPrivateMethodInitSpec(this, _mapObjects);
-
- _classPrivateFieldInitSpec(this, _onError, {
- writable: true,
- value: function () {}
+ // Copy values from 'src' to 'dst'. Allocate dst if it's null or undefined.
+ function serializeTopic(dst, src) {
+ const res = dst || {
+ name: src.name
+ };
+ topic_fields.forEach((f) => {
+ if (src.hasOwnProperty(f)) {
+ res[f] = src[f];
+ }
});
-
- _classPrivateFieldInitSpec(this, _logger, {
- writable: true,
- value: function () {}
- });
-
- _defineProperty(this, "db", null);
-
- _defineProperty(this, "disabled", false);
-
- _classPrivateFieldSet(this, _onError, onError || _classPrivateFieldGet(this, _onError));
-
- _classPrivateFieldSet(this, _logger, logger || _classPrivateFieldGet(this, _logger));
+ if (Array.isArray(src._tags)) {
+ res.tags = src._tags;
+ }
+ if (src.acs) {
+ res.acs = src.getAccessMode().jsonHelper();
+ }
+ return res;
}
- initDatabase() {
- return new Promise((resolve, reject) => {
- const req = IDBProvider.open(DB_NAME, DB_VERSION);
-
- req.onsuccess = event => {
- this.db = event.target.result;
- this.disabled = false;
- resolve(this.db);
- };
-
- req.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', "failed to initialize", event);
-
- reject(event.target.error);
-
- _classPrivateFieldGet(this, _onError).call(this, event.target.error);
- };
-
- req.onupgradeneeded = function (event) {
- this.db = event.target.result;
-
- this.db.onerror = function (event) {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', "failed to create storage", event);
-
- _classPrivateFieldGet(this, _onError).call(this, event.target.error);
- };
-
- this.db.createObjectStore('topic', {
- keyPath: 'name'
- });
- this.db.createObjectStore('user', {
- keyPath: 'uid'
- });
- this.db.createObjectStore('subscription', {
- keyPath: ['topic', 'uid']
- });
- this.db.createObjectStore('message', {
- keyPath: ['topic', 'seq']
- });
- };
+ // Copy data from src to Topic object.
+ function deserializeTopic(topic, src) {
+ topic_fields.forEach((f) => {
+ if (src.hasOwnProperty(f)) {
+ topic[f] = src[f];
+ }
});
+ if (Array.isArray(src.tags)) {
+ topic._tags = src.tags;
+ }
+ if (src.acs) {
+ topic.setAccessMode(src.acs);
+ }
+ topic.seq |= 0;
+ topic.read |= 0;
+ topic.unread = Math.max(0, topic.seq - topic.read);
}
- deleteDatabase() {
- if (this.db) {
- this.db.close();
- this.db = null;
+ function serializeSubscription(dst, topicName, uid, sub) {
+ const fields = ['updated', 'mode', 'read', 'recv', 'clear', 'lastSeen', 'userAgent'];
+ const res = dst || {
+ topic: topicName,
+ uid: uid
+ };
+
+ fields.forEach((f) => {
+ if (sub.hasOwnProperty(f)) {
+ res[f] = sub[f];
+ }
+ });
+
+ return res;
+ }
+
+ function serializeMessage(dst, msg) {
+ // Serializable fields.
+ const fields = ['topic', 'seq', 'ts', '_status', 'from', 'head', 'content'];
+ const res = dst || {};
+ fields.forEach((f) => {
+ if (msg.hasOwnProperty(f)) {
+ res[f] = msg[f];
+ }
+ });
+ return res;
+ }
+
+ function mapObjects(source, callback, context) {
+ if (!db) {
+ return disabled ?
+ Promise.resolve([]) :
+ Promise.reject(new Error("not initialized"));
}
return new Promise((resolve, reject) => {
- const req = IDBProvider.deleteDatabase(DB_NAME);
-
- req.onblocked = function (event) {
- if (this.db) {
- this.db.close();
- }
-
- const err = new Error("blocked");
-
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'deleteDatabase', err);
-
- reject(err);
- };
-
- req.onsuccess = event => {
- this.db = null;
- this.disabled = true;
- resolve(true);
- };
-
- req.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'deleteDatabase', event.target.error);
-
+ const trx = db.transaction([source]);
+ trx.onerror = (event) => {
+ logger("PCache", "mapObjects", source, event.target.error);
reject(event.target.error);
};
- });
- }
-
- isReady() {
- return !!this.db;
- }
-
- updTopic(topic) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['topic'], 'readwrite');
-
- trx.oncomplete = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'updTopic', event.target.error);
-
- reject(event.target.error);
- };
-
- const req = trx.objectStore('topic').get(topic.name);
-
- req.onsuccess = event => {
- trx.objectStore('topic').put(_classStaticPrivateMethodGet(DB, DB, _serializeTopic).call(DB, req.result, topic));
- trx.commit();
- };
- });
- }
-
- markTopicAsDeleted(name, deleted) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['topic'], 'readwrite');
-
- trx.oncomplete = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'markTopicAsDeleted', event.target.error);
-
- reject(event.target.error);
- };
-
- const req = trx.objectStore('topic').get(name);
-
- req.onsuccess = event => {
- const topic = event.target.result;
-
- if (topic._deleted != deleted) {
- topic._deleted = true;
- trx.objectStore('topic').put(topic);
- }
-
- trx.commit();
- };
- });
- }
-
- remTopic(name) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['topic', 'subscription', 'message'], 'readwrite');
-
- trx.oncomplete = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'remTopic', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('topic').delete(IDBKeyRange.only(name));
- trx.objectStore('subscription').delete(IDBKeyRange.bound([name, '-'], [name, '~']));
- trx.objectStore('message').delete(IDBKeyRange.bound([name, 0], [name, Number.MAX_SAFE_INTEGER]));
- trx.commit();
- });
- }
-
- mapTopics(callback, context) {
- return _classPrivateMethodGet(this, _mapObjects, _mapObjects2).call(this, 'topic', callback, context);
- }
-
- deserializeTopic(topic, src) {
- _classStaticPrivateMethodGet(DB, DB, _deserializeTopic).call(DB, topic, src);
- }
-
- updUser(uid, pub) {
- if (arguments.length < 2 || pub === undefined) {
- return;
- }
-
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['user'], 'readwrite');
-
- trx.oncomplete = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'updUser', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('user').put({
- uid: uid,
- public: pub
- });
- trx.commit();
- });
- }
-
- remUser(uid) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['user'], 'readwrite');
-
- trx.oncomplete = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'remUser', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('user').delete(IDBKeyRange.only(uid));
- trx.commit();
- });
- }
-
- mapUsers(callback, context) {
- return _classPrivateMethodGet(this, _mapObjects, _mapObjects2).call(this, 'user', callback, context);
- }
-
- getUser(uid) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['user']);
-
- trx.oncomplete = event => {
- const user = event.target.result;
- resolve({
- user: user.uid,
- public: user.public
- });
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'getUser', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('user').get(uid);
- });
- }
-
- updSubscription(topicName, uid, sub) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['subscription'], 'readwrite');
-
- trx.oncomplete = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'updSubscription', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('subscription').get([topicName, uid]).onsuccess = event => {
- trx.objectStore('subscription').put(_classStaticPrivateMethodGet(DB, DB, _serializeSubscription).call(DB, event.target.result, topicName, uid, sub));
- trx.commit();
- };
- });
- }
-
- mapSubscriptions(topicName, callback, context) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve([]) : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['subscription']);
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'mapSubscriptions', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('subscription').getAll(IDBKeyRange.bound([topicName, '-'], [topicName, '~'])).onsuccess = event => {
+ trx.objectStore(source).getAll().onsuccess = (event) => {
if (callback) {
- event.target.result.forEach(topic => {
+ event.target.result.forEach((topic) => {
callback.call(context, topic);
});
}
-
resolve(event.target.result);
};
});
}
- addMessage(msg) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
+ return {
+ /**
+ * Initialize persistent cache: open or create/upgrade if needed.
+ * @returns {Promise} promise to be resolved/rejected when the DB is initialized.
+ */
+ initDatabase: function() {
+ return new Promise((resolve, reject) => {
+ // Open the database and initialize callbacks.
+ const req = IDBProvider.open(DB_NAME, DB_VERSION);
+ req.onsuccess = (event) => {
+ db = event.target.result;
+ disabled = false;
+ resolve(db);
+ };
+ req.onerror = (event) => {
+ logger("PCache", "failed to initialize", event);
+ reject(event.target.error);
+ onError(event.target.error);
+ };
+ req.onupgradeneeded = function(event) {
+ db = event.target.result;
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['message'], 'readwrite');
+ db.onerror = function(event) {
+ logger("PCache", "failed to create storage", event);
+ onError(event.target.error);
+ };
- trx.onsuccess = event => {
- resolve(event.target.result);
- };
+ // Individual object stores.
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'addMessage', event.target.error);
+ // Object store (table) for topics. The primary key is topic name.
+ db.createObjectStore('topic', {
+ keyPath: 'name'
+ });
- reject(event.target.error);
- };
+ // Users object store. UID is the primary key.
+ db.createObjectStore('user', {
+ keyPath: 'uid'
+ });
- trx.objectStore('message').add(_classStaticPrivateMethodGet(DB, DB, _serializeMessage).call(DB, null, msg));
- trx.commit();
- });
- }
+ // Subscriptions object store topic <-> user. Topic name + UID is the primary key.
+ db.createObjectStore('subscription', {
+ keyPath: ['topic', 'uid']
+ });
- updMessageStatus(topicName, seq, status) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
+ // Messages object store. The primary key is topic name + seq.
+ db.createObjectStore('message', {
+ keyPath: ['topic', 'seq']
+ });
+ };
+ });
+ },
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction(['message'], 'readwrite');
-
- trx.onsuccess = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'updMessageStatus', event.target.error);
-
- reject(event.target.error);
- };
-
- const req = trx.objectStore('message').get(IDBKeyRange.only([topicName, seq]));
-
- req.onsuccess = event => {
- const src = req.result || event.target.result;
-
- if (!src || src._status == status) {
- trx.commit();
- return;
- }
-
- trx.objectStore('message').put(_classStaticPrivateMethodGet(DB, DB, _serializeMessage).call(DB, src, {
- topic: topicName,
- seq: seq,
- _status: status
- }));
- trx.commit();
- };
- });
- }
-
- remMessages(topicName, from, to) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve() : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- if (!from && !to) {
- from = 0;
- to = Number.MAX_SAFE_INTEGER;
- }
-
- const range = to > 0 ? IDBKeyRange.bound([topicName, from], [topicName, to], false, true) : IDBKeyRange.only([topicName, from]);
- const trx = this.db.transaction(['message'], 'readwrite');
-
- trx.onsuccess = event => {
- resolve(event.target.result);
- };
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'remMessages', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('message').delete(range);
- trx.commit();
- });
- }
-
- readMessages(topicName, query, callback, context) {
- if (!this.isReady()) {
- return this.disabled ? Promise.resolve([]) : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- query = query || {};
- const since = query.since > 0 ? query.since : 0;
- const before = query.before > 0 ? query.before : Number.MAX_SAFE_INTEGER;
- const limit = query.limit | 0;
- const result = [];
- const range = IDBKeyRange.bound([topicName, since], [topicName, before], false, true);
- const trx = this.db.transaction(['message']);
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'readMessages', event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore('message').openCursor(range, 'prev').onsuccess = event => {
- const cursor = event.target.result;
-
- if (cursor) {
- if (callback) {
- callback.call(context, cursor.value);
+ /**
+ * Delete persistent cache.
+ */
+ deleteDatabase: function() {
+ return new Promise((resolve, reject) => {
+ const req = IDBProvider.deleteDatabase(DB_NAME);
+ req.onblocked = function(event) {
+ if (db) {
+ db.close();
}
+ };
+ req.onsuccess = (event) => {
+ db = null;
+ disabled = true;
+ resolve(true);
+ };
+ req.onerror = (event) => {
+ logger("PCache", "deleteDatabase", event.target.error);
+ reject(event.target.error);
+ };
+ });
+ },
- result.push(cursor.value);
+ /**
+ * Check if persistent cache is ready for use.
+ * @memberOf DB
+ * @returns {boolean} true if cache is ready, false otherwise.
+ */
+ isReady: function() {
+ return !!db;
+ },
- if (limit <= 0 || result.length < limit) {
- cursor.continue();
+ // Topics.
+ /**
+ * Save to cache or update topic in persistent cache.
+ * @memberOf DB
+ * @param {Topic} topic - topic to be added or updated.
+ * @returns {Promise} promise resolved/rejected on operation completion.
+ */
+ updTopic: function(topic) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['topic'], 'readwrite');
+ trx.oncomplete = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "updTopic", event.target.error);
+ reject(event.target.error);
+ };
+ const req = trx.objectStore('topic').get(topic.name);
+ req.onsuccess = (event) => {
+ trx.objectStore('topic').put(serializeTopic(req.result, topic));
+ trx.commit();
+ };
+ });
+ },
+
+ /**
+ * Remove topic from persistent cache.
+ * @memberOf DB
+ * @param {string} name - name of the topic to remove from database.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ remTopic: function(name) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['topic', 'subscription', 'message'], 'readwrite');
+ trx.oncomplete = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "remTopic", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('topic').delete(IDBKeyRange.only(name));
+ trx.objectStore('subscription').delete(IDBKeyRange.bound([name, '-'], [name, '~']));
+ trx.objectStore('message').delete(IDBKeyRange.bound([name, 0], [name, Number.MAX_SAFE_INTEGER]));
+ trx.commit();
+ });
+ },
+
+ /**
+ * Execute a callback for each stored topic.
+ * @memberOf DB
+ * @param {function} callback - function to call for each topic.
+ * @param {Object} context - the value or this inside the callback.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ mapTopics: function(callback, context) {
+ return mapObjects('topic', callback, context);
+ },
+
+ /**
+ * Copy data from serialized object to topic.
+ * @memberOf DB
+ * @param {Topic} topic - target to deserialize to.
+ * @param {Object} src - serialized data to copy from.
+ */
+ deserializeTopic: function(topic, src) {
+ deserializeTopic(topic, src);
+ },
+
+ // Users.
+ /**
+ * Add or update user object in the persistent cache.
+ * @memberOf DB
+ * @param {string} uid - ID of the user to save or update.
+ * @param {Object} pub - user's public information.
+ * @returns {Promise} promise resolved/rejected on operation completion.
+ */
+ updUser: function(uid, pub) {
+ if (arguments.length < 2 || pub === undefined) {
+ // No point inupdating user with invalid data.
+ return;
+ }
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['user'], 'readwrite');
+ trx.oncomplete = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "updUser", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('user').put({
+ uid: uid,
+ public: pub
+ });
+ trx.commit();
+ });
+ },
+
+ /**
+ * Remove user from persistent cache.
+ * @memberOf DB
+ * @param {string} uid - ID of the user to remove from the cache.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ remUser: function(uid) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['user'], 'readwrite');
+ trx.oncomplete = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "remUser", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('user').delete(IDBKeyRange.only(uid));
+ trx.commit();
+ });
+ },
+
+ /**
+ * Execute a callback for each stored user.
+ * @memberOf DB
+ * @param {function} callback - function to call for each topic.
+ * @param {Object} context - the value or this inside the callback.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ mapUsers: function(callback, context) {
+ return mapObjects('user', callback, context);
+ },
+
+ /**
+ * Read a single user from persistent cache.
+ * @memberOf DB
+ * @param {string} uid - ID of the user to fetch from cache.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ getUser: function(uid) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['user']);
+ trx.oncomplete = (event) => {
+ const user = event.target.result;
+ resolve({
+ user: user.uid,
+ public: user.public
+ });
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "getUser", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('user').get(uid);
+ });
+ },
+
+ // Subscriptions.
+
+ /**
+ * Add or update subscription in persistent cache.
+ * @memberOf DB
+ * @param {string} topicName - name of the topic which owns the message.
+ * @param {string} uid - ID of the subscribed user.
+ * @param {Object} sub - subscription to save.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ updSubscription: function(topicName, uid, sub) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['subscription'], 'readwrite');
+ trx.oncomplete = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "updSubscription", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('subscription').get([topicName, uid]).onsuccess = (event) => {
+ trx.objectStore('subscription').put(serializeSubscription(event.target.result, topicName, uid, sub));
+ trx.commit();
+ };
+ });
+ },
+
+ /**
+ * Execute a callback for each cached subscription in a given topic.
+ * @memberOf DB
+ * @param {string} topicName - name of the topic which owns the subscriptions.
+ * @param {function} callback - function to call for each subscription.
+ * @param {Object} context - the value or this inside the callback.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ mapSubscriptions: function(topicName, callback, context) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve([]) :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['subscription']);
+ trx.onerror = (event) => {
+ logger("PCache", "mapSubscriptions", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('subscription').getAll(IDBKeyRange.bound([topicName, '-'], [topicName, '~'])).onsuccess = (event) => {
+ if (callback) {
+ event.target.result.forEach((topic) => {
+ callback.call(context, topic);
+ });
+ }
+ resolve(event.target.result);
+ };
+ });
+ },
+
+ // Messages.
+
+ /**
+ * Save message to persistent cache.
+ * @memberOf DB
+ * @param {string} topicName - name of the topic which owns the message.
+ * @param {Object} msg - message to save.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ addMessage: function(msg) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['message'], 'readwrite');
+ trx.onsuccess = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "addMessage", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('message').add(serializeMessage(null, msg));
+ trx.commit();
+ });
+ },
+
+ /**
+ * Update delivery status of a message stored in persistent cache.
+ * @memberOf DB
+ * @param {string} topicName - name of the topic which owns the message.
+ * @param {number} seq - ID of the message to update
+ * @param {number} status - new delivery status of the message.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ updMessageStatus: function(topicName, seq, status) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ const trx = db.transaction(['message'], 'readwrite');
+ trx.onsuccess = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "updMessageStatus", event.target.error);
+ reject(event.target.error);
+ };
+ const req = trx.objectStore('message').get(IDBKeyRange.only([topicName, seq]));
+ req.onsuccess = (event) => {
+ const src = req.result || event.target.result;
+ if (!src || src._status == status) {
+ trx.commit();
+ return;
+ }
+ trx.objectStore('message').put(serializeMessage(src, {
+ topic: topicName,
+ seq: seq,
+ _status: status
+ }));
+ trx.commit();
+ };
+ });
+ },
+
+ /**
+ * Remove one or more messages from persistent cache.
+ * @memberOf DB
+ * @param {string} topicName - name of the topic which owns the message.
+ * @param {number} from - id of the message to remove or lower boundary when removing range (inclusive).
+ * @param {number=} to - upper boundary (exclusive) when removing a range of messages.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ remMessages: function(topicName, from, to) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve() :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ if (!from && !to) {
+ from = 0;
+ to = Number.MAX_SAFE_INTEGER;
+ }
+ const range = to > 0 ? IDBKeyRange.bound([topicName, from], [topicName, to], false, true) :
+ IDBKeyRange.only([topicName, from]);
+ const trx = db.transaction(['message'], 'readwrite');
+ trx.onsuccess = (event) => {
+ resolve(event.target.result);
+ };
+ trx.onerror = (event) => {
+ logger("PCache", "remMessages", event.target.error);
+ reject(event.target.error);
+ };
+ trx.objectStore('message').delete(range);
+ trx.commit();
+ });
+ },
+
+ /**
+ * Retrieve messages from persistent store.
+ * @memberOf DB
+ * @param {string} topicName - name of the topic to retrieve messages from.
+ * @param {function} callback to call for each retrieved message.
+ * @param {Object} query - parameters of the message range to retrieve.
+ * @param {number=} query.since - the least message ID to retrieve (inclusive).
+ * @param {number=} query.before - the greatest message ID to retrieve (exclusive).
+ * @param {number=} query.limit - the maximum number of messages to retrieve.
+ * @return {Promise} promise resolved/rejected on operation completion.
+ */
+ readMessages: function(topicName, query, callback, context) {
+ if (!this.isReady()) {
+ return disabled ?
+ Promise.resolve([]) :
+ Promise.reject(new Error("not initialized"));
+ }
+ return new Promise((resolve, reject) => {
+ query = query || {};
+ const since = query.since > 0 ? query.since : 0;
+ const before = query.before > 0 ? query.before : Number.MAX_SAFE_INTEGER;
+ const limit = query.limit | 0;
+
+ const result = [];
+ const range = IDBKeyRange.bound([topicName, since], [topicName, before], false, true);
+ const trx = db.transaction(['message']);
+ trx.onerror = (event) => {
+ logger("PCache", "readMessages", event.target.error);
+ reject(event.target.error);
+ };
+ // Iterate in descending order.
+ trx.objectStore('message').openCursor(range, 'prev').onsuccess = (event) => {
+ const cursor = event.target.result;
+ if (cursor) {
+ if (callback) {
+ callback.call(context, cursor.value);
+ }
+ result.push(cursor.value);
+ if (limit <= 0 || result.length < limit) {
+ cursor.continue();
+ } else {
+ resolve(result);
+ }
} else {
resolve(result);
}
- } else {
- resolve(result);
- }
- };
- });
- }
-
- static setDatabaseProvider(idbProvider) {
- IDBProvider = idbProvider;
- }
-
-}
-
-exports.default = DB;
-
-function _mapObjects2(source, callback, context) {
- if (!this.db) {
- return disabled ? Promise.resolve([]) : Promise.reject(new Error("not initialized"));
- }
-
- return new Promise((resolve, reject) => {
- const trx = this.db.transaction([source]);
-
- trx.onerror = event => {
- _classPrivateFieldGet(this, _logger).call(this, 'PCache', 'mapObjects', source, event.target.error);
-
- reject(event.target.error);
- };
-
- trx.objectStore(source).getAll().onsuccess = event => {
- if (callback) {
- event.target.result.forEach(topic => {
- callback.call(context, topic);
- });
- }
-
- resolve(event.target.result);
- };
- });
-}
-
-function _deserializeTopic(topic, src) {
- _classStaticPrivateFieldSpecGet(DB, DB, _topic_fields).forEach(f => {
- if (src.hasOwnProperty(f)) {
- topic[f] = src[f];
+ };
+ });
}
- });
-
- if (Array.isArray(src.tags)) {
- topic._tags = src.tags;
- }
-
- if (src.acs) {
- topic.setAccessMode(src.acs);
- }
-
- topic.seq |= 0;
- topic.read |= 0;
- topic.unread = Math.max(0, topic.seq - topic.read);
-}
-
-function _serializeTopic(dst, src) {
- const res = dst || {
- name: src.name
};
-
- _classStaticPrivateFieldSpecGet(DB, DB, _topic_fields).forEach(f => {
- if (src.hasOwnProperty(f)) {
- res[f] = src[f];
- }
- });
-
- if (Array.isArray(src._tags)) {
- res.tags = src._tags;
- }
-
- if (src.acs) {
- res.acs = src.getAccessMode().jsonHelper();
- }
-
- return res;
}
-function _serializeSubscription(dst, topicName, uid, sub) {
- const fields = ['updated', 'mode', 'read', 'recv', 'clear', 'lastSeen', 'userAgent'];
- const res = dst || {
- topic: topicName,
- uid: uid
- };
- fields.forEach(f => {
- if (sub.hasOwnProperty(f)) {
- res[f] = sub[f];
- }
- });
- return res;
-}
-
-function _serializeMessage(dst, msg) {
- const fields = ['topic', 'seq', 'ts', '_status', 'from', 'head', 'content'];
- const res = dst || {};
- fields.forEach(f => {
- if (msg.hasOwnProperty(f)) {
- res[f] = msg[f];
- }
- });
- return res;
-}
-
-var _topic_fields = {
- writable: true,
- value: ['created', 'updated', 'deleted', 'read', 'recv', 'seq', 'clear', 'defacs', 'creds', 'public', 'trusted', 'private', 'touched', '_deleted']
+/**
+ * To use DB in a non browser context, supply indexedDB provider.
+ * @static
+ * @memberof DB
+ * @param idbProvider indexedDB provider, e.g. for node require('fake-indexeddb').
+ */
+DB.setDatabaseProvider = function(idbProvider) {
+ IDBProvider = idbProvider;
};
-},{}],6:[function(require,module,exports){
+if (typeof module != 'undefined') {
+ module.exports = DB;
+}
+
+},{}],5:[function(require,module,exports){
/**
- * @copyright 2015-2022 Tinode LLC.
+ * @copyright 2015-2021 Tinode
* @summary Minimally rich text representation and formatting for Tinode.
* @license Apache 2.0
+ * @version 0.18
*
* @file Basic parser and formatter for very simple text markup. Mostly targeted at
* mobile use cases similar to Telegram, WhatsApp, and FB Messenger.
@@ -1694,64 +1905,94 @@ var _topic_fields = {
* ]
* }
*/
+
'use strict';
+// NOTE TO DEVELOPERS:
+// Localizable strings should be double quoted "строка на другом языке",
+// non-localizable strings should be single quoted 'non-localized'.
+
const MAX_FORM_ELEMENTS = 8;
const MAX_PREVIEW_ATTACHMENTS = 3;
const MAX_PREVIEW_DATA_SIZE = 64;
const JSON_MIME_TYPE = 'application/json';
const DRAFTY_MIME_TYPE = 'text/x-drafty';
const ALLOWED_ENT_FIELDS = ['act', 'height', 'mime', 'name', 'ref', 'size', 'url', 'val', 'width'];
-const INLINE_STYLES = [{
- name: 'ST',
- start: /(?:^|[\W_])(\*)[^\s*]/,
- end: /[^\s*](\*)(?=$|[\W_])/
-}, {
- name: 'EM',
- start: /(?:^|\W)(_)[^\s_]/,
- end: /[^\s_](_)(?=$|\W)/
-}, {
- name: 'DL',
- start: /(?:^|[\W_])(~)[^\s~]/,
- end: /[^\s~](~)(?=$|[\W_])/
-}, {
- name: 'CO',
- start: /(?:^|\W)(`)[^`]/,
- end: /[^`](`)(?=$|\W)/
-}];
-const FMT_WEIGHT = ['QQ'];
-const ENTITY_TYPES = [{
- name: 'LN',
- dataName: 'url',
- pack: function (val) {
- if (!/^[a-z]+:\/\//i.test(val)) {
- val = 'http://' + val;
- }
- return {
- url: val
- };
+// Regular expressions for parsing inline formats. Javascript does not support lookbehind,
+// so it's a bit messy.
+const INLINE_STYLES = [
+ // Strong = bold, *bold text*
+ {
+ name: 'ST',
+ start: /(?:^|[\W_])(\*)[^\s*]/,
+ end: /[^\s*](\*)(?=$|[\W_])/
},
- re: /(?:(?:https?|ftp):\/\/|www\.|ftp\.)[-A-Z0-9+&@#\/%=~_|$?!:,.]*[A-Z0-9+&@#\/%=~_|$]/ig
-}, {
- name: 'MN',
- dataName: 'val',
- pack: function (val) {
- return {
- val: val.slice(1)
- };
+ // Emphesized = italic, _italic text_
+ {
+ name: 'EM',
+ start: /(?:^|\W)(_)[^\s_]/,
+ end: /[^\s_](_)(?=$|\W)/
},
- re: /\B@([\p{L}\p{N}][._\p{L}\p{N}]*[\p{L}\p{N}])/ug
-}, {
- name: 'HT',
- dataName: 'val',
- pack: function (val) {
- return {
- val: val.slice(1)
- };
+ // Deleted, ~strike this though~
+ {
+ name: 'DL',
+ start: /(?:^|[\W_])(~)[^\s~]/,
+ end: /[^\s~](~)(?=$|[\W_])/
},
- re: /\B#([\p{L}\p{N}][._\p{L}\p{N}]*[\p{L}\p{N}])/ug
-}];
+ // Code block `this is monospace`
+ {
+ name: 'CO',
+ start: /(?:^|\W)(`)[^`]/,
+ end: /[^`](`)(?=$|\W)/
+ }
+];
+
+// Relative weights of formatting spans. Greater index in array means greater weight.
+const FMT_WEIGHT = ['QQ'];
+
+// RegExps for entity extraction (RF = reference)
+const ENTITY_TYPES = [
+ // URLs
+ {
+ name: 'LN',
+ dataName: 'url',
+ pack: function(val) {
+ // Check if the protocol is specified, if not use http
+ if (!/^[a-z]+:\/\//i.test(val)) {
+ val = 'http://' + val;
+ }
+ return {
+ url: val
+ };
+ },
+ re: /(?:(?:https?|ftp):\/\/|www\.|ftp\.)[-A-Z0-9+&@#\/%=~_|$?!:,.]*[A-Z0-9+&@#\/%=~_|$]/ig
+ },
+ // Mentions @user (must be 2 or more characters)
+ {
+ name: 'MN',
+ dataName: 'val',
+ pack: function(val) {
+ return {
+ val: val.slice(1)
+ };
+ },
+ re: /\B@([\p{L}\p{N}][._\p{L}\p{N}]*[\p{L}\p{N}])/ug
+ },
+ // Hashtags #hashtag, like metion 2 or more characters.
+ {
+ name: 'HT',
+ dataName: 'val',
+ pack: function(val) {
+ return {
+ val: val.slice(1)
+ };
+ },
+ re: /\B#([\p{L}\p{N}][._\p{L}\p{N}]*[\p{L}\p{N}])/ug
+ }
+];
+
+// HTML tag name suggestions
const HTML_TAGS = {
BN: {
name: 'button',
@@ -1807,7 +2048,7 @@ const HTML_TAGS = {
},
RW: {
name: 'div',
- isVoid: false
+ isVoid: false,
},
QQ: {
name: 'div',
@@ -1816,9 +2057,10 @@ const HTML_TAGS = {
ST: {
name: 'b',
isVoid: false
- }
+ },
};
+// Convert base64-encoded string into Blob.
function base64toObjectUrl(b64, contentType, logger) {
if (!b64) {
return null;
@@ -1829,7 +2071,6 @@ function base64toObjectUrl(b64, contentType, logger) {
const length = bin.length;
const buf = new ArrayBuffer(length);
const arr = new Uint8Array(buf);
-
for (let i = 0; i < length; i++) {
arr[i] = bin.charCodeAt(i);
}
@@ -1850,185 +2091,215 @@ function base64toDataUrl(b64, contentType) {
if (!b64) {
return null;
}
-
contentType = contentType || 'image/jpeg';
return 'data:' + contentType + ';base64,' + b64;
}
+// Helpers for converting Drafty to HTML.
const DECORATORS = {
+ // Visial styles
ST: {
- open: function () {
+ open: function() {
return '';
},
- close: function () {
+ close: function() {
return '';
}
},
EM: {
- open: function () {
+ open: function() {
return '';
},
- close: function () {
- return '';
+ close: function() {
+ return '