threema.d.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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. // Types used for the Threema Webclient.
  18. declare const angular: ng.IAngularStatic;
  19. declare namespace threema {
  20. interface Avatar {
  21. // Low resolution avatar path
  22. low?: string;
  23. // High resolution avatar path
  24. high?: string;
  25. }
  26. interface AvatarRegistry {
  27. contact: Avatar;
  28. group: Avatar;
  29. distributionList: Avatar;
  30. }
  31. /**
  32. * Messages that are sent through the secure data channel as encrypted msgpack bytes.
  33. */
  34. interface WireMessage {
  35. type: string;
  36. subType: string;
  37. args?: any;
  38. data?: any;
  39. }
  40. type WireMessageCallback = (message: WireMessage, result: any) => boolean;
  41. type MessageType = 'text' | 'image' | 'video' | 'audio' | 'location' | 'status' | 'ballot' | 'file';
  42. type MessageState = 'delivered' | 'read' | 'send-failed' | 'sent' | 'user-ack' | 'user-dec' | 'pending' | 'sending';
  43. type InitializationStep = 'client info' | 'conversations' | 'receivers';
  44. interface InitializationStepRoutine {
  45. requiredSteps: InitializationStep[];
  46. callback: any;
  47. }
  48. interface Thumbnail {
  49. img?: string;
  50. preview: string;
  51. width: number;
  52. height: number;
  53. }
  54. /**
  55. * A Threema chat message.
  56. */
  57. interface Message {
  58. type: MessageType;
  59. id: number;
  60. body: string;
  61. thumbnail?: Thumbnail;
  62. date: string;
  63. partnerId: string;
  64. isOutbox: boolean;
  65. isStatus: boolean;
  66. caption?: string;
  67. statusType?: 'text' | 'firstUnreadMessage';
  68. unread?: boolean;
  69. state?: MessageState;
  70. quote?: Quote;
  71. file?: FileInfo;
  72. video?: VideoInfo;
  73. audio?: AudioInfo;
  74. location?: LocationInfo;
  75. // only for temporary Messages
  76. temporaryId?: string;
  77. errorMessage?: string;
  78. }
  79. interface FileInfo {
  80. description: string;
  81. name: string;
  82. size: number;
  83. type: string;
  84. inApp: boolean;
  85. }
  86. interface VideoInfo {
  87. duration: number;
  88. size: number;
  89. }
  90. interface AudioInfo {
  91. duration: number;
  92. }
  93. interface LocationInfo {
  94. lat: number;
  95. lon: number;
  96. accuracy: number;
  97. address: string;
  98. poi: string;
  99. }
  100. /**
  101. * All possible receiver types.
  102. */
  103. type ReceiverType = 'me' | 'contact' | 'group' | 'distributionList';
  104. /**
  105. * Access Object for receivers
  106. */
  107. interface ReceiverAccess {
  108. canDelete?: boolean;
  109. }
  110. interface ContactReceiverAccess extends ReceiverAccess {
  111. canChangeAvatar: boolean;
  112. canChangeFirstName: boolean;
  113. canChangeLastName: boolean;
  114. }
  115. interface GroupReceiverAccess extends ReceiverAccess {
  116. canChangeAvatar: boolean;
  117. canChangeName: boolean;
  118. canChangeMembers?: boolean;
  119. canLeave?: boolean;
  120. canSync?: boolean;
  121. }
  122. interface DistributionListReceiverAccess extends ReceiverAccess {
  123. canChangeMembers?: boolean;
  124. }
  125. /**
  126. * A generic receiver.
  127. *
  128. * Note that the id is not unique for all receivers, only for all receivers
  129. * of a certain type. The primary key for a receiver is the tuple (type, id).
  130. */
  131. interface Receiver {
  132. type?: ReceiverType;
  133. id: string;
  134. displayName: string;
  135. color: string;
  136. avatar?: Avatar; // May be set if already fetched
  137. access: ReceiverAccess;
  138. locked?: boolean;
  139. visible?: boolean;
  140. }
  141. /**
  142. * A contact.
  143. */
  144. interface ContactReceiver extends Receiver {
  145. type?: 'contact' | 'me';
  146. publicNickname?: string;
  147. firstName?: string;
  148. lastName?: string;
  149. verificationLevel?: number;
  150. state: string;
  151. featureLevel: number | null;
  152. publicKey: ArrayBuffer;
  153. systemContact?: SystemContact;
  154. access: ContactReceiverAccess;
  155. }
  156. /**
  157. * Own contact.
  158. */
  159. interface MeReceiver extends ContactReceiver {
  160. type?: 'me';
  161. }
  162. /**
  163. * A group.
  164. */
  165. interface GroupReceiver extends Receiver {
  166. type?: 'group';
  167. disabled: boolean;
  168. members: string[];
  169. administrator: string;
  170. access: GroupReceiverAccess;
  171. }
  172. /**
  173. * A distribution list.
  174. */
  175. interface DistributionListReceiver extends Receiver {
  176. type?: 'distributionList';
  177. members: string[];
  178. access: DistributionListReceiverAccess;
  179. }
  180. interface SystemContact {
  181. emails?: SystemContactEmail[];
  182. phoneNumbers?: SystemContactPhone[];
  183. }
  184. interface SystemContactEmail {
  185. label: string;
  186. address: string;
  187. }
  188. interface SystemContactPhone {
  189. label: string;
  190. number: string;
  191. }
  192. /**
  193. * A conversation.
  194. */
  195. interface Conversation {
  196. type: ReceiverType;
  197. id: string;
  198. position: number;
  199. messageCount: number;
  200. unreadCount: number;
  201. latestMessage: Message;
  202. receiver?: Receiver;
  203. avatar?: ArrayBuffer;
  204. }
  205. /**
  206. * Connection state in the welcome dialog.
  207. *
  208. * States:
  209. *
  210. * - new: Initial state
  211. * - connecting: Connecting to signaling server
  212. * - push: When trying to reconnect, waiting for push notification to arrive
  213. * - manual_start: When trying to reconnect, waiting for manual session start
  214. * - waiting: Waiting for new-responder message from signaling server
  215. * - peer_handshake: Doing SaltyRTC handshake with the peer
  216. * - loading: Loading initial data
  217. * - done: Initial loading is finished
  218. * - closed: Connection is closed
  219. *
  220. */
  221. type ConnectionBuildupState = 'new' | 'connecting' | 'push' | 'manual_start' | 'waiting'
  222. | 'peer_handshake' | 'loading' | 'done' | 'closed';
  223. /**
  224. * Connection state of the WebRTC peer connection.
  225. */
  226. type RTCConnectionState = 'new' | 'connecting' | 'connected' | 'disconnected';
  227. /**
  228. * Connection state of the WebRTC peer connection.
  229. */
  230. type GlobalConnectionState = 'ok' | 'warning' | 'error';
  231. /**
  232. * Type of message to be sent to a receiver.
  233. */
  234. type MessageContentType = 'text' | 'file';
  235. /**
  236. * Possible message types sent to the receiver.
  237. */
  238. type MessageDataType = 'text' | 'file';
  239. interface MessageData {
  240. // optional quote object
  241. quote?: Quote;
  242. }
  243. /**
  244. * Payload for a file message.
  245. */
  246. interface FileMessageData extends MessageData {
  247. // File name
  248. name: string;
  249. // File MIME type
  250. fileType: string;
  251. // Size in bytes
  252. size: number;
  253. // File bytes
  254. data: ArrayBuffer;
  255. // Caption string
  256. caption?: String;
  257. // Send as file message
  258. sendAsFile?: boolean;
  259. }
  260. /**
  261. * Payload for a text message.
  262. */
  263. interface TextMessageData extends MessageData {
  264. // Text to be sent
  265. text: string;
  266. }
  267. /**
  268. * The $stateParams format used for the welcome controller.
  269. */
  270. interface WelcomeStateParams extends ng.ui.IStateParamsService {
  271. initParams: null | {keyStore: saltyrtc.KeyStore, peerTrustedKey: Uint8Array};
  272. }
  273. interface CreateReceiverStateParams extends ng.ui.IStateParamsService {
  274. type: string;
  275. initParams: null | {identity: null};
  276. }
  277. /**
  278. * State service.
  279. */
  280. interface StateService {
  281. // WebRTC states
  282. signalingConnectionState: saltyrtc.SignalingState;
  283. rtcConnectionState: RTCConnectionState;
  284. // Global connection state
  285. state: 'ok' | 'warning' | 'error';
  286. stage: 'signaling' | 'rtc';
  287. wasConnected: boolean;
  288. // Connection buildup
  289. connectionBuildupState: ConnectionBuildupState;
  290. progress: number;
  291. slowConnect: boolean;
  292. // Update states
  293. updateSignalingConnectionState(state: saltyrtc.SignalingState): void;
  294. updateRtcConnectionState(state: RTCConnectionState): void;
  295. updateConnectionBuildupState(state: ConnectionBuildupState): void;
  296. reset(): void;
  297. }
  298. /**
  299. * Notification service.
  300. */
  301. interface NotificationService {
  302. getNotificationPermission(): boolean;
  303. getWantsNotifications(): boolean;
  304. setWantsNotifications(wantsNotifications: boolean): void;
  305. setWantsPreview(wantsPreview: boolean): void;
  306. getWantsPreview(): boolean;
  307. init(): void;
  308. isNotificationApiAvailable(): boolean;
  309. showNotification(id: string, title: string, body: string,
  310. avatar: string | null, clickCallback: any | null): boolean;
  311. clearCache(tag: string): void;
  312. hideNotification(tag: string): boolean;
  313. }
  314. interface MessageService {
  315. getAccess(message: Message, receiver: Receiver): MessageAccess;
  316. createTemporary(receiver: Receiver, type: string, messageData: MessageData): Message;
  317. showStatusIcon(message: Message, receiver: Receiver): boolean;
  318. getFileName(message: Message): string;
  319. }
  320. interface MessageAccess {
  321. quote: boolean;
  322. ack: boolean;
  323. dec: boolean;
  324. delete: boolean;
  325. download: boolean;
  326. copy: boolean;
  327. }
  328. interface Quote {
  329. identity: string;
  330. text: string;
  331. }
  332. interface Identity {
  333. identity: string;
  334. publicNickname: String;
  335. publicKey: ArrayBuffer;
  336. fingerprint: string;
  337. }
  338. interface TrustedKeyStoreData {
  339. ownPublicKey: Uint8Array;
  340. ownSecretKey: Uint8Array;
  341. peerPublicKey: Uint8Array;
  342. pushToken: string | null;
  343. }
  344. interface TrustedKeyStoreService {
  345. blocked: boolean;
  346. storeTrustedKey(ownPublicKey: Uint8Array, ownSecretKey: Uint8Array, peerPublicKey: Uint8Array,
  347. pushToken: string | null, password: string): void;
  348. hasTrustedKey(): boolean;
  349. retrieveTrustedKey(password: string): TrustedKeyStoreData;
  350. clearTrustedKey(): void;
  351. }
  352. interface PushService {
  353. init(pushToken: string): void;
  354. reset(): void;
  355. isAvailable(): boolean;
  356. sendPush(session: Uint8Array): Promise<boolean>;
  357. }
  358. interface BrowserInfo {
  359. chrome: boolean;
  360. firefox: boolean;
  361. msie: boolean;
  362. opera: boolean;
  363. safari: boolean;
  364. version: string;
  365. textInfo: string;
  366. }
  367. interface BrowserService {
  368. getBrowser(): BrowserInfo;
  369. isVisible(): boolean;
  370. }
  371. interface TitleService {
  372. updateUnreadCount(count: number): void;
  373. }
  374. interface FingerPrintService {
  375. generate(publicKey: ArrayBuffer): string;
  376. }
  377. interface ContactService {
  378. requiredDetails(contactReceiver: ContactReceiver): Promise<ContactReceiver>;
  379. }
  380. interface ControllerModelService {
  381. contact(receiver: ContactReceiver, mode: any): ControllerModel;
  382. group(receiver: GroupReceiver, mode: any): ControllerModel;
  383. distributionList(receiver: DistributionListReceiver, mode: any): ControllerModel;
  384. }
  385. interface QrCodeService {
  386. buildQrCodePayload(initiatorKey: Uint8Array, authToken: Uint8Array, serverKey: Uint8Array | null,
  387. host: string, port: number,
  388. persistent: boolean): string;
  389. }
  390. interface PromiseCallbacks {
  391. resolve: (arg: any) => void;
  392. reject: (arg: any) => void;
  393. }
  394. interface PromiseRequestResult<T> {
  395. success: boolean;
  396. message?: string;
  397. data?: T;
  398. }
  399. interface ControllerModel {
  400. subject: string;
  401. isLoading: boolean;
  402. save(): any;
  403. isValid(): boolean;
  404. canEdit(): boolean;
  405. getMode(): number;
  406. setOnRemoved(callback: any): void;
  407. }
  408. interface AvatarControllerModel {
  409. onChangeAvatar: (image: ArrayBuffer) => void;
  410. getAvatar(): ArrayBuffer | null;
  411. }
  412. interface Alert {
  413. source: string;
  414. type: string;
  415. message: string;
  416. }
  417. interface ReceiverListener {
  418. onRemoved(receiver: Receiver);
  419. }
  420. interface Config {
  421. SELF_HOSTED: boolean;
  422. SALTYRTC_PORT: number;
  423. SALTYRTC_SERVER_KEY: string | null;
  424. SALTYRTC_HOST: string | null;
  425. SALTYRTC_HOST_PREFIX: string | null;
  426. SALTYRTC_HOST_SUFFIX: string | null;
  427. ICE_SERVERS: RTCIceServer[];
  428. PUSH_URL: string;
  429. }
  430. interface BrowserMinVersions {
  431. FF: number;
  432. CHROME: number;
  433. OPERA: number;
  434. }
  435. interface MimeService {
  436. isImage(mimeType: string): boolean;
  437. isAudio(mimeType: string): boolean;
  438. isVideo(mimeType: string): boolean;
  439. getLabel(mimeType: string): string;
  440. getIconUrl(mimeType: string): string;
  441. }
  442. interface ReceiverService {
  443. setActive(activeReceiver: Receiver): void;
  444. getActive(): Receiver;
  445. isConversationActive(conversation: Conversation): boolean;
  446. compare(a: Conversation | Receiver, b: Conversation| Receiver): boolean;
  447. isContact(receiver: Receiver): boolean;
  448. isGroup(receiver: Receiver): boolean;
  449. isDistributionList(receiver: Receiver): boolean;
  450. isBusinessContact(receiver: Receiver): boolean;
  451. }
  452. interface SettingsService {
  453. storeUntrustedKeyValuePair(key: string, value: string): void;
  454. retrieveUntrustedKeyValuePair(key: string): string;
  455. }
  456. interface WebClientDefault {
  457. getAvatar(type: string, highResolution: boolean): string;
  458. }
  459. interface WebClientService {
  460. salty: saltyrtc.SaltyRTC;
  461. messages: Container.Messages;
  462. conversations: Container.Conversations;
  463. receivers: Container.Receivers;
  464. alerts: Alert[];
  465. defaults: WebClientDefault;
  466. receiverListener: ReceiverListener[];
  467. me: MeReceiver;
  468. contacts: Map<string, ContactReceiver>;
  469. groups: Map<string, GroupReceiver>;
  470. distributionLists: Map<string, DistributionListReceiver>;
  471. typing: Container.Typing;
  472. buildQrCodePayload(persistent: boolean): string;
  473. init(keyStore?: saltyrtc.KeyStore, peerTrustedKey?: Uint8Array, resetField?: boolean): void;
  474. start(): ng.IPromise<any>;
  475. stop(requestedByUs: boolean, deleteStoredData?: boolean, resetPush?: boolean, redirect?: boolean): void;
  476. registerInitializationStep(name: string): void;
  477. setReceiverListener(listener: ReceiverListener): void;
  478. requestClientInfo(): void;
  479. requestReceivers(): void;
  480. requestConversations(): void;
  481. requestMessages(receiver: Receiver, reloadExisting?: boolean): number;
  482. requestAvatar(receiver: Receiver, highResolution: boolean): Promise<any>;
  483. requestThumbnail(receiver: Receiver, message: Message): Promise<any>;
  484. requestBlob(msgId: number, receiver: Receiver): Promise<ArrayBuffer>;
  485. requestRead(receiver, newestMessageId: number): void;
  486. requestContactDetail(contactReceiver: ContactReceiver): Promise<any>;
  487. sendMessage(receiver, type: MessageContentType, message: MessageData): Promise<Promise<any>>;
  488. ackMessage(receiver, message: Message, acknowledged?: boolean): void;
  489. deleteMessage(receiver, message: Message): void;
  490. sendMeIsTyping(receiver, isTyping: boolean): void;
  491. sendKeyPersisted(): void;
  492. addContact(threemaId: String): Promise<ContactReceiver>;
  493. modifyContact(threemaId: String, firstName: String, lastName: String, avatar?: ArrayBuffer):
  494. Promise<ContactReceiver>;
  495. createGroup(members: String[], name: String, avatar?: ArrayBuffer): Promise<GroupReceiver>;
  496. modifyGroup(id: string, members: String[], name: String, avatar?: ArrayBuffer): Promise<GroupReceiver>;
  497. leaveGroup(group: GroupReceiver): Promise<any>;
  498. deleteGroup(group: GroupReceiver): Promise<any>;
  499. syncGroup(group: GroupReceiver): Promise<any>;
  500. createDistributionList(members: String[], name: String): Promise<DistributionListReceiver>;
  501. modifyDistributionList(id: string, members: String[], name: String): Promise<DistributionListReceiver>;
  502. deleteDistributionList(distributionList: DistributionListReceiver): Promise<any>;
  503. isTyping(receiver: ContactReceiver): boolean;
  504. getMyIdentity(): Identity;
  505. getQuote(receiver: Receiver): Quote;
  506. setQuote(receiver: Receiver, message?: Message): void;
  507. setDraft(receiver: Receiver, message: string): void;
  508. getDraft(receiver: Receiver): string;
  509. setPassword(password: string): void;
  510. clearCache(): void;
  511. getMaxTextLength(): number;
  512. }
  513. interface ControllerService {
  514. setControllerName(name: string): void;
  515. getControllerName(): string;
  516. }
  517. interface StringService {
  518. /**
  519. * create chunks of a string by counting the used bytes
  520. * @param str
  521. * @param byteLength
  522. * @param offset
  523. */
  524. byteChunk(str: string, byteLength: number, offset: number): string[];
  525. }
  526. namespace Container {
  527. interface ReceiverData {
  528. me: MeReceiver;
  529. contacts: ContactReceiver[];
  530. groups: GroupReceiver[];
  531. distributionLists: DistributionListReceiver[];
  532. }
  533. interface Converter {
  534. unicodeToEmoji(message);
  535. addReceiverToConversation(receivers: Receivers);
  536. }
  537. interface Filters {
  538. hasData(receivers);
  539. hasContact(contacts);
  540. isValidMessage(contacts);
  541. }
  542. interface Receivers {
  543. me: MeReceiver;
  544. contacts: Map<string, ContactReceiver>;
  545. groups: Map<string, GroupReceiver>;
  546. distributionLists: Map<string, DistributionListReceiver>;
  547. get(receiverType: ReceiverType): Receiver | Map<string, Receiver>;
  548. getData(receiver: Receiver): Receiver | null;
  549. set(data: ReceiverData): void;
  550. setMe(data: MeReceiver): void;
  551. setContacts(data: ContactReceiver[]): void;
  552. setGroups(data: GroupReceiver[]): void;
  553. setDistributionLists(data: DistributionListReceiver[]): void;
  554. extend(receiverType: ReceiverType, data: Receiver): Receiver;
  555. extendDistributionList(data: DistributionListReceiver): DistributionListReceiver;
  556. extendGroup(data: GroupReceiver): GroupReceiver;
  557. extendMe(data: MeReceiver): MeReceiver;
  558. extendContact(data: ContactReceiver): ContactReceiver;
  559. }
  560. interface Conversations {
  561. get(): Conversation[];
  562. set(data: Conversation[]): void;
  563. add(conversation: Conversation): void;
  564. updateOrAdd(conversation: Conversation): void;
  565. remove(conversation: Conversation): void;
  566. setFilter(filter: (data: Conversation[]) => Conversation[]): void;
  567. setConverter(converter: (data: Conversation) => Conversation): void;
  568. }
  569. interface Messages {
  570. converter: (data: Message) => Message;
  571. getList(receiver: Receiver): Message[];
  572. clear($scope: ng.IScope): void;
  573. clearReceiverMessages(receiver: Receiver): Number;
  574. contains(receiver: Receiver): boolean;
  575. hasMore(receiver: Receiver): boolean;
  576. setMore(receiver: Receiver, more: boolean): void;
  577. getReferenceMsgId(receiver: Receiver): number;
  578. isRequested(receiver: Receiver): boolean;
  579. setRequested(receiver: Receiver): void;
  580. clearRequested(receiver): void;
  581. addNewer(receiver: Receiver, messages: Message[]): void;
  582. addOlder(receiver: Receiver, messages: Message[]): void;
  583. update(receiver: Receiver, message: Message): boolean;
  584. setThumbnail(receiver: Receiver, messageId: number, thumbnailImage: string): boolean;
  585. remove(receiver: Receiver, messageId: number): boolean;
  586. removeTemporary(receiver: Receiver, temporaryMessageId: string): boolean;
  587. bindTemporaryToMessageId(receiver: Receiver, temporaryId: string, messageId: number): boolean;
  588. notify(receiver: Receiver, $scope: ng.IScope): void;
  589. register(receiver: Receiver, $scope: ng.IScope, callback: any): Message[];
  590. updateFirstUnreadMessage(receiver: Receiver);
  591. }
  592. interface Typing {
  593. setTyping(receiver: ContactReceiver): void;
  594. unsetTyping(receiver: ContactReceiver): void;
  595. isTyping(receiver: ContactReceiver): boolean;
  596. }
  597. interface Drafts {
  598. setQuote(receiver: Receiver, quote: Quote): void;
  599. removeQuote(receiver: Receiver): void;
  600. getQuote(receiver: Receiver): Quote;
  601. setText(receiver: Receiver, draftMessage: string): void;
  602. removeText(receiver: Receiver): void;
  603. getText(receiver: Receiver): string;
  604. }
  605. interface Factory {
  606. Converter: Container.Converter;
  607. Filters: Container.Filters;
  608. createReceivers: () => Receivers;
  609. createConversations: () => Conversations;
  610. createMessages: () => Messages;
  611. createTyping: () => Typing;
  612. createDrafts: () => Drafts;
  613. }
  614. }
  615. }