Reputation: 1353
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?
Upvotes: 3
Views: 685
Reputation: 2386
EDIT: disregard answer, does not address OP's desire for DRY code or optimistic updates.
be default, Meteor implements an eager-loading scheme with regard to files. your methods.js is an example of that.
if a file is in a directory that's anywhere under a folder called "client", that file is served only to the client. likewise, any file under "server" is served only to the folder. so that's how you handle ensuring a file is served only to the server.
as you discovered, the "Meteor.isServer" construct only limits what's run in an environment, not what is served there.
i typically implement Meteor methods like this:
server/some/path/stuff.js:
// this is only on the server
Meteor.methods({
'getStuff': function() {
return "stuff";
}
});
client/some/path/clientStuff.js:
// this is only on the client, calling the server method
Meteor.call('stuff', function(error, result) {
if (error && error.reason) {
alert(error.reason);
}
else {
// use result
}
});
regarding require, why are you using it?
i see that you have code in /imports, which Meteor treats as special and does not eagerly load. that tells me you're explicitly importing this stuff.
MDG has some strong recommendations for directory structure, ES15 modules and how to load code, which they detail here:
https://guide.meteor.com/structure.html
you'll note they don't use require, and are specifying modules differently from how you're doing it.
Upvotes: 0
Reputation: 1353
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.
Upvotes: 1