@quasar/app-webpack
Version:
Quasar Framework App CLI with Webpack
359 lines (291 loc) • 10.2 kB
JavaScript
const fs = require('node:fs')
const { basename, dirname, join } = require('node:path')
const { globSync } = require('tinyglobby')
const { stringifyJSON, parseJSON } = require('confbox')
const { log, warn } = require('../../utils/logger.js')
const { ensureConsistency } = require('./ensure-consistency.js')
// necessary for Capacitor 4+
const { getPackageJson } = require('../../utils/get-package-json.js')
// for Capacitor 1-3
function getAndroidMainActivity (capVersion, appId) {
if (capVersion === 1) {
return `
package ${ appId };
import android.net.http.SslError;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class EnableHttpsSelfSigned {
public static void enable(WebView webview) {
webview.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
handler.proceed();
}
});
}
}`
}
// capVersion 2+
return `
package ${ appId };
import android.net.http.SslError;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import com.getcapacitor.Bridge;
import com.getcapacitor.BridgeWebViewClient;
public class EnableHttpsSelfSigned {
public static void enable(Bridge bridge) {
bridge.getWebView().setWebViewClient(new BridgeWebViewClient(bridge) {
@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
handler.proceed();
}
});
}
}`
}
const sslSkipVersion = {
4: '^0.2.0',
5: '^0.3.0',
6: '^0.4.0',
7: '^0.4.0',
default: '^0.4.0'
}
module.exports.CapacitorConfigFile = class CapacitorConfigFile {
#ctx
#tamperedFiles = []
async prepare (quasarConf, target) {
this.#ctx = quasarConf.ctx
const { appPaths, cacheProxy } = quasarConf.ctx
await ensureConsistency(this.#ctx)
this.#updateCapPkg(quasarConf)
log('Updated src-capacitor/package.json')
this.#tamperedFiles = []
// TODO: support other formats: .js and .ts
const capJsonPath = appPaths.resolve.capacitor('capacitor.config.json')
const capJson = parseJSON(
fs.readFileSync(capJsonPath, 'utf-8')
)
const { capVersion } = cacheProxy.getModule('capCli')
this.#tamperedFiles.push({
path: capJsonPath,
name: 'capacitor.config.json',
content: this.#updateCapJson(quasarConf, capJson, capVersion, target),
originalContent: stringifyJSON(capJson)
})
this.#save()
this.#updateSSL(quasarConf, target, capVersion)
}
reset () {
if (this.#tamperedFiles.length === 0) return
this.#tamperedFiles.forEach(file => {
file.content = file.originalContent
})
this.#save()
this.#tamperedFiles = []
}
#save () {
this.#tamperedFiles.forEach(file => {
fs.writeFileSync(file.path, file.content, 'utf8')
log(`Updated ${ file.name }`)
})
}
#updateCapJson (quasarConf, originalCapCfg, capVersion, target) {
const capJson = { ...originalCapCfg }
capJson.appName = quasarConf.capacitor.appName || this.#ctx.pkg.appPkg.productName || 'Quasar App'
if (capVersion < 5) {
capJson.bundledWebRuntime = false
}
if (quasarConf.ctx.dev) {
capJson.server = capJson.server || {}
capJson.server.url = quasarConf.metaConf.APP_URL
if (target === 'android' && capVersion >= 2) {
capJson.server.cleartext = true
}
}
else {
capJson.webDir = 'www'
// ensure we don't run from a remote server
if (capJson.server) {
delete capJson.server.url
delete capJson.server.cleartext
}
}
return stringifyJSON(capJson)
}
#updateCapPkg (quasarConf) {
const {
appPaths,
pkg: { appPkg }
} = this.#ctx
const capPkgPath = appPaths.resolve.capacitor('package.json')
const capPkg = parseJSON(
fs.readFileSync(capPkgPath, 'utf-8')
)
Object.assign(capPkg, {
name: quasarConf.capacitor.appName || appPkg.name,
version: quasarConf.capacitor.version || appPkg.version,
description: quasarConf.capacitor.description || appPkg.description,
author: appPkg.author
})
fs.writeFileSync(capPkgPath, stringifyJSON(capPkg), 'utf-8')
}
#updateSSL (quasarConf, target, capVersion) {
const { appPaths, cacheProxy } = this.#ctx
const add = quasarConf.ctx.dev ? quasarConf.devServer.server.type === 'https' : false
if (capVersion >= 4) {
const hasPlugin = getPackageJson('@jcesarmobile/ssl-skip', appPaths.capacitorDir) !== void 0
// nothing to do
if (add ? hasPlugin : !hasPlugin) return
const fn = `${ add ? '' : 'un' }installPackage`
const version = sslSkipVersion[ capVersion ] || sslSkipVersion.default
const nameParam = add ? `@jcesarmobile/ssl-skip@${ version }` : '@jcesarmobile/ssl-skip'
const nodePackager = cacheProxy.getModule('nodePackager')
nodePackager[ fn ](nameParam, {
cwd: appPaths.capacitorDir,
displayName: 'Capacitor (DEVELOPMENT ONLY) SSL support'
})
// make sure "cap sync" is run before triggering IDE or build
return
}
if (target === 'ios') {
this.#handleSSLonIOS(add)
}
else {
this.#handleSSLonAndroid(add, capVersion)
}
}
// for Capacitor 1-3
#handleSSLonIOS (add) {
const file = this.#getIosCapacitorBridgeFile()
const needle = 'public func getWebView() -> WKWebView {'
const content = `
// The following part was dynamically added by Quasar.
// This should NOT be part of the app when building for production,
// and it will be removed by Quasar automatically on "quasar build":
public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let cred = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, cred)
}
`
if (add) {
this.#injectIntoFile(file, needle, content)
}
else {
this.#removeFromFile(file, content)
}
}
// for Capacitor 1-3
#getIosCapacitorBridgeFile () {
const { appPaths } = this.#ctx
// we need to try multiple files because
// @capacitor/ios changed the location during its v2
const fileList = [
'node_modules/@capacitor/ios/ios/Capacitor/Capacitor/CAPBridgeViewController.swift',
'node_modules/@capacitor/ios/Capacitor/Capacitor/CAPBridgeViewController.swift'
]
for (let i = 0; i < fileList.length; i++) {
const file = appPaths.resolve.capacitor(fileList[ i ])
if (fs.existsSync(file)) {
return file
}
}
}
// for Capacitor 1-3
#injectIntoFile (file, needle, content) {
const sslWarn = () => {
const shortFilename = basename(file)
warn()
warn()
warn()
warn()
warn(`${ shortFilename } not found or content is unrecognized.`)
warn('Your App will revoke the devserver\'s SSL certificate.')
warn('Please disable HTTPS from quasar.config file > devServer > server > type: \'https\'')
warn()
warn()
warn()
warn()
}
if (!file) {
sslWarn()
return
}
const originalContent = fs.readFileSync(file, 'utf-8')
// it's already there
if (originalContent.indexOf(content) > -1) return
const index = originalContent.indexOf(needle)
if (index === -1) {
sslWarn()
return
}
const newContent = originalContent.substring(0, index) + content + originalContent.substring(index)
fs.writeFileSync(file, newContent, 'utf-8')
}
// for Capacitor 1-3
#removeFromFile (file, content) {
if (!file) return
const originalContent = fs.readFileSync(file, 'utf-8')
const index = originalContent.indexOf(content)
if (index > -1) {
const newContent = originalContent.replace(content, '')
fs.writeFileSync(file, newContent, 'utf-8')
}
}
// for Capacitor 1-3
#handleSSLonAndroid (add, capVersion) {
const { appPaths } = this.#ctx
const capacitorSrcPath = appPaths.resolve.capacitor('android/app/src/main/java')
let mainActivityPath = globSync('**/MainActivity.java', { cwd: capacitorSrcPath, absolute: true })
if (mainActivityPath.length > 0) {
if (mainActivityPath.length > 1) {
warn(`Found multiple matches for MainActivity.java file, https might not work. Using file ${ mainActivityPath[ 0 ] }.`)
}
mainActivityPath = mainActivityPath[ 0 ]
}
else if (mainActivityPath.length === 0) {
warn()
warn('IMPORTANT! Could not find MainActivity.java file and therefore cannot enable devServer > server > type: \'https\' support.')
warn()
return
}
const enableHttpsSelfSignedPath = join(dirname(mainActivityPath), 'EnableHttpsSelfSigned.java')
if (fs.existsSync(mainActivityPath)) {
let mainActivity = fs.readFileSync(mainActivityPath, 'utf8')
const sslString = `
if (BuildConfig.DEBUG) {
EnableHttpsSelfSigned.enable(${ capVersion === 1 ? 'findViewById(R.id.webview)' : 'this.bridge' });
}
`
if (add) {
// Allow unsigned certificates in MainActivity
if (!/EnableHttpsSelfSigned\.enable/.test(mainActivity)) {
mainActivity = mainActivity.replace(
/this\.init\(.*}}\);/ms,
match => `${ match }
${ sslString }
`
)
}
// Add helper file
if (!fs.existsSync(enableHttpsSelfSignedPath)) {
const appId = mainActivity.match(/package ([\w\.]*);/)[ 1 ]
fs.writeFileSync(
enableHttpsSelfSignedPath,
getAndroidMainActivity(capVersion, appId)
)
}
}
else {
if (/EnableHttpsSelfSigned\.enable/.test(mainActivity)) {
mainActivity = mainActivity.replace(sslString, '')
}
if (fs.existsSync(enableHttpsSelfSignedPath)) {
fs.unlinkSync(enableHttpsSelfSignedPath)
}
}
fs.writeFileSync(mainActivityPath, mainActivity, 'utf-8')
}
}
}