message.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /**
  2. * This file is part of Threema Web.
  3. *
  4. * Threema Web is free software: you can redistribute it and/or modify it
  5. * under the terms of the GNU Affero General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or (at
  7. * your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. import {isContactReceiver} from '../typeguards';
  18. import {ReceiverService} from './receiver';
  19. import {TimeoutService} from './timeout';
  20. export class MessageAccess {
  21. public quote = false;
  22. public ack = false;
  23. public dec = false;
  24. public delete = false;
  25. public download = false;
  26. public copy = false;
  27. }
  28. export class MessageService {
  29. // Maximum thumbnail size (width and height)
  30. // Note: Keep this in sync with `.thumbnail`s `max-width` and `max-height`
  31. // properties!
  32. private static readonly MAX_THUMBNAIL_SIZE = 350;
  33. // Own services
  34. private receiverService: ReceiverService;
  35. private timeoutService: TimeoutService;
  36. // Other
  37. private timeoutDelaySeconds = 30;
  38. public static $inject = ['ReceiverService', 'TimeoutService'];
  39. constructor(receiverService: ReceiverService, timeoutService: TimeoutService) {
  40. this.receiverService = receiverService;
  41. this.timeoutService = timeoutService;
  42. }
  43. public getAccess(message: threema.Message, receiver: threema.Receiver): MessageAccess {
  44. const access = new MessageAccess();
  45. if (message !== undefined) {
  46. access.quote = (message.type === 'text')
  47. || (message.type === 'location')
  48. || (message.caption !== undefined && message.caption !== null && message.caption.length > 0);
  49. access.copy = access.quote;
  50. if (receiver !== undefined && message.temporaryId === undefined) {
  51. if (message.isOutbox === false
  52. && isContactReceiver(receiver)
  53. && message.type !== 'voipStatus') {
  54. access.ack = message.state !== 'user-ack';
  55. access.dec = message.state !== 'user-dec';
  56. }
  57. switch (message.type) {
  58. case 'image':
  59. case 'video':
  60. case 'audio':
  61. case 'file':
  62. access.download = true;
  63. break;
  64. default:
  65. access.download = false;
  66. }
  67. access.delete = true;
  68. }
  69. }
  70. return access;
  71. }
  72. /**
  73. * Return the quotable text in this message, or null.
  74. */
  75. public getQuoteText(message: threema.Message): string | null {
  76. let quoteText = null;
  77. if (message !== null && message !== undefined) {
  78. switch (message.type) {
  79. case 'text':
  80. quoteText = message.body;
  81. break;
  82. case 'location':
  83. quoteText = message.location.description;
  84. break;
  85. case 'file':
  86. case 'image':
  87. quoteText = message.caption;
  88. break;
  89. default:
  90. // Ignore (handled below)
  91. }
  92. }
  93. return quoteText;
  94. }
  95. public showStatusIcon(message: threema.Message, receiver: threema.Receiver): boolean {
  96. if (message !== null && receiver !== null) {
  97. const messageState = message.state;
  98. // group message/distribution list message icons only on pending or failing states
  99. switch (receiver.type) {
  100. case 'group':
  101. if (message.isOutbox && (message.temporaryId === undefined || message.temporaryId === null)) {
  102. return messageState === 'send-failed'
  103. || messageState === 'sending'
  104. || (messageState === 'pending' && message.type !== 'ballot');
  105. }
  106. return false;
  107. case 'distributionList':
  108. return false;
  109. case 'contact':
  110. if (!message.isOutbox) {
  111. return messageState === 'user-ack'
  112. || messageState === 'user-dec';
  113. } else if (this.receiverService.isBusinessContact(receiver)) {
  114. // move this into a service
  115. return messageState === 'send-failed'
  116. || messageState === 'sending'
  117. || messageState === 'pending';
  118. }
  119. return true;
  120. default:
  121. return false;
  122. }
  123. }
  124. return false;
  125. }
  126. /**
  127. * Get a preview Thumbnail object from a data URI.
  128. */
  129. public getPreviewThumbnail(uri: string, preview: ArrayBuffer): threema.Thumbnail {
  130. const image = new Image();
  131. image.src = uri;
  132. // Downscale image (if necessary)
  133. if (image.width > MessageService.MAX_THUMBNAIL_SIZE || image.height > MessageService.MAX_THUMBNAIL_SIZE) {
  134. const scale = Math.min(
  135. MessageService.MAX_THUMBNAIL_SIZE / image.width,
  136. MessageService.MAX_THUMBNAIL_SIZE / image.height);
  137. image.width = Math.round(scale * image.width);
  138. image.height = Math.round(scale * image.height);
  139. }
  140. return {
  141. previewDataUrl: uri,
  142. preview: preview,
  143. width: image.width,
  144. height: image.height,
  145. };
  146. }
  147. /**
  148. * Create a message object with a temporary id.
  149. */
  150. public createTemporary(
  151. temporaryId: string,
  152. receiver: threema.Receiver,
  153. type: threema.MessageType,
  154. data: threema.MessageData,
  155. previewDataUrl?: string,
  156. ): threema.Message {
  157. // Populate base message
  158. const timestampS = Math.floor(Date.now() / 1000);
  159. const message: threema.Message = {
  160. type: type,
  161. id: undefined, // Note: Hack, violates the interface
  162. date: timestampS,
  163. sortKey: Number.MAX_SAFE_INTEGER, // Note: Ugly hack
  164. partnerId: receiver.id,
  165. isOutbox: true,
  166. isStatus: false,
  167. state: 'pending',
  168. quote: data.quote,
  169. temporaryId: temporaryId,
  170. } as threema.Message;
  171. // Populate message depending on type
  172. switch (type) {
  173. case 'text':
  174. const textData = data as threema.TextMessageData;
  175. message.body = textData.text;
  176. break;
  177. case 'image': {
  178. const fileData = data as threema.FileMessageData;
  179. message.caption = fileData.caption;
  180. if (previewDataUrl !== undefined) {
  181. message.thumbnail = this.getPreviewThumbnail(previewDataUrl, fileData.data);
  182. }
  183. break;
  184. }
  185. case 'video': {
  186. const fileData = data as threema.FileMessageData;
  187. message.video = {
  188. duration: null, // Note: Hack, violates the interface
  189. size: fileData.size,
  190. };
  191. break;
  192. }
  193. case 'file': {
  194. const fileData = data as threema.FileMessageData;
  195. message.caption = fileData.caption;
  196. message.file = {
  197. name: fileData.name,
  198. size: fileData.size,
  199. type: fileData.fileType,
  200. inApp: false,
  201. };
  202. if (previewDataUrl !== undefined) {
  203. message.thumbnail = this.getPreviewThumbnail(previewDataUrl, fileData.data);
  204. }
  205. break;
  206. }
  207. default:
  208. throw new Error(`Cannot create temporary message for type: ${type}`);
  209. }
  210. return message;
  211. }
  212. }