Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@
* reactions: array<string, integer>|\stdClass,
* // When the user reacted this is the list of emojis the user reacted with
* reactionsSelf?: list<string>,
* // A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability)
* // A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/
* referenceId: string,
* // Timestamp in seconds and UTC time zone
* timestamp: int,
Expand Down
2 changes: 1 addition & 1 deletion openapi-backend-sipbridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@
},
"referenceId": {
"type": "string",
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability)"
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/"
},
"timestamp": {
"type": "integer",
Expand Down
2 changes: 1 addition & 1 deletion openapi-federation.json
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@
},
"referenceId": {
"type": "string",
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability)"
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/"
},
"timestamp": {
"type": "integer",
Expand Down
2 changes: 1 addition & 1 deletion openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,7 @@
},
"referenceId": {
"type": "string",
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability)"
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/"
},
"timestamp": {
"type": "integer",
Expand Down
2 changes: 1 addition & 1 deletion openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@
},
"referenceId": {
"type": "string",
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability)"
"description": "A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/"
},
"timestamp": {
"type": "integer",
Expand Down
10 changes: 7 additions & 3 deletions src/stores/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ export const useUploadStore = defineStore('upload', () => {
function initialiseUpload({ uploadId, token, threadId, files, rename = false, isVoiceMessage }: { uploadId: string, token: string, threadId?: number, files: File[], rename?: boolean, isVoiceMessage?: boolean }) {
// Set last upload id
currentUploadId.value = uploadId
const stagedFilesMaxIndex = Math.max(
0,
...getUploadsArray(uploadId).map(([index, _uploadedFile]) => Number(index.split('_').pop())),
)
for (let i = 0; i < files.length; i++) {
let file = files[i]

Expand All @@ -342,9 +346,9 @@ export const useUploadStore = defineStore('upload', () => {
? URL.createObjectURL(file)
: undefined

// Create a unique index for each file
const date = new Date()
const index = 'temp_' + date.getTime() + Math.random()
// Create a unique index for each file (append _1, _2, ... at the end to track file order)
const index = `temp_${uploadId}_${stagedFilesMaxIndex + 1 + i}`
Comment thread
Antreesy marked this conversation as resolved.

// Create temporary message for the file and add it to the message list
const temporaryMessage = createTemporaryMessage({
message: '{file}',
Expand Down
2 changes: 1 addition & 1 deletion src/types/openapi/openapi-backend-sipbridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export type components = {
};
/** @description When the user reacted this is the list of emojis the user reacted with */
reactionsSelf?: string[];
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability) */
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/ */
referenceId: string;
/**
* Format: int64
Expand Down
2 changes: 1 addition & 1 deletion src/types/openapi/openapi-federation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ export type components = {
};
/** @description When the user reacted this is the list of emojis the user reacted with */
reactionsSelf?: string[];
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability) */
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/ */
referenceId: string;
/**
* Format: int64
Expand Down
2 changes: 1 addition & 1 deletion src/types/openapi/openapi-full.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3040,7 +3040,7 @@ export type components = {
};
/** @description When the user reacted this is the list of emojis the user reacted with */
reactionsSelf?: string[];
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability) */
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/ */
referenceId: string;
/**
* Format: int64
Expand Down
2 changes: 1 addition & 1 deletion src/types/openapi/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2450,7 +2450,7 @@ export type components = {
};
/** @description When the user reacted this is the list of emojis the user reacted with */
reactionsSelf?: string[];
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability) */
/** @description A reference string that was given while posting the message to be able to identify a sent message again (only available with `chat-reference-id` capability). For grouped file uploads support, expected format is `{sha256(uploadId)}-{order}`, matching /^[a-f0-9]{60}-[0-9]{3}$/ */
referenceId: string;
/**
* Format: int64
Expand Down
27 changes: 15 additions & 12 deletions src/utils/__tests__/prepareTemporaryMessage.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { prepareTemporaryMessage } from '../prepareTemporaryMessage.ts'

describe('prepareTemporaryMessage', () => {
const TOKEN = 'XXTOKENXX'
const UPLOAD_ID = String(new Date('2020-01-01T20:00:00').getTime())

beforeEach(() => {
vi.useFakeTimers().setSystemTime(new Date('2020-01-01T20:00:00'))
Expand Down Expand Up @@ -63,27 +64,28 @@ describe('prepareTemporaryMessage', () => {
const textFilePayload = {
...defaultPayload,
message: '{file}',
uploadId: 'upload-id-1',
index: 'upload-index-1',
uploadId: UPLOAD_ID,
index: `temp_${UPLOAD_ID}_1`,
file: textFile,
localUrl: 'local-url://original-name.txt',
}
const textFileResult = {
...defaultResult,
id: expect.stringMatching(/^temp-1577908800000-upload-id-1-0\.[0-9]*$/),
id: `temp-${UPLOAD_ID}-001`,
message: '{file}',
messageParameters: {
file: {
type: 'file',
file: textFile,
mimetype: 'text/plain',
id: expect.stringMatching(/^temp-1577908800000-upload-id-1-0\.[0-9]*$/),
id: `temp-${UPLOAD_ID}-001`,
name: 'new-name.txt',
uploadId: 'upload-id-1',
uploadId: UPLOAD_ID,
localUrl: 'local-url://original-name.txt',
index: 'upload-index-1',
index: `temp_${UPLOAD_ID}_1`,
},
},
referenceId: expect.stringMatching(/^[a-f0-9]{60}-[0-9]{3}$/),
}

const audioFile = {
Expand All @@ -94,28 +96,29 @@ describe('prepareTemporaryMessage', () => {
...defaultPayload,
message: '{file}',
messageType: MESSAGE.TYPE.VOICE_MESSAGE,
uploadId: 'upload-id-1',
index: 'upload-index-1',
uploadId: UPLOAD_ID,
index: `temp_${UPLOAD_ID}_1`,
file: audioFile,
localUrl: 'local-url://original-name.txt',
}
const audioFileResult = {
...defaultResult,
id: expect.stringMatching(/^temp-1577908800000-upload-id-1-0\.[0-9]*$/),
id: `temp-${UPLOAD_ID}-001`,
message: '{file}',
messageType: MESSAGE.TYPE.VOICE_MESSAGE,
messageParameters: {
file: {
type: 'file',
file: audioFile,
mimetype: 'audio/wav',
id: expect.stringMatching(/^temp-1577908800000-upload-id-1-0\.[0-9]*$/),
id: `temp-${UPLOAD_ID}-001`,
name: 'Voice message 2020-01-01 20-00-00 (Note to self).wav',
uploadId: 'upload-id-1',
uploadId: UPLOAD_ID,
localUrl: 'local-url://original-name.txt',
index: 'upload-index-1',
index: `temp_${UPLOAD_ID}_1`,
},
},
referenceId: expect.stringMatching(/^[a-f0-9]{60}-[0-9]{3}$/),
}
const threadPayload = {
...defaultPayload,
Expand Down
22 changes: 18 additions & 4 deletions src/utils/combineFileMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ function isCombinableFileMessage(message: ChatMessage): boolean {
return file.mimetype !== 'text/vcard' && !file.mimetype.startsWith('audio/')
}

/**
* Checks whether file share message referenceId follows the group pattern
* (see prepareTemporaryMessage.ts for the format)
*
* @param id referenceId
* @return uploadHash (repeating part for single context upload) or original referenceId
*/
function getUploadHashFromReferenceId(id: string): string {
const [uploadHash, order] = id.split('-')
if (uploadHash.length === 60 && Number.isInteger(+order)) {
return uploadHash
} else {
return id
}
}

/**
* Check whether two file shares belong to each other: they are replies to the same message
* (or no replies at all) and they were shared together in a single context
Expand All @@ -58,10 +74,8 @@ function isCombinableFileMessage(message: ChatMessage): boolean {
*/
function canBeCombinedWith(message1: ChatMessage, message2: ChatMessage): boolean {
return message1.parent?.id === message2.parent?.id
// FIXME the timestamp is not a reliable indicator here, should instead
// create referenceId differently (e.g. as `${SHA(uploadId) + SHA(Math.random())}`)
// and base splitting on this (should be aligned with mobile clients as well)
&& message1.timestamp - message2.timestamp <= 30
&& !!message1.referenceId && !!message2.referenceId
&& getUploadHashFromReferenceId(message1.referenceId) === getUploadHashFromReferenceId(message2.referenceId)
}

/**
Expand Down
26 changes: 22 additions & 4 deletions src/utils/prepareTemporaryMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export type TempChatMessageWithFile = Omit<ChatMessage, 'messageParameters'> & {
* @param payload.message message string;
* @param payload.token conversation token;
* @param payload.uploadId upload id;
* @param payload.index index;
* @param payload.index index of file. must be provided with file and uploadId;
* @param payload.file file to upload;
* @param payload.localUrl local URL of file to upload;
* @param payload.messageType specify when the temporary file is a voice message
Expand Down Expand Up @@ -98,10 +98,25 @@ export function prepareTemporaryMessage({
isThread,
}: PrepareTemporaryMessagePayload): ChatMessage | TempChatMessageWithFile {
const date = new Date()
let tempId = 'temp-' + date.getTime()
let tempId: string = 'temp-'
let referenceId: string
const messageParameters: ChatMessage['messageParameters'] = {}
if (file) {
tempId += '-' + uploadId + '-' + Math.random()
if (!index || !uploadId) {
throw new Error('[prepareTemporaryMessage]: index/uploadId is required for file messages')
}
const appendedIndex = index.split('_').pop()!.padStart(3, '0')
tempId += uploadId + '-' + appendedIndex

/**
* Construct file share message referenceId in the following format:
* /[a-f0-9]{60}-[0-9]{3}/, where:
* /[a-f0-9]{60}/ - uploadId hashed with SHA-256 algorithm
* /-/ - mandatory delimiter
* /[0-9]{3}/ - order of file in given upload (natural integers, padded with zeroes)
*/
referenceId = Hex.stringify(SHA256(uploadId)).slice(0, 60) + '-' + appendedIndex

messageParameters.file = {
type: 'file',
// @ts-expect-error: 'file' does not exist in type RichObjectParameter
Expand All @@ -114,6 +129,9 @@ export function prepareTemporaryMessage({
localUrl,
index,
}
} else {
tempId += date.getTime()
referenceId = Hex.stringify(SHA256(tempId))
}

if (parent && 'token' in parent && parent.token !== token) {
Expand All @@ -140,7 +158,7 @@ export function prepareTemporaryMessage({
parent,
isReplyable: false,
reactions: {},
referenceId: Hex.stringify(SHA256(tempId)),
referenceId,
actorId,
actorType,
actorDisplayName,
Expand Down
Loading