Skip to content

Commit

Permalink
now script will load a packed zip plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
zyxkad committed Oct 24, 2023
1 parent 6dfda60 commit dcff43e
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 123 deletions.
34 changes: 25 additions & 9 deletions script/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,41 @@ We use [goja](https://github.com/dop251/goja) as our javascript engine.
Goja support full ES5, but it **DOES NOT** support full ES6 yet.
Please read their [Features](https://github.com/dop251/goja#features) and [Known incompatibilities](https://github.com/dop251/goja#known-incompatibilities-and-caveats) to see what you can use and what you cannot

# TS support
# Setup

1. Install the package below:
## Lazy setup

```sh
npm init glp@latest "<directory name>"
```

Note: Typescript will be automaticly configured.

## Manually setup

You may want to configure your project manually, then you have to do the commands below

1. Install [go-liter-plugin](https://www.npmjs.com/package/go-liter-plugin):
```sh
npm install --save-dev go-liter-plugin
npm install go-liter-plugin
```

2. Add the following element to your `tsconfig.json`
```js
{
"compilerOptions": {
"typeRoots": [
"node_modules/@types",
"node_modules/go-liter-plugin"
]
}
"compilerOptions": {
"typeRoots": [
"node_modules/@types",
"node_modules/go-liter-plugin"
]
}
}
```

# Typescript

Check files under [./types/lib/](./types/lib/) for more information

# Events

TODO
125 changes: 43 additions & 82 deletions script/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,48 @@
package script

import (
"archive/zip"
"context"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"sync"

"github.com/dop251/goja"
"github.com/dop251/goja/ast"
"github.com/dop251/goja/file"
"github.com/dop251/goja_nodejs/eventloop"
"github.com/dop251/goja_nodejs/require"
"github.com/kmcsr/go-logger"
"github.com/kmcsr/go-logger/logrus"
"github.com/kmcsr/go-liter"
"github.com/kmcsr/go-liter/script/console"
)

var (
fileNameRe = regexp.MustCompile(`^([a-z_][0-9a-z_]{0,31})(?:@(\d+(?:\.\d+)*))?(?:-.+)?\..+$`)
)

var (
ErrFileNameInvalid = errors.New("The plugin's filename is invalid")
ErrPluginLoaded = errors.New("The plugin is already loaded")
ErrScriptInvalid = errors.New("Invalid script")
)
type PluginIdLoadedError struct {
Id string
}

func (e *PluginIdLoadedError)Error()(string){
return fmt.Sprintf("Plugin %s is already loaded", e.Id)
}

type PluginLoadError struct {
Path string
Origin error
}

func (e *PluginLoadError)Unwrap()(error){
return e.Origin
}

func (e *PluginLoadError)Error()(string){
return fmt.Sprintf("Error when loading %q: %v", e.Path, e.Origin)
}

type Manager struct {
logger logger.Logger
Expand Down Expand Up @@ -59,7 +73,7 @@ func (m *Manager)SetLogger(loger logger.Logger){
defer m.scriptMux.Unlock()
m.logger = loger
for _, s := range m.scripts {
s.console.SetLogger(setPrefixLogger(loger, s.id))
s.console.SetLogger(setPrefixLogger(loger, s.Id))
}
}

Expand All @@ -86,87 +100,33 @@ func (m *Manager)Load(path string)(script *Script, err error){
return m.LoadWithContext(context.Background(), path)
}

// LoadWithContext will load a script plugin use the given filepath.
// Script's filename must match `^([a-z_][0-9a-z_]{0,31})(?:@(\d+(?:\.\d+)*)(?:-.+)?)?\..+$`
// LoadWithContext will load a plugin packet use the given filepath.
// The first capture group will be the script's ID. The second capture group is the script's version
// If the script's ID is already loaded, then an error will be returned
func (m *Manager)LoadWithContext(ctx context.Context, path string)(script *Script, err error){
name := filepath.Base(path)
matches := fileNameRe.FindStringSubmatch(name)
if matches == nil {
return nil, ErrFileNameInvalid
}
id := matches[1]
version := matches[2]

m.scriptMux.Lock()
if _, ok := m.scripts[id]; ok {
m.scriptMux.Unlock()
return nil, ErrPluginLoaded
}
m.scripts[id] = nil // reserve the slot
m.scriptMux.Unlock()

data, err := os.ReadFile(path)
if err != nil {
var packet *zip.ReadCloser
if packet, err = zip.OpenReader(path); err != nil {
return
}
parsed, err := goja.Parse(path, (string)(data))
meta, err := loadScriptMeta(packet)
if err != nil {
return
}
// wrap code as `function($, console){ ... }`
parsed.Body = []ast.Statement{&ast.ExpressionStatement{
Expression: &ast.FunctionLiteral{
Function: -1,
Name: nil,
ParameterList: &ast.ParameterList{
Opening: -1,
List: []*ast.Binding{
{ Target: &ast.Identifier{ Name:"$", Idx: -1 } },
{ Target: &ast.Identifier{ Name:"console", Idx: -1 } },
},
Closing: -1,
},
Body: &ast.BlockStatement{
LeftBrace: 0,
List: parsed.Body,
RightBrace: (file.Idx)(len(data) - 1),
},
DeclarationList: parsed.DeclarationList,
},
}}
prog, err := goja.CompileAST(parsed, true)
if err != nil {
return
m.scriptMux.Lock()
if _, ok := m.scripts[meta.Id]; ok {
m.scriptMux.Unlock()
return nil, &PluginIdLoadedError{ Id: meta.Id }
}

script = newScript(id, version, path, prog, m.loop)
m.scripts[meta.Id] = nil // reserve the slot
m.scriptMux.Unlock()

errCh := make(chan error, 1)
m.loop.RunOnLoop(func(vm *goja.Runtime){
defer close(errCh)
if ctx.Err() != nil {
return
}
var err error
var res goja.Value
if res, err = vm.RunProgram(prog); err != nil {
errCh <- err
return
}
cb, ok := goja.AssertFunction(res)
if !ok {
errCh <- ErrScriptInvalid
return
}
if ctx.Err() != nil {
return
}
script.init(vm)
script.console = console.NewConsole(vm, setPrefixLogger(m.logger, id))
if _, err = cb(nil, script.doll, script.console.Exports()); err != nil {
defer func(){
errCh <- err
}()
if script, err = loadScript(packet, meta, m.logger, vm, m.loop); err != nil {
return
}
})
Expand All @@ -180,7 +140,7 @@ func (m *Manager)LoadWithContext(ctx context.Context, path string)(script *Scrip
}
m.scriptMux.Lock()
defer m.scriptMux.Unlock()
m.scripts[id] = script
m.scripts[script.Id] = script
return
}

Expand Down Expand Up @@ -232,9 +192,9 @@ func (m *Manager)UnloadAll()(scripts []*Script){
return
}

// LoadFromDir will load all plugins under the path which have the ext `.js`,
// LoadFromDir will load all plugins under the path which have the ext `.zip`,
// and return all successfully loaded plugins with a possible error
// If the target path is not exists, LoadFromDir will do nothing and return no error
// If the target path is not exists, LoadFromDir will do nothing and return without error
// If there are errors during load any plugin, the errors will be wrapped use `errors.Join`,
// and other plugins will continue to be load.
func (m *Manager)LoadFromDir(path string)(scripts []*Script, err error){
Expand All @@ -249,10 +209,11 @@ func (m *Manager)LoadFromDir(path string)(scripts []*Script, err error){
for _, e := range entries {
name := e.Name()
if !e.IsDir() {
if strings.HasSuffix(name, ".js") {
s, err := m.Load(filepath.Join(path, name))
if strings.HasSuffix(name, ".zip") {
p := filepath.Join(path, name)
s, err := m.Load(p)
if err != nil {
errs = append(errs, err)
errs = append(errs, &PluginLoadError{ Path: p, Origin: err })
continue
}
scripts = append(scripts, s)
Expand Down
Loading

0 comments on commit dcff43e

Please sign in to comment.