[SOLVED] Calling a Meteor.method manually over websocket?

I’m trying to call a Meteor method over websocket, but I get a response saying

Malformed method invocation

The code is in Node.js and looks like this:


const WebSocket = require('ws')

let ws = undefined
let methodId = 1

function meteorConnect(host) {
    var resolve, reject
    const promise = new Promise((res, rej) => {resolve = res; reject = rej})

    ws = new WebSocket(`ws://${host}/websocket`)

    ws.on('open', () => {
        ws.send(JSON.stringify({
            msg: 'connect',
            version: '1',
            support: [ '1', 'pre2', 'pre1' ]
        }))
    })

    const onconnect = msg => {
        msg = JSON.parse(msg)

        if (msg.msg && msg.msg == 'failed') reject(new Error('connection failed.'))
        if (msg.msg && msg.msg == 'error') reject(new Error(msg.reason))

        if (msg.msg && msg.msg == 'connected') {
            //ws.off('message', onconnect)
            resolve(msg.session)
        }
    }

    const onping = msg => {
        msg = JSON.parse(msg)

        if (msg.msg && msg.msg == 'ping') {
            ws.send(JSON.stringify({
                msg: 'pong'
            }))

            setTimeout(() => {
                ws.send(JSON.stringify({
                    msg: 'ping'
                }))
            }, 25000)
        }
    }

    ws.on('message', onconnect)
    ws.on('message', onping)

    return promise
}

function meteorCall(methodName, ...args) {
    var resolve, reject
    const promise = new Promise((res, rej) => {resolve = res; reject = rej})

    const currentMethodId = methodId++

    ws.send(JSON.stringify({
        msg: 'method',
        method: methodName,
        params: [...args],
        id: currentMethodId,
    }))

    const onmethod = msg => {
        msg = JSON.parse(msg)

        if (msg.msg && msg.msg == 'failed') reject(new Error('Method call failed.'))
        if (msg.msg && msg.msg == 'error') reject(new Error(msg.reason))

        if (msg.msg && msg.msg == 'result' && msg.id == '8') {
            console.log(' --- method result:', msg)
            //ws.off('message', onmethod)
            resolve(msg.result)
        }
    }

    ws.on('message', onmethod)
}

meteorConnect('192.168.0.5:3000')
.then(sessionId => {
    console.log('session ID:', sessionId)
    return meteorCall('foo', 1, 2, 3)
})
.then(result => {
    console.log('method result:', result)
})
.catch(e => {
    throw e
})

What I did so far is look at Chrome’s Network tab and at the websocket frames to see what the client is sending when I use Meteor.call('foo', 1, 2, 3), but when I try to replicate that manually in the above program it fails, although I think my payload is exactly the same (except for the method id which I’m incrementing for my session).

What am I missing?

Looking very quickly at your code and https://github.com/meteor/meteor/blob/8005532f4ba847d3e8bb4c1100d09bf53136ff0a/packages/ddp-server/livedata_server.js#L638, might it be that your currentMethodId is not a String?

1 Like

I just realized a couple dumb mistakes in my code: I hard coded a method ID of "8" in that onmethod conditional check. I also forgot to return the promise from the meteorCall method!! Doh!!

I must’ve been really tired.

But even after fixing that, I still needed to use a string and not a number for the method ID for it to work. That was not obvious at all. Thanks!!!

I would’ve never guessed the string thing without diving into the code. Is it documented? Maybe I just missed it?

1 Like

Cool, glad it works! Is it documented? No, I don’t think so. I just quickly scanned the code using your error message.

1 Like