Skip to content

Commit

Permalink
Unedited Safari foundations
Browse files Browse the repository at this point in the history
  • Loading branch information
cezaraugusto committed Dec 15, 2024
1 parent 106c845 commit da1d07d
Show file tree
Hide file tree
Showing 11 changed files with 564 additions and 6 deletions.
2 changes: 1 addition & 1 deletion examples/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ const ALL_TEMPLATES_BUT_DEFAULT = ALL_TEMPLATES.filter(
(template) => template.name !== 'init'
)

const SUPPORTED_BROWSERS: string[] = ['chrome', 'edge', 'firefox']
const SUPPORTED_BROWSERS: string[] = ['chrome', 'edge', 'firefox', 'safari']

export {
SUPPORTED_BROWSERS,
Expand Down
97 changes: 92 additions & 5 deletions programs/develop/commands/commands-lib/config-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,22 @@ export interface GeckoOptions extends BrowserOptionsBase {
geckoBinary?: string
}

export interface WebKitOptions extends BrowserOptionsBase {
browser: 'gecko-based'
webKitBinary?: string
}

export interface NonBinaryOptions extends BrowserOptionsBase {
browser: Exclude<BrowserType, 'chromium-based' | 'gecko-based'>
browser: Exclude<
BrowserType,
'chromium-based' | 'gecko-based' | 'webkit-based'
>
}

export type ExtendedBrowserOptions =
| ChromiumOptions
| GeckoOptions
| WebKitOptions
| NonBinaryOptions

export interface DevOptions extends BrowserOptionsBase {
Expand All @@ -41,6 +50,7 @@ export interface DevOptions extends BrowserOptionsBase {
// Narrow down the options based on `browser`
chromiumBinary?: ChromiumOptions['chromiumBinary']
geckoBinary?: GeckoOptions['geckoBinary']
webKitBinary?: WebKitOptions['webKitBinary']
}

export interface BuildOptions {
Expand All @@ -56,31 +66,102 @@ export interface PreviewOptions extends BrowserOptionsBase {
mode: 'production'
chromiumBinary?: ChromiumOptions['chromiumBinary']
geckoBinary?: GeckoOptions['geckoBinary']
webKitBinary?: WebKitOptions['webKitBinary']
}

export interface StartOptions extends BrowserOptionsBase {
mode: 'production'
polyfill?: boolean
chromiumBinary?: ChromiumOptions['chromiumBinary']
geckoBinary?: GeckoOptions['geckoBinary']
webKitBinary?: WebKitOptions['webKitBinary']
}

export interface BrowserConfig extends BrowserOptionsBase {
browserFlags?: string[]
preferences?: Record<string, unknown>
chromiumBinary?: ChromiumOptions['chromiumBinary']
geckoBinary?: GeckoOptions['geckoBinary']
webKitBinary?: WebKitOptions['webKitBinary']
}

export interface XCodeConfig {
// Based off XCode's --project-location
// Save the generated app and Xcode project to the file path.
// Defaults to: xcode
projectLocation?: string

// Based off XCode's --rebuild-project
// Rebuild the existing Safari web extension Xcode project at the
// file path with different options or platforms. Use this option
// to add iOS to your existing macOS project.
// Defaults to: false
rebuildProject?: boolean

// Based off XCode's --app-name
// Use the value to name the generated app and the Xcode project.
// Defaults to: the name of the extension in the manifest.json file.
appName?: string

// Based off XCode's --bundle-identifier
// Use the value as the bundle identifier for the generated app.
// This identifier is unique to your app in your developer account.
// A reverse-DNS-style identifier is recommended (for example, com.company.extensionName).
// Defaults to: org.extensionjs.[extension_name]
bundleIdentifier?: string

// Based off XCode's --swift
// Use Swift in the generated app.
// Defaults to: true
swift?: boolean

// Based off XCode's --objc
// Use Objective-C in the generated app.
// Defaults to: false
objc?: boolean

// Based off XCode's --ios-only
// Create an iOS-only project.
// Defaults to: false
iosOnly?: boolean

// Based off XCode's --macos-only
// Create a macOS-only project.
// Defaults to: true
macosOnly?: boolean

// Based off XCode's --copy-resources
// Copy the extension files into the generated project.
// If you don’t specify this parameter, the project references
// the original extension files.
// Defaults to: false
copyResources?: boolean

// Based off XCode's --no-open
// Don’t open the generated Xcode project when complete.
// Defaults to: true
noOpen?: boolean

// Based off XCode's --no-prompt
// Don’t show the confirmation prompt.
// Defaults to: false
noPrompt?: boolean

// Based off XCode's --force
// Overwrite the output directory, if one exists.
// Defaults to: false
force?: boolean
}

export interface FileConfig {
browser?: {
chrome?: BrowserConfig
firefox?: BrowserConfig
edge?: BrowserConfig
safari?: BrowserConfig
safari?: BrowserConfig & {xcode?: XCodeConfig}
'chromium-based'?: BrowserConfig
'gecko-based'?: BrowserConfig
'webkit-based'?: BrowserConfig
'webkit-based'?: BrowserConfig & {xcode?: XCodeConfig}
}
commands?: {
dev?: Pick<
Expand All @@ -89,6 +170,7 @@ export interface FileConfig {
| 'profile'
| 'chromiumBinary'
| 'geckoBinary'
| 'webKitBinary'
| 'open'
| 'polyfill'
> & {
Expand All @@ -98,15 +180,20 @@ export interface FileConfig {

start?: Pick<
StartOptions,
'browser' | 'profile' | 'chromiumBinary' | 'geckoBinary' | 'polyfill'
| 'browser'
| 'profile'
| 'chromiumBinary'
| 'geckoBinary'
| 'webKitBinary'
| 'polyfill'
> & {
browserFlags?: string[]
preferences?: Record<string, unknown>
}

preview?: Pick<
PreviewOptions,
'browser' | 'profile' | 'chromiumBinary' | 'geckoBinary'
'browser' | 'profile' | 'chromiumBinary' | 'geckoBinary' | 'webKitBinary'
> & {
browserFlags?: string[]
preferences?: Record<string, unknown>
Expand Down
2 changes: 2 additions & 0 deletions programs/develop/plugin-browsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,15 @@ export class BrowsersPlugin {
browser: this.browser
// profile
}).apply(compiler)
break

default: {
new RunChromiumPlugin({
...browserConfig,
browser: this.browser
// profile
}).apply(compiler)
break
}
}
}
Expand Down
101 changes: 101 additions & 0 deletions programs/develop/plugin-browsers/run-safari/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import fs from 'fs'
import os from 'os'
import path from 'path'
import {type Compiler} from 'webpack'
import * as messages from '../browsers-lib/messages'

Check failure on line 5 in programs/develop/plugin-browsers/run-safari/index.ts

View workflow job for this annotation

GitHub Actions / build (20)

'messages' is declared but its value is never read.
import {PluginInterface} from '../browsers-types'
import {DevOptions} from '../../module'
import {launchSafari} from './safari/launch-safari'
import {
checkXcodeCommandLineTools,
ensureXcodeDirectory,

Check failure on line 11 in programs/develop/plugin-browsers/run-safari/index.ts

View workflow job for this annotation

GitHub Actions / build (20)

'ensureXcodeDirectory' is declared but its value is never read.
checkSafariWebExtensionConverter
} from './xcode/setup-xcode'
import {generateSafariProject} from './xcode/generate-project'

export class RunSafariPlugin {
public readonly extension: string | string[]
public readonly browser: DevOptions['browser']
public readonly browserFlags?: string[]
public readonly profile?: string
public readonly preferences?: Record<string, any>
public readonly startingUrl?: string

constructor(options: PluginInterface) {
this.extension = options.extension
this.browser = options.browser
this.browserFlags = options.browserFlags || []
this.profile = options.profile
this.preferences = options.preferences
this.startingUrl = options.startingUrl
}

private isMacOS(): boolean {
return os.platform() === 'darwin'
}

apply(compiler: Compiler): void {
compiler.hooks.done.tapAsync('RunSafariPlugin', (stats, done) => {
if (stats.hasErrors()) {
console.error('Build failed. Aborting Safari launch.')
done()
return
}

try {
// Ensure the environment is properly configured for Safari extension development
checkXcodeCommandLineTools()
// const xcodePath = ensureXcodeDirectory(process.cwd())
const xcodePath = compiler.options.output.path || ''
checkSafariWebExtensionConverter()

console.log(
`Xcode configuration verified. Using directory: ${xcodePath}`
)

// Check if the xcode folder is populated with the expected project
const outputPath = path.join(
xcodePath,
'printfriendly-safari.xcodeproj'
)
if (!fs.existsSync(outputPath)) {
console.log(
`'xcode' folder is empty. Generating Xcode project for Safari Web Extension...`
)
const userExtension = Array.isArray(this.extension)
? this.extension[0]
: this.extension
// AppName is the parsed Manifest.json name
const manifestJson = JSON.parse(
fs.readFileSync(path.join(userExtension, 'manifest.json'), 'utf8')
)
// Ensure appname is valid
const appName = manifestJson.name
// .replace(/[^a-zA-Z0-9]/g, '')

// i.e com.example.myextension
const identifier = manifestJson.homepage_url
? manifestJson.homepage_url.replace(/[^a-zA-Z0-9]/g, '')
: 'org.extensionjs.extension'

generateSafariProject(userExtension, xcodePath, appName, identifier)
console.log(`Xcode project successfully created at: ${outputPath}`)
} else {
console.log(`Existing Xcode project found at: ${outputPath}`)
}

// Launch Safari using the extracted logic
launchSafari({
startingUrl: this.startingUrl,
isMacOS: this.isMacOS(),
browser: this.browser
})
} catch (error: any) {
console.error(error.message)
process.exit(1)
}

done()
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {type PluginInterface} from '../../browsers-types'
import {createProfile} from '../create-profile'

Check failure on line 2 in programs/develop/plugin-browsers/run-safari/safari/browser-config.ts

View workflow job for this annotation

GitHub Actions / build (20)

Cannot find module '../create-profile' or its corresponding type declarations.

export function browserConfig(configOptions: PluginInterface) {
const extensionsToLoad = Array.isArray(configOptions.extension)
? configOptions.extension
: [configOptions.extension]

const userProfilePath = createProfile(
configOptions.browser,
configOptions.profile,
configOptions.preferences
)

// Flags set by default:
// https://github.com/GoogleChrome/chrome-launcher/blob/master/src/flags.ts
// Added useful flags for tooling:
// Ref: https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md
return [
`--load-extension=${extensionsToLoad.join()}`,
`--user-data-dir=${userProfilePath}`,
// Disable Chrome's native first run experience.
'--no-first-run',
// Disables client-side phishing detection
'--disable-client-side-phishing-detection',
// Disable some built-in extensions that aren't affected by '--disable-extensions'
'--disable-component-extensions-with-background-pages',
// Disable installation of default apps
'--disable-default-apps',
// Disables the Discover feed on NTP
'--disable-features=InterestFeedContentSuggestions',
// Disables Chrome translation, both the manual option and the popup prompt when a
// page with differing language is detected.
'--disable-features=Translate',
// Hide scrollbars from screenshots.
'--hide-scrollbars',
// Mute any audio
'--mute-audio',
// Disable the default browser check, do not prompt to set it as such
'--no-default-browser-check',
// Avoids blue bubble "user education" nudges
// (eg., "… give your browser a new look", Memory Saver)
'--ash-no-nudges',
// Disable the 2023+ search engine choice screen
'--disable-search-engine-choice-screen',
// Avoid the startup dialog for
// `Do you want the application “Chromium.app” to accept incoming network connections?`.
// Also disables the Chrome Media Router which creates background networking activity
// to discover cast targets.
// A superset of disabling DialMediaRouteProvider.
'--disable-features=MediaRoute',
// Use mock keychain on Mac to prevent the blocking permissions dialog about
// "Chrome wants to use your confidential information stored in your keychain"
'--use-mock-keychain',
// Disable various background network services, including extension updating,
// safe browsing service, upgrade detector, translate, UMA
'--disable-background-networking',
// Disable crashdump collection (reporting is already disabled in Chromium)
'--disable-breakpad',
// Don't update the browser 'components' listed at chrome://components/
'--disable-component-update',
// Disables Domain Reliability Monitoring, which tracks whether the browser
// has difficulty contacting Google-owned sites and uploads reports to Google.
'--disable-domain-reliability',
// Disables autofill server communication. This feature isn't disabled via other 'parent' flags.
'--disable-features=AutofillServerCommunicatio',
'--disable-features=CertificateTransparencyComponentUpdate',
// Disable syncing to a Google account
'--disable-sync',
// Used for turning on Breakpad crash reporting in a debug environment where crash
// reporting is typically compiled but disabled.
// Disable the Chrome Optimization Guide and networking with its service API
'--disable-features=OptimizationHints',
// A weaker form of disabling the MediaRouter feature. See that flag's details.
'--disable-features=DialMediaRouteProvider',
// Don't send hyperlink auditing pings
'--no-pings',
// Ensure the side panel is visible. This is used for testing the side panel feature.
'--enable-features=SidePanelUpdates',

// Flags to pass to Chrome
// Any of http://peter.sh/experiments/chromium-command-line-switches/
...(configOptions.browserFlags || [])
]
}
Loading

0 comments on commit da1d07d

Please sign in to comment.