I'm trying to wrap my head around how to hide secret server code from a client in Meteor Methods. The docs seem to imply that the following is a general pattern of how it's done. https://guide.meteor.com/structure.html#using-require
(Note that, as per the docs, I'm using require
instead of import, since import
cannot be used in a conditional block.)
First, here's a Method defined in a file called methods.js
and imported both on the client and the server:
/* methods.js */
let ServerCode
if (Meteor.isServer) {
ServerCode = require('/imports/server-code.js')
}
Meteor.Methods({
'myMethodName': function() {
// ... common code
if (Meteor.isServer) {
ServerCode.secretFunction()
}
// ... common code
}
})
Second, here's the secret server code in /imports/server-code.js
which I am trying not to send to the client:
/* server-code.js */
class ServerCode {
secretFunction() {
console.log('TOP SECRET!!!!')
}
}
const ServerCodeSingleton = new ServerCode()
module.exports = ServerCodeSingleton
But when I examine the source sent over to the client browser, I'm still seeing my secret server code being sent to the client:
Even when I do a production build, I can still search and find that 'TOP SECRET!!' string. I feel like I'm being too naive in my understanding of how require
works, but the Meteor docs make it seem so simple. So what is the correct way to hide secret code that is called from Meteor Methods?
I finally figured this out I think.
The short version is, ignore what it says here; I believe it's incorrect or at least misleading:
https://guide.meteor.com/structure.html#using-require
And follow what it says here instead:
https://guide.meteor.com/security.html#secret-code
A longer explanation is: In a server-only file, import
the secret code and assign it to a global variable. Then, in a common file, use isServer
(or !isSimulation
) to conditionally refer to that global variable.
So my original example might be re-written like this:
/* /imports/methods.js */
// Note: no conditional use of require this time
Meteor.Methods({
'myMethodName': function() {
// ... common code
if (Meteor.isServer) {
ServerCode.secret() // <-- Defined as a global outside of this file!
}
// ... common code
}
})
And so the secret code file might look like this:
/* /imports/server-code.js */
class _ServerCode {
secret() {
console.log("Shhhhhh, I'm secret()!")
}
}
// Here's the global variable:
SecretCode = new _SecretCode()
And then in a server-only file it might look like this:
/* /server/server-main.js */
import '/imports/secret-code' // <-- declare the global
import '/imports/methods' // <-- use the global in here
And then in a client-only file it might look like so:
/* /client/client-main.js */
import '/imports/methods'
//...
Meteor.call('myMethodName')
Now the client and server can both execute some of the same exact code in the Method body (DRY), while some secret code can be server-only and won't get sent to the client as well. It's a little annoying to have to resort to using a global variable, but perhaps that's the cleanest option until a fancier version of import
comes along that supports built-in lazy-loading of modules.