tus-node-server icon indicating copy to clipboard operation
tus-node-server copied to clipboard

Add Hapi Integration

Open corusm opened this issue 2 years ago • 3 comments

Is it possible to add Hapi integration similar to this? The Code doesn't seem to work anymore. any ideas how to fix?

corusm avatar Mar 27 '22 16:03 corusm

It's not officially supported and I haven't researched Hapi to know what is required for the integration. PRs welcome :)

Murderlon avatar Mar 28 '22 05:03 Murderlon

@corusm It's bad practice, but I've forked tus-node-server into hapi-tus-node-server, so anyone using Hapi can install hapi-tus-node-server and use it as a Hapi plugin. Feel free to give it a try if you like. Hat tip to everyone who posted in the thread TUS Resumable Uploads, and of course, all the awesome people who have brought us tus!

deplorable avatar Apr 22 '22 04:04 deplorable

@corusm, I am trying also to implement with Hapi. I haven't use hapi-tus-node-server but I instead used the exemple in the Quick Guide and adapted it to work with Hapi.

The only issue I seem to have so fare is that the [file-id].info is empty on S3 and is not created in the file storage. I wonder if this a bug in the package or something wrong with Hapi but here's what I have came up with.

I you use hapi-cors, you have to add headers used by tus

await server.register({
    plugin: require('hapi-cors'),
    options: {
      origins: ['*'],
      headers: [
        'Accept', 'Content-Type', 'Authorization', 
        /* EXTRA HEADERS */
        'tus-resumable','upload-length','upload-metadata','x-http-method-override','upload-offset',
        'x-requested-with', 'x-forwarded-host','x-forwarded-proto','Forwarded'
       /* ****************** */
      ],
      methods: ['POST, GET, PUT, DELETE, PATCH, HEAD']
    }
  });

Make sure to also add "PATCH and HEAD" in methods

These are the 3 routes I needed


const path = require('path');
const handlers = require(path.resolve('handlers/commons/media'));
const basic = '/api/v1/commons/media'

module.exports = [
  {
    method: 'PATCH',
    path: basic + '/files/{any*}',
    options: {
      handler: handlers.uploadFiles,
      description: 'Upload file',
      auth: {
        access: {
          scope: ['clientscope:profile']
        }
      },
      payload:{
        maxBytes: 104857600,
        output: 'stream',
        parse: true,
        multipart: true
      },
      tags: ['api', 'commons'],
    }
  },
  {
    method: '*',
    path: basic + '/files/{any*}',
    options: {
      handler: handlers.uploadFiles,
      description: 'Upload file',
      auth: {
        access: {
          scope: ['clientscope:profile']
        }
      },
      tags: ['api', 'commons'],
    }
  },
  {
    method: '*',
    path: basic + '/files',
    options: {
      handler: handlers.uploadFiles,
      description: 'Upload file',
      auth: {
        access: {
          scope: ['clientscope:profile']
        }
      },
      tags: ['api', 'commons'],
    }
  }
];

And here is my handler :

const path = require('path');
const {s3Config} = require(path.resolve("config/config"));
const {Server} = require('@tus/server')
const {S3Store} = require('@tus/s3-store')
const {FileStore} = require('@tus/file-store')

class media {
  static async uploadFiles(request,h){

    try{
      
      // IF YOU WANT TO USE S3
      const s3Store = new S3Store({
        partSize: 8 * 2 ** 20, //If you want to use MultiParts
        s3ClientConfig: s3Config,
      })
      
      // IF YOU WANT TO USE FileStore
      const fileStore = new FileStore({ directory: './temp/files' })
      
      const tusServer = new Server({
        //PATH will be sent back to your frontend so make sure to set the same path as your route ex: basic + '/files/{any*}'
        path: '/api/v1/commons/media/files',
        datastore: s3Store, // Use fileStore if you want to use local upload
        async onUploadCreate(req, res, upload) {
          
          /*
          DO YOUR STUFF HERE before it starts to upload
          Eg: Save meta in DB before upload
           */
          
          return res
        },
        async onUploadFinish(req, res, upload) {

          /*
          DO YOUR STUFF HERE after the upload is completed
          Eg: Update status or do other actions
           */
          
          return res
        }
      })
      await tusServer.handle(request.raw.req, request.raw.res)
      return h.response(); //Make sure to return a 204. you could also use h.response().code(204)
      
    }catch (e) {
      // Handle errors here
      console.log(e);
      return h.response().code(500)
    }
  }
}

module.exports = media;

I still have to figure out an issue which is an ERR_HTTP_HEADERS_SENT. I will come back it in a later time but if anyone have a fix it would be great. I hid it (FOR NOW ;) ) by adding :

process.on('unhandledRejection', (err) => {
  if(err.code !== 'ERR_HTTP_HEADERS_SENT'){
    console.log(err);
  }
  //process.exit(1);
});

in my unhandledRejection event in my app.js

m4xleb avatar Jul 10 '23 19:07 m4xleb