Merge pull request #383 from ThaUnknown/node-js-mobile

feat: node js mobile
This commit is contained in:
Cas_ 2023-12-07 16:04:51 +01:00 committed by GitHub
commit df3fc91704
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 660 additions and 2114 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "capacitor/git_modules/nodejs-mobile"]
path = capacitor/git_modules/nodejs-mobile
url = https://github.com/nodejs-mobile/nodejs-mobile

View file

@ -7,13 +7,6 @@ android/app/src/*
!android/app/src/main
android/app/src/main/*
!android/app/src/main/AndroidManifest.xml
!android/app/src/main/java
android/app/src/main/java/*
!android/app/src/main/java/watch
android/app/src/main/java/watch/*
!android/app/src/main/java/watch/miru
android/app/src/main/java/watch/miru/*
!android/app/src/main/java/watch/miru/MainActivity.java
ios/
package-lock.json

View file

@ -1,31 +0,0 @@
package watch.miru;
import android.os.Bundle;
import android.webkit.ServiceWorkerClient;
import android.webkit.ServiceWorkerController;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {
// this isn't generated! required patch to be able to register service workers
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
ServiceWorkerController swController = null;
swController = ServiceWorkerController.getInstance();
swController.setServiceWorkerClient(new ServiceWorkerClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
if (request.getUrl().toString().contains("index.html")) {
request.getRequestHeaders().put("Accept", "text/html");
}
return bridge.getLocalServer().shouldInterceptRequest(request);
}
});
}
}
}

View file

@ -4,7 +4,6 @@ const config = {
appId: 'watch.miru',
appName: 'Miru',
webDir: 'build',
bundledWebRuntime: false,
android: {
buildOptions: {
keystorePath: './watch.miru',
@ -14,7 +13,11 @@ const config = {
},
plugins: {
SplashScreen: { launchShowDuration: 0 },
CapacitorHttp: { enabled: false }
CapacitorHttp: { enabled: false },
CapacitorNodeJS: { nodeDir: 'nodejs' }
},
server: {
cleartext: true
}
}

48
capacitor/config-node.sh Normal file
View file

@ -0,0 +1,48 @@
#!/bin/bash
# instructions
# Install Android NDK
# Set $ANDROID_NDK_PATH (example: ~/Android/Sdk/ndk/26.1.10909125)
# install other npm packages like normal
# run this script
toolchain_target_arch=aarch64
node_target_arch=arm64
android_api_level=22
toolchain_folder=$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin
export LIBNODE_PATH=$(pwd)/git_modules/nodejs-mobile
export PATH=$toolchain_folder:$PATH
export CC=$toolchain_folder/${toolchain_target_arch}-linux-android${android_api_level}-clang
export CXX=$toolchain_folder/${toolchain_target_arch}-linux-android${android_api_level}-clang++
export LINK=$toolchain_folder/${toolchain_target_arch}-linux-android${android_api_level}-clang++
export AR=$toolchain_folder/llvm-ar
export npm_config_verbose=1
export npm_config_nodedir=${LIBNODE_PATH}
export npm_config_node_gyp=$(pwd)/../node_modules/nodejs-mobile-gyp/bin/node-gyp.js
export npm_config_arch=${node_target_arch}
export npm_config_plaform=android
export npm_config_format=make-android
export npm_gyp_defines="target_arch=$node_target_arch v8_target_arch=$node_target_arch android_target_arch=$node_target_arch host_os=linux OS=android"
#mv node_modules ../node_modules.bak
# --from-from-source is used by node-pre-gyp
cd public/nodejs
npm rebuild --build-from-source
# Remove executable permissions from native node modules
# find node_modules -iname '*.node' -exec chmod -x '{}' \;
# buildroot=$(pwd)/ncc
# target=arm64-android
# buildpath="${buildroot}/${target}"
# if [ ! -d "${buildpath}" ]; then
# mkdir -p "${buildpath}"
# fi
# ncc build --source-map -d --asset-builds --target es2022 -o ${buildpath} src/offline.ts
# rm -rf node_modules
# mv ../node_modules.bak node_modules

View file

@ -2,6 +2,7 @@
"name": "capacitor",
"private": true,
"scripts": {
"postinstall": "cd public/nodejs/ && npm install",
"build:app": "cross-env NODE_ENV=production run-s build:web build:assets build:android",
"build:web": "webpack build",
"build:android": "cap build android",
@ -18,21 +19,14 @@
},
"devDependencies": {
"@capacitor/assets": "^3.0.1",
"assert": "^2.1.0",
"buffer": "^6.0.3",
"chrome-dgram": "^3.0.6",
"chrome-net": "^3.3.4",
"cordova-res": "^0.15.4",
"crypto-browserify": "^3.12.0",
"hybrid-chunk-store": "^1.2.2",
"nodejs-mobile-gyp": "^0.3.1",
"npm-run-all": "^4.1.5",
"path-esm": "^1.0.0",
"querystring": "^0.2.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"timers-browserify": "^2.0.12",
"util": "^0.12.5",
"webpack-cli": "^5.1.4"
"webpack-cli": "^5.1.4",
"webpack-merge": "^5.10.0"
},
"dependencies": {
"@capacitor/android": "^5.5.1",
@ -42,17 +36,11 @@
"@capacitor/core": "^5.5.1",
"@capacitor/ios": "^5.5.1",
"@capacitor/status-bar": "^5.0.6",
"@superfrogbe/cordova-plugin-chrome-apps-sockets-udp": "github:superfrogbe/cordova-plugin-chrome-apps-sockets-udp",
"capacitor-default-gateway": "github:funniray/capacitor-default-gateway",
"capacitor-dns": "github:funniray/capacitor-dns",
"capacitor-os-interfaces-hack": "github:funniray/capacitor-os-interfaces-hack",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.6/capacitor-nodejs.tgz",
"capacitor-plugin-safe-area": "^2.0.5",
"common": "workspace:*",
"cordova-plugin-chrome-apps-common": "^1.0.7",
"cordova-plugin-chrome-apps-sockets-tcp": "github:KoenLav/cordova-plugin-chrome-apps-sockets-tcp",
"cordova-plugin-navigationbar": "^1.0.31",
"cordova-plugin-screen-orientation": "^3.0.4",
"es6-promise-plugin": "^4.2.2",
"webpack-merge": "^5.10.0"
"es6-promise-plugin": "^4.2.2"
}
}

View file

@ -0,0 +1,9 @@
{
"name": "nodejs-capacitor-thread",
"private": true,
"dependencies": {
"utp-native": "^2.5.3"
},
"version": "1.0.0",
"main": "./index.js"
}

View file

@ -3,9 +3,9 @@ import { StatusBar, Style } from '@capacitor/status-bar'
import { SafeArea } from 'capacitor-plugin-safe-area'
import { App } from '@capacitor/app'
import { Browser } from '@capacitor/browser'
import { ipcRendererUI, main } from './ipc.js'
// import { ipcRendererUI, main } from './ipc.js'
main.on('open', url => Browser.open({ url }))
// main.on('open', url => Browser.open({ url }))
App.addListener('appUrlOpen', ({ url }) => handleProtocol(url))

View file

@ -1,466 +0,0 @@
// @ts-nocheck
/*! chrome-dgram. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/* global chrome */
/**
* UDP / Datagram Sockets
* ======================
*
* Datagram sockets are available through require('chrome-dgram').
*/
const EventEmitter = require('events')
const series = require('run-series')
const BIND_STATE_UNBOUND = 0
const BIND_STATE_BINDING = 1
const BIND_STATE_BOUND = 2
// Track open sockets to route incoming data (via onReceive) to the right handlers.
const sockets = {}
const noop = () => {}
// Thorough check for Chrome App since both Edge and Chrome implement dummy chrome object
if (typeof chrome?.sockets?.udp === 'object') {
chrome.sockets.udp.onReceive.addListener(onReceive)
chrome.sockets.udp.onReceiveError.addListener(onReceiveError)
}
function onReceive (info) {
if (info.socketId in sockets) {
sockets[info.socketId]._onReceive(info)
} else {
console.error('Unknown socket id: ' + info.socketId)
}
}
function onReceiveError (info) {
if (info.socketId in sockets) {
sockets[info.socketId]._onReceiveError(info.resultCode)
} else {
console.error('Unknown socket id: ' + info.socketId)
}
}
/**
* dgram.createSocket(type, [callback])
*
* Creates a datagram Socket of the specified types. Valid types are `udp4`
* and `udp6`.
*
* Takes an optional callback which is added as a listener for message events.
*
* Call socket.bind if you want to receive datagrams. socket.bind() will bind
* to the "all interfaces" address on a random port (it does the right thing
* for both udp4 and udp6 sockets). You can then retrieve the address and port
* with socket.address().address and socket.address().port.
*
* @param {string} type Either 'udp4' or 'udp6'
* @param {function} listener Attached as a listener to message events.
* Optional
* @return {Socket} Socket object
*/
exports.createSocket = function (type, listener) {
return new Socket(type, listener)
}
/**
* Class: dgram.Socket
*
* The dgram Socket class encapsulates the datagram functionality. It should
* be created via `dgram.createSocket(type, [callback])`.
*
* Event: 'message'
* - msg Buffer object. The message
* - rinfo Object. Remote address information
* Emitted when a new datagram is available on a socket. msg is a Buffer and
* rinfo is an object with the sender's address information and the number
* of bytes in the datagram.
*
* Event: 'listening'
* Emitted when a socket starts listening for datagrams. This happens as soon
* as UDP sockets are created.
*
* Event: 'close'
* Emitted when a socket is closed with close(). No new message events will
* be emitted on this socket.
*
* Event: 'error'
* - exception Error object
* Emitted when an error occurs.
*/
class Socket extends EventEmitter {
constructor (options, listener) {
super()
if (typeof options === 'string') options = { type: options }
if (options.type !== 'udp4' && options.type !== 'udp6') throw new Error('Bad socket type specified. Valid types are: udp4')
if (typeof listener === 'function') this.on('message', listener)
this._destroyed = false
this._bindState = BIND_STATE_UNBOUND
this._bindTasks = []
}
/**
* socket.bind(port, [address], [callback])
*
* For UDP sockets, listen for datagrams on a named port and optional address.
* If address is not specified, the OS will try to listen on all addresses.
* After binding is done, a "listening" event is emitted and the callback(if
* specified) is called. Specifying both a "listening" event listener and
* callback is not harmful but not very useful.
*
* A bound datagram socket keeps the node process running to receive
* datagrams.
*
* If binding fails, an "error" event is generated. In rare case (e.g. binding
* a closed socket), an Error may be thrown by this method.
*
* @param {number} port
* @param {string} address Optional
* @param {function} callback Function with no parameters, Optional. Callback
* when binding is done.
*/
bind (port, address, callback) {
if (typeof address === 'function') {
callback = address
address = undefined
}
if (!address) address = '0.0.0.0'
if (!port) port = 0
if (this._bindState !== BIND_STATE_UNBOUND) throw new Error('Socket is already bound')
this._bindState = BIND_STATE_BINDING
if (typeof callback === 'function') this.once('listening', callback)
chrome.sockets.udp.create(createInfo => {
this.id = createInfo.socketId
sockets[this.id] = this
const bindFns = this._bindTasks.map(({ fn }) => fn)
series(bindFns, err => {
if (err) return this.emit('error', err)
chrome.sockets.udp.bind(this.id, address, port, result => {
if (result < 0) {
this.emit('error', new Error('Socket ' + this.id + ' failed to bind. ' +
chrome.runtime.lastError.message))
return
}
chrome.sockets.udp.getInfo(this.id, socketInfo => {
if (!socketInfo.localPort || !socketInfo.localAddress) {
this.emit('error', new Error('Cannot get local port/address for Socket ' + this.id))
return
}
this._port = socketInfo.localPort
this._address = socketInfo.localAddress
this._bindState = BIND_STATE_BOUND
this.emit('listening')
for (const t of this._bindTasks) {
t.callback()
}
})
})
})
})
}
/**
* Internal function to receive new messages and emit `message` events.
*/
_onReceive (info) {
const buf = Buffer.from(new Uint8Array(info.data))
const rinfo = {
address: info.remoteAddress,
family: 'IPv4',
port: info.remotePort,
size: buf.length
}
this.emit('message', buf, rinfo)
}
_onReceiveError (resultCode) {
this.emit('error', new Error('Socket ' + this.id + ' receive error ' + resultCode))
}
/**
* socket.send(buf, offset, length, port, address, [callback])
*
* For UDP sockets, the destination port and IP address must be
* specified. A string may be supplied for the address parameter, and it will
* be resolved with DNS. An optional callback may be specified to detect any
* DNS errors and when buf may be re-used. Note that DNS lookups will delay
* the time that a send takes place, at least until the next tick. The only
* way to know for sure that a send has taken place is to use the callback.
*
* If the socket has not been previously bound with a call to bind, it's
* assigned a random port number and bound to the "all interfaces" address
* (0.0.0.0 for udp4 sockets, ::0 for udp6 sockets).
*
* @param {Buffer|Arrayish|string} buf Message to be sent
* @param {number} offset Offset in the buffer where the message starts. Optional.
* @param {number} length Number of bytes in the message. Optional.
* @param {number} port destination port
* @param {string} address destination IP
* @param {function} callback Callback when message is done being delivered.
* Optional.
*
* Valid combinations:
* send(buffer, offset, length, port, address, callback)
* send(buffer, offset, length, port, address)
* send(buffer, offset, length, port)
* send(bufferOrList, port, address, callback)
* send(bufferOrList, port, address)
* send(bufferOrList, port)
*
*/
send (buffer, offset, length, port, address, callback = noop) {
let list
const cb = callback || noop
if (address || (port && typeof port !== 'function')) {
buffer = this.sliceBuffer(buffer, offset, length)
} else {
callback = port
port = offset
address = length
}
if (!Array.isArray(buffer)) {
if (typeof buffer === 'string') {
list = [Buffer.from(buffer)]
} else if (!(buffer instanceof Uint8Array)) {
throw new TypeError('First argument must be a buffer or a string')
} else {
list = [Buffer.from(buffer)]
}
} else if (!(list = this.fixBufferList(buffer))) {
throw new TypeError('Buffer list arguments must be buffers or strings')
}
port = port >>> 0
if (port === 0 || port > 65535) {
throw new RangeError('Port should be > 0 and < 65536')
}
if (this._bindState === BIND_STATE_UNBOUND) this.bind(0)
// If the socket hasn't been bound yet, push the outbound packet onto the
// send queue and send after binding is complete.
if (this._bindState !== BIND_STATE_BOUND) {
// If the send queue hasn't been initialized yet, do it, and install an
// event handler that flishes the send queue after binding is done.
if (!this._sendQueue) {
this._sendQueue = []
this.once('listening', () => {
// Flush the send queue.
for (let i = 0; i < this._sendQueue.length; i++) {
this.send.apply(this, this._sendQueue[i])
}
this._sendQueue = undefined
})
}
this._sendQueue.push([buffer, offset, length, port, address, callback])
return
}
const ab = Buffer.concat(list).buffer
chrome.sockets.udp.send(this.id, ab, address, port, sendInfo => {
if (sendInfo.resultCode < 0) {
const err = new Error('Socket ' + this.id + ' send error ' + sendInfo.resultCode)
cb(err)
this.emit('error', err)
} else {
cb(null)
}
})
}
sliceBuffer (buffer, offset, length) {
buffer = Buffer.from(buffer)
offset = offset >>> 0
length = length >>> 0
// assuming buffer is browser implementation (`buffer` package on npm)
let buf = buffer.buffer
if (buffer.byteOffset || buffer.byteLength !== buf.byteLength) {
buf = buf.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
}
if (offset || length !== buffer.length) {
buf = buf.slice(offset, length)
}
return Buffer.from(buf)
}
fixBufferList (list) {
const newlist = new Array(list.length)
for (let i = 0, l = list.length; i < l; i++) {
const buf = list[i]
if (typeof buf === 'string') {
newlist[i] = Buffer.from(buf)
} else if (!(buf instanceof Uint8Array)) {
return null
} else {
newlist[i] = Buffer.from(buf)
}
}
return newlist
}
/**
* Close the underlying socket and stop listening for data on it.
*/
close () {
if (this._destroyed) return
delete sockets[this.id]
chrome.sockets.udp.close(this.id)
this._destroyed = true
this.emit('close')
}
/**
* Returns an object containing the address information for a socket. For UDP
* sockets, this object will contain address, family and port.
*
* @return {Object} information
*/
address () {
return {
address: this._address,
port: this._port,
family: 'IPv4'
}
}
setBroadcast (flag) {
// No chrome.sockets equivalent
}
setTTL (ttl) {
// No chrome.sockets equivalent
}
// NOTE: Multicast code is untested. Pull requests accepted for bug fixes and to
// add tests!
/**
* Sets the IP_MULTICAST_TTL socket option. TTL stands for "Time to Live," but
* in this context it specifies the number of IP hops that a packet is allowed
* to go through, specifically for multicast traffic. Each router or gateway
* that forwards a packet decrements the TTL. If the TTL is decremented to 0
* by a router, it will not be forwarded.
*
* The argument to setMulticastTTL() is a number of hops between 0 and 255.
* The default on most systems is 1.
*
* NOTE: The Chrome version of this function is async, whereas the node
* version is sync. Keep this in mind.
*
* @param {number} ttl
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
* operation is done.
*/
setMulticastTTL (ttl, callback = noop) {
const setMulticastTTL = callback => {
chrome.sockets.udp.setMulticastTimeToLive(this.id, ttl, callback)
}
if (this._bindState === BIND_STATE_BOUND) {
setMulticastTTL(callback)
} else {
this._bindTasks.push({
fn: setMulticastTTL,
callback
})
}
}
/**
* Sets or clears the IP_MULTICAST_LOOP socket option. When this option is
* set, multicast packets will also be received on the local interface.
*
* NOTE: The Chrome version of this function is async, whereas the node
* version is sync. Keep this in mind.
*
* @param {boolean} flag
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
* operation is done.
*/
setMulticastLoopback (flag, callback = noop) {
const setMulticastLoopback = callback => {
chrome.sockets.udp.setMulticastLoopbackMode(this.id, flag, callback)
}
if (this._bindState === BIND_STATE_BOUND) {
setMulticastLoopback(callback)
} else {
this._bindTasks.push({
fn: setMulticastLoopback,
callback
})
}
}
/**
* Tells the kernel to join a multicast group with IP_ADD_MEMBERSHIP socket
* option.
*
* If multicastInterface is not specified, the OS will try to add membership
* to all valid interfaces.
*
* NOTE: The Chrome version of this function is async, whereas the node
* version is sync. Keep this in mind.
*
* @param {string} multicastAddress
* @param {string} [multicastInterface] Optional
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
* operation is done.
*/
addMembership (multicastAddress, multicastInterface, callback = noop) {
chrome.sockets.udp.joinGroup(this.id, multicastAddress, callback)
}
/**
* Opposite of addMembership - tells the kernel to leave a multicast group
* with IP_DROP_MEMBERSHIP socket option. This is automatically called by the
* kernel when the socket is closed or process terminates, so most apps will
* never need to call this.
*
* NOTE: The Chrome version of this function is async, whereas the node
* version is sync. Keep this in mind.
*
* If multicastInterface is not specified, the OS will try to drop membership
* to all valid interfaces.
*
* @param {[type]} multicastAddress
* @param {[type]} multicastInterface Optional
* @param {function} callback CHROME-SPECIFIC: Called when the configuration
* operation is done.
*/
dropMembership (multicastAddress, multicastInterface, callback = noop) {
chrome.sockets.udp.leaveGroup(this.id, multicastAddress, callback)
}
unref () {
// No chrome.sockets equivalent
}
ref () {
// No chrome.sockets equivalent
}
}
exports.Socket = Socket

File diff suppressed because it is too large Load diff

View file

@ -1,9 +0,0 @@
import { gateway4async } from 'capacitor-default-gateway'
let v4Gateway
export const v4 = () => {}
v4.sync = () => v4Gateway
export const ready = gateway4async().then(gateway => {
v4Gateway = gateway
})

View file

@ -1,47 +1,45 @@
import EventEmitter from 'events'
import { ready } from './gateway.js'
import { NodeJS } from 'capacitor-nodejs'
export const ipcRendererUI = new EventEmitter()
export const main = new EventEmitter()
let portListener
const ready = NodeJS.whenReady()
export default {
emit: (event, data) => {
main.emit(event, data)
emit: async (event, data) => {
if (event === 'portRequest') return portRequest()
await ready
NodeJS.send({ eventName: event, args: [data] })
},
on: (event, callback) => {
ipcRendererUI.on(event, (...args) => callback(...args))
on: async (event, callback) => {
NodeJS.addListener(event, ({ args }) => callback(...args))
await ready
},
once: (event, callback) => {
ipcRendererUI.once(event, (...args) => callback(...args))
once: async (event, callback) => {
if (event === 'port') portListener = callback
await ready
const handle = NodeJS.addListener(event, ({ args }) => {
NodeJS.removeListener(handle)
callback(...args)
})
},
off: event => {
ipcRendererUI.removeAllListeners(event)
NodeJS.removeAllListeners(event)
}
}
main.on('portRequest', portRequest)
async function portRequest (data) {
const { port1, port2 } = new MessageChannel()
globalThis.port = {
onmessage: cb => {
port2.onmessage = ({ type, data }) => cb({ type, data })
NodeJS.addListener('ipc', ({ args }) => cb(args[0]))
},
postMessage: (a, b) => {
port2.postMessage(a, b)
postMessage: (data, b) => {
NodeJS.send({ eventName: 'ipc', args: [{ data }] })
}
}
await globalThis.controller
await globalThis.prefetchNetworkInterfaces
await ready
await new Promise(resolve => setTimeout(() => resolve(), 50))
ipcRendererUI.emit('port', { ports: [port2] })
ipcRendererWebTorrent.emit('port', { ports: [port1] })
portListener()
NodeJS.send({ eventName: 'port-init', args: [localStorage.getItem('settings')] })
}
export const ipcRendererWebTorrent = new EventEmitter()
const [_platform, arch] = navigator.platform.split(' ')
globalThis.version = {

View file

@ -1,2 +1,31 @@
import './webtorrent.js'
import './capacitor.js'
import TorrentClient from 'common/modules/webtorrent.js'
import { channel } from 'bridge'
async function storageQuota (directory) {
return Infinity
}
if (typeof localStorage === 'undefined') {
const data = {}
globalThis.localStorage = {
setItem: (k,v) => { data[k] = v },
getItem: (k) => data[k] || null
}
}
channel.on('port-init', data => {
localStorage.setItem('settings', data)
const port = {
onmessage: _ => {},
postMessage: data => {
channel.send('ipc', { data })
}
}
channel.on('ipc', console.log)
channel.on('ipc', a => port.onmessage(a))
channel.emit('port', ({
ports: [port]
}))
})
globalThis.client = new TorrentClient(channel, storageQuota, 'node')

View file

@ -4,10 +4,10 @@ export const SUPPORTS = {
offscreenRender: false,
update: false,
angle: false,
doh: false,
doh: true,
dht: true,
discord: false,
torrentPort: false,
torrentPort: true,
torrentPath: false,
torrentPersist: false
torrentPersist: true
}

View file

@ -1,34 +0,0 @@
import TorrentClient from 'common/modules/webtorrent.js'
import { ipcRendererWebTorrent } from './ipc.js'
import { prefetchNetworkInterfaces } from 'os'
import { ready } from './gateway.js'
globalThis.chrome.runtime = { lastError: false, id: 'something' }
const controller = (async () => {
const reg = await navigator.serviceWorker.register(new URL('webtorrent/dist/sw.min.js', import.meta.url))
const worker = reg.active || reg.waiting || reg.installing
if (!worker) throw new Error('No worker registration')
return new Promise(resolve => {
function checkState ({ state }) {
if (state === 'activated') {
resolve(reg)
return true
}
}
if (!checkState(worker)) worker.addEventListener('statechange', ({ target }) => checkState(target))
})
})()
globalThis.prefetchNetworkInterfaces = await prefetchNetworkInterfaces()
globalThis.controller = controller
async function storageQuota () {
const { quota, usage } = await navigator.storage.estimate()
return quota - usage
}
await prefetchNetworkInterfaces
await ready
globalThis.client = new TorrentClient(ipcRendererWebTorrent, storageQuota, 'browser', controller, { torrentPort: Math.floor(Math.random() * 65535 + 1) })

View file

@ -1,63 +1,54 @@
const webpack = require('webpack')
const commonConfig = require('common/webpack.config.cjs')
const { merge } = require('webpack-merge')
const { join, resolve } = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const mode = process.env.NODE_ENV?.trim() || 'development'
/** @type {import('webpack').Configuration} */
const capacitorConfig = {
devtool: 'source-map',
entry: [join(__dirname, 'src', 'main.js')],
output: {
path: join(__dirname, 'build', 'nodejs'),
filename: 'index.js'
},
mode,
plugins: [
new webpack.ProvidePlugin({
process: 'webtorrent/polyfills/process-fast.js',
Buffer: ['buffer', 'Buffer']
}),
new webpack.DefinePlugin({ global: 'globalThis' })
],
externals: {
'utp-native': 'require("utp-native")',
bridge: 'require("bridge")'
},
resolve: {
aliasFields: [],
mainFields: ['module', 'main', 'node'],
alias: {
wrtc: false,
'bittorrent-tracker/lib/client/http-tracker.js': resolve('../node_modules/bittorrent-tracker/lib/client/http-tracker.js')
}
},
target: 'node',
devServer: {
devMiddleware: { writeToDisk: true },
devMiddleware: {
writeToDisk: true
},
hot: true,
client: {
overlay: { errors: true, warnings: false, runtimeErrors: false }
},
port: 5001
}
}
const alias = {
fs: false,
ws: false,
'default-gateway': join(__dirname, 'src', 'gateway.js'),
'load-ip-set': false,
'node-fetch': false,
'webtorrent/lib/utp.cjs': false,
'@/modules/ipc.js': join(__dirname, 'src', 'ipc.js'),
'@/modules/support.js': join(__dirname, 'src', 'support.js'),
net: join(__dirname, 'src', 'chrome-net.js'),
dgram: join(__dirname, 'src', 'chrome-dgram.js'),
os: 'capacitor-os-interfaces-hack',
dns: 'capacitor-dns',
http: 'stream-http',
https: 'stream-http',
'utp-native': false,
socks: false,
assert: 'assert',
ut_pex: 'ut_pex',
path: 'path-esm',
'fs-chunk-store': 'hybrid-chunk-store',
stream: 'stream-browserify',
timers: 'timers-browserify',
crypto: 'crypto-browserify',
buffer: 'buffer',
'@silentbot1/nat-api': '@silentbot1/nat-api',
'bittorrent-tracker': 'bittorrent-tracker',
querystring: 'querystring',
zlib: 'webtorrent/polyfills/inflate-sync-web.js',
'bittorrent-tracker/server.js': false,
'cross-fetch-ponyfill': resolve('../node_modules/cross-fetch-ponyfill/browser.js'),
'abort-controller': false,
'bittorrent-tracker/lib/client/http-tracker.js': resolve('../node_modules/bittorrent-tracker/lib/client/http-tracker.js')
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: join(__dirname, 'public', 'nodejs') }
]
}),
]
}
module.exports = merge(commonConfig(__dirname, alias, 'node', 'index'), capacitorConfig)
const alias = {
'@/modules/ipc.js': join(__dirname, 'src', 'ipc.js'),
'@/modules/support.js': join(__dirname, 'src', 'support.js')
}
module.exports = [capacitorConfig, merge(commonConfig(__dirname, alias, 'browser', 'index'), { entry: [join(__dirname, 'src', 'capacitor.js')] })]

View file

@ -49,7 +49,7 @@ class TorrentWorker extends EventTarget {
export const client = new TorrentWorker()
client.send('load')
client.send('load', localStorage.getItem('torrent'))
client.on('files', ({ detail }) => {
files.set(detail)
@ -68,6 +68,7 @@ client.on('warn', ({ detail }) => {
export async function add (torrentID, hide) {
if (torrentID) {
console.info('Torrent: adding torrent', { torrentID })
localStorage.setItem('torrent', torrentID)
files.set([])
if (!hide) page.set('player')
client.send('torrent', torrentID)

View file

@ -12,7 +12,7 @@ export function countdown (s) {
return tmp.join(' ')
}
const formatter = new Intl.RelativeTimeFormat('en')
const formatter = (typeof Intl !== 'undefined') && new Intl.RelativeTimeFormat('en')
const ranges = {
years: 3600 * 24 * 365,
months: 3600 * 24 * 30,

View file

@ -33,7 +33,7 @@ try {
export default class TorrentClient extends WebTorrent {
static excludedErrorMessages = ['WebSocket', 'User-Initiated Abort, reason=', 'Connection failed.']
constructor (ipc, storageQuota, serverMode, controller, settingOverrides = {}) {
constructor (ipc, storageQuota, serverMode, settingOverrides = {}, controller) {
const settings = { ...defaults, ...storedSettings, ...settingOverrides }
super({
dht: !settings.torrentDHT,
@ -46,12 +46,12 @@ export default class TorrentClient extends WebTorrent {
this._ready = new Promise(resolve => {
ipc.on('port', ({ ports }) => {
this.message = ports[0].postMessage.bind(ports[0])
resolve()
ports[0].onmessage = ({ data }) => {
if (data.type === 'load') this.loadLastTorrent()
if (data.type === 'load') this.loadLastTorrent(data.data)
if (data.type === 'destroy') this.destroy()
this.handleMessage({ data })
}
resolve()
})
ipc.on('destroy', this.destroy.bind(this))
})
@ -93,8 +93,8 @@ export default class TorrentClient extends WebTorrent {
this.on('error', this.dispatchError.bind(this))
}
loadLastTorrent () {
const torrent = localStorage.getItem('torrent')
loadLastTorrent (t) {
const torrent = localStorage.getItem('torrent') || t
if (torrent) this.addTorrent(new Uint8Array(JSON.parse(torrent)), JSON.parse(localStorage.getItem('lastFinished')))
}
@ -209,6 +209,8 @@ export default class TorrentClient extends WebTorrent {
switch (data.type) {
case 'current': {
if (data.data) {
console.log('adding torrent')
console.log(data.data)
const torrent = await this.get(data.data.infoHash)
const found = torrent?.files.find(file => file.path === data.data.path)
if (!found) return

View file

@ -31,5 +31,8 @@
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webtorrent": "^2.1.29"
},
"devDependencies": {
"npm-run-all": "^4.1.5"
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
packages:
- './*'