Unverified Commit 4bfba42d authored by Simon Esposito's avatar Simon Esposito Committed by GitHub
Browse files

Add js entrypoint config (#519)

If no entrypoint is specified, look for index.js file in module path, skip it if not found. Otherwise, load the specified JS entrypoint file.
parent 0b1fad47
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@
  <tbody>
    <tr *ngFor="let m of runtimeInfo.js_rpc_functions">
      <td><code>{{m}}</code></td>
      <td style="width: 180px"><a class="btn btn-sm btn-secondary" [routerLink]="['/apiexplorer']" [queryParams]="{'endpoint': m}">API Explorer</a></td>
      <td style="width: 180px; text-align: center;"><a class="btn btn-sm btn-secondary" [routerLink]="['/apiexplorer']" [queryParams]="{'endpoint': m}">API Explorer</a></td>
    </tr>
    <tr *ngIf="runtimeInfo.js_rpc_functions.length === 0"><td colspan="2" class="text-muted">No JavaScript RPC functions were found.</td></tr>
  </tbody>
+13 −0
Original line number Diff line number Diff line
@@ -244,6 +244,18 @@ func CheckConfig(logger *zap.Logger, config Config) map[string]string {
		config.GetRuntime().Path = filepath.Join(config.GetDataDir(), "modules")
	}

	// If JavaScript entrypoint is set, make sure it points to a valid file.
	if config.GetRuntime().JsEntrypoint != "" {
		p := filepath.Join(config.GetRuntime().Path, config.GetRuntime().JsEntrypoint)
		info, err := os.Stat(p)
		if err != nil {
			logger.Fatal("JavaScript entrypoint must be a valid path", zap.Error(err))
		}
		if filepath.Ext(info.Name()) != ".js" {
			logger.Fatal("JavaScript entrypoint must point to a .js file", zap.String("runtime.js_entrypoint", p))
		}
	}

	configWarnings := make(map[string]string, 8)

	// Log warnings for insecure default parameter values.
@@ -670,6 +682,7 @@ type RuntimeConfig struct {
	EventQueueWorkers  int               `yaml:"event_queue_workers" json:"event_queue_workers" usage:"Number of workers to use for concurrent processing of events. Default 8."`
	ReadOnlyGlobals    bool              `yaml:"read_only_globals" json:"read_only_globals" usage:"When enabled marks all Lua runtime global tables as read-only to reduce memory footprint. Default true."` // Kept for backwards compatibility
	LuaReadOnlyGlobals bool              `yaml:"lua_read_only_globals" json:"lua_read_only_globals" usage:"When enabled marks all Lua runtime global tables as read-only to reduce memory footprint. Default true."`
	JsEntrypoint       string            `yaml:"js_entrypoint" json:"js_entrypoint" usage:"Specifies the location of the bundled JavaScript runtime source code."`
}

// Function to allow backwards compatibility for MinCount config
+2 −2
Original line number Diff line number Diff line
@@ -529,7 +529,7 @@ func CheckRuntime(logger *zap.Logger, config Config) error {
	}

	// Check any JavaScript runtime modules.
	err = CheckRuntimeProviderJavascript(logger, config, paths)
	err = CheckRuntimeProviderJavascript(logger, config)
	if err != nil {
		return err
	}
@@ -564,7 +564,7 @@ func NewRuntime(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *
		return nil, nil, err
	}

	jsModules, jsRPCFunctions, jsBeforeRtFunctions, jsAfterRtFunctions, jsBeforeReqFunctions, jsAfterReqFunctions, jsMatchmakerMatchedFunction, jsTournamentEndFunction, jsTournamentResetFunction, jsLeaderboardResetFunction, err := NewRuntimeProviderJS(logger, startupLogger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, metrics, streamManager, router, allEventFunctions.eventFunction, runtimeConfig.Path, paths, matchProvider)
	jsModules, jsRPCFunctions, jsBeforeRtFunctions, jsAfterRtFunctions, jsBeforeReqFunctions, jsAfterReqFunctions, jsMatchmakerMatchedFunction, jsTournamentEndFunction, jsTournamentResetFunction, jsLeaderboardResetFunction, err := NewRuntimeProviderJS(logger, startupLogger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, metrics, streamManager, router, allEventFunctions.eventFunction, runtimeConfig.Path, runtimeConfig.JsEntrypoint, matchProvider)
	if err != nil {
		startupLogger.Error("Error initialising JavaScript runtime provider", zap.Error(err))
		return nil, nil, err
+35 −32
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
@@ -38,7 +39,7 @@ import (
	"google.golang.org/grpc/codes"
)

const JS_ENTRYPOINT_NAME = "index.js"
const JsEntrypointFilename = "index.js"

type RuntimeJS struct {
	logger       *zap.Logger
@@ -489,10 +490,10 @@ func (rp *RuntimeProviderJS) Put(r *RuntimeJS) {
	}
}

func NewRuntimeProviderJS(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, metrics *Metrics, streamManager StreamManager, router MessageRouter, eventFn RuntimeEventCustomFunction, rootPath string, paths []string, matchProvider *MatchProvider) ([]string, map[string]RuntimeRpcFunction, map[string]RuntimeBeforeRtFunction, map[string]RuntimeAfterRtFunction, *RuntimeBeforeReqFunctions, *RuntimeAfterReqFunctions, RuntimeMatchmakerMatchedFunction, RuntimeTournamentEndFunction, RuntimeTournamentResetFunction, RuntimeLeaderboardResetFunction, error) {
	startupLogger.Info("Initialising JavaScript runtime provider", zap.String("path", rootPath))
func NewRuntimeProviderJS(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, metrics *Metrics, streamManager StreamManager, router MessageRouter, eventFn RuntimeEventCustomFunction, path, entrypoint string, matchProvider *MatchProvider) ([]string, map[string]RuntimeRpcFunction, map[string]RuntimeBeforeRtFunction, map[string]RuntimeAfterRtFunction, *RuntimeBeforeReqFunctions, *RuntimeAfterReqFunctions, RuntimeMatchmakerMatchedFunction, RuntimeTournamentEndFunction, RuntimeTournamentResetFunction, RuntimeLeaderboardResetFunction, error) {
	startupLogger.Info("Initialising JavaScript runtime provider", zap.String("path", path), zap.String("entrypoint", entrypoint))

	modCache, err := cacheJavascriptModules(startupLogger, rootPath, paths)
	modCache, err := cacheJavascriptModules(startupLogger, path, entrypoint)
	if err != nil {
		startupLogger.Fatal("Failed to load JavaScript files", zap.Error(err))
	}
@@ -1419,8 +1420,8 @@ func NewRuntimeProviderJS(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbM
	return modCache.Names, rpcFunctions, beforeRtFunctions, afterRtFunctions, beforeReqFunctions, afterReqFunctions, matchmakerMatchedFunction, tournamentEndFunction, tournamentResetFunction, leaderboardResetFunction, nil
}

func CheckRuntimeProviderJavascript(logger *zap.Logger, config Config, paths []string) error {
	modCache, err := cacheJavascriptModules(logger, config.GetRuntime().Path, paths)
func CheckRuntimeProviderJavascript(logger *zap.Logger, config Config) error {
	modCache, err := cacheJavascriptModules(logger, config.GetRuntime().Path, config.GetRuntime().JsEntrypoint)
	if err != nil {
		return err
	}
@@ -1435,25 +1436,31 @@ func CheckRuntimeProviderJavascript(logger *zap.Logger, config Config, paths []s
	return err
}

func cacheJavascriptModules(logger *zap.Logger, rootPath string, paths []string) (*RuntimeJSModuleCache, error) {
func cacheJavascriptModules(logger *zap.Logger, path, entrypoint string) (*RuntimeJSModuleCache, error) {
	moduleCache := &RuntimeJSModuleCache{
		Names:   make([]string, 0),
		Modules: make(map[string]*RuntimeJSModule),
	}

	for _, path := range paths {
		if strings.ToLower(filepath.Base(path)) != JS_ENTRYPOINT_NAME {
			continue
	var absEntrypoint string
	if entrypoint == "" {
		// If entrypoint is not set, look for index.js file in path; skip if not found.
		absEntrypoint = filepath.Join(path, JsEntrypointFilename)
		if _, err := os.Stat(absEntrypoint); os.IsNotExist(err) {
			return moduleCache, nil
		}
	} else {
		absEntrypoint = filepath.Join(path, entrypoint)
	}

	var content []byte
	var err error
		if content, err = ioutil.ReadFile(path); err != nil {
			logger.Error("Could not read JavaScript module", zap.String("path", path), zap.Error(err))
	if content, err = ioutil.ReadFile(absEntrypoint); err != nil {
		logger.Error("Could not read JavaScript module", zap.String("entrypoint", absEntrypoint), zap.Error(err))
		return nil, err
	}

		modName := filepath.Base(path)
	modName := filepath.Base(entrypoint)
	prg, err := goja.Compile(modName, string(content), true)
	if err != nil {
		logger.Error("Could not compile JavaScript module", zap.String("module", modName), zap.Error(err))
@@ -1462,14 +1469,10 @@ func cacheJavascriptModules(logger *zap.Logger, rootPath string, paths []string)

	moduleCache.Add(&RuntimeJSModule{
		Name:    modName,
			Path:    path,
		Path:    absEntrypoint,
		Program: prg,
	})

		// Only load a single js entrypoint file
		break
	}

	return moduleCache, nil
}

@@ -1730,8 +1733,8 @@ func evalRuntimeModules(rp *RuntimeProviderJS, modCache *RuntimeJSModuleCache, m

		_, err = initModFn(goja.Null(), goja.Null(), jsLoggerInst, nkInst, initializerInst)

		// Running a dry run, parse JavaScript but do not execute the init module function
		if dryRun {
			// Parse JavaScript code for syntax errors but do not execute the InitModule function.
			return nil, nil, nil
		}