pear-electron 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,12 +19,11 @@ import Runtime from 'pear-electron'
19
19
  import Bridge from 'pear-bridge'
20
20
 
21
21
  const runtime = new Runtime()
22
- await runtime.ready()
23
22
 
24
23
  const bridge = new Bridge()
25
24
  await bridge.ready()
26
25
 
27
- const pipe = runtime.start(bridge.info())
26
+ const pipe = runtime.start({ bridge })
28
27
  Pear.teardown(() => pipe.end())
29
28
  ```
30
29
 
@@ -40,11 +39,13 @@ Create the runtime instances with `new Runtime()`.
40
39
 
41
40
  Prepare the runtime, runtime binaries for the runtime version may be bootstrapped peer-to-peer at this point. This only runs once per version and any prior bootstraps can be reused for subsequent versions where state hasn't changed. In a production scenario any bootstrapping would be performed in advance by the application distributable.
42
41
 
43
- ### `runtime.start(info <String>)`
42
+ ### `runtime.start(opts)`
44
43
 
45
- Opens the UI. The `info` string is passed as a `--runtime-info` flag to the UI executable. The `pear-api/state` integration library then includes this flag value as `state.runtimeInfo` which can then be used by `pear-electron`. The `info` string by convention is a JSON string, with the shape, `{ type, data }`.
44
+ Opens the UI.
46
45
 
47
- In the usage example, `bridge.info()` is passed to `runtime.start()`, `pear-electron` later uses this to determine the bridge localhost address to load with electron.
46
+ #### Options
47
+
48
+ * `bridge` - An instance of `pear-bridge`.
48
49
 
49
50
  ## User-Interface API
50
51
 
package/boot.js CHANGED
@@ -3,14 +3,12 @@
3
3
  const { isElectron, isElectronRenderer, isElectronWorker, isWindows } = require('which-runtime')
4
4
  const BOOT_ELECTRON_MAIN = 1
5
5
  const BOOT_ELECTRON_PRELOAD = 2
6
- const rtix = process.argv.indexOf('--runtime-info')
7
- const rti = rtix > -1 && process.argv[rtix + 1]
8
- const state = rti ? null : JSON.parse(process.argv.slice(isWindows ? -2 : -1)[0])
9
- const RUNTIME_INFO = rti ? JSON.parse(rti) : state.runtimeInfo
6
+ const rtiFlagIx = process.argv.indexOf('--rti')
7
+ const RTI = rtiFlagIx > -1 && process.argv[rtiFlagIx + 1]
8
+ const state = RTI ? null : JSON.parse(process.argv.slice(isWindows ? -2 : -1)[0])
10
9
 
11
10
  class API {
12
- static CHECKOUT = RUNTIME_INFO.checkout
13
- static MOUNT = RUNTIME_INFO.mount
11
+ static RTI = RTI ? JSON.parse(RTI) : state.rti
14
12
  static get CONSTANTS () { return require('pear-api/constants') }
15
13
  config = {}
16
14
  }
package/electron-main.js CHANGED
@@ -20,6 +20,7 @@ run.running?.catch(console.error)
20
20
 
21
21
  async function electronMain (cmd) {
22
22
  const state = new State({
23
+ dir: global.Pear.constructor.RTI.dir,
23
24
  link: cmd.args.link.replace('_||', '://'), // for Windows
24
25
  flags: cmd.flags,
25
26
  args: cmd.rest
package/gui/gui.js CHANGED
@@ -806,8 +806,8 @@ class GuiCtrl {
806
806
  this.parentId = parentId
807
807
  this.closed = true
808
808
  this.id = null
809
- this.runtimeInfo = this.state.runtimeInfo
810
- this.bridge = this.runtimeInfo?.bridge ?? null
809
+ this.rti = this.state.rti
810
+ this.bridge = this.rti?.bridge ?? null
811
811
  this.entry = this.bridge === null ? entry : `${this.bridge}${entry}`
812
812
  this.sessname = sessname
813
813
  this.appkin = appkin
@@ -1020,7 +1020,7 @@ class Window extends GuiCtrl {
1020
1020
  preload: require.main.filename,
1021
1021
  ...(decal === false ? { session } : {}),
1022
1022
  partition: 'persist:pear',
1023
- additionalArguments: [JSON.stringify({ ...this.state.config, runtimeInfo: this.runtimeInfo, isDecal: true })],
1023
+ additionalArguments: [JSON.stringify({ ...this.state.config, rti: this.rti, isDecal: true })],
1024
1024
  autoHideMenuBar: true,
1025
1025
  experimentalFeatures: true,
1026
1026
  nodeIntegration: true,
@@ -1119,7 +1119,7 @@ class Window extends GuiCtrl {
1119
1119
  webPreferences: {
1120
1120
  preload: require.main.filename,
1121
1121
  session,
1122
- additionalArguments: [JSON.stringify({ ...this.state.config, runtimeInfo: this.runtimeInfo, parentWcId: this.win.webContents.id, decalled: true })],
1122
+ additionalArguments: [JSON.stringify({ ...this.state.config, rti: this.rti, parentWcId: this.win.webContents.id, decalled: true })],
1123
1123
  autoHideMenuBar: true,
1124
1124
  experimentalFeatures: true,
1125
1125
  nodeIntegration: true,
@@ -1134,6 +1134,7 @@ class Window extends GuiCtrl {
1134
1134
 
1135
1135
  if (options.afterNativeViewCreated) options.afterNativeViewCreated(this)
1136
1136
  this.view.setBounds({ x: 0, y: 0, width, height })
1137
+
1137
1138
  const viewLoading = this.view.webContents.loadURL(this.entry)
1138
1139
  viewInitialized()
1139
1140
  this.view.webContents.once('did-finish-load', () => { viewLoaded() })
@@ -1322,7 +1323,7 @@ class View extends GuiCtrl {
1322
1323
  webPreferences: {
1323
1324
  preload: require.main.filename,
1324
1325
  session,
1325
- additionalArguments: [JSON.stringify({ ...this.state.config, ...(options?.view?.config || options.config || {}), runtimeInfo: this.runtimeInfo, parentWcId: this.win.webContents.id })],
1326
+ additionalArguments: [JSON.stringify({ ...this.state.config, ...(options?.view?.config || options.config || {}), rti: this.rti, parentWcId: this.win.webContents.id })],
1326
1327
  autoHideMenuBar: true,
1327
1328
  experimentalFeatures: true,
1328
1329
  nodeIntegration: true,
@@ -1574,6 +1575,7 @@ class PearGUI extends ReadyResource {
1574
1575
  }
1575
1576
 
1576
1577
  static async ctrl (type, entry, { state, parentId = 0, ua, sessname = null, appkin }, options = {}, openOptions = {}) {
1578
+ entry = entry || '/'
1577
1579
  ;[entry] = entry.split('+')
1578
1580
  if (entry.slice(0, 2) === './') entry = entry.slice(1)
1579
1581
  if (entry[0] !== '/') entry = `/~${entry}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pear-electron",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Pear User-Interface Library for Electron",
5
5
  "main": "index.js",
6
6
  "bin": "bin.js",
package/preload.js CHANGED
@@ -9,7 +9,7 @@ module.exports = (state) => {
9
9
  const { isMac, isWindows, platform } = require('which-runtime')
10
10
  const API = require('pear-api')
11
11
  const GUI = require('./gui')
12
- const { parentWcId, env, id, runtimeInfo, ...config } = state
12
+ const { parentWcId, env, id, rti, ...config } = state
13
13
  const isDecal = state.isDecal || false
14
14
  if (config.key?.type === 'Buffer') config.key = Buffer.from(config.key.data)
15
15
  const dir = config.dir
@@ -43,6 +43,76 @@ module.exports = (state) => {
43
43
  })
44
44
  }
45
45
 
46
+ const { warn } = console
47
+ console.warn = (msg, ...args) => {
48
+ // if (/Insecure Content-Security-Policy/.test(msg)) return.
49
+ warn.call(console, msg, ...args)
50
+ }
51
+
52
+ if (location.hostname === 'localhost' && location.port) {
53
+ const gunk = require('pear-api/gunk')
54
+ const runtime = require('script-linker/runtime')
55
+
56
+ // platform runtime:
57
+ const pltsl = runtime({
58
+ builtins: gunk.builtins,
59
+ map: gunk.platform.map,
60
+ mapImport: gunk.platform.mapImport,
61
+ symbol: gunk.platform.symbol,
62
+ protocol: gunk.platform.protocol,
63
+ getSync (url) {
64
+ const xhr = new XMLHttpRequest()
65
+ xhr.open('GET', url, false)
66
+ xhr.send(null)
67
+ return xhr.responseText
68
+ },
69
+ resolveSync (req, dirname, { isImport }) {
70
+ const xhr = new XMLHttpRequest()
71
+ const type = isImport ? 'esm' : 'cjs'
72
+ const url = `${dirname}/~${req}+platform-resolve+${type}`
73
+ xhr.open('GET', url, false)
74
+ xhr.send(null)
75
+ return xhr.responseText
76
+ }
77
+ })
78
+
79
+ // app runtime:
80
+ const appsl = runtime({
81
+ builtins: gunk.builtins,
82
+ map: gunk.app.map,
83
+ mapImport: gunk.app.mapImport,
84
+ symbol: gunk.app.symbol,
85
+ protocol: gunk.app.protocol,
86
+ getSync (url) {
87
+ const xhr = new XMLHttpRequest()
88
+ xhr.open('GET', url, false)
89
+ xhr.send(null)
90
+ return xhr.responseText
91
+ },
92
+ resolveSync (req, dirname, { isImport }) {
93
+ const xhr = new XMLHttpRequest()
94
+ const type = isImport ? 'esm' : 'cjs'
95
+ const url = `${dirname}/~${req}+resolve+${type}`
96
+ xhr.open('GET', url, false)
97
+ xhr.send(null)
98
+ if (xhr.status !== 200) throw new Error(`${xhr.status} ${xhr.responseText}`)
99
+ return xhr.responseText
100
+ }
101
+ })
102
+
103
+ async function warm () {
104
+ for await (const { batch, protocol } of Pear[Pear.constructor.UI].warming()) {
105
+ let sl = null
106
+ if (protocol === 'pear' || protocol === 'holepunch') sl = pltsl
107
+ if (protocol === 'app') sl = appsl
108
+ if (sl === null) continue
109
+ for (const { filename, source } of batch) sl.sources.set(filename, source)
110
+ }
111
+ }
112
+
113
+ if (Pear.config.isDecal === false) warm().catch(console.error)
114
+ }
115
+
46
116
  function descopeGlobals () {
47
117
  delete global.require
48
118
  delete global.module
@@ -50,21 +120,7 @@ module.exports = (state) => {
50
120
  delete global.__filename
51
121
  }
52
122
 
53
- const { warn } = console
54
- console.warn = (msg, ...args) => {
55
- if (/Insecure Content-Security-Policy/.test(msg)) return
56
- warn.call(console, msg, ...args)
57
- }
58
-
59
- if (Pear.config.options.gui?.preload) {
60
- const run = require('node-bare-bundle')
61
- const key = Pear.config.options.gui.preload
62
- Pear.get(key, { bundle: true }).then((preload) => {
63
- run(preload, { mount: '/', entrypoint: key })
64
- }, console.error).finally(descopeGlobals)
65
- } else {
66
- descopeGlobals()
67
- }
123
+ descopeGlobals()
68
124
 
69
125
  customElements.define('pear-ctrl', class extends HTMLElement {
70
126
  #onfocus = null
package/runtime.js CHANGED
@@ -8,11 +8,11 @@ const Pipe = require('bare-pipe')
8
8
  const { spawn } = require('bare-subprocess')
9
9
  const env = require('bare-env')
10
10
  const { command } = require('paparam')
11
- const { isLinux, isWindows } = require('which-runtime')
11
+ const { isLinux, isWindows, isMac } = require('which-runtime')
12
12
  const { pathToFileURL, fileURLToPath } = require('url-file-url')
13
13
  const constants = require('pear-api/constants')
14
14
  const parseLink = require('pear-api/parse-link')
15
- const { ERR_INVALID_INPUT } = require('pear-api/errors')
15
+ const { ERR_INVALID_INPUT, ERR_INVALID_APPLING } = require('pear-api/errors')
16
16
  const run = require('pear-api/cmd/run')
17
17
  const pear = require('pear-api/cmd')
18
18
  const EXEC = isWindows
@@ -43,8 +43,7 @@ class PearElectron {
43
43
  const isFile = link.startsWith('file://')
44
44
  const isPath = isPear === false && isFile === false
45
45
 
46
- let cwd = os.cwd()
47
- const originalCwd = cwd
46
+ const cwd = os.cwd()
48
47
  let dir = cwd
49
48
  let base = null
50
49
  if (key === null) {
@@ -53,12 +52,7 @@ class PearElectron {
53
52
  } catch { /* ignore */ }
54
53
  base = project(dir, pathname, cwd)
55
54
  dir = base.dir
56
- if (dir !== cwd) {
57
- global.Bare.on('exit', () => os.chdir(originalCwd)) // TODO: remove this once Pear.shutdown is used to close
58
- Pear.teardown(() => os.chdir(originalCwd))
59
- os.chdir(dir)
60
- cwd = dir
61
- }
55
+ if (dir.length > 1 && dir.endsWith('/')) dir = dir.slice(0, -1)
62
56
  if (isPath) {
63
57
  link = pathToFileURL(path.join(dir, base.entrypoint || '/')).pathname
64
58
  }
@@ -66,20 +60,35 @@ class PearElectron {
66
60
 
67
61
  if (isPath) argv[indices.args.link] = 'file://' + (base.entrypoint || '/')
68
62
  argv[indices.args.link] = argv[indices.args.link].replace('://', '_||') // for Windows
63
+
69
64
  if ((isLinux || isWindows) && !flags.sandbox) argv.splice(indices.args.link, 0, '--no-sandbox')
70
65
  const info = JSON.stringify({
71
66
  checkout: constants.CHECKOUT,
72
67
  mount: constants.MOUNT,
73
- bridge: opts.bridge?.addr ?? undefined
68
+ bridge: opts.bridge?.addr ?? undefined,
69
+ dir
74
70
  })
75
- argv = [BOOT, '--runtime-info', info, ...argv]
71
+ argv = [BOOT, '--rti', info, ...argv]
76
72
  const stdio = args.detach ? 'ignore' : ['ignore', 'inherit', 'pipe', 'pipe']
77
- const sp = spawn(this.bin, argv, {
73
+ const options = {
78
74
  stdio,
79
75
  cwd,
80
76
  windowsHide: true,
81
77
  ...{ env: { ...env, NODE_PRESERVE_SYMLINKS: 1 } }
82
- })
78
+ }
79
+
80
+ const sp = spawn(this.bin, argv, options)
81
+ if (args.appling) {
82
+ const { appling } = args
83
+ const applingApp = isMac ? appling.split('.app')[0] + '.app' : appling
84
+ try {
85
+ fs.statSync(applingApp)
86
+ } catch {
87
+ throw ERR_INVALID_APPLING('Appling does not exist')
88
+ }
89
+ if (isMac) spawn('open', [applingApp, '--args', ...argv], options).unref()
90
+ else spawn(applingApp, argv, options).unref()
91
+ }
83
92
  if (args.detach) return null
84
93
  const pipe = sp.stdio[3]
85
94
  sp.on('exit', (code) => { Pear.exit(code) })