Skip to content

Commit

Permalink
add matrix upload/download file methods
Browse files Browse the repository at this point in the history
  • Loading branch information
celeduc committed Aug 31, 2020
1 parent 7c0bc7a commit 5da80a3
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 3 deletions.
63 changes: 62 additions & 1 deletion packages/comms/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module.exports = class Comms {
this.context = {
serverName: homeserver.split('//')[1],
api: homeserver + '/_matrix/client/r0/',
media: homeserver + '/_matrix/media/r0/',
matrixUser: '',
tokenAccess: ''
}
Expand Down Expand Up @@ -255,7 +256,7 @@ module.exports = class Comms {
}

/**
* Sends a Message.
* Boxes a Message for sending.
*
* @param {object} senderSecretKey to sign the message
* @param {object} senderPublicKey to sign the message
Expand Down Expand Up @@ -293,4 +294,64 @@ module.exports = class Comms {
box.msg = this.crypto.unboxObj(box.msg, box.publicKey, receiverSecretKey)
return box
}

/**
* Upload a file to the Matrix content repository
*
* @param {Buffer} file contents
* @param {string} filename filename
* @param {string=} type mime-type
* @returns {*} containing content_uri (string) of the MXC URI to the uploaded content.
*/
uploadFile (file, filename, type = 'application/octet-stream') {
return new Promise((resolve, reject) => {
// Constructing route
const apiCall = this.context.media +
'upload?filename=' + filename +
'&access_token=' + this.connection.access_token

// Calling Matrix API
axios.post(apiCall, file, { headers: { 'Content-type': type } })
.then((res) => {
resolve(res)
})
.catch((e) => {
debug(e)
reject(e)
})
})
}

/**
* Parse the media ID from the content_uri generated by Matrix
*
* @param {string} contentUri media ID like `mxc://homeserver.matrix.org/FCVtZVLJbpPMKjBzWusvkHyP`
* @returns {string} mediaId like `FCVtZVLJbpPMKjBzWusvkHyP`
*/
mediaIdFromURI (contentUri) {
const uriParts = contentUri.split('/')
return uriParts[uriParts.length - 1]
}

/**
* Download a file from the Matrix content repository
*
* @param {string} mediaId media ID
* @param {string} filename file name
* @param {string=} serverName server name
* @returns {Promise} of Buffer containing file
*/
downloadFile (mediaId, filename, serverName = this.context.serverName) {
return new Promise((resolve, reject) => {
const apiCall = this.context.media + 'download/' + serverName + '/' + mediaId + '/' + filename
axios.get(apiCall, { responseType: 'arraybuffer' })
.then((res) => {
resolve(Buffer.from(res.data))
})
.catch((e) => {
debug(e)
reject(e)
})
})
}
}
45 changes: 43 additions & 2 deletions packages/comms/src/index.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable quote-props */
/* eslint-disable quotes */
/* eslint-disable key-spacing */
const fs = require('fs')
const path = require('path')
const Comms = require('./index')
const LorenaCrypto = require('@caelumlabs/crypto')
const crypto = new LorenaCrypto()
Expand All @@ -9,7 +11,7 @@ const testMatrixServer = 'https://labdev.matrix.lorena.tech'
const m1 = new Comms(testMatrixServer)
const m2 = new Comms(testMatrixServer)
const u1 = m1.randomUsername()
const u2 = m1.randomUsername()
const u2 = m2.randomUsername()
const p1 = 'rndPass'
const p2 = 'rndPass'
let roomId
Expand Down Expand Up @@ -112,7 +114,7 @@ test('should use matrix as a comms interface to Lorena', async done => { // esli
await m2.acceptConnection(msg.value.roomId)
break
case 'contact-message' :
boxReceived = m2.unboxMessage(msg.value.msg, receiver.box.secretKey)
boxReceived = m2.unboxMessage(msg.value.msg, receiver.box.secretKey, sender.box.publicKey)
expect(boxReceived.msg.recipeId).toEqual('ping')
expect(boxReceived.msg.stateId).toEqual(10)
expect(boxReceived.msg.credentials).toEqual(credential)
Expand All @@ -138,9 +140,11 @@ test('should box and unbox the message', async () => { // eslint-disable-line je
const sender = crypto.keyPair()
const receiver = crypto.keyPair()
const box = await m1.boxMessage(sender.box.secretKey, sender.box.publicKey, receiver.box.publicKey, 'ping', 'Hello this is a test message...', credential, 10)
// test unboxing without public key of sender
const msgReceived1 = m2.unboxMessage(box, receiver.box.secretKey)
expect(msgReceived1.msg.recipeId).toEqual('ping')
expect(msgReceived1.msg.stateId).toEqual(10)
// test unboxing with public key of sender
const msgReceived2 = m2.unboxMessage(box, receiver.box.secretKey, sender.box.publicKey)
expect(msgReceived2.msg.recipeId).toEqual('ping')
expect(msgReceived2.msg.stateId).toEqual(10)
Expand All @@ -162,3 +166,40 @@ test('should leave a room', async () => {
result = await m2.leaveRoom(roomId)
expect(result.status).toBe(200)
})

let contentUri, prairieDogFilename, prairieDogFile

test('should upload a file to matrix', async () => {
const type = 'image/gif'
prairieDogFilename = 'Dramatic-Prairie-Dog.gif'
prairieDogFile = fs.readFileSync(path.resolve(__dirname, `../../crypto/${prairieDogFilename}`))

const response = await m1.uploadFile(prairieDogFile, prairieDogFilename, type)
expect(response.status).toBe(200)
contentUri = response.data.content_uri
expect(contentUri).toBeDefined()
})

test('should download a file from matrix', async () => {
// Example of uploaded files:
// `mxc://${matrixServer}/FCVtZVLJbpPMKjBzWusvkHyP`
const filename = m1.randomUsername() + '.gif'
const mediaId = m1.mediaIdFromURI(contentUri)
const result = await m1.downloadFile(mediaId, filename)
expect(prairieDogFile).toEqual(result)
})

test('should fail to download a nonexistent file from matrix', async () => {
expect.assertions(1)
return m1.downloadFile('d3aDbeEfPiE4SaLe', 'simple').catch(e =>
// Not found
expect(e.response.status).toEqual(404))
})

test('should fail to upload file if not connected', () => {
expect.assertions(1)
const m3 = new Comms(testMatrixServer)
return m3.uploadFile('file').catch(e =>
// Unauthorized
expect(e.response.status).toBe(401))
})

0 comments on commit 5da80a3

Please sign in to comment.