From ca206452cd8bc33db6fb91d8de9d370ba252c719 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 28 Jan 2026 07:00:33 +1100 Subject: [PATCH 01/18] The panic handling has been ported to v2. Here's a summary of the changes: ## Summary of Changes **1. Created `v2/internal/frontend/desktop/linux/panic_handler.go`** - Ported the panic recovery logic from v3 - Includes `getStackTrace()` function for generating readable stack traces - `handlePanic()` function that recovers from panics and either calls the custom handler or logs the error **2. Added to `v2/pkg/options/options.go`** - Added `PanicDetails` struct with `StackTrace`, `Error`, `Time`, and `FullStackTrace` fields - Added `PanicHandler` type: `func(*PanicDetails)` - Added `PanicHandler` field to the `App` struct with documentation **3. Modified `v2/internal/frontend/desktop/linux/frontend.go`** - Added `defer handlePanic(f.frontendOptions.PanicHandler, f.logger)` to the goroutine in `processMessage()` (line 468) ## Usage Example Users can now configure a custom panic handler in their v2 Wails application: ```go app := wails.Run(&options.App{ Title: "My App", // ... other options PanicHandler: func(details *options.PanicDetails) { // Custom panic handling logic log.Printf("Panic occurred at %v: %v\n%s", details.Time, details.Error, details.StackTrace) // Could show error dialog, send to error tracking service, etc. }, }) ``` If no `PanicHandler` is set, panics will be logged via the application logger and the application will continue running instead of crashing with a signal handler error. --- .../frontend/desktop/linux/frontend.go | 2 + .../frontend/desktop/linux/panic_handler.go | 100 ++++++++++++++++++ v2/pkg/options/options.go | 19 ++++ 3 files changed, 121 insertions(+) create mode 100644 v2/internal/frontend/desktop/linux/panic_handler.go diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go index 2942a112e..d3c886018 100644 --- a/v2/internal/frontend/desktop/linux/frontend.go +++ b/v2/internal/frontend/desktop/linux/frontend.go @@ -501,6 +501,8 @@ func (f *Frontend) processMessage(message string) { } go func() { + defer handlePanic(f.frontendOptions.PanicHandler, f.logger) + result, err := f.dispatcher.ProcessMessage(message, f) if err != nil { f.logger.Error(err.Error()) diff --git a/v2/internal/frontend/desktop/linux/panic_handler.go b/v2/internal/frontend/desktop/linux/panic_handler.go new file mode 100644 index 000000000..a4c301268 --- /dev/null +++ b/v2/internal/frontend/desktop/linux/panic_handler.go @@ -0,0 +1,100 @@ +//go:build linux +// +build linux + +package linux + +import ( + "fmt" + "runtime" + "runtime/debug" + "strings" + "time" + + "github.com/wailsapp/wails/v2/pkg/options" +) + +func getStackTrace(skipStart int, skipEnd int) string { + // Get all program counters first + pc := make([]uintptr, 32) + n := runtime.Callers(skipStart+1, pc) + if n == 0 { + return "" + } + + pc = pc[:n] + frames := runtime.CallersFrames(pc) + + // Collect all frames first + var allFrames []runtime.Frame + for { + frame, more := frames.Next() + allFrames = append(allFrames, frame) + if !more { + break + } + } + + // Remove frames from the end + if len(allFrames) > skipEnd { + allFrames = allFrames[:len(allFrames)-skipEnd] + } + + // Build the output string + var builder strings.Builder + for _, frame := range allFrames { + fmt.Fprintf(&builder, "%s\n\tat %s:%d\n", + frame.Function, frame.File, frame.Line) + } + return builder.String() +} + +type handlePanicOptions struct { + skipEnd int +} + +func newPanicDetails(err error, trace string) *options.PanicDetails { + return &options.PanicDetails{ + Error: err, + Time: time.Now(), + StackTrace: trace, + FullStackTrace: string(debug.Stack()), + } +} + +// handlePanic recovers from panics and processes them through the configured handler. +// Returns true if a panic was recovered. +func handlePanic(handler options.PanicHandler, logger interface{ Error(string, ...interface{}) }, opts ...handlePanicOptions) bool { + // Try to recover + e := recover() + if e == nil { + return false + } + + // Get the error + err, ok := e.(error) + if !ok { + err = fmt.Errorf("%v", e) + } + + // Get the stack trace + var stackTrace string + skipEnd := 0 + if len(opts) > 0 { + skipEnd = opts[0].skipEnd + } + stackTrace = getStackTrace(3, skipEnd) + + panicDetails := newPanicDetails(err, stackTrace) + + // Use custom handler if provided + if handler != nil { + handler(panicDetails) + return true + } + + // Default behavior: log the panic + if logger != nil { + logger.Error("panic error: %v\n%s", panicDetails.Error, panicDetails.StackTrace) + } + return true +} diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go index 0f62d5e4b..6e6f3e8c4 100644 --- a/v2/pkg/options/options.go +++ b/v2/pkg/options/options.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "runtime" + "time" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" @@ -69,6 +70,12 @@ type App struct { // ErrorFormatter overrides the formatting of errors returned by backend methods ErrorFormatter ErrorFormatter + // PanicHandler is called when a panic occurs in a bound method. + // If not set, the panic will be logged and the application will continue. + // This is particularly useful on Linux where panics in cgo callbacks + // can cause signal handler issues. + PanicHandler PanicHandler + // CSS property to test for draggable elements. Default "--wails-draggable" CSSDragProperty string @@ -108,6 +115,18 @@ type App struct { type ErrorFormatter func(error) any +// PanicDetails contains information about a panic that occurred in a bound method +type PanicDetails struct { + StackTrace string + Error error + Time time.Time + FullStackTrace string +} + +// PanicHandler is a function that handles panics in bound methods. +// If not set, panics will be logged to the application logger. +type PanicHandler func(*PanicDetails) + type RGBA struct { R uint8 `json:"r"` G uint8 `json:"g"` From bc4ee373b5477e34d009dc95b76f1e74c4d2476c Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 28 Jan 2026 08:34:46 +1100 Subject: [PATCH 02/18] ci: add cross-compilation tests to Docker image workflow - Add test-cross-compile job that tests CGO builds for all 6 platform/arch combos - Add test-non-cgo job for pure Go cross-compilation verification - Add test-summary job with GitHub Actions summary output - Add skip_tests input for manual workflow dispatch - Verify Linux binaries link to required GTK/WebKit libraries - Verify binary format matches expected architecture Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build-cross-image.yml | 332 ++++++++++++++++++++++++ 1 file changed, 332 insertions(+) diff --git a/.github/workflows/build-cross-image.yml b/.github/workflows/build-cross-image.yml index b06dc47a7..83b40f2be 100644 --- a/.github/workflows/build-cross-image.yml +++ b/.github/workflows/build-cross-image.yml @@ -19,6 +19,11 @@ on: description: 'Image version tag' required: true default: 'latest' + skip_tests: + description: 'Skip cross-compilation tests' + required: false + default: 'false' + type: boolean push: branches: - v3-alpha @@ -35,6 +40,8 @@ jobs: permissions: contents: read packages: write + outputs: + image_tag: ${{ steps.vars.outputs.image_version }} steps: - name: Checkout @@ -89,3 +96,328 @@ jobs: MACOS_SDK_VERSION=${{ steps.vars.outputs.sdk_version }} cache-from: type=gha cache-to: type=gha,mode=max + + # Test cross-compilation for all platforms + test-cross-compile: + needs: build + if: ${{ inputs.skip_tests != 'true' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # Darwin targets (Zig + macOS SDK) - no platform emulation needed + - os: darwin + arch: arm64 + platform: "" + expected_file: "Mach-O 64-bit.*arm64" + - os: darwin + arch: amd64 + platform: "" + expected_file: "Mach-O 64-bit.*x86_64" + # Linux targets (GCC) - need platform to match architecture + - os: linux + arch: amd64 + platform: "linux/amd64" + expected_file: "ELF 64-bit LSB.*x86-64" + - os: linux + arch: arm64 + platform: "linux/arm64" + expected_file: "ELF 64-bit LSB.*ARM aarch64" + # Windows targets (Zig + mingw) - no platform emulation needed + - os: windows + arch: amd64 + platform: "" + expected_file: "PE32\\+ executable.*x86-64" + - os: windows + arch: arm64 + platform: "" + expected_file: "PE32\\+ executable.*Aarch64" + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} + + - name: Set up QEMU + if: matrix.platform != '' + uses: docker/setup-qemu-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create test CGO project + run: | + mkdir -p test-project + cd test-project + + # Create a minimal CGO test program + cat > main.go << 'EOF' + package main + + /* + #include + + int add(int a, int b) { + return a + b; + } + */ + import "C" + import "fmt" + + func main() { + result := C.add(1, 2) + fmt.Printf("CGO test: 1 + 2 = %d\n", result) + } + EOF + + cat > go.mod << 'EOF' + module test-cgo + + go 1.21 + EOF + + - name: Build ${{ matrix.os }}/${{ matrix.arch }} (CGO) + run: | + cd test-project + PLATFORM_FLAG="" + if [ -n "${{ matrix.platform }}" ]; then + PLATFORM_FLAG="--platform ${{ matrix.platform }}" + fi + + docker run --rm $PLATFORM_FLAG \ + -v "$(pwd):/app" \ + -e APP_NAME="test-cgo" \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag || 'latest' }} \ + ${{ matrix.os }} ${{ matrix.arch }} + + - name: Verify binary format + run: | + cd test-project/bin + ls -la + + # Find the built binary + if [ "${{ matrix.os }}" = "windows" ]; then + BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }}.exe 2>/dev/null || ls *.exe | head -1) + else + BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }} 2>/dev/null || ls test-cgo* | grep -v '.exe' | head -1) + fi + + echo "Binary: $BINARY" + FILE_OUTPUT=$(file "$BINARY") + echo "File output: $FILE_OUTPUT" + + # Verify the binary format matches expected + if echo "$FILE_OUTPUT" | grep -qE "${{ matrix.expected_file }}"; then + echo "✅ Binary format verified: ${{ matrix.os }}/${{ matrix.arch }}" + else + echo "❌ Binary format mismatch!" + echo "Expected pattern: ${{ matrix.expected_file }}" + echo "Got: $FILE_OUTPUT" + exit 1 + fi + + - name: Check library dependencies (Linux only) + if: matrix.os == 'linux' + run: | + cd test-project/bin + BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }} 2>/dev/null || ls test-cgo* | grep -v '.exe' | head -1) + + echo "## Library Dependencies for $BINARY" + echo "" + + # Use readelf to show dynamic dependencies + echo "### NEEDED libraries:" + readelf -d "$BINARY" | grep NEEDED || echo "No dynamic dependencies (statically linked)" + + # Verify expected libraries are linked + echo "" + echo "### Verifying required libraries..." + NEEDED=$(readelf -d "$BINARY" | grep NEEDED) + + MISSING="" + for lib in libwebkit2gtk-4.1.so libgtk-3.so libglib-2.0.so libc.so; do + if echo "$NEEDED" | grep -q "$lib"; then + echo "✅ $lib" + else + echo "❌ $lib MISSING" + MISSING="$MISSING $lib" + fi + done + + if [ -n "$MISSING" ]; then + echo "" + echo "ERROR: Missing required libraries:$MISSING" + exit 1 + fi + + # Test non-CGO builds (pure Go cross-compilation) + test-non-cgo: + needs: build + if: ${{ inputs.skip_tests != 'true' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - os: darwin + arch: arm64 + expected_file: "Mach-O 64-bit.*arm64" + - os: darwin + arch: amd64 + expected_file: "Mach-O 64-bit.*x86_64" + - os: linux + arch: amd64 + expected_file: "ELF 64-bit LSB" + - os: linux + arch: arm64 + expected_file: "ELF 64-bit LSB.*ARM aarch64" + - os: windows + arch: amd64 + expected_file: "PE32\\+ executable.*x86-64" + - os: windows + arch: arm64 + expected_file: "PE32\\+ executable.*Aarch64" + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch || github.ref }} + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create test non-CGO project + run: | + mkdir -p test-project + cd test-project + + # Create a pure Go test program (no CGO) + cat > main.go << 'EOF' + package main + + import "fmt" + + func main() { + fmt.Println("Pure Go cross-compilation test") + } + EOF + + cat > go.mod << 'EOF' + module test-pure-go + + go 1.21 + EOF + + - name: Build ${{ matrix.os }}/${{ matrix.arch }} (non-CGO) + run: | + cd test-project + + # For non-CGO, we can use any platform since Go handles cross-compilation + # We set CGO_ENABLED=0 to ensure pure Go build + docker run --rm \ + -v "$(pwd):/app" \ + -e APP_NAME="test-pure-go" \ + -e CGO_ENABLED=0 \ + --entrypoint /bin/sh \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag || 'latest' }} \ + -c "GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -o bin/test-pure-go-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.os == 'windows' && '.exe' || '' }} ." + + - name: Verify binary format + run: | + cd test-project/bin + ls -la + + # Find the built binary + if [ "${{ matrix.os }}" = "windows" ]; then + BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}.exe" + else + BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}" + fi + + echo "Binary: $BINARY" + FILE_OUTPUT=$(file "$BINARY") + echo "File output: $FILE_OUTPUT" + + # Verify the binary format matches expected + if echo "$FILE_OUTPUT" | grep -qE "${{ matrix.expected_file }}"; then + echo "✅ Binary format verified: ${{ matrix.os }}/${{ matrix.arch }} (non-CGO)" + else + echo "❌ Binary format mismatch!" + echo "Expected pattern: ${{ matrix.expected_file }}" + echo "Got: $FILE_OUTPUT" + exit 1 + fi + + - name: Check library dependencies (Linux only) + if: matrix.os == 'linux' + run: | + cd test-project/bin + BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}" + + echo "## Library Dependencies for $BINARY (non-CGO)" + echo "" + + # Non-CGO builds should have minimal dependencies (just libc or statically linked) + echo "### NEEDED libraries:" + readelf -d "$BINARY" | grep NEEDED || echo "No dynamic dependencies (statically linked)" + + # Verify NO GTK/WebKit libraries (since CGO is disabled) + NEEDED=$(readelf -d "$BINARY" | grep NEEDED || true) + if echo "$NEEDED" | grep -q "libwebkit\|libgtk"; then + echo "❌ ERROR: Non-CGO binary should not link to GTK/WebKit!" + exit 1 + else + echo "✅ Confirmed: No GTK/WebKit dependencies (expected for non-CGO)" + fi + + # Summary job + test-summary: + needs: [build, test-cross-compile, test-non-cgo] + if: always() && inputs.skip_tests != 'true' + runs-on: ubuntu-latest + steps: + - name: Check test results + run: | + echo "## Cross-Compilation Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.test-cross-compile.result }}" = "success" ]; then + echo "✅ **CGO Tests**: All passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **CGO Tests**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.test-non-cgo.result }}" = "success" ]; then + echo "✅ **Non-CGO Tests**: All passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Non-CGO Tests**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Tested Platforms" >> $GITHUB_STEP_SUMMARY + echo "| Platform | Architecture | CGO | Non-CGO |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------------|-----|---------|" >> $GITHUB_STEP_SUMMARY + echo "| Darwin | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Darwin | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Linux | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Linux | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Windows | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY + echo "| Windows | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY + + # Fail if any test failed + if [ "${{ needs.test-cross-compile.result }}" != "success" ] || [ "${{ needs.test-non-cgo.result }}" != "success" ]; then + echo "" + echo "❌ Some tests failed. Check the individual job logs for details." + exit 1 + fi From 8fd0340404829da8d91e2bba88048f359c285ee6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:32:06 +1100 Subject: [PATCH 03/18] chore: update sponsors.svg (#4911) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 93 ++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 5ed181252..82feb16e4 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -16,12 +16,20 @@ text { } Silver Sponsors - Orb + Orb - + - + + + + Johno Scott + + + + + Bronze Sponsors Cody Bentley @@ -62,44 +70,36 @@ text { Covering Costs - Marcus + Marcus - + - + - Iain + Iain - + - + - Michael + Michael - + - - - - BlueSky... - - - - - + - Digital... + Digital... - + - + Buying Breakfast Tai Groot @@ -297,71 +297,78 @@ text { Helpers - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + - + - + - + - + - + - + From 896344eb66b97327c40968503d08a6a983a98b9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:40:10 +1100 Subject: [PATCH 04/18] chore: update sponsors.svg (#4942) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 208 ++++++++++++++++---------------- 1 file changed, 103 insertions(+), 105 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 82feb16e4..ed115ff31 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -70,99 +70,83 @@ text { Covering Costs - Marcus + Marcus - + - - - - Iain - - - - - + - Michael + Michael - + - + - Digital... + Digital... - + - + Buying Breakfast - Tai Groot + Tai Groot - + - + - Tom Wu + Tom Wu - + - + - vaaski + vaaski - + - + - Sander + Sander - + - + - Kevin + Kevin - + - - - - elapse2039 - - - - - + - Zach + Zach - + - + - John + John - + - + Buying Coffee @@ -184,191 +168,205 @@ text { + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + Helpers - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + From 01b661f6a5185075efda778879f4ff3beaa27f9b Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 2 Feb 2026 18:55:57 +1100 Subject: [PATCH 05/18] feat(v2): add runtime.ResetSignalHandlers() for Linux panic recovery (#4921) * feat(v2): add runtime.ResetSignalHandlers() for Linux panic recovery Add a new runtime function that allows users to reset signal handlers before code that might panic from nil pointer dereferences. On Linux, WebKit installs signal handlers without the SA_ONSTACK flag, which prevents Go from properly recovering from panics caused by SIGSEGV and other signals. This function adds SA_ONSTACK to the relevant signal handlers (SIGSEGV, SIGBUS, SIGFPE, SIGABRT). Usage: ```go go func() { defer func() { if err := recover(); err != nil { log.Printf("Recovered: %v", err) } }() runtime.ResetSignalHandlers() // Code that might panic... }() ``` The function is a no-op on macOS and Windows. Fixes #3965 Co-Authored-By: Claude Opus 4.5 * test(v2): add panic-recovery-test example Add an example that demonstrates the Linux signal handler issue (#3965) and verifies the fix using runtime.ResetSignalHandlers(). The example includes: - A Greet function that triggers a nil pointer dereference after a delay - Auto-call from frontend after 5 seconds - README with reproduction steps Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- v2/examples/panic-recovery-test/README.md | 76 ++++++ v2/examples/panic-recovery-test/app.go | 44 ++++ .../panic-recovery-test/frontend/index.html | 12 + .../panic-recovery-test/frontend/package.json | 13 + .../panic-recovery-test/frontend/src/app.css | 54 ++++ .../frontend/src/assets/fonts/OFL.txt | 93 +++++++ .../fonts/nunito-v16-latin-regular.woff2 | Bin 0 -> 18972 bytes .../src/assets/images/logo-universal.png | Bin 0 -> 139695 bytes .../panic-recovery-test/frontend/src/main.js | 55 ++++ .../frontend/src/style.css | 26 ++ .../frontend/wailsjs/go/main/App.d.ts | 4 + .../frontend/wailsjs/go/main/App.js | 7 + .../frontend/wailsjs/runtime/package.json | 24 ++ .../frontend/wailsjs/runtime/runtime.d.ts | 249 ++++++++++++++++++ .../frontend/wailsjs/runtime/runtime.js | 242 +++++++++++++++++ v2/examples/panic-recovery-test/go.mod | 5 + v2/examples/panic-recovery-test/main.go | 36 +++ v2/examples/panic-recovery-test/wails.json | 13 + v2/pkg/runtime/signal_linux.go | 65 +++++ v2/pkg/runtime/signal_other.go | 18 ++ website/docs/guides/linux.mdx | 54 ++++ website/docs/reference/runtime/intro.mdx | 43 +++ 22 files changed, 1133 insertions(+) create mode 100644 v2/examples/panic-recovery-test/README.md create mode 100644 v2/examples/panic-recovery-test/app.go create mode 100644 v2/examples/panic-recovery-test/frontend/index.html create mode 100644 v2/examples/panic-recovery-test/frontend/package.json create mode 100644 v2/examples/panic-recovery-test/frontend/src/app.css create mode 100644 v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt create mode 100644 v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 create mode 100644 v2/examples/panic-recovery-test/frontend/src/assets/images/logo-universal.png create mode 100644 v2/examples/panic-recovery-test/frontend/src/main.js create mode 100644 v2/examples/panic-recovery-test/frontend/src/style.css create mode 100755 v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts create mode 100755 v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js create mode 100644 v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json create mode 100644 v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts create mode 100644 v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js create mode 100644 v2/examples/panic-recovery-test/go.mod create mode 100644 v2/examples/panic-recovery-test/main.go create mode 100644 v2/examples/panic-recovery-test/wails.json create mode 100644 v2/pkg/runtime/signal_linux.go create mode 100644 v2/pkg/runtime/signal_other.go diff --git a/v2/examples/panic-recovery-test/README.md b/v2/examples/panic-recovery-test/README.md new file mode 100644 index 000000000..c0a6a7e5a --- /dev/null +++ b/v2/examples/panic-recovery-test/README.md @@ -0,0 +1,76 @@ +# Panic Recovery Test + +This example demonstrates the Linux signal handler issue (#3965) and verifies the fix using `runtime.ResetSignalHandlers()`. + +## The Problem + +On Linux, WebKit installs signal handlers without the `SA_ONSTACK` flag, which prevents Go from recovering panics caused by nil pointer dereferences (SIGSEGV). Without the fix, the application crashes with: + +``` +signal 11 received but handler not on signal stack +fatal error: non-Go code set up signal handler without SA_ONSTACK flag +``` + +## The Solution + +Call `runtime.ResetSignalHandlers()` immediately before code that might panic: + +```go +import "github.com/wailsapp/wails/v2/pkg/runtime" + +go func() { + defer func() { + if err := recover(); err != nil { + log.Printf("Recovered: %v", err) + } + }() + runtime.ResetSignalHandlers() + // Code that might panic... +}() +``` + +## How to Reproduce + +### Prerequisites + +- Linux with WebKit2GTK 4.1 installed +- Go 1.21+ +- Wails CLI + +### Steps + +1. Build the example: + ```bash + cd v2/examples/panic-recovery-test + wails build -tags webkit2_41 + ``` + +2. Run the application: + ```bash + ./build/bin/panic-recovery-test + ``` + +3. Wait ~10 seconds (the app auto-calls `Greet` after 5s, then waits another 5s before the nil pointer dereference) + +### Expected Result (with fix) + +The panic is recovered and you see: +``` +------------------------------"invalid memory address or nil pointer dereference" +``` + +The application continues running. + +### Without the fix + +Comment out the `runtime.ResetSignalHandlers()` call in `app.go` and rebuild. The application will crash with a fatal signal 11 error. + +## Files + +- `app.go` - Contains the `Greet` function that demonstrates panic recovery +- `frontend/src/main.js` - Auto-calls `Greet` after 5 seconds to trigger the test + +## Related + +- Issue: https://github.com/wailsapp/wails/issues/3965 +- Original fix PR: https://github.com/wailsapp/wails/pull/2152 diff --git a/v2/examples/panic-recovery-test/app.go b/v2/examples/panic-recovery-test/app.go new file mode 100644 index 000000000..ceb46e8d5 --- /dev/null +++ b/v2/examples/panic-recovery-test/app.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/wailsapp/wails/v2/pkg/runtime" +) + +// App struct +type App struct { + ctx context.Context +} + +// NewApp creates a new App application struct +func NewApp() *App { + return &App{} +} + +// startup is called when the app starts. The context is saved +// so we can call the runtime methods +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +// Greet returns a greeting for the given name +func (a *App) Greet(name string) string { + go func() { + defer func() { + if err := recover(); err != nil { + fmt.Printf("------------------------------%#v\n", err) + } + }() + time.Sleep(5 * time.Second) + // Fix signal handlers right before potential panic using the Wails runtime + runtime.ResetSignalHandlers() + // Nil pointer dereference - causes SIGSEGV + var t *time.Time + fmt.Println(t.Unix()) + }() + + return fmt.Sprintf("Hello %s, It's show time!", name) +} diff --git a/v2/examples/panic-recovery-test/frontend/index.html b/v2/examples/panic-recovery-test/frontend/index.html new file mode 100644 index 000000000..d7aa4e942 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + panic-test + + +
+ + + diff --git a/v2/examples/panic-recovery-test/frontend/package.json b/v2/examples/panic-recovery-test/frontend/package.json new file mode 100644 index 000000000..a1b6f8e1a --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/package.json @@ -0,0 +1,13 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^3.0.7" + } +} \ No newline at end of file diff --git a/v2/examples/panic-recovery-test/frontend/src/app.css b/v2/examples/panic-recovery-test/frontend/src/app.css new file mode 100644 index 000000000..59d06f692 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/src/app.css @@ -0,0 +1,54 @@ +#logo { + display: block; + width: 50%; + height: 50%; + margin: auto; + padding: 10% 0 0; + background-position: center; + background-repeat: no-repeat; + background-size: 100% 100%; + background-origin: content-box; +} + +.result { + height: 20px; + line-height: 20px; + margin: 1.5rem auto; +} + +.input-box .btn { + width: 60px; + height: 30px; + line-height: 30px; + border-radius: 3px; + border: none; + margin: 0 0 0 20px; + padding: 0 8px; + cursor: pointer; +} + +.input-box .btn:hover { + background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); + color: #333333; +} + +.input-box .input { + border: none; + border-radius: 3px; + outline: none; + height: 30px; + line-height: 30px; + padding: 0 10px; + background-color: rgba(240, 240, 240, 1); + -webkit-font-smoothing: antialiased; +} + +.input-box .input:hover { + border: none; + background-color: rgba(255, 255, 255, 1); +} + +.input-box .input:focus { + border: none; + background-color: rgba(255, 255, 255, 1); +} \ No newline at end of file diff --git a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt b/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt new file mode 100644 index 000000000..9cac04ce8 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2f9cc5964455b8f5ca989db989250dbebb1a5f66 GIT binary patch literal 18972 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

``Cl35~rb|Fd`a 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�!`*oO;=jM?E`SO+{FWpg3CNSvZuu}NUH-8pO%PCnq`u!Mb3$S z5pHdQ3Ou4f7XGD9=l=R{1x>VYG;2LE_&C-;f~f~QKyW7EIR1t7sV)Eb^~)BUwA2T4 z|8F`OFk@F1%0dJGDCx?W8ctT5%&g+fA802J(2n&1)tp!cR)^67tqG6in2J~(v##~I zEL{fP#i8yvu0Hzm4@yZn=E@|F`I%2VB>0fO1MnK;R>BD5k#kY&^$E$4Lb@V>OIX8c zqu%lPRKOetGOTRZCiPd#jrU#$_NK%nEgqn~FShu=wF4c^$8Qv_8eo+2AXu9kI~(8U zaoMF=!ElWwtkJ;vYqX=GhL8LPNC)E4Z@t_jl$n%e$Vi7zbO=Co)7foz?3ia$`U+t- zKZwz;Hpe7TW6$FEh0U)R8*&296fys-ZfVXgJ-ru_NUwh<4O}n00eCM(wR%kG&i7A2 ziMvpZVl9TF3_NMNI|a(O(h*9Sk3lwLbN-jQVl>H38iNiGH%yR)r5o*pWd!X${SO93 zvss^Gr+%LC3r^C$sg}0_nKaVby;{epmJ1ga)@VIiWMc~llWh6d_P|#-e0x=9k)#n} znRGcQo4JT>KCsDbmle1*&Dcu3V~%Z{Eqw24+WW!I=7}G0`9Avfp(?A{reW)S6(bF@ zcb`1uh3S{zB^qm{zK7*f(S3Y0AahH@w3uT)x!`G${V-s9&WUGkMP!Lrz1CKuIFQnh zVTM@v=sSPtvl6+uZ|Hl|6`K$AL3NQoAJJ+K#w}IWXRTnf$Pg^4dS`SbG`sn!8bee_ z*CPWyXq<0ZBk<5u{I6t9WAS7~4S0dz<2G$@NM1-{K=lem@};~AE{&^AIR)_4F)$kD z>nv*~ATL?NP*za0x|ch0@@#M-(fP-_^h3-g#_yZP!}{Pz$)-;l8@qA$-(piR*qy5S zw)7IOn4jTbn5R;itdDA~lYhPY#kmJ;#OX;Z1cdY|(^_@q%fx?bM$r)7UcPwoE`)bH zTXM1t!KQO-=+GGqza+75fC*%?K$;k4A>8HEe|}Sgq$n!Vk`g89N}-Ca0+Ny1NR$NJ zInEeV&>6HGqws&S{m8xiu`XG~!48s{Es|%TeF3T~-{${kthxT*nDu-yMjP&|IcV|f zd6}xUyB;lYD7c*Wuq+lW-NwvtcoTSb8k%=Lc$2ggPN9i2|I{*O{lw?mK=f%5N^y4D zVE_B^cvnPeX(?h1>1LSHcO@XrYON!78P+zHsJ&$By@o6R+ZF7#Z?Fzxh|p_2PbVBb zd%IC8gzJpb)+oV`w@Dj1pSK*0d9`b7b90o&D^+*5@kYctIKRCZ>Xq$#mdpB8k#a0P zC-!M6#e;%Y`Tmq@NF|~(s(2g$V~s*RSgi*3>`}J@COAb+00n z!FNg#R9rt+PAN2(%vQECCLa1ZNof4^Fdx6T z@hadBXg$UBJC+_Tb44P~9>QWGB{_y)s%RacUg8LGUJp z9NwI+-o;(Rk7PlH|GY;A`zgVHt_@6Td!{hZ_%xG_r@LpfS!Qe~I%M_Ab1W_U7>cABtc+qJO> za9H%Dx~#e$G88(9dm?=S&m0fSZIMad{Hy-N$yrK{GFl?abD11kgRdJ?=jY2Le|qV( zfQwzb@C!eok8ec)^u@RE5m-*pN=&ECANc}Q66Ukis+Usq20gua@}maef^;s?AZ zQ*K#)0+b{Tca|wu%@cNeaq=%nCKa6foi&nbtCmSN^wO_zk=}W@&ExPiv)0W=v}B`Msx0Kr z+`oA%AHcA3bmjPIjTAomI%^sAoo;{0(%PO3+!k(25s6dIv*vwd3i=p7I$zpQgdG%7 zt{)s0@r1r#9)76TiCZpXmj&- zGnN^a96U7%m{$ek@#$=@+T#FmV?rpKH-KTwKiowB`CiN{Kw7yqYiH5?lQ z4jjW(uaBxkC9IM0UbC5!O9(KYfI5}XR?n-5H_)!Btv6d_2nrFP=g@;#1DT##tP(z+ z$v)*akAg8a?L2KmlJ`o;L7e?Z--QSHJP(8M9+sV=-XZ}?I+ECOAAiSyV3()l`~mcJ zQ~?WX>+1;RKh}XiyM)NBPph*pEd84$(jv*_?Xv+c zr4KC{nwnNxaqZ-sxMGng+y!J1Kij$Hkfk9_4E6$&Q=6DUgENpk`lVy%xl#b1RvHb} zfC?|(&5@#9(wYW6Xnz-_tXgoJdF!iAD(A(HZxnI4z$W!%ZD2uUFYY~|% z)-nK0(IYm`TY$NA*-hN97hX|o%939y&dAHwC&Q-J-CZzL6FU9T)s`{1davQo%&0(B z$93Sw%jnr9H_2T8v_0DJD@~8SDA(2q5cr}e2X3PtXbXQ1NX&*6{pWw^?JWm^l|;h0 zkHUBzX-a}CvM2zdYurM%#^y|njan8h3DG)3WDzbEH76rd<^sgqva!^J$Et2;fnil# zjz;&ncBKtxg@nK16^A_6bRdiJKAK1{$w`Djc z7DD3jFBQ8@7h9BcQ88-;ITNNKr-j*HsRu0|GeRvjB^N>{BY!7KpfX}JwgZ&vSA)1T zpZ~sZ)W&?xv^94pfX7&sX2hH$Tbe){sB(i;h@=C#yEH(gqzzYpN)i&qF%kZBl({DT zw-(8C#bjF!$rYhLF}7yGUr1*{ss>4#ORpmx&kp_ITFndig`U7Tz*wX`lV*u;<78aV zSd!C`gVwxctWSlq5y!1w{bDr-zi5M+_H*~`^JK(GWCzZJlifE0* z&TcrDw3)sxydOp*x2aHqDh4Sj%2wV)kR`@*{<4?jM{h}$71}4=kVv)NTqfY7U9_*j zc;ygZxiCf&H+E|VfE(ZuZ0*78$oqm=VrZe=RYOxm~U!8XNqps*%fJWy8Xun#g2+PS})1U zTkQ;+!Q{_>r())K!z)=lXweQMC@y(U<0meE`@mJdl~ueidTchNLuFKPO((Ih7tOP@ zq6bZWzHHJIo!=;=2XZdqSXmKA@a~#}4O9z~+u#7DUkSl`MTZFY9(-A0j)ni``pK}i z8?%h^#p09eFm?@1ig859mF~fdB7*RGf_XKAAk$$emU$ulzKszweAYLh19YPI)WT;i zH%-61Kgm?W-RI1c22HxQ=V4Vn(E3c8$~PZ4XOuqWw=SnmTwv?qA{X5JwcKa-_u)c} zP>BdQ@1^6j`<&+Vo!Fzc4GFnbypW8YQbEX*w`a-PNs*4k+KKeA`k94>IAEM#I^;=X zKcG4E)+|X}lffe0`O#=a_E#Gh$#*$J{Ftq8+z4%3BG#)$GA7p}3 zzR^&$CsyRx_4`^VGyPc_TOkz{pc&929?WjwE1WVo&$yYEGOFnDxV%*l<;%RQl0jBW zlNpYw3|m@Em@fU(8sx85z-lUmM|fZ^4I$?_VPdM~q_6c$NkPO6NhJtvBqox!)-y-+ zd>Jpo7BX%DwC2rc!~S1~aa-kVP*1>rl=0#>%w)On`&oa%On15?C}fg}Z5curgzALsmA z_>WD?&^7@X8_Lg%@jNEL+@cyu4u^XN3w-- z)%b|xr>>qF(zS>aQ36sDi^CD?>bY-jr$ma*m0r4%x|IziZQ>6SSF24-5E6zQjLlr2 z%?+M4z)kR=XZgc2VCpEbz3+ybq~$I#SK_;d{4C5i2X{V!a9=-I^Al#pzt&v z#ESR|N2zOBgp}m4C*`6sbTV~#xIxRqesz^sDiKj}k-?*guOKYg!#XTtj9BR=&3Dkz zq{i{#ssq?uw3c|NsZ{&NWPHgT+#4#B z4L{R>ZCPyt8f~muwI+Yd?6U+!w1=$$VIBH zWzGQ!yM)fNqvalC%`d`^J6t5naZPovQMhNP@RwNEM0>Oz||>m56* z5AYL~vCSV#PE|Ki38jB+u<|G(Gg2*CpJ-j2WdB^A&RgKNHi|KC)j|%MF_gS2(wh4q z6;JR^0*+^5+4O8jyBtii)YsaEE0}<-4p1jmkMh54Zu9TB{@YXmC;<-VbI9d=)q zW-%@T_W}tQU9<-+9i5iC-}rUDs9SPqVOubdUD3)^#cd;JF;f_CnN3TS*ldX-d6hKn ziu9i2OG`CE#Umccs$Z`JI*m@(Ld!jDu*$Iprxu6D+r+7SC^EsCc!V}*PyJ6Gcs4Db zaY~a+1P>eYtl^AT(V#VSV4i9PaeP_v;E=i!c!S9_Qk5EVSx=~!FO_zp)Q?1tg zl!GtfVYt%QJj4z$nl9XfRggaND9!cfkES&UnpC0#3!2qDO&*tK|6&ELL&X;!Tt}2# zJtB-?UHgs058*v%KtOqW`)Jv-Na(l)*MN|5g7#+etk0f3Ld{#^p5Pu%#hh}w3;k;s ziNKYKL*SVNLa%kG=&g@wzLIt?_yxXDoq@upmzyB|`HDaPS66bvl?rx0dJx;3a~-GG z)_3vpk(_5C&WDYypg}!g9r8`32fPk%h1ogDyVT%vBF&2W)OPyq&u!wLp?GWCQyQzPORw{(tC>Z?lv>Gn4+U$&$ zh)tXFl$UmocOy`x_Q}V-aATj$6<7)GuRt%!;(8@o`Q$R8_){3LRsmkrOjSgFNZB2x zu4DTg^r+T#GmrQs_mlKFk%5-!+%x04pTio-==PUG%pH{k{1$d%Ixpl*yJrK$|s z#0uKmR2;@jDba9VO%OXh-GIPyqtsvNl^2Ca&yj4&auT&K6BB0K;v@DHcw=tU9xKiS+M+$$&W z8mzeooy^l;wZ)~dk)<;q6#QxKA2!II=73|lIHbH)W74H=e66>_4UOJ3rKv{Ic;k5Q zC?I2{CSYbqDm7DAZ}ZfRRK$UtZ`R)u4d`BZJL2HIDQqug?M-^c{ys{Mtq`H|>Z=QFg_f}xx>fhK|1TNSaFJ7h&i9Q0C&X~GbgIew$_%>a7VGY8n5?)uL6GJHH zKI$Lp)S{qg@q-np*M^PFH1vj4K{V6C(rfxT_>e(+;{!#I)S{D?}i$2@G>Y7dyFb&mc4A=xU|O(Yv^VK3ELYv-C=< zV(Sgv!ydqw#25q|lutg%=oACb?w;a~3890>1Qhe|#Q{@)qJOj&_%Nz^<+oJiJOaG! zxXluHac%G%Bl+BjOh-}&3Y-}bTr=8BXraKUMWJar)rslz;wZNgTekNc4M0CkH(HQ) z@k3#Ia}ji&IGZek;>Mt)`Hmsj(A+o9C^a{~W`MXiu$1YB{kh|X6H;~^n*rmI@TYg7 z`tl=U^q<<#L^2Quc))T~Y|)SvAcS)|&qLVz9ZO1tBh)=Zavg@_RHuMj_W57`9{CBe|jc5 zpR}ji$h5l{)16|q0C zaG6}z2D@VNsk@E-z~Aw0HInzfIVzKxdsI9qF#I+lrQ=k~{5A6kiU`DRIfa@Nh^bqj4H!Twm*iI@ycx@}km6iK@otcra|vTNlP353 zAp4FkPh-Z9U0APRWTc>&<%;sohyJQaY9Uk$&8kFv#`4# zuJBvq?*6xg)=f-hhVA&*7UM$3fEGovFUvXF+l{Z~fyY~?*I?qFjmr5qa|vaMZQ`Ns z7;~hd=)r7I!s|=Vt_DtB3uI~Rh5Zi2BSX#Cxs6@{f;&%dC!&Q|Dtl0!%Z!8;Y3kUN<*8%p~_)dt%{+{IB{)r z7=<_1cHr|NN73LV^>9cGm8Gv3Sx|=R8m``b);uE&xl@|i&v|H9i1lGghB;&kYStb4 zMLJO+X6ejo&M-Ky6fb7&wP^d3Roo!a<- zE92eHGN#W7N_CKH3_&$o3}H-j()cUnbmO3o_k4*!SM1vjf*(gPebI3@q#hSU@3nt$ z8j9GYsQ;RT+pmoM9ynp@y8&9;!IBL`HJ#dr9KqTvr!1ygr=s-VN2>aU)a`loUb$r! zarXXOg3-jZt=ZGWe4CmlW$Ah*i&>M@J1X_~1Tio3Q2R_$=3;@6!Q$KM*3n`j;yuc>V3 zVcQ~b+(<9B%yMZtGIY{{>zNfMpa&5vtUseazVqifd~0yw(hHU!U=z8m0786@8~lI9 z#lZ`77kph1VHe!2=ivj{*%VsKcW&JdzECat1>SzAogSk6yzd`Of z)v4&y(HPQgMjN?7>gHi1Kt)(B4OL4>32EaC^(<5WC3v$j=sfDIs{jyYFEM-+>(y<} zBm81g^%qt4xN5x^~q)_gwIaIe^4;depm^Rqw8IN^(SAfMsB-}?Xmy)o@3?+1lW0@@aUw+WPl0;_d+N)gn zST?4UuIymAa=dCjMb$$k8#bTztHT?)*SzbtG0oWPSTRlP=HGs}7wcIDop&%j5L^&g zrCpB@`gD_a8_RcTj#)n2F!hylfjf-qU?G?@1kzx!ImueC!rr$a`wrm>ihJP*uN=^Z zQqq}Q*5R~6C-~<1e@UT49jVqa@P3+2ARirVaEjT=~M5IvPr2_lF$px}#VNy>78`hkfQ=em(`BZWtF=u@94J^Fzk* zd1=6Rqv;0@_7=;7_1_ zyjGAmJ;VgAmOgRm2erc-;Fe$8qW`^%gdwl>7_UYebR)LFd=RieGUS-zUC+77UnTb* zeB=vlHKvwKt~vcXlL!J0sM>*MB?)RPD}Sc)O5YEcKuRe*KA!L%#;hiKlk|#C#HdFc zY=u*6ekbDIj7YIVRL_Bk+vs`Suw3@275AEfV!CuyKWfQO1cqO3%o~Lx%~)CAPb%!C zjI#@-X3ukCXE;k+=5^k8UnZ38zV;sxwO?)OELy{-3*iU^9XpgUo|~=vheoLnC27~gX1YHV%p=pUvHcP) zUp23=VUACl?N)QtUPJeI{zGh%08=B@(Vgz!)1~jL?d|3l5KTkmbvCASqvG7uqOf3J z3s%+a-q8oBbFa8sc+*Etv%FWShdeviL4Y;3^Con#kYN%y+W?L>;CZ+hUO9^vV$1)> z#E{x*Zr!+;0y>wh?e#9i5E>@`I&~a{O*nr=@od2_(n3LM6E>(A$bo!AkZb7uf}9Z6 zUZ;RUoL-F<31s1~AvN7MqWT^*A(}I-Er0Ma6RsA_r((ry*_0aD`jE0d=R%_uz?2yz z|E?!lh|9bQXzF%v=@hY+JML2Z$&lTw#Ise_AdBS-Aezdof5Y3=ng#8llL{PQXGlNG z*?VuYB&G#5u3963v{8G+XEZ{BIr&>X?Oj8%wR>m}BtzU5;GTnGWDn1>AJgzdv@Dyp zsucS(eoA!I4(f*UUA#q5eJQ|wuK;pB+zml3KJkmQD4wR~Ex%^5#?e0`J;rSG^v4F; zVC9;6ju#Bfgf0v`9&OGBC@$5;7V z^F8#_Oj!71G6L#*0<4tED01m#ryPMGw8tqlQkemQJP4+Mmv7(R{3I2^1Dt2gc{ z;hSLlagJWgvChKzkT-R11aA=CaBOp(wg>uucVb*#TQ&oW`+n0r*oV>qV*gfP@8GaB z(t4jkh4p$}ovfUmkg)JBE==t(ABA}|#(^)IVkiC~mBybCHuZAB9$@J(ksOo!!?>y@ zH+D0}t|(MU!~>)0fXsULt5{*T7^!u8O9N7#dhzKi!;AVD?ANBh`yVBkTf#dx;Wm(g z7Aaoas3uKMrx<641he<}HN<6=id8R_KC#E=jk@gGOJy0DFJq}z>jX}m`-0BbD5-#Z zKW&6xT$Y^~%?>kw?KxS*2eTSN*SlrM>o%=gf9XtAIOzlJ`~x6Ydi#azGrgcq9y%w?@8j0pQ|jSTf_kU*UnfS;&g|I*O@(j0OzE?%kGQEs zGnJ=4YAWU4s7J2ky#=N0ADY4DO-$xVv(OiV9DfWyFyg6sb=l9BaH0{>@!1Yz;_G6O z7R{urp(fRH)!HA&d7f$WB88>8o2_<&F_W*21Z&&Q>d} z>Ga6Ix;1I4=fS4!XnOCaf(>-gX|``t;$>fnU%w}6BdEx|gNwB!xR9#&&rHVM2|$C% zT_)p#H}23er02w4UtRWa7$V`QhSiczIdEN1-HboQE|q#4Hbz(qv>{* zsm76QvS}`+2oHv+b(o6-iA|e4zb52th5R}s8;FOHLcDPCR;U@ObI`%-1(DQnnZAw> z@AF=ov+o!+)b_yy*r`TTi(+t)Z@lTQz@i->axaT<`9?SGGo1B#$X?h)@0UDRb-0vb zlg?OEyob#1vWjlvoNL}BDUw1GtNw4b#7#Xj5n@){;PI7b)fjSB_gRu;2SI>+>A+vsgC^Q zYuV2b)r8Hr;(@s+)2}Y1aforI`A9^UGlJN?(TOKM0weeKlHnnD!aM^HW!#Wj=3@6{&D07Z zs4|>HCHpm@h!#d)m&N=0@v&K78ZAG~$D`z!4b36yk;WHs)aWd&>tqNq_=;WDn1)gF zlF!D^i2;7FpB1M=xLvjn?!XaiFe#7O3fm=hp!3wX5GiW8B$_^*q8uI1>}aGAD&~cp zX~I&Ox*yL_5vDH z)ZGdOa?ppBoxC~o<_6J62AGypiE%xb8>^Ax}<#4I~TayTJ!tj`-gSKA#}e9l{Z< zfgbkJ>s_ALV}L(F4{L&4QSolCwo~yuvAJFALD+k=G5lhRP!99)!1$|&s5#B}RZnjB z3e#d(^ii~>gW$r)zPy&(s0D_ic&mz435RI~5R?kj?1X%?DPi`QBa8AA&2<@DP6yly z0aYj9_4A0{3K*#ewJ_YGX{EAuqiM*_d5l!TMLtfB2geDtmHSJ0WcVb5L;qW+ldV5= z_)c4{c#Q;|i)vdU5V40F(fbk}c;;F%?Wg|-spE|`*s(B4q6Jr;K{dilebBq7+%!nW zjCGd73$=BvS-rmY^5;QoMZ--cFnX4vvB$1ea;W`LMqN-sF_xx{kN0e}_R%c6PX&J1 z7%!*O7`^$ueobSVKWRFj$GY}gQ3WZqB)V>T0i#EzG{nDgs%O24+rJ7gkCWg#8x~wl zZX0D9>?06A_8lqyVr%h~YmTxE^s?g%Bcvr6La@pfZ&cBa8Em9_1eZUqCtd3X{wvps z@jC-V9kKV!LzVT+(#-jvGboS|N1ud0RirrSXMONg9zhT5T>kRSH<`78ZX|WZX1>Pd z+y~A~(X2`uulMHk6#qoRLhv(*P%|q$`dCP5CD=}7Pg7tRDPNz>SBFmcdco7U^@fU> zKMnS~>KMur52oyeB2T$t$1o^>Uxwm0g;AbO1!>Mb+-qT91V9+Q&&Qg8n&l9Af zSSi&-OOLS(K~~+~_1H^mmP|^KTVNat3GMkL0x&^5-jBwC)=5PXRB)f^%l;Vc3NOA# zcs9BkMEVvFQh1-Nlq|IXz6>_5S$&NsuE))VIEv-U+YlNn|B(yFJ484vIjlTLi3n)Y z+u;*6$#{LgM=AledD(Q3s0=}PAGwKHv535hk??2fDJkNnD*MSD$wH7-&)o=Xn5C~=m4WnLq!fNUz`@try!*7)*8PR915mwU`q&iQu&M< zjv^=_5{FCtp~Ti;bn%N6<31#Eb~VXny6^r=?Jjqq$juwBrPdplYzB~Pq~tSJ@MTE6 zt6Ntr4pB=jH2#~qOobmYcq7<_*1bINCT0ny@}3l+Ehs;EsuyxikTabvk|e;`d<$?H zWlo4MdusiMr-nn2;;;$w0PiTtK>dicZ#wO@epK}Lh91Al2jdB3Pm|z6`8;igAW6Js z2P3C~{^9#v*&it>KkYSznKQ2(l|G^Km~S^k-a`+o7dnw?5Mom9&-D65)44*<9?S%5 z^~(7>xF9+~Xme`$%wYV$2%;PKt!y#JUTJ@`a+BBCzvn4DZ6f@!%w zkc56_oY>4k;FylIJT({mCJ6NSwRQcj z|M(kSnk)yj-+neN1ix!Ci=5gfp7W5Zeso95n+n=Qf-6SADLoUqlrz7Vhj%fs_N$Ze;;hz2Tv~WZI%0T z3fi*<134#+O~4b~>iS2_^3{)jV3t)J!g|3iCrfPyIbFImrM)J?z}74jN% zI|nz&S#Rn8zu22R1dqL32#+L?+>4V%Z5{>N^44Qv>rKJ6y@r_X`fLL$T8o~I)D^b< zr>RN#?<%jx)`$7bMv7X3X5We;WFr8dQi+AWJK{?4u=3J_++fbuiH(%vfu*=vYA=KbvNblU&$#!6DmMJ3ax^t@`&d z`8bF{ADjw!u3h%883bPadq(t*Om4?z1*qU}5@Pt95dE-#EO%p;${+KLlzHlF~90~XK*?o142Tm zJi@~Tz5(+c+|1_{oP=`>!mNxvbRD%njASiIA?(H%XqGo0B9U}g+OgfUgKTWJS8uKi z#Nrdtdet%&KL?wS4e*I{3UOCryewZ4hwNW?%R90(?h+I4lSokeULoqG>FF;XBzU z{62F`zBtN^(nIdb1^O{8U^xns+%(Wdg7(_jc5+pu@2GD`iA-IZK zXT|RS?sIetzJKL{zgdm(IK^d=S2j>+TcT6y7w}}wYlE#aIT=gUx2SlUoAAC?{-dHSZ zLgM6cpXPIUov~4~l|j}h0V=7s0Kb-1Q9>3R_A8eA{78;DuEMrxwU`^d&qoDY}uF=vM_TSWzOWTqg=>7M1LaM;{TUlTq| zSmr#TGR^)PLDH4A%f@HWuxM_o4nZULjLc)jccMOYtY)$d_61yZgV>1|wRRw|$kWb~ zW##SKfID&n_#gy2vC}y5jOg=6!N29bR~#c9I6`i}f$gOdnySjxfDI>;<2uRD?Tyg+ z9JGesnlUXl@BAM5_n_a$m`ZXpIXZue`kMIEX|=&mu9EY$q;I2sNFTN;H?aW#1dx}J z(lk|gR8LLlkA(TcFBG5+Q>k0wa(kz>uCm zr3>#1(WT4HgdntzrY~(U#!VkR%fqc&@q3^uubF6*PW^hjD!?CfImeVf{u**raj%Ec z5xO%c-*wo4%_ky}@G9+HM=#8NT+CPe5aLr4XGA~Gs1_bGR5WMQJ&sR^r+fF%bNxgL zM}tcX#P-g|&1Q+;0Yi$jCX`wfRXxW zT4EPH%E=Ps@Xl&~J&@;fZ}vsvO06Pp>VP`!;9|PQ)AwT<;xOP`z z`F#xHlYtN)7D@lF8FN;%Q30t|LmoY(5JN9ldM}Gpt4cERXoM7!ncxejQJJ5Q0jy*;+`7*B#Y40b7)BP`M~ez1VIuB%`*BfrF7UGis~Gi^yE9K_9Qr|7V|B` z2#4^_0#0?7|CxytZ*hdCQ?)QBib?Ct*9c}(RC#zL_!+{@Gfe?fdsBA3Oy72t*%S^YXe zPt-PO|hQn+m=etJzpxdeYhsuyK464ELw3*oYa3$Y&@b=4QN^6SXS%$c*sZE6ojh zqH!Qc9Ip7L%h>Vq$8Ow8#82U}ETX8VD~-ALiB-rwufR)j<;QEN2fJ%EUD3Ar8`R6Z zuXL%d4?iO>&3(fjt~C%D&xM_mnsn^K?zDH#;UA!1IUwQCc)?Nm5*?zO!H}?6frKFT zqxEue<hQpf3FQc@}J#Zu=EB~d~Or_ zc=FT6?9hVs(--B|df1r%y48OD)BIcoys`VaL)^0?D9wuPVHKs$3qg|Qi%GGiz)BYP zj5)QJIy|ne_!$f-7(C@$tiR;H(p8heot+-|323Un%b}y6=$bAp1u9ps;iVpWDm60< zo>qj1+(j6BOs|WHKvRSt|FI*ycs4MzqG`+<5EPl$>~RDUJrgJO4CJ*6n5yy{gWj$LqLj;*fcaOg7 z)mM^LqsvSP(~^t@bnMrHmJxoqJQ|fSIFdbFa%n%tS=JX~LbM1_6F*zD|CTVQIws@w zr01qp=#k9Q=tCsv}K?B4=`V!EBp05l*=`~@R0dc#c zN6y+8)0?r{jRp9h_5*?;+GZnyXm)BEp~xr})|kj9=`in01RZERb8BY*KJ;eIfNab=WmkyNgi8tX!N#>dO2Yznz zvI{47KU?Jx3|ZVSPDda@ePPQtvS+!E-L2`ALG3Q};VGak4#osLAeC%y{DDgwPm`gq z5K(Crd2<2?gagPsI;so)H}m2$qt~@uMF7vL`QRmIDw&VH?Cn9vpz5bJ8C=t!DlrJl zYK&Pa%{Hcj2;B-_XwX(t;>}(Reg6BVYAd-1u+x_cA{x`o1S>@^5V6=;5I;C&;L*s* z^MqOWs+Uv%kkfCc^4TmyBxCvk=ryW-61$N+Qb-(v^It>;Qvgrm4E9Ut<9z^U2aIMA zb#nE(A6T#LArVX)9;K!O2-MdnSy6PK%jXBRPA1SayN3Fac<=d1jXHL(HV!DC z2j<-mw;5MX{3qcV2w%%g?dul~}Ye1IRhp>rVBNwoCY zSxR;_Xx?hi16Fg)TchBQen#EX?9E=6uRsUEn;&}_6_0+@>xz*-Xode zh(pIfH>$Qn4{bUMlEyL$xh?&ro$3y0FBgj^^f4@T4b1Y-mGI#U!sAcPF--bfvR_0X za1}zhIe4tXg${rEFe#b%=yi6jn1879{Aq4P#gI*iUFPEH-a^%Qmuch=vKm4e7FYRP zu&iVxbzD1908!1@aQVw^E<8dSx-gyJcb*}vXFG~s` ztNuz}wG%OznpEj#FX7KH)wh$&&NT>4{~u9T9nkdGy|>XI(hY)0 zNFxYHhbSFNOG`=!h>T{Vlu%mf5Rq=AYp94wDcwv!YQTsAqqg6N@B4n=-yhrG_ukLB z=RD_}d!9#iUc0C%t6rn26Q4|&@sx?L;Z3qR=}$a#Te$UCbhiVfb`Evqwz@SwT2e_X z05Tvt^GNRQ7L$%-33mP9S3zRREXkw74=?{35eS9dob>PYU-(sjo=8o$7X-%nxAT4b z`9}M~=!KA>dW1ln7b5<$S3A=rN}?n$l63mQs5axIr%Hh1? zaD38lwc(g)K~T(;jNN4$;-&J7dGq>_@lQ=Fva7uPAHD0`8t1egeYA*WHy>n%d1&uX=MG0)IN!?M~3+ zrUn!E;o|;!vP$#KjW@?1{YL)!3TyU0O0Jx;VK;0(3H(Jw zdJIzmM!EXANibih>B}q$*Cw0~m&A%9cJ9#H5(D~(UKZIy*FR4*{!93G^PhlOPr6u! zNPH0Ux=ioy)2YITq~f@9W5nNeJ~Ns=YK8E=R!f zZaW!-AJqCVg0(!tzoFffd^_Ph|6YdQD{Hfy8eo$6Oo!CTu#k@=@^XZA8?L2WgP`D+ z<#tOb{t*YDZh7p*gp?$=x;D;ASNW4CnDMFtwL9KM6^J5@S9?Nh&YQw>I9U_j=o|hC z83bSJVv}us^rI1R!ImO5*=Rv}>sFdE*SPx7co6jj?$+ra?aPn%<5%6i#<}XGW1`Fh z@qmcRX*|^=I}X#t;T4T6d3vh&>z*(Nm}3s0erCE~j2zGa6hlH%0M&%c(d`b;`Zpdo}yUQP6fn+Pm45zQs4BZ5UJxA z7=6a;{60PDfeSO6v4?NtPrVJw?99|3{Fv-XW*_U^F^W=o81$_kUrBpw4%;A>va0F! zGm~c5W#T$qi{rJWBNzkTrcGX0S^!Mx0WA^}t%#&}ja%RIo>kiHvgXw%Zw%XtBN4_l zf2IV-@_~i-Ow}G!=cWOgmLl;aG4PwdR9w=PmAMhg8D*dH)H@+d2&4Gtrs)midOg*) zn?C)QEI$lC=8DgtZA~byp^okVIKq2;6ak zx8eFwWXmyw5G0M0RiT#Sm|PZN_``)c4Sdu4FEg6y%)eB*B(ne9I&g;ugR-z%cvigd zecs}08R^N+msPjKYvIz6E}BC6|z1%a(Nz zJk|U@2uWylh`_(PUG#8{Qq4dTb=g=@%(GHN8sUuVeqFb*(ANRO$C)h#SP$%a-JnH& z0x1a~jLH*|9lN#&d!`Jyd))rwbYH6{TTV`d?$=)g>I7Zv$U%h1=#hlBgzxu*1S9Ag z#a(R#ZZd2>nmHHf*7&&idbCm4-K=~rV$d(v?93%Hg{3$Pb;icD+qp)?S$TTCxg+1j zgy09K(RzX+u{R7+sng|fa~hg;r|}4Z;`AkQ((5L7|D1o^;1U-RZXsKy12kAA1JnqYguz^N5y=*&vcY*0Ul2;EO>g;vMo@%?i8uDTqHj9R zN4CNE>{{Xw`AO@>{ViIbAZkIVKT``L5}yYC49tgbzOOES`^9lzBydAXN1LtCn>$vy z1uz`gVNXtX{!G^j6XAhx5^0k|EUdF!?q;_g#gFgRwiTJ7oL^4Tdg1ltp!1Wb<$48h z4<&jZMfemr00k7+K3BE;SN({QO3ey*Chlc~?1WqxlIz}oCO;QK#l~DIj9L3RE&tet zRpx|MI@gr&*SpE`lgtE0hm}?$yA%v+&fSjTx)G9xfZ@4=X3U!wS+Q!XRvc6(M_k;* zbW=q9(~y*EVdU-U<)!Ns-hb8J!LF)ifkp$n$~RULM`m#_wS7RTZ+7I;DuCNGNqtXD z$4Wy^>S^9;>zj#(k2FUV`RNcdc-N4ExXpO8dSZRM#h)vk&xHM}dQ+Nvz z#i#MW`5SsT%Ko)-svy;elZ60U!st-Sce?SgHd}^&Zdf0uIs_RDnc!VSLen+!=yTT{e0@J_x5>MkfOJ7w!F4WDBNKyfD#YNRo)_?_h!Fbk~ z6pzUK%X2~LIOsM@wOIp;U)(&B+f$0vdl=FTYNJRJaq(ImPqdk*K1c*Y$2C0B$J9ostj6!wlJ-4AzcuK9x8GoZxwsNU7X>Yh4K;G{C` zo8MVu?No2nhX5U%C(k~OIl4@uIHycA;^NdkGpSAdPP|faqEgE6J&~Lnk#8n-E)R$! zrNL03D>FRNeM@B6*heSL&`>Qr>~q~)2;9L5#P3r4O-#muooG)UBnp7YYA+$L|0KwT zz0LZN4k|>22*RJu5)hP?SEq<^=r1(6AjzOWJiX<1ev1kJ{cMHf1K0eQVZoOaL(9>^ z@AlX7WpYxj6q6tSQ z$=ZL8l=U=+a5*b1NQ$-Eh8)csCUFM`X(c=(pi>% z-Q%(X<$~5Gh$joz87QR^Hqm^t18}y-M!>Lwge77U7vwi-E^NC65oPN;t;NBlnsA^1 zTSqHwl{;-4pe(N1g;S#6pA4y+T1qB-r2gwYKD*92DvIaiz=z(<9@ushK#tPc{I8gT zzVxF1_sNot91A@7j(qJS+1V+F>q@`Ae=ompO@`RUn=kRZo%6H4BR@{@P#y!v^d&m| zqz&1vBgFQ_#SY@c+IZFLKjJwoMIL!=y?u~hMws*sH{dY~r1f#uzHqnDI>vyXK z7H5hytNK6q=;eTR-#=ye(-cOJHR2C{@`v+{$6fb7nl?epFUWu*r1Hf^@W`C+kP9aS zf;OF13WvA!@BhAC+Tz*G)^xN z3gr0AwK02woYL(`aHzMAO7X6zO!5HZnMsgyxcNhcFm7j=b}$0TNVEOe+VO;_z5D8Z zyf5m#uK~#k9X^3zZ_^v7fy4&Z`3pnla3XZXg49x=fAWBs+*)mir5 zqEtKhEX&5`f*G~XCQxcrNxZb_wVCU^43EzzHsA9LiI&HYM#+jZ1`Uymj&HFO*Eezq zHQu)lTFi6D_1?eZuk1e@sQgHM(Rx#RIuG{;-RPh)ndr6j%;-3jNQw-J4mTBny!jlK z(+381gOQZL;A8s#D5eZA=OvZzw!%8%ihkpDhQWPgYvO6+x>QCm~ zdlFjbbYiJ+aiyAlYWq@jvXST3j-O=x`l~7-6%j`MBB?Tn8Kb#b5aEr2*>bMTfa1*S zG1j*vsuD>hBz5$&B{|j43@s~WkDTHyvIW&5bJ{uFqofHT-$kUWb=o zlO*?^V3A(Jqg6<@?%L>{9U)ayeH}zVx-FH9<1Y*yWWF9NmKr2+jjwXV-5R>^IAo za#?3+yjEdW(6iTV7p}Cjnu4PSRSnd}7L?$ZyNx%NJ_lC(I9uzRw*DI$n zL^`pGZg*uIoh@?&Lv0TY2j{HUb54wDTH8Q#D)ZMB;8W_PN%8oZkrLj^-_dYV(owV< zAXC!=G*LQ%Cd$;myuQDmaxeO?HI=9y-RC|ams*B->~A5W&-sG2Qy(8^@RH2jHSml& zr+jYg2;BjZVO?0-1hdU&oS%_{xMRgFo@c+IgMa02dCbs$mV)E#I0fnqUi+m9iFu*J zXrVo2y~h4)BXlWS=_-usde`2cCXN^qHu+F`7Y9XsvMWDR%RAC^eG)Nl^V$T;8CFc{ zgkPRiuskvMG850hxXT2^cKp}- zQeHLpPA<@b2p9T*2nKLeyVrcz$Z#+DxAqq>8xv|WKWF=Ay^@mrv5a@SNRfV^Fn1tl zp8wzexnr+*jqieS%-l<3=t;-I8SkHo*%Y&ZemnN56pO4}16>*9FHT?RSVGGP_o;X* zC93?W!FufJuIEyJ&pE=N@uX0{H~>eCcAo>{3m>lTLGkh}pvC103DFHl*ZFot9bkmN z4hlMk5YIL9fy@n8T^@$3@s34y|VfI42q|3n0c0G;C2olOwV9FCq; zf#@DpH|VvCl^nGmmConRfSlQ$5)&4O6ptNVQ~#3omwE7^@Z3g8`NiQqVZ=MPpc4Qh z^g730ed|N<<_v`a$a`VSDr6^4*F+H#X1bOyfN{CRv219(=*oN*E^O)Wc~Zhog03c{R{6cFC8XeQcK}M}zF}Eu4!*Rx*nRTm%^U2pqplJwF z5)yJEJ|#$js1q#YZ{(@lc5uUAj>HoksrvHIQqMA`iRc}nE#EqT!l?eCN?rVag^rt7 z+1VHomX79B)NGMi`on!b!uj&}ufCMo-3q8FuR^)@{mL_gOJesIf2@CxQUuuj{LA12 zo-Z_?-UFLy0-eo}pK?7Dg<^jotQuvtfpD?;(i?1 zN%z6<(zB0ljY2&SxSDd)9ug`r@Am-`E@3T{|Lq49M*s75SwKqHCeo{Bi3^-1*@!?6 zvNievbM2H;b%&5*qN433-U^cMZla)ppLj|ZZ*!9xVeWQul1sCWwktzc(t>kB_P{2g zYJ72JECu{KYP<^F`ZJtXgJTgxc~&u*$GQRI;hn!Ilq1eq-;@!=aDhUY0X{J#LsO6L825>1_)2*T=rqpa^+(+RR~H8CZ=;3d86eakBZ8>-il4*s%@ zcZ6IMUapO?Dl5Ma#g8n!Z~#o$^$rc^QqZmwDC|{un+^VJB#bvn0OSmLKhq#o%C?Lu zzX{2qvmCi$EM$S^0Uw5WV*c({?vSOTKD0D?vHWQ2b3$=n>T<`iYF&HCB|h*?ZIP4_ zbM8Y`WpmVa(e2sod{U!4?t4rjFB*_Q3Sc(~K98>OM>khiM09}}h7%pmPb8j(Z*YqY z3u)3Ez$w4+Aw|GQ_RfH6)7t;8YM);Re$aD-aE}Hg-#{rya5)bVuRD(z$+G~fHnQms z$qbS1^>{x1x;7aWNPlUBd@sV?9)zGO08I7sy?*TUq;EV34cSY7L5qOwWP$9OLS`g6 zgZqOGpHkrbGI z--8`1*q7(g>n0wYDqJ*K1c%JZ?e;+BNe$(*TR56fiKp8s!i>`L37!M z;R;4KQ0(0yvQcO(o%mw-G1zggKK>D=6%WZaH zcy$MX5kmgv+;$^|eA*r(BHABacFO6~p|gD4#PyHD1+HW4aa|s%j7R#FPn09;9?ibw zBKzTDo~lB1dt&o!EPA*Et`exn>|MCJYGi_M>ojzIFWP<-84I;egj&D_sn&m@~ArmN;fhbIv4F?nnec)u*=^&H5l_ao%R}~ zV_-uwflL=dG^Yl~I0~B2PNE>FM?E8)yU|%ab^uz7L3{v1#yj9Wcs(usq9CvhGS5%KjIriNL4}7?wxg|EMoZt|D|cc42HDl0ma3HSi#XH z@TK{6+8ov(fV#o#zj(E-Y=yw(d2ls^lf)am%*Wu@ z-HjHh@+<>764UABaY#mv=riCENERnR;Y>*3z^bN}Jj2bvWXhbH6^WPV6gMyL!7p;HAm6o(R!ABOa4&^1(?b z6i)%DDaK#F^oS&8g`7o4dQ#u`4vu?tq{Hcaw*tTTRo1pf4*x}xXj&w~W!gPKqn60M-E zxUCOaT#&`KuxDzw#bVNf!WF!tWx|7><-@#mT+YuKwyhL1-N@cRRGHFn4ySF3&4aTw zk=HGDW$g=$mAPn{-AJ!lZgDQ$Oo6_sVd$Y4={nom{gcyRmJj=bhamlhxza(4I;A*#duc#k zMBX!ivjc|C|J$v023|mOA^I4md)UE)#rxu1X5?$qArPzmTeepvQ;{35c@Q%aCo^H9 zhaWfrN9`8cSj~f!0f`b9SG>&(NKy!+YDPK~M(a+C($Yyh+8vfSHOj4POk<2B7O%83jfk=W+cY0-Rltt_I*=Kz}ud3SqGUA6^XLXBpK}YTo;og%b$m$XQeZsJv z<<3F$B9TkQ_ubO={!5TD1#%7G8O7nQSb1k(Ir?hc!u(g=o-t8uuRFA_0UUBI^q9Vj zvm+xVgkpMZyM8BLW(I$)64F(ua=#Upf2Rg65rI>=rvVhMOy1{u;(|Kqh4}g;_8!3y zVfGk4X1s+p1AIp&(ISLjj#(S$@D9{)1M`j%_a(iN#5|(t)@1Bu$#zDJ-CTTi+t!+f zn<%;!S(qP=lyE;Wi`lW~kvqF10IeLHe=~!324h>||DC;@yj`HjSOqD#63zzJyx$T# z@?05h0a6l7Vme`22gBpQ2DF~zZhJE>XG9;7wiXw<$;m`g8I0?A{&7*NtylsIQ;j5k z@LnuU*gzG<191{O7b%NWkNBI{U*276{PIG9>{MVhV;e)p)5!VIzjn@c5)&0#zXMKudwn0!0<;?hMOFMNT5{ zD-vCth+vS_6-^?n7Wgxoy<&JD@%_*8e=qycTs8GFK}zI5be8#kSZqFms8|_v4rM&4 zlSf1B(smxD5Bfhe9}?WE@VQ}#aG6fMfeTPNd=plB*UnC<6fKFk{YPvvkNVv?h-p2$ zT%{lrs}8?ov{iFw_u|X=MIaY3bqwLt52TMz5g2mg{nm!+uB~^8s~wBMf8q@YFs$JU z1Eyg0M=CcDdz%#KKhpf>{78mxH2)?8x{!d|T4fJ)0didbaSp&NB`~N1-NSQ0PGcZT zEchx-&mA*J# zAiiE+KcwGVbr&e->+1S2{8na0E@lR&nSgk(i)0s+C*5syhsvlVmKtkj#;K3wIcJ3x z)orNcoTVun>~c3baz`zu;+}FY1w0B$BguEE_>4z|g02q)2mq%8Veov`TS59`Bj{=d z=E^{%{jQ|dU2gC3`|j1;ST&?Zg*T=g1Q6qPmPo*|1WlLdgj09-zmo(w?mPw1(_;Cb zC&_ww>f8>Gh-((*^B`DPs@rQW1lYtE5qau&%zX!iU%MxRl!fe79!vdV$yl6;M@?*b zej>g}3PN=t@jY(W(^L{PJJKcQtMdbj*0DsvHG!`oUeUK6e2dkqKV^Hv{XKB5kSLL# z04onV-UVj234l@h5Iwe(KAX=P9sjlIy38K@82BOpEXD`p5yMB_%8sZNHmE6aQ@PuARrAbL0@Wtc}C9 zFUP40KAOvY`*eMQ%Y6#^+excL-|9HFg#$pB^J>1XB-TkLqnGBm%dWHNCAy$oCT59{LruPuFU9p1S}2_@NP4CVV# zh0A%cLPmFUffpawl%PYDR;DuFg|lxui`T)mSHG6*4_|GKNnwZPDNHcelE0Un+P#Hb zwFy0OFHW>Q3cjpCpTOs|+9qWeBpZqBOnOFVGrtHk-CN$4z1AQ`NV&0RqcxI@Da_r( zIO~&;fq;kU?*dT0ea}&UZC6%rJRw>Qew2<0fKEdnG`r&}L{BGf>`k=6rjoB^bT2OO z5{yn(zU1EU_^X*87S;wqHjCZIpvffhSQoG)$_;awx~*c4(|lZk-nZX=TP!iSkj zE9iaE!ou&3DSwTR!9Lo+G$aTld<)zCw#ZDj1nmvoZ__oqREL+z!M&XEohLg-V{SEJ zHB&|Bk)D|xGT2nF$)B&mFiB1h64wSUxz5I1R1CJw`K2|`1UAexBs!m%4`$GHDNeRp zTYGcAJmPQLwsJLS`f%uQ5cQlI<9ZKqtACa4g8D#ql_ekLktK&a7uEVn4-pu zniNcoDfK+fuU_7R{iZN$j*H00ezrtYUDj)y-^Z$0E-;i&7%=UoeK=#8OShrm^0Cl0 zs}pEoKYS*SU``Cdqfp2-bAF1t{B$Z$I7o>Z5Uc#|m~Q*hI#toudgNXPRn04oR5STU z)Lv!lANw*1znJBY&I>+zlM3hk3a20*OV1UmK0hk zg)Wsw9I~rt5E9k5OQE-7*naa8tQ}C9oZ@UaTAC7@rAm>YteScq`tq5x>gShb$fYpP z@~ob&r|<7gnf$VqaW+1G62zGxVwLr)t324`3t!#Em*%qv*vWiU6((o+9VX*Lh?vzG z&b`K0AMfA!=BD;;KyS9oxlAM{#h$p9SSscZU?VK;AAft^-yF-=g)T&D-BqUe2w#Rp}P6U~!#XyL{ zR^&lqzekJG7-wt=l%xj6C3NK1c)PasS)ov zf-KEJ^6Hq6uiZktev<0*C$pb~fAsbb=kACT>| zvPo=I?J^)ltM=g&Nc;R0q_NH4f)0*fz1S(Ru3jh-y=d6fL(l%j9X%%E)y7RMt{HIj z84ktB%&}z^%vCJz6v(Bk)_VQi_LNd%eAnBDLY;VOmZ+QI4^CR0@NQn6uC`;Q<^g^p z!5d*z#^`tDA&Y!sg}x`U{0mMla-Dv+m)NEhk>N~oo0loOj?KD*gXi}o$r{#J$3WCN z`+8bpX_y?q#>Hg*J5i_R3Bn0FvzuPE zi6QK{ZO7X$D(dVedKaaf{`#rRg|(gWiEh-Qvz6Pc{T3!r=iZZ3(lblB+fUG+>)568*K!v z$sO=I=)Y-6R~DriG}qfVoP;TPab`1Dr_@?J&#OH*^yEeR?hdLLY8)1r8Zy7E}|u5p5gwv zQY$ues>^B=U(0kbd5|^LuFsQVq`#R9hUY1mj#dCmKS3uvGyojYFft7jxpe~ss?F=^ z9vkSGqO-ayHE(i^F1(H}WBXw0pyJZJCYg)VGYf2Z7xYUjY@|epw)_C6X!Q6wKBw)G z`;m}%-|Ejkl0Jr=5W}Ip^8%KFist&clUH>O_O||Icn=C$rtSAgx#7k&!c%KY#>v09 zVsoz2ZyCPqTLFom6NG*XjFidG@NA~ak8LWxKE8Z$NpK^8P4>iwf5D7AUEA^ZIbTKj z?Y2Z3^H<&JnCz6iX35;J@G;eYoKRV_O~~eErmxtMjll~JUi^Ok3Uahsz<~H8dE}|v z-0Q4kI#oo`c}3aN?CC;ekT?kjn+xmyjA@W_EWtjBPK>$ z`4eQZf-Slj8}X4k{)VHcr-g?Jol1EJVpex;Ih#1%OzoE>eyo0CNOv)brfH~Lq3+ph z-qkG`>){38z=}BVcTQ-P^gkdgy9egdN>Bku?)hnT^KZ5So71?*QIfz5n2Vif$7PB` ziPP|9f8JDT+QtT2KB8}uGU?fE7op=7xKZ&NMPHwQ&B4iVz5bVqbPF_t=k;rg=g!2+ zIX%xw(Du|jF7ga5#qq&XDT44LX?T1yuubfVD#A8>bL81{`U4K!N^#_(!c?A_1hcSDAk%VDtGq2@k2(({fm8gMnR$bnF1T&CCUp;47;IGi+nI3U z_F$coxr<&gOZPbgkAcP*D!Cw7`f zOU!$eYD7pcpJ;8T5m&}mAXrM#l;CTv>V>uP^AGEW1qLvtU5wmtOr;IWXKQelN4S1h zY6O3L`iZ>s+(bbv9Zm^6z@H- znCs;BkH1$Qn&pT;<5#=BnF2VyvzuXJp_uTc7o1vXzu{aqzLKny_`zT{=2`>IGe0f7 zyNlc> z{x73zZsf|!2u;bTni_tv>$~;IpL@^#S61Wtc_bapbCW3yk!Ei5BoxZ=mc9a^JT3Q2 zrZ0V-Ph9&sp;n3@R*aL)OviD}-|`4qDAeG>7&dK9b?kn;t}F3rQ~8&YObIgXLzwcy z`s_)V@#si#Q%hyD0uQz?sHLQ6U3(UYM7XhC*EC=4Gh%9%<`7=&jZ$G8xDP3E?>M4tO-)=rCb?$)BY?W{fW^H|F1PE#3rY~ z&dviXaOsJfg0qoM32gq<#>g?}o)6?p+K;&!y?5h%s<8U6-6wvVFc%WsWCFQQKA6+$ z*5P=+;4AUpqj!bVw0=20lKKMaaBz_eolfd*Ryr10Iw(nL|C0B$(v4@6T^?k|u^_zJ zo011ukImQ3e~e>N0NcH4zE-q!Vq?%hIQW7cBt(99y{e)H^4HB&@%oLO$$&C=Fyme6 z#AF?!15uXi?V)YubDt_G;v@jbx?EY!fah~aoMX!rq^cl0pJlzUT1>>esSbt!nFK7j~;p_aDh>~ zZ?lfb0w#uEYU}8nTYMD&3?h;OC#LjXE2t|y?{V+5;8>O4trq@aW;kskd}iUAmq{&J zm+CZ#cv`0ClSsV3{hsY*=p=cDb*^S9fxtRFzj%2KY5Vxxbqx6#A>rF=LoI)|>jDou zpu&2AVM%+2Zr^SB5XY)}%3^zHkJ^oFDKbFrN&Qv)u)m+ROW)Q<5!4qO(z5l9H$OYQ z%GE;xJXcV@aXCf&RHwn)i|Ed^Lz|#B-vd8C?Mr359mU6^6Ddk9@~oF10n{a|Pj;0L ziK}>ZuV$oTlyfn6(@FJLlwZ4VTf4{vaz4mAl6G~r?65%GAS!eEoZGrCKRM8ecj`3-fGy_R<#J)L@qqAyl5?@Ec7kA^Epvu+PCH- zXQaWKJEd+hO-wI;`?PPEhiFKA8OE8#M$5G#=VD`t+MaGK0n|EZshVVr<%CoDIrp9e zY4*xvt?pZ!Mu2yd1j3@c=Q-KU9unXZu6QzW`VMb)2iN3RN6^l>iZ(4h`5WNgG5+0W z=$h|Ss#9Z0SudA`G51x^JsYBo;8>pT9k~>b=gwxzkGA?HTUVdp1K2tOpdCS0bXM)d zKr}tfn(yIqqetfGykCAj^(zhKFWjq%=BCao88?ltvZl*<-`aHPtmVI$sqe*na5B$- z15?nDi@BlIx8|8i<`Dy=Z1=TBW-|4aKfmRNlV2SysaGx$ek2t$_G0Kwu)!;Q*K0jRh0ioDPQQH*DGwR)UTE7|1p6SiBMKB5ezeIYCp~aFnUKR{ zx!NQAnWWftcjS_mQ3Z1(WR$!R+#t_wj~SSo1}L9dV{bs}S;ktO2|%-8QhE%HR?^NQ z)FJ#y(powX>#sI{jqVJ#@*R>2KET&loz&CWeZ4nC`_gNgdeoQcYH$8t(mTqx#uHnVM(T9JkMlrLtu{?U(gUBNZis+xx(uMH10lrAu<@ z$BjSlIQeZx8Vs|$`25deB8gZtZ7Z9_&x@5J=(JSzsU!*=+Tv;7!KNxyXaD}`?KaVg8pY_h z^iIRkKUWiStGv#rS|*!ft^42Z1!aGt=F>@TRb^_jsncKj2wW%1lxli52&gWe_ws#_ z0RUz3VL&21(8dAMxmvSCB;M9%RBUGW!lYn#z^?Ahx13P16e8Y}@B7E|?PXSEdfME& ze}CWLTqr`+x>oPF2(t@YVVoGU3nh4slah{{;m(-)f`v zO1a{etecI8DpSXiP0#EX{w|fU$z1=n3w-Vqzw4EZH> z4|}lYA?y3HCWjb{PJQK2M{6xmFne6Y-)+!(3+roZC{4a@^U_dJ|HFGse0MrzyYB7f zr@u-1a`=o6w0QnupC$6_TI1wq`0V7CuE8m?V4!r#plDc>8cw zlsIeaSG%YsBh)ZJ8oc}>@bF#P5o%F=UfS+`TT_aAp3{iN01d5bjrCVfl0|zaT&Zk< zzzLQ7#p|1X{VSAhnI)rU>)I(AkC(B{ai3?8Af*+O#8Ga5j?_|`wlBo$ks=cl(;Fxxgeiyg}AVza-GM#D>vriN7T;>%e0@-#qfQoXJi+cVkw_;c> z@IqSnN3n2`#;Ux9X2sj*iI5+Ge{3hrMHjckszKhrCnG0j4+A_qb46w@pXb- z<&oV#&ZaZq9Qf8w3>Wh2SqT@zjn3(x#HUYGlQss?o2^SN+6dy`CQ0naY^3YIT^f)+ zW3ABCPTfaLL9J)q)@gaQ2CMHx|C%jJSv?i%R9w=JxED=9Qh)nHid&)ir9YqKA*V=R ztsdnHapWI;GAAOHP|$=njMLb5n*3D@HzIrL=U_08e(ALD)@1P6R-7L@X87fn)Eonn z;=+;O`i^u;-p$Q30Ax4;Ch*l%mKmUg0U^G0uB1393vIUksjSmkj4ER7mimOXkL(&Z zlRrFC(kA^@!{C3~T=UM)?}CaI-PqiqbHu1gDYHGuDTAU&ToKpX-@c={dHpO`CIb3u zX%Oq;)^tKaOLtV4fm}3}ns&f&kg*VvF)-adYFL^5l|9}M9AuIo$HlHICNjn5i@`l) z+T}YS{XA;`(mvCxOKg`LiHC`9PIIfoJjVYCisil}3C9`zkc-7WU1-0~vYF z9rv=^?vQ_IkmpKumlCZc@Qv1jZ*exfmtR8o5*2J#DcNi`p41{f$I@^t)VPYa|N|-YUu(`^7F{~ z2ACQKXtt)_+~DMg9Cvt0&CgZHFSOeUANi_NUh_cS=;%V-@SmnxYW|7yJmA8~r?<@b z?h_P#V$S^XiE8K!x5hsE3DRYgl5DeVc;w@qHDzAcKlwSq;jrsL&g5s7g8dPm(1?5} zLTz5BA?kD&IL=Do(-t~iPy-m}o)fxdby_(Yv-EiAa!cjOW;J%WHyX1efwvP0)5r{- z!X4Yw76f*1q$Wl<)zng{GGI%b&)3u2n@_|@A&yfE*Tlj+ACSQVg70S*bdwJ?s3VsA z0H3;Kb*^pPfG^`w-)ecRm4t0Yt(>@b55tsUb&|PC&|E4z|GPmgMKbaI>UO`MzsGQE zjR4@hhWE-15^vTd+`wz3gv|w`%a7)v`OtJy!LQ0~c0%%OujHd?4(1;pY)m>&`Ykr6 zfUXL|32i?SbL*~NjZ@^PA=te`PUvZb$f;SH@U)j#01)(OrNi$U4 zFwM|ssLoi2ot|Xtkw$cYNl%A0)0V?li@*Y66oz8_CsF8bS<`~s(#;V-LW4S;Lm@WS z(#uw)^-vH92?!js+paX#h$km)8*!V z>ywCTlinkm`sUuW-IE}M+BmK`JDXndYO<?h{_IzFhOtBAFDCU||cKSt!h1waE-mFKG42@f@R^>}l?8r(1N&j+%? z{}dM!u5N}o&`vhi>dux;AEH=}cz(%;H6Uv5_e6V0wyING`){5ssFsagNbUL{JmqoC z5v#639rr|Nyr`IKPV<}f^5fsDeVaJ<`DJ?~{)4_#Gm~^$Mt}||PFi6TjMdNC`BP~) zIw_WOX__#vFsKyKX&~pDhw~st(p1Ude_I}nqIH4*d~jy}j|XAt!5i^hcKM2;+<450 zwuAXL-fb44<A`Cm=DsHsDB7m zOn|%Bviroq>va57fYZYTzjBzA`AvJ=Syh`yykpz%8k-2|9+LL0Zj81mw|vaOla1)Mc}^`QlD1}iE2BV};bLx8BS}+y zfT&!A(5R=|6SGb`0ol)}=eqO3PONi$mk`LjDr7$cwO zTi@LL_KJPLKs$&yo!&eaJp&k2Iwmf6^QBj}*jhF{2*f^pWmdN`ePP?5Zk4G7E@mA# zfA44|(t?Kh+e3Oe0~i#3phssaxpl)Z3=EOk%b% z16wN&Ge-AT&=b#JGMnl1Gz;9Fda!0S9TgS&R=BO2WOLRlRdVMOoh3RxYlqmIq6C7d zucZJi=$`8yK61EKxjf?k*!$8)D8D!Ed+cN>64|Rk*~`9DiJ_#jW>2W>`_3Rr3!=!b zno_nQ*_RoFtRrR1HrW~bzKogYOyA%0KRhoUug$s7IoI~NKG${5i4Yx$(R@b7+0%rJ z>V2eI?SH3k5tDUH^-mjN%zC&Ov+#bk>xC~jO|3EHN@tQ@?{S5!LjWR-Z8&#!&E=zR_FLEuaJM1)_!N1a5d2Gw>;K*cxta_aMXjKZI*PCS*(|IMG;OOLu2vA_ExgE-Ai4Pc8!M=Csx z!=4)1Gdm5#ro}Ek(<*BL3PHUzUo5_9G_Y1>olbq8%w}J%m7;&|?L|7m+y}bI{@afS zDv(lP5=9Q%4endc&Wp>XX3@L1ZVML_zvB1kEkTD_S7sx9#^E!Mv_Gf|jj3lUkDN$p`Gm=&Qbzeq8)iQ~xm5nPBYv7@Li-Ed=$%=>fiG>Gsp+;p^XRp)!cJBMgS`@+1KC`f*`s>S_F=S3hfeBi96x~JiRC5`qSjS<1)0TDKI6tov6>-ST+bQ5x zR=&J|z5S=a>9F3zW0#A*?kD?WyAf?Jx>kibJp1;}cx80N@TX|QqV2;kp8|U_vy^({ zqse&UpYI400sJri?fj&}LYLOD0zba2_LGA)Q)pB6ybGHN$#OJg`V@l$h5sv){zftf zydDJ}Q;gEDwb5iqnMxd`7v8xsRV#D(n|+0}DskjlRM(PAPPJ*K036aTK$%fF%5c>WvjW6}twv84H1|-7p_V+H#(U?vx#}8wfZxxH1fA^Lzst&@>etpo{ zq;$riK*dVzC;Re^d8@Z}LVKDgQ((|P64aE3sA5 zQeL_3^W|E!$WYJZ)oa9Zky;sPtiM!n(pUJ|g6E_#mkZZs|CsQs`}r$G8i_d6#M1+k zh?z?>9|mdh5hKv3f=u5}?Aq_xwcX{|iU!QS6MDnmo-8T&c}I3Jjj(d&I5F#vivi|0 z!^bMC31N&_cvj((S#Q0K=BQ0cSoZsCk#P?CFTYp&JCpEAp@FRV->;fVMXWXM)#Q9o z(ddthoK?Cs_iZjLi)O(*4w>xZ3X-kkc`&Vse}GqEFQO2|p|E$8F26xMwCJ+UV7$|t zN8Cf4JsEb&r5|mS2m;~SYO3{I=)g+hJtyN$r}6@?rG2MXHCEE{PsK5kH9`4y8++Gc z#`4j$RRQe1$484UAJxMNDSewNJLOn)jgcopK7Fq?{G2;4b`-(F%6zo?lOypF_MfRB zEef3@ft#LR*R37yB3nF*^5wN_wcPmFu~FboyhIo+nkRQUKJ7=tVspfk&KJ?1bg&ru5ocxp| zm{z9B`kiI3N#$uFEB@EZV_C=A%0&h5^MnqY=hld%S){{&#~7g3tbsvD5#0n$Ko6Y@rh=H{K=X-ahx6b#k3V6RiL^ZE z{K$-Zh~e8KEpEk62FrKtLb!%n!$bU%>-_aBO@zK%9y)qCXw@>lj~ku37P)>ZtfzW5 zHNArg?dupLIbl{mW4ryQTT{;9cuh3fSTJfhLXlp5B+TCMEZy@-xOLCN|0Dx)HchL( z0;|(Rai$gPGCXEw#n$cIAQRIK>T3Uz(zPn zKI?s}i7;CD;o@cBo)9WeJ)l9yJRIA2pNdNXwkqhQFKY!{r>N38Od3Zc5 z%On57tl#eH-Fvsp{I4RI4$HqRGH*?uLU4gJ&=zkD%^%|ga&UECBO4*mDZcJwi9II# zpa-4M9I*rO%NUwaOurR{$9=y_ohu&^1o#jxXZm$_vtWf?~$jrr=jNt5$Vb z)?FWloZ6OrYOw28!Y_TA!L}fzyTOIEfuzn_HT9J&aX>#edH-r)&yvA+#>YDfQT8*< zHbFNg9XUqf#ou54LyDLlU?24V>?0dbw#?f4qq0|Nxl_JFbV+R&XUZ**+FBNpwQBcp z{vbQ2VzA@(QG3>^8oXoAihfXEyKLm~sW+@Tpk6VKfp?Fd@YlL(T$3lv2zFd^Mac5t z;|}e&H_Y1DxbivGq)Q68Tp2Gr z4xWbbYp?7gkmhtyt|GCE9o`Y0$d8*zi<2F;;-Tr+vJMT{5q|eFh=I<-ivj<8sAE`$fSo>I#R=$C1bki58 zF^W9M|*K}bzgG7ng|H!yzswKr-*ZR#@;FGYGkfTsMwdqy%w(E zbA8#O7&83ih`tFL&k!0HZsp88#?^CRhS>>y+vR!dUbASW(u4Rj{RLQ(jT!Vd|AqSI zc@K7pet=0XME{u~jvv;n+wu{AO?xEvByWvn(m^1gLfFXq&H431<-9>>!+=7u$*I$B zc_qAIoI)e$(wS+0VJ%LgVjPtj9jZ9oijTf6WO!tLxU=)nvsKeZa6-WBSDI~{-8dbw z(c#UCNAH`_r#a$jU7p2djCLa3*3PT%$-5%^f4dBsw-+uUSXP%GZv9ja(7ydq{7iHs zcwif8v$iP+`r71_gNao>!5`{YmOAuCGPc+HyS%@)+wMnbpRQhFuDNhQQQODL_POD9 zzxaMBjuMBZnlC=H?Q<__qqhQ*HnJ98pJjoL>Cl2L;GNCVO%U`k^jNbAs3@B+(;V`f zw(rgO$TL9CGX19!h#T)^4fE!#P~8lvr_DOXv)DhCKhPxFY-uK<@IE50<}gGsr8Y#~ zKwxs%>yS?YYq+H)cZ0rJ?6I)}HgI1%(e<-OM-#f|Zl`3EmZmHsSwf72PUy@FP{iWL zZuCX}U!P@|{GlzO1MtsMU5jo9R{SMxWB39mpNgQ&u`}2alWo7XRTXM|XLqS6E#+GQ zD^2c?v$FOJydF!ELS^W9O9p4w#|3nQ$0F3@;|EP9y|mxjG}wt>NIWQ$Rp#arWzWAF zi%#b0-gveZ6wjf20~dAa0-o;{IoRPe^bh$9ErV^aT|z19)jZD_neCw|`Dwd%Ke^WF zMg%9SM`b7D$+elCFT<-s>xt$Q0v^&sFK`#cqbC=u;@^HA+L3r5D)Xo9=jnOTNynT& z8@=60XDs@MHUIpt$eN=A<34uKFwzte%Wjy2t9Bz zB#ZlLys*!g+`7|OhNeBqP7(gZ_3PoBfg`M>e)gwbfeG@+E?H1FS`O`1(^bghX%z8X`Wn9{&p=dUG6CITU>}ewdze&| zqEpX|$qx%!+~1yckwm#NQsWD+)2~|{r;}W|^YaGk6txSFRqQ;ceaZNN0mJx$QY1Y%^+feWtxM(>0(7~TWVib#mM>(NvsLyJ zm=5kJp6tIA6^K#h;;I_iCOI92Cqru@*cF>fO%Gl*E@QT<20~ULl3^|tV=Mm0b6!x7 zUX796>%J&o&)j4;v13%?(8*qVQt8*Ikj9wncIB%TK%#{|`vuvZpg?OomU=>8=gJC7 z5mBS7R-45%ekZbSnw@{eTJ?Mu*LgF{5-W92dHPXMxj5^|Edfuzl8g&hKa9HL=`p`u zzs$XMS9*}(U18WThU)ERBrRWTY0JqE^^C($h_bV1y`wB3~zi3Fm2fMz`>8HV>fI(GHJ^dIkjLQ+^Y%SqsiCR#c%jmp-A!C4J# zRD?&Wbz;TEPM>NAT59vv#=0j|ip2snC#_s6{uFNp{HzFDZdq~El0w%9S5hVW8U@d4 zhKprv{8%hsXKFpmvF2P{e_xx)d5}PsvaKr?G8%m?ZT-sV&4F`qNO^<{A}YT$<_44O zT$CFrvmLKQMRses1VP6dof#mgnT;gvYv_^W`^D`A7tQ;I`uI_CcSGmFy*%grspX?6 zvO?B+K^n1#_ZXMRxk>C`nx|TSpbW!XRd#MKPdCxEsXnq&AZx{}Pq9xbb&j5fTw|4N z7^@ZG$L&T8zvY|@Je@6AQxg&Wa+d|c1?r%-Q^t@ICj}$O*sa!VeN|PR8<#moYTnpX z`dq?|j0Fi0%RuIj+@WML6T+2cR4IM)8*0rD&v zAXDSB_Tyccto2Hlsd~nesng`buD~I!+Jp2i3Hk>nkkW{5=3sAfJl zhm}iO_s5N=p%KEIq5I$ca$qqoi&N#Kp7K<_q2;w2n{(lH?>Ft84+&#T9rV7EL3&d3 zPiu&^$`1O%rlI21CswTRR(E0&-S(9vR zY026Fq)X43I|w(BRc238pIsR;P2{6N^>ig=JaPJTwc)O`t)@o#09Kx`SoZn0kd(7W z(TG`%LwKRseu4qn5_jtjA!CA*|4M;Awgvafrom`g!!9AOpfc)i8z&w465?4`_3sO` z@C7wQ!nE8!t_2%QvB9j>Y0~8lw!&JXZq|}(Tu|K#`Kfrc!Qj%##AOcc8lgbHPkf-a zd35>DuJOa*a*c+dhOfUc@5`5FTujiZ!YM*~##oQ2fNyF>e#ie5hDlGg<}fwlba;?S?5axbKwHRA8|O~&xu~-SF$tl~k;YYnXwVb^(f}(Mb|DgfUYYmqD zX6uo%dKGKQ4J`||O)iD~J1#7tM~&kr&L3X&yc?#i7M=4*EA4tOV%T}L^+iMF9I-J*wWXlHrs_f@BV_0mupO-t0u?6nh z4gMI}*b8*g$YDr#(+nH26dJ!Kz2eYYKL$=99kf^;`SF{XI>GYupZy+Ot*p1LK_kTr z=&b66?}hSVFI161rWf9dK7WQw!G&OLlq3*} z4dHm5e*QqH5tBeFO+ORx^HhY%2W?(C(G9v!X*Rz+WztYaAx|Jn+FOcRFFF&CeAG2S>3nR{!c>X#%|{oZ5>EMGr) z`t^`*zA%0D5t-+MTBnnwe5=hc-@3FHZcp@EKi#}z3dsHVGAmEjp^IDeL{MA za%20JEQgEy^fl2Vzg;85@T+$(8S&o{KYKItTx#%gyfA0+wqA~5tS?+H$%J`(Nwb8# zQop7XD7Ug9Ei(F_a+9LTn#t*S&3Kz-wQtD=bbm|+52}9G+h`nb>8jWyFHvi%JAgNO zvwUXQBR%UQ)I(NBM)2hk9#5Lb<@z(Yh)+7%+4*VvZFY`_R=aJOWupuGD|%^1j{n)@ zRlMZto{~Nr-d~6*%Kz-o2&F7NMT!28cCmxT{T3XZmGV;C7uLOGm3Y@oW!tqOTl&9$ z2f+0ln3vBAAbDBg4HHNMy&vxnBO<#Ot<^b*DJGm@!ovIct(zpO${= z+Q`?4kr~YC4-X-3dct;Lghjnw20WN{gvDRdcI}pCZ1CyWFYH5`GeLegG-2-VWgw6+ z*a4DW3>4P1jVLfSs^0k&5o0d0SE=n|zOq8t*c6f3F4C$z=-qKDYOtPgcOD7*R!}dw z3!b?P=30V|r?xpuxgIEb%r*p#mB=*!hD<-!c+HmI!RiK}o-@wSv9N!*|9uHlk8pae z`!{`VOcre|sl(7Z*Hr4b%|ixncZtX{kS-o~)(=+8gF%#@W>frFs*V4j?7`=mzCnD# ze=Jr$$*4#k_bt)fX|` zrgQUleeoD4wkLGtc93ee`hTW9PS|;-ZlOlHS;)|WeX4I4rpn7vtJ^78v-q8MccdM zG#gIF>&!t$XD71LDqoTo%eU6waE8#~OR?nX9aaMpfj)eCiIRCDg2 zluqIw^<%8QckK072ub@wQ68se&Dxmkj!KCfW?X1%rS_1%)s)N=pA`7)1mI#lrS-({pv7v7(g z(pkIZrkziP4akV`U|A?!Y3nau3zf82W_TP z>qN)|(~k97Y9q7-TeELV#-CwU@}?@`(DY&0DBc5Cnebt8CnsGyfFCoWPoG!vOo(}{ zxg&01=V_eVQ6GX`@b})xY|Pnt$_Z`?deE8y20jZvsz?p*pnKl`B8>A|t7UJ?$YhIl zrE6aBW&qHu{C75--~TuTxaVjoNWH4WotAQtfmK$jGdF3#iateq-ENjU^*Qy@+sE3U zN_MqOVvZ|FFt?=*)5XU1kQB5N)8Lm&LSP<_uGt}=KadUozYHNtDQ{w3BUn>+u{=#+=c>5yuHz$vSJHcV!fH)xqPfSd`q-v$dTeYi$jT@l z?bKiOHmSoK2r|6j3R%P)*P*^_eNs*^?q09Ni~VSL)UrSc*D}F5I(*KmK6Z?*=f;Z> znko~lfHC~@h4Nh{)rH#c3Ne1nf=AYa17Q`@jSAxhSjF7Lhxy5i}WU03o`54q3jQmjgCi@1GMpiYCt)H)3SR2vc}X&Tk|5 z3HS0j!dgB5blZtwr*uY@&4gl!R{=g9(*^6pd0o@&W&1YI1f65q&7^4vM+B)SWy2x; zZ;ne1oyNus&-3G#tD1$6ZwL7-PhT~oIyYXA)fvncXQXmv_+POdi=ZQuE&9kNv_)n3 zvf*@TP9i<*n>D)llaI~N>*D=`noRXuvo?OM=-c!*H%yR<1X8s8MqF;?LkVvAA7!?w zZ?h)K+L2d1-8xBk=9*S~kB`1k0^uomABYyUfkHmuLN3(bRogx}N!08}>gOOJSyoEO!w-XC=PpU$lIbF261{zm%iz z>RX&4Z96KQPAoUP>SDF5!%>0JHuDxWyV^Y)N5-NuXOWCV`5K&5jsqbfCQtN7~!qx_We!tn^_(@%fho6yXO#i>$nNYHRW8Hl5>Fe|>Q~sAC**%$DWcYLzAT zPc09GG^Pa4a5;)#E8CoGrQKP%v!g#Vo%MV12u;9QZ3e8nWMok}cb-rQmeG5ydQZHR z8Pa0Ui37~Rs)qltY}Qt1e`rNPMWQUMWR)Rw>OG{YYJHR)%F4m7$`woS(v6rns40Qh!IEPCX9%GG7?XnVb@TruIH%BpT-gsw1B_B4%I<)VwG z$9C|}dOu+VQ&~%W-)~B5LLc6BM_99NSNnq|=4M~6%K6j>O!Fr}k5;Pi-qwCk*To_D zW%aXV-`9pv9CiivcgvtXSCo$Mv!kV>lJHFc`K*IWS ziph7&BV{g;OZmc76%pF`2~@~az#R0(YXLX9+(?#I%5!cJp5N3^`enV_oa>8AsR~fD zxO3*XhOU}+j*jq##aXZ8(#j<+hlIJO@y)uCnBAGF-%^dZ->$TumF(?X%TpgDDXqqy zD>unO+sdk)5?rnm>c-e$PaQsU>V(`UjiHj98BmSzStD;IMGUxRMQ|im=x=eRq=rcU z%0=`4IQ%}*@@lj29=5XSeoo-Bkdgl~IxhKmnorsCQCDG20+p*^eXJtkaO!So2J_k> z&6+}F@5$v1F<6W&NnZ_p=MKr%?U=cwI0Noc>XtBl0Dtq_tWNUc;pfM-4#qWTdKYc!-RET-`Y}fBK2emjn z>|nOiHC~>4thG-+2VZrv$ zz7UpFEXdU&nQ8H_tPXe+I^vvO@Ke;PScLl3zTsn`ao_|X;nL|f4aA^+di?WAgUm4S zud$o8=2VR^wQy;z> z_5^jv7Wx(kzNT1xS3D$P^k!cGtKE6Ugy)kH-a5gxJ>c?{_DJTr?y2{Cyu}@@b(QgY z!G$Q@6d1V@q2kuwT&2^;E1Z?++h28_lp-E&qVn<%G@l1QiiW+IniH;fj%?c^OL;A6 zez0f_IYBB#MJOW)>y}A+&{n`53Z`=Un0m$k4eF*(i|Oozq;Pv^YA+Ga4jolr?c1cg zl+uPc3o9YzTy!?5`C6A#g@Y!G+EOs#E=hB%V#zR3_)&M&c{MX;IE0+3@e-Qa`$LsO zXg3fzJgZqJAe%^7r)rQ7mcJW~w@oiPg2PHwQd#fw(L5gU%KB*Wm7c)62hQN3zH27? zHflZDU!FnRzS$xnvH|em0y}oCSO_o*o$K3?FQy8P9jw*nq3ET8mO_s49L9)qhjfU- z>!Uqh+E`bMOx01RKEo#FmISoX`D&JQ&X{*%$u>#hhGk4Y$_5|I?+zB$%v&U@MP#Gq zUOtaP$p*jiVnV(wfC4v|S{ptyA$8fN@75(I)!BIb28V6f5RhHYZu{w^71hMQMz9io zMkL$I=l2piOHDOJN8vbf42l3(cj%6aAW$JN;J$=pHF7i}JE5uu^zdyte-MKP)L?5v zkygh#$cNjH<(Uc*P+?CPoc|20g!A(jww;DNXK-)_LDuP8;Po~Khc_7!t7+5wcd9z( zsSQA}J|B8Jz0WU4@UHb|M1H+6dYm0v1u8u0xvnaHT9eKg>hK?AXF z$l5bJ^STup)A=)_)}!6uu@?dTMy z@#*iApYNs2_a}f~jKQH;xAUA(7t^kbo+{*7^n{<7^JRFa?&hDU=A-v0oM-<}dQf%a z{Yqm8%#I0$M#oc`?r49V zD9K0?UhsHujTuZMLcx&{`$2fjiJt>XR@1cZ3t+?Z5HC)!)*qN6QeT0CZXP=pTn+rO z-dUO|QqMA~_@cMP@8*m-oQm9t0Ja(II~Dt4s$HD#@Qc$I!LtjaoH=Amc>QCKCqRK> z9YkOyJX|bz=gTon$Z-BC6^Cq~hA31-t>cp$R3HmM3b4GR!n@>s-Sr&S33}t`m6s0I zu}^ks(^*$;;18~iR5c(dhg_kutBt-K$AomN99cwaqnll0P@IoR%;n^s9bRbbtzvh`o3|P#ksRrxe4^*Albo~f?zkZi$Z9z$@DTL zKSL<=R-enB!u;~Jo`(v_Wj*uMC8N7tK}jo@693AGk4pPPS{<_Rv6nQI*1!4AnqA0z zga!-8tzKAY5EnQ!Xw_N2(ZCYCxy)mlCe4Wa%X@oRniCp%1>sfLuBJi67D-K(I>CPv zJ}w3sios;sH;)Oc`$AKi!`9Gw@D%fBagEh9;zXJ%fqS5>!9ouTUZzS&(I zzr(4xe;a=wC-tm;y1WXiq%PKYvAGQBs6HH%{LClCoX z>s7YqBFNqZ(TB1s^U098R7X>Fj}>8g>ng(8V_&%{{`)zKQ!HI{gq{>repJnKjqwN} z48heOK#`m?lX%=TjUQS@Xv7nD~G$S`4&x;8{5C*C7?bNCfPi{bkb;O*?P;~KV$rSL- zYD-WFf9z|a>APt2>Z6HDmcXYV2QV$m&m{$jecX>bW+v66z4yg7z+-ikO(wI9CRZ*wrib56-7L z%ry969YP{ewtQSId8}*u0bGa)(S<{Km*1f)XKbJU^I0hmvc#Yj>4j`CS$cOW^Kh;4 zh!}^kX0YUL%Lc_%mrWk!>W#Mz*qLP<9489|4n%OT)sK5c4SSI@Dg1%e$q2t*dJsxi zh1G5QHbsNNKcRH>4o>seJxZp5QlbPPY*)TG#F`8OA%vT-7lQ=KCQEOVCeY%FTUF+C zy_n~6;swI6lKszHNWVPT_%Gsj`zZ3&OZZP-x9a?@PNuqm>jie@ET@3`0#pqYbPW>wf)4)C9!bkv8z zl9Z&LY!UgtQQ@mlwZ)#9ugSZp>nm2(jZgHb>_XU~t|qrOzuCKNZHZzK zWqKZ4`&2;aRVY!tgEI(|o9`3Ahp&w@0p(_vhE|(jKAD#a-`B!TXlFph-aF*ZN6w?A z*bp9sE&{@zWd10TY4LZt3Rv@B?%TB3a)W)W1e2!Jv(%IcHq3N=JZi6tuNwe}v9 z?V%Qd48)a72|-U391E{ne2wBK_UtT4)YIj8-s{o{t3g&)skAx7cGNg zLVgs9>2QoAyC%(5PPri+f!(~daHPd*2S9;!u^Hb+6e;dcR&Ipd+2lNVwH=PbrEGmL z2p?wCKNw=SO`y@jtiIcXkHyenO@)3&4+T;4bgG-KzlVPS&ey!5(F}S}HAAM?2ls4! zHmz(KbcWZzstodwO8vGp{Cup*5J2qHUl3QdjX4ZR%;x}d(tdZ=V6UM|IB z&KvZelhq7!><~yVqO*%zv{QPVqzy@64dkO4JDJ9+4l#hzC0a1Q>872aW#&IMD#?uUkuA%E7XzYwsv~rl)+P59fmbYZ?l@vI_Iu$KrA(MD^5c`znhbxC*}6 zk{X|^56XvEP7j6#okrb)l_;M-Hm4%}&zIB?m*6<*>93TP44YcO*80Pu=C1c84=BTg z7H)|XgEphQZUv4{K38n_$?Jbj&M|Lv=4M2O|KyG*)Lpl|RYq3PgTD8?+9G>eP-fs- zPzViqFk(SLYb0G1GPNL)FOQGc2Yp68H}jk#eKLVV9ryW86)t&s=R`6;0r-TnA`d<0 zWN4@j2RyfGwH#$TiW^O#2YWN*D~6DjtQ3>e)`8Cqlg%a$MBqF7bBKehnb1nXGNN)=$5e{6(ls>(mRjmdF0qXSaNDRuZqnksQJsbd0R&rrnoQJEM9DJ#L z(+7U^gUROLJCkO!IbF%H_TOw2f#RvVl&s6{;E2ry4aPxi9bWYHJP-DfAc(#6)x%r`oNre6V=3 zeI}R6POD?JmW$0dFoE-k68(qtu7HxO!wx1vvN-lFwAbl%>m(RvbBnWXmKQ8DdVkds z*8$eFL<5yI9o?gcx;AyG1xCnM;N+Sv74j<1^XMnGEg%+E zAQGf8dpiuEX@SZ+ATN%Wz;NMV3|R&mYW(hbMWSn#qhXt0ykD!HY{xpz?4`T7Vj zAK=@rB#q0j*^giA%elEhw5+-ZG^7yIXbio!EDXa%fe@JPR$lg+g2r%?(JhXL=_s=` z|CR1H;=yAaahMNOc1J9ZrhBU&=#tF~pTn~NzMx-|mdBK?9nX|r*oMP^M9d=!@*zD* zSYRQljEi6ED0+hCxo^f(;nac)7^bgfS?bt@7U8v6<|S96M~ zY)*3ri48X?E1pm@)2mr# zvZ>U!c?Ai{fRr>FIH%@4#k&bvJS*oH5xtHPuKz&X{3-^S zzJm5LQ5byDXok4iR*jh-QQZJy07^303?h3Ah)o?c+!bG+t%?rbuTphnN8;!KKG1{s z%&lPH3QR7EJ$V}qsb`iU*CCo(3VL==lCo^94!t;{y6uoOx0|^2nX_(dPJTo%m?6i0 z6fe1>?$>}i_RswtwA^OnQ(#RVE-qz}qL0?kCOV zc@0zKAz1JkVsIjN;wLu*Y^Jsi457mJL~`<@6lJ@}j@=m`+x>!B;>g!jDD&OlC5xLz zRZ6z6wkkpR4G+&0U62CMkrQ%3SrxA*^N@<6Hy~E7!k0kllgCmlv)g0yr;6`Plh~_ zwr&nS_;E9J%f(6=Z0aqvTqA?+PT^r$eMWqWCo|QAmACqQxl;Q+0}ycWHPqu{5Z}s za2=#Bya>&@_oG^dL5 zb}s>d%H2`8w(Y1D89r(TMq5W9d4<()^_9C^n$F3Z-go#Leo|R~tJ$qbmNv z{fs1Yv5FuB68FGVeLqf~dqp^XTbk0}Lj5&K1nGEf?~sdF2YeC9Q3R5+6mM8cas_S` z#9P}#8W1*2OjG#)YUYf+z2J3Jb>982x9fd&{GFL2-BP;dFfcLPOg=@ne8BewK1Pj5 zt02?M#%|d%A{|r75mU&>v#f%-Sp^o!A5B1?UVtE;5MTHp6b>1V#ivY2d^-jNtn-mR zeQd7WPT-I}|xtoDKvH&%#H%SjQBy$89S73;?T49wRnHYDA^QPZwJg_iee~ zIB9k$Z>10RGKNO$CY|X*qu)3Yj@?d|q~|{PiWQuG^xwojLwmt&j7S_J_%I2xy83Yj z&6W4huGY%YD)>R584a(eWjCU0yIZIT*7bfn3g2oJu!xxryT@MHvZ zT(GA{F3X_!p{jwAWST&X6K9+vbKu*Xllqh?G6QcCc%2OwbT20&z{Q(yvkvH-;(E$* z%N?`5KM~s7lSg`N}&V@gG;%~Tjx{A!E(QN?+I^v-I1VEwKhRQPx5 ze9Z*YL@0152{$6R>2F=<-M#Y5ROxX6>w3Aasghv|%=OxT!z9h`{{}|gIx|!Q$MOmv z^=UoX{R~!`T|mJDi<%PduY5O?Yri-w%16@K4NZgLzkqHD!AjO+jZYcc0liJvPmnZOIlpO)$l<}S%~Ot>W3@)BS{){m zeYa1+p?T5-;3;zo`rU8(PA1yo*rBMVJ6_ST80#SwvRbuPA1(0SaoNj;RG~I~Y!CN2 zvk$A;%qXXVfzB*S&d`sEh(g85zAPTUY>2t25n#x+4cBsPfo6`|bu_i<{H^BWjmQ!f zgY>9Qwx|jptzcvQ&X~~w#>d`#P8bqp-SZ=4LV+;F<@##x7_>S+$j;6Pf|UnT_=2(~ zKnzz8d{%RQzzFg_RjFgZ;tDEzI3Y8)#@(Z1LC>WC5&)3&gg9SQoctlMe$S2AOR`;s zYyMVq()tGzR;(b|RpD>wE4=B9sF5_IEAZsWgBS{3K)MBtVGoA!x0dZ$m%N|kH71R>7^KS$vnC=pgy&&I{ENbmz* zw-@}{HJA!(aKJI1T@MUD#1r5Y*IPz`$*7y2|8gB4x-Hq5t89-P<$w55^RT~!K;Yy? zQaBqF%0&^d%CUg3O|Nv8E}lLKzN{2#Djg~ z_kKC0B0_TS{C+zjl6wf(iDP>pv19~JCu5;POIj?Kyvw7qc1ENb zD2C9Dr`HFn1Y7FX24H8eRrAk_ zjd^J0&H2<_p&*xfzXC|fi#@?24}>DUU}29=8?`rgw5aUjVlNRCzDeOTPAz4LEF^&> z3Qz=mslF)UrYyYPCuy$DOey=2ZGIH=wEbJq9a^j+yxzZ=1Bw7#GH;Nj6j4Myc}uPb zICshGP-s-nh5DL2)VspnPgHi?0*MUR3EB3|2(|XjkK+jd)lqflzlxpQtWyWm5`uYh zqbWF6K@Fz0AEDV0VyCDLqQ4*u1c*Iwx!9XBP=-M%8g7K98T`za&E4E+0o&0vLJh2@ z-!czajp2qDAqmms8 zNNO>xH2tRxU1fw z`yE(svUf$mc}8SeicAV-E`OB}DY{?P_6D;f5=0u<$ezOibQE``P7t_^>ZEMBUY|{s zaRG|9H`f?LR^@*gk)Y1hwCQiP$8r!RKay~ovKz29Oq-7O;4%r&NjsLlb;ij{tFXMi zz3Y7eZ!p1@CieaPF3 zRN13}JX&l@wgnlHCU*2t@utZ8^!pKPR%@Hqrlh+8g4|p!6B|3&{9c;l3`M{f#@z`e z_JVgdRi&8R3r_(m=QJZwOYtUF2&l~o#*atp$UBK!cMV|_+*JzDc1AV?Dm#J+N#B|u zf^g6hc?Qr8)-{f822^(Oz~8@HHella?V?0WS$Z27nhEiXie za7d9s$)K;EVW^@>fi}ceTr=S!`3QS^s=`Ar_*fncsN-MmU@$#exi(&qJ4?6ely;7I z{?#5@7CwTU@2w+1Cticp>0TV*YV;t9jwcU{Yo$BLwW~hv({dDBKMrD-PM;*Tz@*8s z!1yzw6t~pIhEU1)RpKc@Jv3p|(S#|sX!G0x#iGhLzCRU&p{hj?TlxzAn>x-Y6&8x< z%TQE;c|J#_ZtM{LGY0WXfCMCfSlc!;&q5%d2ocK5oheHqas4;x(~lkw^n*_aMRP!C zr-YT3jOqQBq=sl&Z7RD?>MI=p_^pNCl@f#gg!@^Yw4M1kS2aL2D3$)_lcw|yVT=&f z6(`paV0NDYUb&3m@)n`0O$vYV3wb72RkT;(I}RJoQ>axN)b z1^F)2uuo8GPdP^c31!k?iMzHU3-emB%`YSwkvhZ4TPLLUt-45+>OKIzc=1pYh)-us zLEQp?jIWK1NDdIUcs%N3LSCanp{YQ?l!m)EHqK1xLCoW0@|m;AfCMeSVE}1d6Vj!^ zCl)7f(Pz>z(}HkQ0fw{}$|4(^W{?x=3$Cq*x5Xuw}EZ72;Pzpj0lt4@ff+@r>Q7eZME{(y)5<)_3F%XH! zNTrAABD4w;v`}>hP$~lqf=Q*LLPw3O6^4k3971G7PL*(^-_^D=o#}tj{UJNqdEfW; z?f3h>*XO%$t+hI0*_BwF(}&%YY?W!*$c82xEx^d-E&bQT-D0BYZ9CEl4?@S8;t>W# z$%hF4Ds)tE;yrz8f1*Bqd$-xLxZO(E`U&=Xs535YdYwP=h#XX+F&J&MoK;O%tRk7~ zOmzTG&kmj)9n9Nrk9!YKJiz(~H!tp47IDA3L^W9Hz{cDNi+(&+A&5v_->UW2XjBTNls`=iriv=4BIXhBgxJGY6-Y1@;$!t+TSE^Rnj?~skQp{8ZBoL2{F z__7%tKQ~~t1P+I8H#*`L)24(3Y46VjL=7H{TkDVy@?tUo7!}2}hDABXkKrHEV9B4S_`Nat3{5 zn?JIYgw5w(34HhyA*IRILFt`;NPuSD;AQC?lv|-jfCFLo5PG}+>|iaZ6g?yv64=bJ zAjdm@b#%f}sVSWq$ETun=}L8~oipqsbbP)nT(i?ob=_=vrhNy&<(Uh`W3|QbniSL^ zZO4P^doXsxQ=wvEn=3*bs&AgCxH#>TLRLL)|7B$9ooG90C4egLuXepO`?a0&QnTcR z1wIGhZ7Mz=a;+^CN)VcyAV;}*>z$27HrkD#g(Z*nFMIhRdV9^yoekr2a~B}@Lbvya zFvb>X4#RajlMWRV*``MBc?9V@y3J}rz!vuzZID|Qvt&5py5w?A&-X|#7Zeo_V>sX{ zDjs?0XWEfGrLZ2jBkpRv8i##l#IeO&q#{Qpj%8MOoWetTPHsgYeY7nOi?X9sQsH_4 zLu1E5j*jhd-gn_$935IUOlz(`ElysM$Zk7cWAPL2=((0iurOo0n83{nK5*?LdH#63 z;YSOs%J5Eo=Hsf6>c-@bqMm9=5-^_6qF`=*{yIw%kZ2152prXxBJD18TuKxB zv|wrgi9{oj)b7~5e-VhpdxaSX|AQd-4S~BoHy;Rm@ezc4K8-KVJ}4FL`%H###a{pX zS?6^iN8ptq6$mm9o;x*B2o&CbQ|uMw@TJ0RnNXaCBVM6g^

Please enter your name below 👇
+
+ + +
+ +`; +document.getElementById('logo').src = logo; + +let nameElement = document.getElementById("name"); +nameElement.focus(); +let resultElement = document.getElementById("result"); + +// Setup the greet function +window.greet = function () { + // Get name + let name = nameElement.value; + + // Check if the input is empty + if (name === "") return; + + // Call App.Greet(name) + try { + Greet(name) + .then((result) => { + // Update result with data back from App.Greet() + resultElement.innerText = result; + }) + .catch((err) => { + console.error(err); + }); + } catch (err) { + console.error(err); + } +}; + +// Auto-call Greet after 5 seconds to trigger the panic test +setTimeout(() => { + console.log("Auto-calling Greet to trigger panic test..."); + Greet("PanicTest") + .then((result) => { + resultElement.innerText = result + " (auto-called - panic will occur in 5s)"; + }) + .catch((err) => { + console.error("Error:", err); + }); +}, 5000); diff --git a/v2/examples/panic-recovery-test/frontend/src/style.css b/v2/examples/panic-recovery-test/frontend/src/style.css new file mode 100644 index 000000000..3940d6c63 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/src/style.css @@ -0,0 +1,26 @@ +html { + background-color: rgba(27, 38, 54, 1); + text-align: center; + color: white; +} + +body { + margin: 0; + color: white; + font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; +} + +@font-face { + font-family: "Nunito"; + font-style: normal; + font-weight: 400; + src: local(""), + url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2"); +} + +#app { + height: 100vh; + text-align: center; +} diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts new file mode 100755 index 000000000..02a3bb988 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts @@ -0,0 +1,4 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function Greet(arg1:string):Promise; diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js new file mode 100755 index 000000000..c71ae77cb --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js @@ -0,0 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function Greet(arg1) { + return window['go']['main']['App']['Greet'](arg1); +} diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json new file mode 100644 index 000000000..1e7c8a5d7 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wailsapp/runtime", + "version": "2.0.0", + "description": "Wails Javascript runtime library", + "main": "runtime.js", + "types": "runtime.d.ts", + "scripts": { + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git" + }, + "keywords": [ + "Wails", + "Javascript", + "Go" + ], + "author": "Lea Anthony ", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://github.com/wailsapp/wails#readme" +} diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts new file mode 100644 index 000000000..4445dac21 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts @@ -0,0 +1,249 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Position { + x: number; + y: number; +} + +export interface Size { + w: number; + h: number; +} + +export interface Screen { + isCurrent: boolean; + isPrimary: boolean; + width : number + height : number +} + +// Environment information such as platform, buildtype, ... +export interface EnvironmentInfo { + buildType: string; + platform: string; + arch: string; +} + +// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) +// emits the given event. Optional data may be passed with the event. +// This will trigger any event listeners. +export function EventsEmit(eventName: string, ...data: any): void; + +// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. +export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) +// sets up a listener for the given event name, but will only trigger a given number times. +export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; + +// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) +// sets up a listener for the given event name, but will only trigger once. +export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) +// unregisters the listener for the given event name. +export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; + +// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) +// unregisters all listeners. +export function EventsOffAll(): void; + +// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) +// logs the given message as a raw message +export function LogPrint(message: string): void; + +// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) +// logs the given message at the `trace` log level. +export function LogTrace(message: string): void; + +// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) +// logs the given message at the `debug` log level. +export function LogDebug(message: string): void; + +// [LogError](https://wails.io/docs/reference/runtime/log#logerror) +// logs the given message at the `error` log level. +export function LogError(message: string): void; + +// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) +// logs the given message at the `fatal` log level. +// The application will quit after calling this method. +export function LogFatal(message: string): void; + +// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) +// logs the given message at the `info` log level. +export function LogInfo(message: string): void; + +// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) +// logs the given message at the `warning` log level. +export function LogWarning(message: string): void; + +// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) +// Forces a reload by the main application as well as connected browsers. +export function WindowReload(): void; + +// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) +// Reloads the application frontend. +export function WindowReloadApp(): void; + +// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) +// Sets the window AlwaysOnTop or not on top. +export function WindowSetAlwaysOnTop(b: boolean): void; + +// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) +// *Windows only* +// Sets window theme to system default (dark/light). +export function WindowSetSystemDefaultTheme(): void; + +// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) +// *Windows only* +// Sets window to light theme. +export function WindowSetLightTheme(): void; + +// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) +// *Windows only* +// Sets window to dark theme. +export function WindowSetDarkTheme(): void; + +// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) +// Centers the window on the monitor the window is currently on. +export function WindowCenter(): void; + +// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) +// Sets the text in the window title bar. +export function WindowSetTitle(title: string): void; + +// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) +// Makes the window full screen. +export function WindowFullscreen(): void; + +// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) +// Restores the previous window dimensions and position prior to full screen. +export function WindowUnfullscreen(): void; + +// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) +// Returns the state of the window, i.e. whether the window is in full screen mode or not. +export function WindowIsFullscreen(): Promise; + +// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) +// Sets the width and height of the window. +export function WindowSetSize(width: number, height: number): void; + +// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) +// Gets the width and height of the window. +export function WindowGetSize(): Promise; + +// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) +// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMaxSize(width: number, height: number): void; + +// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) +// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMinSize(width: number, height: number): void; + +// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) +// Sets the window position relative to the monitor the window is currently on. +export function WindowSetPosition(x: number, y: number): void; + +// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) +// Gets the window position relative to the monitor the window is currently on. +export function WindowGetPosition(): Promise; + +// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) +// Hides the window. +export function WindowHide(): void; + +// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) +// Shows the window, if it is currently hidden. +export function WindowShow(): void; + +// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) +// Maximises the window to fill the screen. +export function WindowMaximise(): void; + +// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) +// Toggles between Maximised and UnMaximised. +export function WindowToggleMaximise(): void; + +// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) +// Restores the window to the dimensions and position prior to maximising. +export function WindowUnmaximise(): void; + +// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) +// Returns the state of the window, i.e. whether the window is maximised or not. +export function WindowIsMaximised(): Promise; + +// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) +// Minimises the window. +export function WindowMinimise(): void; + +// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) +// Restores the window to the dimensions and position prior to minimising. +export function WindowUnminimise(): void; + +// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) +// Returns the state of the window, i.e. whether the window is minimised or not. +export function WindowIsMinimised(): Promise; + +// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) +// Returns the state of the window, i.e. whether the window is normal or not. +export function WindowIsNormal(): Promise; + +// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) +// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. +export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; + +// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) +// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. +export function ScreenGetAll(): Promise; + +// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) +// Opens the given URL in the system browser. +export function BrowserOpenURL(url: string): void; + +// [Environment](https://wails.io/docs/reference/runtime/intro#environment) +// Returns information about the environment +export function Environment(): Promise; + +// [Quit](https://wails.io/docs/reference/runtime/intro#quit) +// Quits the application. +export function Quit(): void; + +// [Hide](https://wails.io/docs/reference/runtime/intro#hide) +// Hides the application. +export function Hide(): void; + +// [Show](https://wails.io/docs/reference/runtime/intro#show) +// Shows the application. +export function Show(): void; + +// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) +// Returns the current text stored on clipboard +export function ClipboardGetText(): Promise; + +// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) +// Sets a text on the clipboard +export function ClipboardSetText(text: string): Promise; + +// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) +// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. +export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void + +// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) +// OnFileDropOff removes the drag and drop listeners and handlers. +export function OnFileDropOff() :void + +// Check if the file path resolver is available +export function CanResolveFilePaths(): boolean; + +// Resolves file paths for an array of files +export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js new file mode 100644 index 000000000..7cb89d750 --- /dev/null +++ b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js @@ -0,0 +1,242 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export function LogPrint(message) { + window.runtime.LogPrint(message); +} + +export function LogTrace(message) { + window.runtime.LogTrace(message); +} + +export function LogDebug(message) { + window.runtime.LogDebug(message); +} + +export function LogInfo(message) { + window.runtime.LogInfo(message); +} + +export function LogWarning(message) { + window.runtime.LogWarning(message); +} + +export function LogError(message) { + window.runtime.LogError(message); +} + +export function LogFatal(message) { + window.runtime.LogFatal(message); +} + +export function EventsOnMultiple(eventName, callback, maxCallbacks) { + return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); +} + +export function EventsOn(eventName, callback) { + return EventsOnMultiple(eventName, callback, -1); +} + +export function EventsOff(eventName, ...additionalEventNames) { + return window.runtime.EventsOff(eventName, ...additionalEventNames); +} + +export function EventsOffAll() { + return window.runtime.EventsOffAll(); +} + +export function EventsOnce(eventName, callback) { + return EventsOnMultiple(eventName, callback, 1); +} + +export function EventsEmit(eventName) { + let args = [eventName].slice.call(arguments); + return window.runtime.EventsEmit.apply(null, args); +} + +export function WindowReload() { + window.runtime.WindowReload(); +} + +export function WindowReloadApp() { + window.runtime.WindowReloadApp(); +} + +export function WindowSetAlwaysOnTop(b) { + window.runtime.WindowSetAlwaysOnTop(b); +} + +export function WindowSetSystemDefaultTheme() { + window.runtime.WindowSetSystemDefaultTheme(); +} + +export function WindowSetLightTheme() { + window.runtime.WindowSetLightTheme(); +} + +export function WindowSetDarkTheme() { + window.runtime.WindowSetDarkTheme(); +} + +export function WindowCenter() { + window.runtime.WindowCenter(); +} + +export function WindowSetTitle(title) { + window.runtime.WindowSetTitle(title); +} + +export function WindowFullscreen() { + window.runtime.WindowFullscreen(); +} + +export function WindowUnfullscreen() { + window.runtime.WindowUnfullscreen(); +} + +export function WindowIsFullscreen() { + return window.runtime.WindowIsFullscreen(); +} + +export function WindowGetSize() { + return window.runtime.WindowGetSize(); +} + +export function WindowSetSize(width, height) { + window.runtime.WindowSetSize(width, height); +} + +export function WindowSetMaxSize(width, height) { + window.runtime.WindowSetMaxSize(width, height); +} + +export function WindowSetMinSize(width, height) { + window.runtime.WindowSetMinSize(width, height); +} + +export function WindowSetPosition(x, y) { + window.runtime.WindowSetPosition(x, y); +} + +export function WindowGetPosition() { + return window.runtime.WindowGetPosition(); +} + +export function WindowHide() { + window.runtime.WindowHide(); +} + +export function WindowShow() { + window.runtime.WindowShow(); +} + +export function WindowMaximise() { + window.runtime.WindowMaximise(); +} + +export function WindowToggleMaximise() { + window.runtime.WindowToggleMaximise(); +} + +export function WindowUnmaximise() { + window.runtime.WindowUnmaximise(); +} + +export function WindowIsMaximised() { + return window.runtime.WindowIsMaximised(); +} + +export function WindowMinimise() { + window.runtime.WindowMinimise(); +} + +export function WindowUnminimise() { + window.runtime.WindowUnminimise(); +} + +export function WindowSetBackgroundColour(R, G, B, A) { + window.runtime.WindowSetBackgroundColour(R, G, B, A); +} + +export function ScreenGetAll() { + return window.runtime.ScreenGetAll(); +} + +export function WindowIsMinimised() { + return window.runtime.WindowIsMinimised(); +} + +export function WindowIsNormal() { + return window.runtime.WindowIsNormal(); +} + +export function BrowserOpenURL(url) { + window.runtime.BrowserOpenURL(url); +} + +export function Environment() { + return window.runtime.Environment(); +} + +export function Quit() { + window.runtime.Quit(); +} + +export function Hide() { + window.runtime.Hide(); +} + +export function Show() { + window.runtime.Show(); +} + +export function ClipboardGetText() { + return window.runtime.ClipboardGetText(); +} + +export function ClipboardSetText(text) { + return window.runtime.ClipboardSetText(text); +} + +/** + * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * + * @export + * @callback OnFileDropCallback + * @param {number} x - x coordinate of the drop + * @param {number} y - y coordinate of the drop + * @param {string[]} paths - A list of file paths. + */ + +/** + * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. + * + * @export + * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) + */ +export function OnFileDrop(callback, useDropTarget) { + return window.runtime.OnFileDrop(callback, useDropTarget); +} + +/** + * OnFileDropOff removes the drag and drop listeners and handlers. + */ +export function OnFileDropOff() { + return window.runtime.OnFileDropOff(); +} + +export function CanResolveFilePaths() { + return window.runtime.CanResolveFilePaths(); +} + +export function ResolveFilePaths(files) { + return window.runtime.ResolveFilePaths(files); +} \ No newline at end of file diff --git a/v2/examples/panic-recovery-test/go.mod b/v2/examples/panic-recovery-test/go.mod new file mode 100644 index 000000000..026042cbf --- /dev/null +++ b/v2/examples/panic-recovery-test/go.mod @@ -0,0 +1,5 @@ +module panic-recovery-test + +go 1.21 + +require github.com/wailsapp/wails/v2 v2.11.0 diff --git a/v2/examples/panic-recovery-test/main.go b/v2/examples/panic-recovery-test/main.go new file mode 100644 index 000000000..f6a38e86c --- /dev/null +++ b/v2/examples/panic-recovery-test/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "embed" + + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" +) + +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // Create an instance of the app structure + app := NewApp() + + // Create application with options + err := wails.Run(&options.App{ + Title: "panic-test", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + OnStartup: app.startup, + Bind: []interface{}{ + app, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} diff --git a/v2/examples/panic-recovery-test/wails.json b/v2/examples/panic-recovery-test/wails.json new file mode 100644 index 000000000..56770f091 --- /dev/null +++ b/v2/examples/panic-recovery-test/wails.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://wails.io/schemas/config.v2.json", + "name": "panic-recovery-test", + "outputfilename": "panic-recovery-test", + "frontend:install": "npm install", + "frontend:build": "npm run build", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "auto", + "author": { + "name": "Lea Anthony", + "email": "lea.anthony@gmail.com" + } +} diff --git a/v2/pkg/runtime/signal_linux.go b/v2/pkg/runtime/signal_linux.go new file mode 100644 index 000000000..6a7ed5db3 --- /dev/null +++ b/v2/pkg/runtime/signal_linux.go @@ -0,0 +1,65 @@ +//go:build linux + +package runtime + +/* +#include +#include +#include +#include + +static void fix_signal(int signum) +{ + struct sigaction st; + + if (sigaction(signum, NULL, &st) < 0) { + return; + } + st.sa_flags |= SA_ONSTACK; + sigaction(signum, &st, NULL); +} + +static void fix_all_signals() +{ +#if defined(SIGSEGV) + fix_signal(SIGSEGV); +#endif +#if defined(SIGBUS) + fix_signal(SIGBUS); +#endif +#if defined(SIGFPE) + fix_signal(SIGFPE); +#endif +#if defined(SIGABRT) + fix_signal(SIGABRT); +#endif +} +*/ +import "C" + +// ResetSignalHandlers resets signal handlers to allow panic recovery. +// +// On Linux, WebKit (used for the webview) may install signal handlers without +// the SA_ONSTACK flag, which prevents Go from properly recovering from panics +// caused by nil pointer dereferences or other memory access violations. +// +// Call this function immediately before code that might panic to ensure +// the signal handlers are properly configured for Go's panic recovery mechanism. +// +// Example usage: +// +// go func() { +// defer func() { +// if err := recover(); err != nil { +// log.Printf("Recovered from panic: %v", err) +// } +// }() +// runtime.ResetSignalHandlers() +// // Code that might panic... +// }() +// +// Note: This function only has an effect on Linux. On other platforms, +// it is a no-op. +func ResetSignalHandlers() { + C.fix_all_signals() +} diff --git a/v2/pkg/runtime/signal_other.go b/v2/pkg/runtime/signal_other.go new file mode 100644 index 000000000..3171a700c --- /dev/null +++ b/v2/pkg/runtime/signal_other.go @@ -0,0 +1,18 @@ +//go:build !linux + +package runtime + +// ResetSignalHandlers resets signal handlers to allow panic recovery. +// +// On Linux, WebKit (used for the webview) may install signal handlers without +// the SA_ONSTACK flag, which prevents Go from properly recovering from panics +// caused by nil pointer dereferences or other memory access violations. +// +// Call this function immediately before code that might panic to ensure +// the signal handlers are properly configured for Go's panic recovery mechanism. +// +// Note: This function only has an effect on Linux. On other platforms, +// it is a no-op. +func ResetSignalHandlers() { + // No-op on non-Linux platforms +} diff --git a/website/docs/guides/linux.mdx b/website/docs/guides/linux.mdx index 1b55297b5..2cfc2e62a 100644 --- a/website/docs/guides/linux.mdx +++ b/website/docs/guides/linux.mdx @@ -70,3 +70,57 @@ If the added package does not resolve the issue, additional GStreamer dependenci - This issue impacts [Tauri apps](https://tauri.app/). Source: [developomp](https://github.com/developomp) on the [Tauri discussion board](https://github.com/tauri-apps/tauri/issues/4642#issuecomment-1643229562). + +## Panic Recovery / Signal Handling Issues + +### App crashes with "non-Go code set up signal handler without SA_ONSTACK flag" + +On Linux, if your application crashes with an error like: + +``` +signal 11 received but handler not on signal stack +fatal error: non-Go code set up signal handler without SA_ONSTACK flag +``` + +This occurs because WebKit (used for the webview) installs signal handlers that interfere with Go's panic recovery mechanism. +Normally, Go can convert signals like SIGSEGV (from nil pointer dereferences) into recoverable panics, but WebKit's signal +handlers prevent this. + +### Solution + +Use the `runtime.ResetSignalHandlers()` function immediately before code that might panic: + +```go +import "github.com/wailsapp/wails/v2/pkg/runtime" + +go func() { + defer func() { + if err := recover(); err != nil { + log.Printf("Recovered from panic: %v", err) + } + }() + // Reset signal handlers right before potentially dangerous code + runtime.ResetSignalHandlers() + + // Your code that might panic... +}() +``` + +:::warning Important + +- Call `ResetSignalHandlers()` in each goroutine where you need panic recovery +- Call it immediately before the code that might panic, as WebKit may reset the handlers at any time +- This is only necessary on Linux - the function is a no-op on other platforms + +::: + +### Why This Happens + +WebKit installs its own signal handlers for garbage collection and other internal processes. These handlers don't include +the `SA_ONSTACK` flag that Go requires to properly handle signals on the correct stack. When a signal like SIGSEGV occurs, +Go's runtime can't recover because the signal is being handled on the wrong stack. + +The `ResetSignalHandlers()` function adds the `SA_ONSTACK` flag to the signal handlers for SIGSEGV, SIGBUS, SIGFPE, and +SIGABRT, allowing Go's panic recovery to work correctly. + +Source: [GitHub Issue #3965](https://github.com/wailsapp/wails/issues/3965) diff --git a/website/docs/reference/runtime/intro.mdx b/website/docs/reference/runtime/intro.mdx index 3c491ecf0..d67e76c64 100644 --- a/website/docs/reference/runtime/intro.mdx +++ b/website/docs/reference/runtime/intro.mdx @@ -98,3 +98,46 @@ interface EnvironmentInfo { arch: string; } ``` + +### ResetSignalHandlers + +Resets signal handlers to allow panic recovery from nil pointer dereferences and other memory access violations. + +Go: `ResetSignalHandlers()` + +:::info Linux Only + +This function only has an effect on Linux. On macOS and Windows, it is a no-op. + +On Linux, WebKit (used for the webview) may install signal handlers without the `SA_ONSTACK` flag, which prevents +Go from properly recovering from panics caused by nil pointer dereferences (SIGSEGV) or other memory access violations. + +Call this function immediately before code that might panic to ensure the signal handlers are properly configured +for Go's panic recovery mechanism. + +::: + +#### Example + +```go +go func() { + defer func() { + if err := recover(); err != nil { + log.Printf("Recovered from panic: %v", err) + } + }() + // Reset signal handlers right before potentially dangerous code + runtime.ResetSignalHandlers() + + // Code that might cause a nil pointer dereference... + var t *time.Time + fmt.Println(t.Unix()) // This would normally crash on Linux +}() +``` + +:::warning + +This function must be called in each goroutine where you want panic recovery to work, and should be called +immediately before the code that might panic, as WebKit may reset the signal handlers at any time. + +::: From 718fd92f85c5a58f0046fa5e8f7d248d64174e2e Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 4 Feb 2026 21:23:07 +1100 Subject: [PATCH 06/18] fix(v2): prevent wails init in non-empty directory with -d flag (#4955) * fix(v2): prevent wails init in non-empty directory with -d flag When using -d to specify a target directory, wails init now checks if the directory is non-empty and errors if so. This prevents accidental data loss (e.g., overwriting .git directories). Fixes #4940 Co-Authored-By: Claude Opus 4.5 * test(v2): add tests for init non-empty directory check Add tests to verify: - Install fails when target directory is non-empty - Install succeeds when target directory is empty Also update changelog with the fix. Co-Authored-By: Claude Opus 4.5 * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- v2/pkg/templates/templates.go | 11 +++++++- v2/pkg/templates/templates_test.go | 45 ++++++++++++++++++++++++++++++ website/src/pages/changelog.mdx | 1 + 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/v2/pkg/templates/templates.go b/v2/pkg/templates/templates.go index 9b42ef365..e18185520 100644 --- a/v2/pkg/templates/templates.go +++ b/v2/pkg/templates/templates.go @@ -186,7 +186,16 @@ func Install(options *Options) (bool, *Template, error) { return false, nil, err } options.TargetDir = targetDir - if !fs.DirExists(options.TargetDir) { + if fs.DirExists(options.TargetDir) { + // Check if directory is non-empty + entries, err := os.ReadDir(options.TargetDir) + if err != nil { + return false, nil, err + } + if len(entries) > 0 { + return false, nil, fmt.Errorf("cannot initialise project in non-empty directory: %s", options.TargetDir) + } + } else { err := fs.Mkdir(options.TargetDir) if err != nil { return false, nil, err diff --git a/v2/pkg/templates/templates_test.go b/v2/pkg/templates/templates_test.go index 3b906601a..658ecadb6 100644 --- a/v2/pkg/templates/templates_test.go +++ b/v2/pkg/templates/templates_test.go @@ -52,3 +52,48 @@ func TestInstall(t *testing.T) { is2.NoErr(err) } + +func TestInstallFailsInNonEmptyDirectory(t *testing.T) { + is2 := is.New(t) + + // Create a temp directory with a file in it + tempDir, err := os.MkdirTemp("", "wails-test-nonempty-*") + is2.NoErr(err) + defer func() { + _ = os.RemoveAll(tempDir) + }() + + // Create a file in the directory to make it non-empty + err = os.WriteFile(filepath.Join(tempDir, "existing-file.txt"), []byte("test"), 0644) + is2.NoErr(err) + + options := &Options{ + ProjectName: "test", + TemplateName: "vanilla", + TargetDir: tempDir, + } + + _, _, err = Install(options) + is2.True(err != nil) // Should fail + is2.True(err.Error() == "cannot initialise project in non-empty directory: "+tempDir) +} + +func TestInstallSucceedsInEmptyDirectory(t *testing.T) { + is2 := is.New(t) + + // Create an empty temp directory + tempDir, err := os.MkdirTemp("", "wails-test-empty-*") + is2.NoErr(err) + defer func() { + _ = os.RemoveAll(tempDir) + }() + + options := &Options{ + ProjectName: "test", + TemplateName: "vanilla", + TargetDir: tempDir, + } + + _, _, err = Install(options) + is2.NoErr(err) // Should succeed in empty directory +} diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 01fa47a69..50208109b 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed `wails init` to prevent initialization in non-empty directories when using the `-d` flag, avoiding accidental data loss [`#4940`](https://github.com/wailsapp/wails/issues/4940) by `@leaanthony` - Fixed missing `EventsOffAll` in runtime templates for all frontend frameworks [#4883](https://github.com/wailsapp/wails/pull/4883) by @narcilee7 - Fixed Linux crash on panic in JS-bound Go methods due to WebKit overriding signal handlers [#3965](https://github.com/wailsapp/wails/issues/3965) by @leaanthony - Fixed code block range in "How Does It Work?" documentation [#4884](https://github.com/wailsapp/wails/pull/4884) by @msal4 From e906751c894b2d641625577eb31f7cc5e0e7878b Mon Sep 17 00:00:00 2001 From: Jay Pipes Date: Sun, 8 Feb 2026 15:49:23 -0500 Subject: [PATCH 07/18] update github.com/jaypipes/ghw dependency (#4970) There's been a ton of improvements in the `ghw` library since the v0.13.0 release, including the update of certain transitive dependencies around Windows and Darwin support libraries. This patch simply brings in those improvements. The `v0.21.3` release of `ghw` is fully backwards-compatible with `v0.13.0`. Signed-off-by: Jay Pipes --- v2/go.mod | 9 ++++----- v2/go.sum | 21 +++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index 1a40badd2..2eb753ee2 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -17,7 +17,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/jackmordaunt/icns v1.0.0 - github.com/jaypipes/ghw v0.13.0 + github.com/jaypipes/ghw v0.21.3 github.com/labstack/echo/v4 v4.13.3 github.com/labstack/gommon v0.4.2 github.com/leaanthony/clir v1.3.0 @@ -53,7 +53,6 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect - github.com/StackExchange/wmi v1.2.1 // indirect github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect @@ -72,7 +71,7 @@ require ( github.com/gorilla/css v1.0.1 // indirect github.com/itchyny/gojq v0.12.13 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect - github.com/jaypipes/pcidb v1.0.1 // indirect + github.com/jaypipes/pcidb v1.1.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect @@ -82,7 +81,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect @@ -101,6 +99,7 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.4 // indirect github.com/yuin/goldmark-emoji v1.0.3 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/image v0.12.0 // indirect golang.org/x/sync v0.11.0 // indirect @@ -108,6 +107,6 @@ require ( golang.org/x/text v0.22.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v1.0.0 // indirect + howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 // indirect mvdan.cc/sh/v3 v3.7.0 // indirect ) diff --git a/v2/go.sum b/v2/go.sum index 53e56707e..f6df3507e 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -24,8 +24,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= @@ -88,7 +86,7 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -117,10 +115,10 @@ github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo= -github.com/jaypipes/ghw v0.13.0 h1:log8MXuB8hzTNnSktqpXMHc0c/2k/WgjOMSUtnI1RV4= -github.com/jaypipes/ghw v0.13.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8= -github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic= -github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4= +github.com/jaypipes/ghw v0.21.3 h1:v5mUHM+RN854Vqmk49Uh213jyUA4+8uqaRajlYESsh8= +github.com/jaypipes/ghw v0.21.3/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE= +github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro= +github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= @@ -178,8 +176,6 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= @@ -263,6 +259,8 @@ github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -340,7 +338,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -348,7 +345,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 h1:eeH1AIcPvSc0Z25ThsYF+Xoqbn0CI/YnXVYoTLFdGQw= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9/go.mod h1:fyFX5Hj5tP1Mpk8obqA9MZgXT416Q5711SDT7dQLTLk= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= From 093aa2d663b1121180201fec1db79940ab2820ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 20:56:49 +1100 Subject: [PATCH 08/18] chore: update sponsors.svg (#4978) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index ed115ff31..be5653517 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -16,20 +16,28 @@ text { } Silver Sponsors - Orb + Orb - + - + - Johno Scott + Johno Scott - + - + + + + James Clark + + + + + Bronze Sponsors Cody Bentley From ae40ca4ac1214bfc4dde7057a8194fc39360b80b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:50:29 +1100 Subject: [PATCH 09/18] chore: update sponsors.svg (#4980) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 75 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index be5653517..92a0c10db 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -78,28 +78,20 @@ text { Covering Costs - Marcus + Marcus - + - + - Michael + Michael - + - - - - Digital... - - - - - + Buying Breakfast Tai Groot @@ -296,85 +288,92 @@ text { Helpers - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + From bbd1b3312222de1a514ef7702e1121d88ac8e48d Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 14 Feb 2026 00:55:40 +1100 Subject: [PATCH 10/18] Add Claude Code GitHub Workflow (#4988) * "Claude PR Assistant workflow" * "Claude Code Review workflow" --- .github/workflows/claude-code-review.yml | 44 +++++++++++++++++++++ .github/workflows/claude.yml | 50 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 000000000..b5e8cfd4d --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,44 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 000000000..d300267f1 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' + From da3ce17161894cc27750012596da67fa64a532a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:43:34 +1100 Subject: [PATCH 11/18] chore: update sponsors.svg (#4993) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 92a0c10db..2cca3abc6 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -1,4 +1,4 @@ - + From 354fee648eda42311323a60e455058ba3c2f15cd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:39:19 +1100 Subject: [PATCH 12/18] chore: update sponsors.svg (#4997) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 2cca3abc6..36d949e28 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -378,8 +378,15 @@ text { - + - + + + + + + + + From c84578721c7f9876873fa180f15fbd95a5dbb7a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 21:18:16 +1100 Subject: [PATCH 13/18] chore: update sponsors.svg (#4999) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 36d949e28..12ed5532a 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -369,24 +369,17 @@ text { - - - - - - - + - + - - - + + - + - + From 4c49f27edf442e83333c977cb17126e44b7ea1eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:27:44 +1100 Subject: [PATCH 14/18] chore: update sponsors.svg (#5000) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 57 +++++++++++++++------------------ 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 12ed5532a..82e0ac390 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -203,88 +203,81 @@ text { - - - - - - - - + - + - + - - + + + - + - - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + Helpers From 033650d792299ddd2593968c2cda5a27212765f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 21:01:45 +1100 Subject: [PATCH 15/18] chore: update sponsors.svg (#5015) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 82e0ac390..5e84bf3fa 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -77,21 +77,13 @@ text { -Covering Costs - Marcus - - - - - - - - Michael +Covering Costs + Michael - + - + Buying Breakfast Tai Groot From 4d0abeb37c7a5b0717c5d9f8985bac7c608b4fa6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:09:40 +1100 Subject: [PATCH 16/18] chore: update sponsors.svg (#5025) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 5e84bf3fa..09df56549 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -1,4 +1,4 @@ - + - - - - - From f8595e3052b98365cc74f638162cbc5c5abfd3cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 13:07:12 +1100 Subject: [PATCH 17/18] chore: update sponsors.svg (#5032) Co-authored-by: leaanthony <1943904+leaanthony@users.noreply.github.com> --- website/static/img/sponsors.svg | 68 +++++++++++++++------------------ 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/website/static/img/sponsors.svg b/website/static/img/sponsors.svg index 09df56549..4dd355fd6 100644 --- a/website/static/img/sponsors.svg +++ b/website/static/img/sponsors.svg @@ -31,13 +31,13 @@ text { - - James Clark + + smarttechlabs-pro... - + - + Bronze Sponsors Cody Bentley @@ -85,60 +85,52 @@ text { Buying Breakfast - Tai Groot + Tai Groot - + - + - Tom Wu + Tom Wu - + - + - vaaski + vaaski - + - + - Sander + Sander - + - - - - Kevin - - - - - + - Zach + Zach - + - + - John + John - + - + Buying Coffee @@ -347,18 +339,18 @@ text { - - - - - - - + + + + + + + - + From 51d30325fc2304201eba1a9cbef67438c935f216 Mon Sep 17 00:00:00 2001 From: Zach Botterman <6074435+popaprozac@users.noreply.github.com> Date: Sat, 14 Mar 2026 19:38:32 -0700 Subject: [PATCH 18/18] [v2] Notifications API (#4256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init v2 * implement macOS and Windows * minor cleanup * fix segfault * linux * 🐰 * remove windows init * formatting * fix win icon * clean * clean * codesign full path * fix en/decoding and notification types * changelog & docs * fix options and channel fix * update docs * correct docs * Update website/docs/reference/runtime/notification.mdx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * work through rabbit suggestions * nil checks, cleanups, docs * locks * Update v2/internal/frontend/desktop/windows/notifications.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * update js runtime * update docs * second pass of comments * coherent JSON key, icon improv --------- Co-authored-by: Lea Anthony Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- v2/go.mod | 1 + v2/go.sum | 2 + .../frontend/desktop/darwin/Application.h | 15 + .../frontend/desktop/darwin/Application.m | 68 ++ .../frontend/desktop/darwin/WailsContext.h | 16 +- .../frontend/desktop/darwin/WailsContext.m | 362 ++++++++++- .../frontend/desktop/darwin/notifications.go | 465 ++++++++++++++ .../frontend/desktop/linux/notifications.go | 594 +++++++++++++++++ .../frontend/desktop/windows/notifications.go | 489 ++++++++++++++ .../frontend/desktop/windows/winc/icon.go | 164 +++++ .../frontend/dispatcher/systemcalls.go | 96 +++ v2/internal/frontend/frontend.go | 62 ++ v2/internal/frontend/runtime/desktop/main.js | 2 + .../frontend/runtime/desktop/notifications.js | 200 ++++++ v2/internal/frontend/runtime/ipc_websocket.js | 8 +- .../frontend/runtime/runtime_debug_desktop.js | 64 +- .../frontend/runtime/runtime_prod_desktop.js | 2 +- .../frontend/runtime/wrapper/runtime.d.ts | 83 ++- .../frontend/runtime/wrapper/runtime.js | 56 ++ v2/pkg/commands/build/build.go | 11 + v2/pkg/runtime/notifications.go | 136 ++++ website/docs/guides/notifications.mdx | 233 +++++++ .../docs/reference/runtime/notification.mdx | 601 ++++++++++++++++++ website/src/pages/changelog.mdx | 1 + 24 files changed, 3722 insertions(+), 9 deletions(-) create mode 100644 v2/internal/frontend/desktop/darwin/notifications.go create mode 100644 v2/internal/frontend/desktop/linux/notifications.go create mode 100644 v2/internal/frontend/desktop/windows/notifications.go create mode 100644 v2/internal/frontend/runtime/desktop/notifications.js create mode 100644 v2/pkg/runtime/notifications.go create mode 100644 website/docs/guides/notifications.mdx create mode 100644 website/docs/reference/runtime/notification.mdx diff --git a/v2/go.mod b/v2/go.mod index 2eb753ee2..f1287bde7 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -51,6 +51,7 @@ require ( atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect dario.cat/mergo v1.0.0 // indirect + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/alecthomas/chroma/v2 v2.14.0 // indirect diff --git a/v2/go.sum b/v2/go.sum index f6df3507e..2cfe9f7ab 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -8,6 +8,8 @@ atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= diff --git a/v2/internal/frontend/desktop/darwin/Application.h b/v2/internal/frontend/desktop/darwin/Application.h index 4d8bbd37b..c3cd8075a 100644 --- a/v2/internal/frontend/desktop/darwin/Application.h +++ b/v2/internal/frontend/desktop/darwin/Application.h @@ -69,6 +69,21 @@ void UpdateMenuItem(void* nsmenuitem, int checked); void RunMainLoop(void); void ReleaseContext(void *inctx); +/* Notifications */ +bool IsNotificationAvailable(void *inctx); +bool CheckBundleIdentifier(void *inctx); +bool EnsureDelegateInitialized(void *inctx); +void RequestNotificationAuthorization(void *inctx, int channelID); +void CheckNotificationAuthorization(void *inctx, int channelID); +void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json); +void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json); +void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle); +void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId); +void RemoveAllPendingNotifications(void *inctx); +void RemovePendingNotification(void *inctx, const char *identifier); +void RemoveAllDeliveredNotifications(void *inctx); +void RemoveDeliveredNotification(void *inctx, const char *identifier); + NSString* safeInit(const char* input); #endif /* Application_h */ diff --git a/v2/internal/frontend/desktop/darwin/Application.m b/v2/internal/frontend/desktop/darwin/Application.m index 38d349c2c..38b2f35ef 100644 --- a/v2/internal/frontend/desktop/darwin/Application.m +++ b/v2/internal/frontend/desktop/darwin/Application.m @@ -367,6 +367,74 @@ void AppendSeparator(void* inMenu) { } +bool IsNotificationAvailable(void *inctx) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + return [ctx IsNotificationAvailable]; +} + +bool CheckBundleIdentifier(void *inctx) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + return [ctx CheckBundleIdentifier]; +} + +bool EnsureDelegateInitialized(void *inctx) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + return [ctx EnsureDelegateInitialized]; +} + +void RequestNotificationAuthorization(void *inctx, int channelID) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + [ctx RequestNotificationAuthorization:channelID]; +} + +void CheckNotificationAuthorization(void *inctx, int channelID) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + [ctx CheckNotificationAuthorization:channelID]; +} + +void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + [ctx SendNotification:channelID :identifier :title :subtitle :body :data_json]; +} + +void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + + [ctx SendNotificationWithActions:channelID :identifier :title :subtitle :body :categoryId :actions_json]; +} + +void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + + [ctx RegisterNotificationCategory:channelID :categoryId :actions_json :hasReplyField :replyPlaceholder :replyButtonTitle]; +} + +void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + + [ctx RemoveNotificationCategory:channelID :categoryId]; +} + +void RemoveAllPendingNotifications(void *inctx) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + [ctx RemoveAllPendingNotifications]; +} + +void RemovePendingNotification(void *inctx, const char *identifier) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + [ctx RemovePendingNotification:identifier]; +} + +void RemoveAllDeliveredNotifications(void *inctx) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + [ctx RemoveAllDeliveredNotifications]; +} + +void RemoveDeliveredNotification(void *inctx, const char *identifier) { + WailsContext *ctx = (__bridge WailsContext*)inctx; + [ctx RemoveDeliveredNotification:identifier]; +} + void Run(void *inctx, const char* url) { WailsContext *ctx = (__bridge WailsContext*) inctx; diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.h b/v2/internal/frontend/desktop/darwin/WailsContext.h index 2ec6d8707..aafc3a1d4 100644 --- a/v2/internal/frontend/desktop/darwin/WailsContext.h +++ b/v2/internal/frontend/desktop/darwin/WailsContext.h @@ -92,10 +92,24 @@ struct Preferences { - (void) ShowApplication; - (void) Quit; --(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength; +- (void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength; - (void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters; - (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters; +- (bool) IsNotificationAvailable; +- (bool) CheckBundleIdentifier; +- (bool) EnsureDelegateInitialized; +- (void) RequestNotificationAuthorization:(int)channelID; +- (void) CheckNotificationAuthorization:(int)channelID; +- (void) SendNotification:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)dataJSON; +- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)actionsJSON; +- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle; +- (void) RemoveNotificationCategory:(int)channelID :(const char *)categoryId; +- (void) RemoveAllPendingNotifications; +- (void) RemovePendingNotification:(const char *)identifier; +- (void) RemoveAllDeliveredNotifications; +- (void) RemoveDeliveredNotification:(const char *)identifier; + - (void) loadRequest:(NSString*)url; - (void) ExecJS:(NSString*)script; - (NSScreen*) getCurrentScreen; diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.m b/v2/internal/frontend/desktop/darwin/WailsContext.m index 7c9660d54..51993eda2 100644 --- a/v2/internal/frontend/desktop/darwin/WailsContext.m +++ b/v2/internal/frontend/desktop/darwin/WailsContext.m @@ -5,6 +5,7 @@ // Created by Lea Anthony on 10/10/21. // +#include "Application.h" #import #import #import "WailsContext.h" @@ -36,6 +37,14 @@ typedef void (^schemeTaskCaller)(id); @end +// Notifications +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 +#import +#endif + +extern void captureResult(int channelID, bool success, const char* error); +extern void didReceiveNotificationResponse(const char *jsonPayload, const char* error); + @implementation WailsContext - (void) SetSize:(int)width :(int)height { @@ -723,6 +732,357 @@ typedef void (^schemeTaskCaller)(id); } +/***** Notifications ******/ +- (bool) IsNotificationAvailable { + if (@available(macOS 10.14, *)) { + return YES; + } else { + return NO; + } +} + +- (bool) CheckBundleIdentifier { + NSBundle *main = [NSBundle mainBundle]; + if (main.bundleIdentifier == nil) { + return NO; + } + return YES; +} + +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14)) { + UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound; + + if (@available(macOS 11.0, *)) { + // These options are only available in macOS 11.0+ + options = UNNotificationPresentationOptionList | + UNNotificationPresentationOptionBanner | + UNNotificationPresentationOptionSound; + } + + completionHandler(options); +} + +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14)) { + + NSMutableDictionary *payload = [NSMutableDictionary dictionary]; + + [payload setObject:response.notification.request.identifier forKey:@"id"]; + [payload setObject:response.actionIdentifier forKey:@"actionIdentifier"]; + [payload setObject:response.notification.request.content.title ?: @"" forKey:@"title"]; + [payload setObject:response.notification.request.content.body ?: @"" forKey:@"body"]; + + if (response.notification.request.content.categoryIdentifier) { + [payload setObject:response.notification.request.content.categoryIdentifier forKey:@"categoryId"]; + } + + if (response.notification.request.content.subtitle) { + [payload setObject:response.notification.request.content.subtitle forKey:@"subtitle"]; + } + + if (response.notification.request.content.userInfo) { + [payload setObject:response.notification.request.content.userInfo forKey:@"userInfo"]; + } + + if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) { + UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *)response; + [payload setObject:textResponse.userText forKey:@"userText"]; + } + + NSError *error = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error]; + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + didReceiveNotificationResponse(NULL, [errorMsg UTF8String]); + } else { + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + didReceiveNotificationResponse([jsonString UTF8String], NULL); + } + + completionHandler(); +} + +- (bool) EnsureDelegateInitialized { + if (@available(macOS 10.14, *)) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + center.delegate = (id)self; + return YES; + } + return NO; +} + +- (void) RequestNotificationAuthorization :(int)channelID { + if (@available(macOS 10.14, *)) { + if (![self EnsureDelegateInitialized]) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge; + + [center requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) { + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + } else { + captureResult(channelID, granted, NULL); + } + }]; + } else { + captureResult(channelID, false, "Notifications not available on macOS versions prior to 10.14"); + } +} + +- (void) CheckNotificationAuthorization :(int) channelID { + if (@available(macOS 10.14, *)) { + if (![self EnsureDelegateInitialized]) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) { + BOOL isAuthorized = (settings.authorizationStatus == UNAuthorizationStatusAuthorized); + captureResult(channelID, isAuthorized, NULL); + }]; + } else { + captureResult(channelID, false, "Notifications not available on macOS versions prior to 10.14"); + } +} + +- (UNMutableNotificationContent *)createNotificationContent:(const char *)title subtitle:(const char *)subtitle body:(const char *)body dataJSON:(const char *)dataJSON error:(NSError **)contentError API_AVAILABLE(macos(10.14)) { + if (title == NULL) title = ""; + if (body == NULL) body = ""; + + NSString *nsTitle = [NSString stringWithUTF8String:title]; + NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @""; + NSString *nsBody = [NSString stringWithUTF8String:body]; + + UNMutableNotificationContent *content = [[[UNMutableNotificationContent alloc] init] autorelease]; + content.title = nsTitle; + if (![nsSubtitle isEqualToString:@""]) { + content.subtitle = nsSubtitle; + } + content.body = nsBody; + content.sound = [UNNotificationSound defaultSound]; + + // Parse JSON data if provided + if (dataJSON) { + NSString *dataJsonStr = [NSString stringWithUTF8String:dataJSON]; + NSData *jsonData = [dataJsonStr dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error = nil; + NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + if (!error && parsedData) { + content.userInfo = parsedData; + } else if (error) { + if (contentError) *contentError = error; + } + } + + return content; +} + +- (void) sendNotificationWithRequest:(UNNotificationRequest *)request channelID:(int)channelID API_AVAILABLE(macos(10.14)) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + } else { + captureResult(channelID, true, NULL); + } + }]; +} + +- (void) SendNotification:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)dataJSON API_AVAILABLE(macos(10.14)) { + if (![self EnsureDelegateInitialized]) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + + NSError *contentError = nil; + UNMutableNotificationContent *content = [self createNotificationContent:title subtitle:subtitle body:body dataJSON:dataJSON error:&contentError]; + if (contentError) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + UNTimeIntervalNotificationTrigger *trigger = nil; + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger]; + + [self sendNotificationWithRequest:request channelID:channelID]; +} + +- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)dataJSON API_AVAILABLE(macos(10.14)) { + if (![self EnsureDelegateInitialized]) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; + + NSError *contentError = nil; + UNMutableNotificationContent *content = [self createNotificationContent:title subtitle:subtitle body:body dataJSON:dataJSON error:&contentError]; + if (contentError) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + content.categoryIdentifier = nsCategoryId; + + UNTimeIntervalNotificationTrigger *trigger = nil; + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger]; + + [self sendNotificationWithRequest:request channelID:channelID]; +} + +- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle API_AVAILABLE(macos(10.14)) { + if (![self EnsureDelegateInitialized]) { + NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; + NSString *actionsJsonStr = actionsJSON ? [NSString stringWithUTF8String:actionsJSON] : @"[]"; + + NSData *jsonData = [actionsJsonStr dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error = nil; + NSArray *actionsArray = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + + if (error) { + NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]]; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + + NSMutableArray *actions = [NSMutableArray array]; + for (NSDictionary *actionDict in actionsArray) { + NSString *actionId = actionDict[@"id"]; + NSString *actionTitle = actionDict[@"title"]; + BOOL destructive = [actionDict[@"destructive"] boolValue]; + + if (actionId && actionTitle) { + UNNotificationActionOptions options = UNNotificationActionOptionNone; + if (destructive) options |= UNNotificationActionOptionDestructive; + + UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:actionId + title:actionTitle + options:options]; + [actions addObject:action]; + } + } + + if (hasReplyField) { + // Defensive NULL checks: if hasReplyField is true, both strings must be non-NULL + if (!replyPlaceholder || !replyButtonTitle) { + NSString *errorMsg = @"hasReplyField is true but replyPlaceholder or replyButtonTitle is NULL"; + captureResult(channelID, false, [errorMsg UTF8String]); + return; + } + NSString *placeholder = [NSString stringWithUTF8String:replyPlaceholder]; + NSString *buttonTitle = [NSString stringWithUTF8String:replyButtonTitle]; + UNTextInputNotificationAction *textAction = + [UNTextInputNotificationAction actionWithIdentifier:@"TEXT_REPLY" + title:buttonTitle + options:UNNotificationActionOptionNone + textInputButtonTitle:buttonTitle + textInputPlaceholder:placeholder]; + [actions addObject:textAction]; + } + + UNNotificationCategory *newCategory = [UNNotificationCategory categoryWithIdentifier:nsCategoryId + actions:actions + intentIdentifiers:@[] + options:UNNotificationCategoryOptionNone]; + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { + NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories]; + + // Remove existing category with same identifier if found + UNNotificationCategory *existingCategory = nil; + for (UNNotificationCategory *category in updatedCategories) { + if ([category.identifier isEqualToString:nsCategoryId]) { + existingCategory = category; + break; + } + } + if (existingCategory) { + [updatedCategories removeObject:existingCategory]; + } + + // Add the new category + [updatedCategories addObject:newCategory]; + [center setNotificationCategories:updatedCategories]; + + captureResult(channelID, true, NULL); + }]; +} + +- (void) RemoveNotificationCategory:(int)channelID :(const char *)categoryId API_AVAILABLE(macos(10.14)) { + NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + + [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { + NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories]; + + // Find and remove the matching category + UNNotificationCategory *categoryToRemove = nil; + for (UNNotificationCategory *category in updatedCategories) { + if ([category.identifier isEqualToString:nsCategoryId]) { + categoryToRemove = category; + break; + } + } + + if (categoryToRemove) { + [updatedCategories removeObject:categoryToRemove]; + [center setNotificationCategories:updatedCategories]; + captureResult(channelID, true, NULL); + } else { + NSString *errorMsg = [NSString stringWithFormat:@"Category '%@' not found", nsCategoryId]; + captureResult(channelID, false, [errorMsg UTF8String]); + } + }]; +} + +- (void) RemoveAllPendingNotifications API_AVAILABLE(macos(10.14)) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeAllPendingNotificationRequests]; +} + +- (void) RemovePendingNotification:(const char *)identifier API_AVAILABLE(macos(10.14)) { + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removePendingNotificationRequestsWithIdentifiers:@[nsIdentifier]]; +} + +- (void) RemoveAllDeliveredNotifications API_AVAILABLE(macos(10.14)) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeAllDeliveredNotifications]; +} + +- (void) RemoveDeliveredNotification:(const char *)identifier API_AVAILABLE(macos(10.14)) { + NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center removeDeliveredNotificationsWithIdentifiers:@[nsIdentifier]]; +} + + - (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen { self.aboutTitle = title; self.aboutDescription = description; @@ -731,7 +1091,7 @@ typedef void (^schemeTaskCaller)(id); self.aboutImage = [[NSImage alloc] initWithData:imageData]; } --(void) About { +- (void) About { WailsAlert *alert = [WailsAlert new]; [alert setAlertStyle:NSAlertStyleInformational]; diff --git a/v2/internal/frontend/desktop/darwin/notifications.go b/v2/internal/frontend/desktop/darwin/notifications.go new file mode 100644 index 000000000..b788841e0 --- /dev/null +++ b/v2/internal/frontend/desktop/darwin/notifications.go @@ -0,0 +1,465 @@ +//go:build darwin +// +build darwin + +package darwin + +/* +#cgo CFLAGS:-x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa + +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 +#cgo LDFLAGS: -framework UserNotifications +#endif + +#import "Application.h" +#import "WailsContext.h" +*/ +import "C" +import ( + "context" + "encoding/json" + "fmt" + "os" + "sync" + "time" + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend" +) + +// Package-scoped variable only accessible within this file +var ( + currentFrontend *Frontend + frontendMutex sync.RWMutex + // Notification channels + channels map[int]chan notificationChannel + channelsLock sync.Mutex + nextChannelID int + + notificationResultCallback func(result frontend.NotificationResult) + callbackLock sync.RWMutex +) + +const DefaultActionIdentifier = "DEFAULT_ACTION" +const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier" + +// setCurrentFrontend sets the current frontend instance +// This is called when RequestNotificationAuthorization or CheckNotificationAuthorization is called +func setCurrentFrontend(f *Frontend) { + frontendMutex.Lock() + defer frontendMutex.Unlock() + currentFrontend = f +} + +// getCurrentFrontend gets the current frontend instance +func getCurrentFrontend() *Frontend { + frontendMutex.RLock() + defer frontendMutex.RUnlock() + return currentFrontend +} + +type notificationChannel struct { + Success bool + Error error +} + +func (f *Frontend) InitializeNotifications() error { + if !f.IsNotificationAvailable() { + return fmt.Errorf("notifications are not available on this system") + } + if !f.checkBundleIdentifier() { + return fmt.Errorf("notifications require a valid bundle identifier") + } + if !bool(C.EnsureDelegateInitialized(f.mainWindow.context)) { + return fmt.Errorf("failed to initialize notification center delegate") + } + + channels = make(map[int]chan notificationChannel) + nextChannelID = 0 + + setCurrentFrontend(f) + + return nil +} + +// CleanupNotifications is a macOS stub that does nothing. +// (Linux-specific cleanup) +func (f *Frontend) CleanupNotifications() { + // No cleanup needed on macOS +} + +func (f *Frontend) IsNotificationAvailable() bool { + return bool(C.IsNotificationAvailable(f.mainWindow.context)) +} + +func (f *Frontend) checkBundleIdentifier() bool { + return bool(C.CheckBundleIdentifier(f.mainWindow.context)) +} + +func (f *Frontend) RequestNotificationAuthorization() (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second) + defer cancel() + + id, resultCh := f.registerChannel() + + C.RequestNotificationAuthorization(f.mainWindow.context, C.int(id)) + + select { + case result := <-resultCh: + close(resultCh) + return result.Success, result.Error + case <-ctx.Done(): + f.cleanupChannel(id) + return false, fmt.Errorf("notification authorization timed out after 3 minutes: %w", ctx.Err()) + } +} + +func (f *Frontend) CheckNotificationAuthorization() (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + id, resultCh := f.registerChannel() + + C.CheckNotificationAuthorization(f.mainWindow.context, C.int(id)) + + select { + case result := <-resultCh: + close(resultCh) + return result.Success, result.Error + case <-ctx.Done(): + f.cleanupChannel(id) + return false, fmt.Errorf("notification authorization timed out after 15s: %w", ctx.Err()) + } +} + +// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body. +func (f *Frontend) SendNotification(options frontend.NotificationOptions) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cIdentifier := C.CString(options.ID) + cTitle := C.CString(options.Title) + cSubtitle := C.CString(options.Subtitle) + cBody := C.CString(options.Body) + defer C.free(unsafe.Pointer(cIdentifier)) + defer C.free(unsafe.Pointer(cTitle)) + defer C.free(unsafe.Pointer(cSubtitle)) + defer C.free(unsafe.Pointer(cBody)) + + var cDataJSON *C.char + if options.Data != nil { + jsonData, err := json.Marshal(options.Data) + if err != nil { + return fmt.Errorf("failed to marshal notification data: %w", err) + } + cDataJSON = C.CString(string(jsonData)) + defer C.free(unsafe.Pointer(cDataJSON)) + } + + id, resultCh := f.registerChannel() + C.SendNotification(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cDataJSON) + + select { + case result := <-resultCh: + close(resultCh) + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("sending notification failed") + } + return nil + case <-ctx.Done(): + f.cleanupChannel(id) + return fmt.Errorf("sending notification timed out: %w", ctx.Err()) + } +} + +// SendNotificationWithActions sends a notification with additional actions and inputs. +// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category. +// If a NotificationCategory is not registered a basic notification will be sent. +func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cIdentifier := C.CString(options.ID) + cTitle := C.CString(options.Title) + cSubtitle := C.CString(options.Subtitle) + cBody := C.CString(options.Body) + cCategoryID := C.CString(options.CategoryID) + defer C.free(unsafe.Pointer(cIdentifier)) + defer C.free(unsafe.Pointer(cTitle)) + defer C.free(unsafe.Pointer(cSubtitle)) + defer C.free(unsafe.Pointer(cBody)) + defer C.free(unsafe.Pointer(cCategoryID)) + + var cDataJSON *C.char + if options.Data != nil { + jsonData, err := json.Marshal(options.Data) + if err != nil { + return fmt.Errorf("failed to marshal notification data: %w", err) + } + cDataJSON = C.CString(string(jsonData)) + defer C.free(unsafe.Pointer(cDataJSON)) + } + + id, resultCh := f.registerChannel() + C.SendNotificationWithActions(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cCategoryID, cDataJSON) + + select { + case result := <-resultCh: + close(resultCh) + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("sending notification failed") + } + return nil + case <-ctx.Done(): + f.cleanupChannel(id) + return fmt.Errorf("sending notification timed out: %w", ctx.Err()) + } +} + +// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions. +// Registering a category with the same name as a previously registered NotificationCategory will override it. +func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cCategoryID := C.CString(category.ID) + defer C.free(unsafe.Pointer(cCategoryID)) + + actionsJSON, err := json.Marshal(category.Actions) + if err != nil { + return fmt.Errorf("failed to marshal notification category: %w", err) + } + cActionsJSON := C.CString(string(actionsJSON)) + defer C.free(unsafe.Pointer(cActionsJSON)) + + var cReplyPlaceholder, cReplyButtonTitle *C.char + if category.HasReplyField { + cReplyPlaceholder = C.CString(category.ReplyPlaceholder) + cReplyButtonTitle = C.CString(category.ReplyButtonTitle) + defer C.free(unsafe.Pointer(cReplyPlaceholder)) + defer C.free(unsafe.Pointer(cReplyButtonTitle)) + } + + id, resultCh := f.registerChannel() + C.RegisterNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID, cActionsJSON, C.bool(category.HasReplyField), + cReplyPlaceholder, cReplyButtonTitle) + + select { + case result := <-resultCh: + close(resultCh) + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("category registration failed") + } + return nil + case <-ctx.Done(): + f.cleanupChannel(id) + return fmt.Errorf("category registration timed out: %w", ctx.Err()) + } +} + +// RemoveNotificationCategory remove a previously registered NotificationCategory. +func (f *Frontend) RemoveNotificationCategory(categoryId string) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cCategoryID := C.CString(categoryId) + defer C.free(unsafe.Pointer(cCategoryID)) + + id, resultCh := f.registerChannel() + C.RemoveNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID) + + select { + case result := <-resultCh: + close(resultCh) + if !result.Success { + if result.Error != nil { + return result.Error + } + return fmt.Errorf("category removal failed") + } + return nil + case <-ctx.Done(): + f.cleanupChannel(id) + return fmt.Errorf("category removal timed out: %w", ctx.Err()) + } +} + +// RemoveAllPendingNotifications removes all pending notifications. +func (f *Frontend) RemoveAllPendingNotifications() error { + C.RemoveAllPendingNotifications(f.mainWindow.context) + return nil +} + +// RemovePendingNotification removes a pending notification matching the unique identifier. +func (f *Frontend) RemovePendingNotification(identifier string) error { + cIdentifier := C.CString(identifier) + defer C.free(unsafe.Pointer(cIdentifier)) + C.RemovePendingNotification(f.mainWindow.context, cIdentifier) + return nil +} + +// RemoveAllDeliveredNotifications removes all delivered notifications. +func (f *Frontend) RemoveAllDeliveredNotifications() error { + C.RemoveAllDeliveredNotifications(f.mainWindow.context) + return nil +} + +// RemoveDeliveredNotification removes a delivered notification matching the unique identifier. +func (f *Frontend) RemoveDeliveredNotification(identifier string) error { + cIdentifier := C.CString(identifier) + defer C.free(unsafe.Pointer(cIdentifier)) + C.RemoveDeliveredNotification(f.mainWindow.context, cIdentifier) + return nil +} + +// RemoveNotification is a macOS stub that always returns nil. +// Use one of the following instead: +// RemoveAllPendingNotifications +// RemovePendingNotification +// RemoveAllDeliveredNotifications +// RemoveDeliveredNotification +// (Linux-specific) +func (f *Frontend) RemoveNotification(identifier string) error { + return nil +} + +func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) { + callbackLock.Lock() + notificationResultCallback = callback + callbackLock.Unlock() +} + +//export captureResult +func captureResult(channelID C.int, success C.bool, errorMsg *C.char) { + f := getCurrentFrontend() + if f == nil { + return + } + + resultCh, exists := f.GetChannel(int(channelID)) + if !exists { + return + } + + var err error + if errorMsg != nil { + err = fmt.Errorf("%s", C.GoString(errorMsg)) + } + + resultCh <- notificationChannel{ + Success: bool(success), + Error: err, + } +} + +//export didReceiveNotificationResponse +func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) { + result := frontend.NotificationResult{} + + if err != nil { + errMsg := C.GoString(err) + result.Error = fmt.Errorf("notification response error: %s", errMsg) + handleNotificationResult(result) + + return + } + + if jsonPayload == nil { + result.Error = fmt.Errorf("received nil JSON payload in notification response") + handleNotificationResult(result) + return + } + + payload := C.GoString(jsonPayload) + + var response frontend.NotificationResponse + if err := json.Unmarshal([]byte(payload), &response); err != nil { + result.Error = fmt.Errorf("failed to unmarshal notification response: %w", err) + handleNotificationResult(result) + return + } + + if response.ActionIdentifier == AppleDefaultActionIdentifier { + response.ActionIdentifier = DefaultActionIdentifier + } + + result.Response = response + handleNotificationResult(result) +} + +func handleNotificationResult(result frontend.NotificationResult) { + callbackLock.Lock() + callback := notificationResultCallback + callbackLock.Unlock() + + if callback != nil { + go func() { + defer func() { + if r := recover(); r != nil { + // Log panic but don't crash the app + fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r) + } + }() + callback(result) + }() + } +} + +// Helper methods + +func (f *Frontend) registerChannel() (int, chan notificationChannel) { + channelsLock.Lock() + defer channelsLock.Unlock() + + // Initialize channels map if it's nil + if channels == nil { + channels = make(map[int]chan notificationChannel) + nextChannelID = 0 + } + + id := nextChannelID + nextChannelID++ + + resultCh := make(chan notificationChannel, 1) + + channels[id] = resultCh + return id, resultCh +} + +func (f *Frontend) GetChannel(id int) (chan notificationChannel, bool) { + channelsLock.Lock() + defer channelsLock.Unlock() + + if channels == nil { + return nil, false + } + + ch, exists := channels[id] + if exists { + delete(channels, id) + } + return ch, exists +} + +func (f *Frontend) cleanupChannel(id int) { + channelsLock.Lock() + defer channelsLock.Unlock() + + if channels == nil { + return + } + + if ch, exists := channels[id]; exists { + delete(channels, id) + close(ch) + } +} diff --git a/v2/internal/frontend/desktop/linux/notifications.go b/v2/internal/frontend/desktop/linux/notifications.go new file mode 100644 index 000000000..80f0ae569 --- /dev/null +++ b/v2/internal/frontend/desktop/linux/notifications.go @@ -0,0 +1,594 @@ +//go:build linux +// +build linux + +package linux + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + + "github.com/godbus/dbus/v5" + "github.com/wailsapp/wails/v2/internal/frontend" +) + +var ( + conn *dbus.Conn + categories map[string]frontend.NotificationCategory = make(map[string]frontend.NotificationCategory) + categoriesLock sync.RWMutex + notifications map[uint32]*notificationData = make(map[uint32]*notificationData) + notificationsLock sync.RWMutex + notificationResultCallback func(result frontend.NotificationResult) + callbackLock sync.RWMutex + appName string + cancel context.CancelFunc +) + +type notificationData struct { + ID string + Title string + Subtitle string + Body string + CategoryID string + Data map[string]interface{} + DBusID uint32 + ActionMap map[string]string +} + +const ( + dbusNotificationInterface = "org.freedesktop.Notifications" + dbusNotificationPath = "/org/freedesktop/Notifications" + DefaultActionIdentifier = "DEFAULT_ACTION" +) + +// Creates a new Notifications Service. +func (f *Frontend) InitializeNotifications() error { + // Clean up any previous initialization + f.CleanupNotifications() + + exe, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable: %w", err) + } + appName = filepath.Base(exe) + + _conn, err := dbus.ConnectSessionBus() + if err != nil { + return fmt.Errorf("failed to connect to session bus: %w", err) + } + conn = _conn + + if err := f.loadCategories(); err != nil { + f.logger.Warning("Failed to load notification categories: %v", err) + } + + var signalCtx context.Context + signalCtx, cancel = context.WithCancel(context.Background()) + + if err := f.setupSignalHandling(signalCtx); err != nil { + return fmt.Errorf("failed to set up notification signal handling: %w", err) + } + + return nil +} + +// CleanupNotifications cleans up notification resources +func (f *Frontend) CleanupNotifications() { + if cancel != nil { + cancel() + cancel = nil + } + + if conn != nil { + conn.Close() + conn = nil + } +} + +func (f *Frontend) IsNotificationAvailable() bool { + return true +} + +// RequestNotificationAuthorization is a Linux stub that always returns true, nil. +// (authorization is macOS-specific) +func (f *Frontend) RequestNotificationAuthorization() (bool, error) { + return true, nil +} + +// CheckNotificationAuthorization is a Linux stub that always returns true. +// (authorization is macOS-specific) +func (f *Frontend) CheckNotificationAuthorization() (bool, error) { + return true, nil +} + +// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body. +func (f *Frontend) SendNotification(options frontend.NotificationOptions) error { + if conn == nil { + return fmt.Errorf("notifications not initialized") + } + + hints := map[string]dbus.Variant{} + + body := options.Body + if options.Subtitle != "" { + body = options.Subtitle + "\n" + body + } + + defaultActionID := "default" + actions := []string{defaultActionID, "Default"} + + actionMap := map[string]string{ + defaultActionID: DefaultActionIdentifier, + } + + hints["x-notification-id"] = dbus.MakeVariant(options.ID) + + if options.Data != nil { + userData, err := json.Marshal(options.Data) + if err == nil { + hints["x-user-data"] = dbus.MakeVariant(string(userData)) + } + } + + // Call the Notify method on the D-Bus interface + obj := conn.Object(dbusNotificationInterface, dbusNotificationPath) + call := obj.Call( + dbusNotificationInterface+".Notify", + 0, + appName, + uint32(0), + "", // Icon + options.Title, + body, + actions, + hints, + int32(-1), + ) + + if call.Err != nil { + return fmt.Errorf("failed to send notification: %w", call.Err) + } + + var dbusID uint32 + if err := call.Store(&dbusID); err != nil { + return fmt.Errorf("failed to store notification ID: %w", err) + } + + notification := ¬ificationData{ + ID: options.ID, + Title: options.Title, + Subtitle: options.Subtitle, + Body: options.Body, + Data: options.Data, + DBusID: dbusID, + ActionMap: actionMap, + } + + notificationsLock.Lock() + notifications[dbusID] = notification + notificationsLock.Unlock() + + return nil +} + +// SendNotificationWithActions sends a notification with additional actions. +func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error { + if conn == nil { + return fmt.Errorf("notifications not initialized") + } + + categoriesLock.RLock() + category, exists := categories[options.CategoryID] + categoriesLock.RUnlock() + + if options.CategoryID == "" || !exists { + // Fall back to basic notification + return f.SendNotification(options) + } + + body := options.Body + if options.Subtitle != "" { + body = options.Subtitle + "\n" + body + } + + var actions []string + actionMap := make(map[string]string) + + defaultActionID := "default" + actions = append(actions, defaultActionID, "Default") + actionMap[defaultActionID] = DefaultActionIdentifier + + for _, action := range category.Actions { + actions = append(actions, action.ID, action.Title) + actionMap[action.ID] = action.ID + } + + hints := map[string]dbus.Variant{} + + hints["x-notification-id"] = dbus.MakeVariant(options.ID) + + hints["x-category-id"] = dbus.MakeVariant(options.CategoryID) + + if options.Data != nil { + userData, err := json.Marshal(options.Data) + if err == nil { + hints["x-user-data"] = dbus.MakeVariant(string(userData)) + } + } + + obj := conn.Object(dbusNotificationInterface, dbusNotificationPath) + call := obj.Call( + dbusNotificationInterface+".Notify", + 0, + appName, + uint32(0), + "", // Icon + options.Title, + body, + actions, + hints, + int32(-1), + ) + + if call.Err != nil { + return fmt.Errorf("failed to send notification: %w", call.Err) + } + + var dbusID uint32 + if err := call.Store(&dbusID); err != nil { + return fmt.Errorf("failed to store notification ID: %w", err) + } + + notification := ¬ificationData{ + ID: options.ID, + Title: options.Title, + Subtitle: options.Subtitle, + Body: options.Body, + CategoryID: options.CategoryID, + Data: options.Data, + DBusID: dbusID, + ActionMap: actionMap, + } + + notificationsLock.Lock() + notifications[dbusID] = notification + notificationsLock.Unlock() + + return nil +} + +// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions. +func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error { + categoriesLock.Lock() + categories[category.ID] = category + categoriesLock.Unlock() + + if err := f.saveCategories(); err != nil { + f.logger.Warning("Failed to save notification categories: %v", err) + } + + return nil +} + +// RemoveNotificationCategory removes a previously registered NotificationCategory. +func (f *Frontend) RemoveNotificationCategory(categoryId string) error { + categoriesLock.Lock() + delete(categories, categoryId) + categoriesLock.Unlock() + + if err := f.saveCategories(); err != nil { + f.logger.Warning("Failed to save notification categories: %v", err) + } + + return nil +} + +// RemoveAllPendingNotifications attempts to remove all active notifications. +func (f *Frontend) RemoveAllPendingNotifications() error { + notificationsLock.Lock() + dbusIDs := make([]uint32, 0, len(notifications)) + for id := range notifications { + dbusIDs = append(dbusIDs, id) + } + notificationsLock.Unlock() + + for _, id := range dbusIDs { + f.closeNotification(id) + } + + return nil +} + +// RemovePendingNotification removes a pending notification. +func (f *Frontend) RemovePendingNotification(identifier string) error { + var dbusID uint32 + found := false + + notificationsLock.Lock() + for id, notif := range notifications { + if notif.ID == identifier { + dbusID = id + found = true + break + } + } + notificationsLock.Unlock() + + if !found { + return nil + } + + return f.closeNotification(dbusID) +} + +// RemoveAllDeliveredNotifications functionally equivalent to RemoveAllPendingNotification on Linux. +func (f *Frontend) RemoveAllDeliveredNotifications() error { + return f.RemoveAllPendingNotifications() +} + +// RemoveDeliveredNotification functionally equivalent RemovePendingNotification on Linux. +func (f *Frontend) RemoveDeliveredNotification(identifier string) error { + return f.RemovePendingNotification(identifier) +} + +// RemoveNotification removes a notification by identifier. +func (f *Frontend) RemoveNotification(identifier string) error { + return f.RemovePendingNotification(identifier) +} + +func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) { + callbackLock.Lock() + defer callbackLock.Unlock() + + notificationResultCallback = callback +} + +// Helper method to close a notification. +func (f *Frontend) closeNotification(id uint32) error { + if conn == nil { + return fmt.Errorf("notifications not initialized") + } + + obj := conn.Object(dbusNotificationInterface, dbusNotificationPath) + call := obj.Call(dbusNotificationInterface+".CloseNotification", 0, id) + + if call.Err != nil { + return fmt.Errorf("failed to close notification: %w", call.Err) + } + + return nil +} + +func (f *Frontend) getConfigDir() (string, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return "", fmt.Errorf("failed to get user config directory: %w", err) + } + + appConfigDir := filepath.Join(configDir, appName) + if err := os.MkdirAll(appConfigDir, 0755); err != nil { + return "", fmt.Errorf("failed to create app config directory: %w", err) + } + + return appConfigDir, nil +} + +// Save notification categories. +func (f *Frontend) saveCategories() error { + configDir, err := f.getConfigDir() + if err != nil { + return err + } + + categoriesFile := filepath.Join(configDir, "notification-categories.json") + + categoriesLock.RLock() + categoriesData, err := json.MarshalIndent(categories, "", " ") + categoriesLock.RUnlock() + + if err != nil { + return fmt.Errorf("failed to marshal notification categories: %w", err) + } + + if err := os.WriteFile(categoriesFile, categoriesData, 0644); err != nil { + return fmt.Errorf("failed to write notification categories to disk: %w", err) + } + + return nil +} + +// Load notification categories. +func (f *Frontend) loadCategories() error { + configDir, err := f.getConfigDir() + if err != nil { + return err + } + + categoriesFile := filepath.Join(configDir, "notification-categories.json") + + if _, err := os.Stat(categoriesFile); os.IsNotExist(err) { + return nil + } + + categoriesData, err := os.ReadFile(categoriesFile) + if err != nil { + return fmt.Errorf("failed to read notification categories from disk: %w", err) + } + + _categories := make(map[string]frontend.NotificationCategory) + if err := json.Unmarshal(categoriesData, &_categories); err != nil { + return fmt.Errorf("failed to unmarshal notification categories: %w", err) + } + + categoriesLock.Lock() + categories = _categories + categoriesLock.Unlock() + + return nil +} + +// Setup signal handling for notification actions. +func (f *Frontend) setupSignalHandling(ctx context.Context) error { + if err := conn.AddMatchSignal( + dbus.WithMatchInterface(dbusNotificationInterface), + dbus.WithMatchMember("ActionInvoked"), + ); err != nil { + return err + } + + if err := conn.AddMatchSignal( + dbus.WithMatchInterface(dbusNotificationInterface), + dbus.WithMatchMember("NotificationClosed"), + ); err != nil { + return err + } + + c := make(chan *dbus.Signal, 10) + conn.Signal(c) + + go f.handleSignals(ctx, c) + + return nil +} + +// Handle incoming D-Bus signals. +func (f *Frontend) handleSignals(ctx context.Context, c chan *dbus.Signal) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-c: + if !ok { + return + } + + switch signal.Name { + case dbusNotificationInterface + ".ActionInvoked": + f.handleActionInvoked(signal) + case dbusNotificationInterface + ".NotificationClosed": + f.handleNotificationClosed(signal) + } + } + } +} + +// Handle ActionInvoked signal. +func (f *Frontend) handleActionInvoked(signal *dbus.Signal) { + if len(signal.Body) < 2 { + return + } + + dbusID, ok := signal.Body[0].(uint32) + if !ok { + return + } + + actionID, ok := signal.Body[1].(string) + if !ok { + return + } + + notificationsLock.Lock() + notification, exists := notifications[dbusID] + if exists { + delete(notifications, dbusID) + } + notificationsLock.Unlock() + + if !exists { + return + } + + appActionID, ok := notification.ActionMap[actionID] + if !ok { + appActionID = actionID + } + + response := frontend.NotificationResponse{ + ID: notification.ID, + ActionIdentifier: appActionID, + Title: notification.Title, + Subtitle: notification.Subtitle, + Body: notification.Body, + CategoryID: notification.CategoryID, + UserInfo: notification.Data, + } + + result := frontend.NotificationResult{ + Response: response, + } + + handleNotificationResult(result) +} + +func handleNotificationResult(result frontend.NotificationResult) { + callbackLock.Lock() + callback := notificationResultCallback + callbackLock.Unlock() + + if callback != nil { + go func() { + defer func() { + if r := recover(); r != nil { + // Log panic but don't crash the app + fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r) + } + }() + callback(result) + }() + } +} + +// Handle NotificationClosed signal. +// Reason codes: +// 1 - expired timeout +// 2 - dismissed by user (click on X) +// 3 - closed by CloseNotification call +// 4 - undefined/reserved +func (f *Frontend) handleNotificationClosed(signal *dbus.Signal) { + if len(signal.Body) < 2 { + return + } + + dbusID, ok := signal.Body[0].(uint32) + if !ok { + return + } + + reason, ok := signal.Body[1].(uint32) + if !ok { + reason = 0 // Unknown reason + } + + notificationsLock.Lock() + notification, exists := notifications[dbusID] + if exists { + delete(notifications, dbusID) + } + notificationsLock.Unlock() + + if !exists { + return + } + + if reason == 2 { + response := frontend.NotificationResponse{ + ID: notification.ID, + ActionIdentifier: DefaultActionIdentifier, + Title: notification.Title, + Subtitle: notification.Subtitle, + Body: notification.Body, + CategoryID: notification.CategoryID, + UserInfo: notification.Data, + } + + result := frontend.NotificationResult{ + Response: response, + } + + handleNotificationResult(result) + } +} diff --git a/v2/internal/frontend/desktop/windows/notifications.go b/v2/internal/frontend/desktop/windows/notifications.go new file mode 100644 index 000000000..0176b7077 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/notifications.go @@ -0,0 +1,489 @@ +//go:build windows +// +build windows + +package windows + +import ( + "encoding/base64" + "encoding/json" + "log" + "sync" + + wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast" + "github.com/google/uuid" + "github.com/wailsapp/wails/v2/internal/frontend" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" + + "fmt" + "os" + "path/filepath" + _ "unsafe" // for go:linkname + + "git.sr.ht/~jackmordaunt/go-toast/v2" + "golang.org/x/sys/windows/registry" +) + +var ( + categories map[string]frontend.NotificationCategory + categoriesLock sync.RWMutex + appName string + appGUID string + iconPath string = "" + exePath string + iconOnce sync.Once + iconErr error + + notificationResultCallback func(result frontend.NotificationResult) + callbackLock sync.RWMutex +) + +const DefaultActionIdentifier = "DEFAULT_ACTION" + +const ( + ToastRegistryPath = `Software\Classes\AppUserModelId\` + ToastRegistryGuidKey = "CustomActivator" + NotificationCategoriesRegistryPath = `SOFTWARE\%s\NotificationCategories` + NotificationCategoriesRegistryKey = "Categories" +) + +// NotificationPayload combines the action ID and user data into a single structure +type NotificationPayload struct { + Action string `json:"action"` + Options frontend.NotificationOptions `json:"payload,omitempty"` +} + +func (f *Frontend) InitializeNotifications() error { + categoriesLock.Lock() + defer categoriesLock.Unlock() + categories = make(map[string]frontend.NotificationCategory) + + exe, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable: %w", err) + } + exePath = exe + appName = filepath.Base(exePath) + + appGUID, err = getGUID() + if err != nil { + return err + } + + iconPath = filepath.Join(os.TempDir(), appName+appGUID+".png") + + // Create the registry key for the toast activator + key, _, err := registry.CreateKey(registry.CURRENT_USER, + `Software\Classes\CLSID\`+appGUID+`\LocalServer32`, registry.ALL_ACCESS) + if err != nil { + return fmt.Errorf("failed to create CLSID key: %w", err) + } + defer key.Close() + + if err := key.SetStringValue("", fmt.Sprintf("\"%s\" %%1", exePath)); err != nil { + return fmt.Errorf("failed to set CLSID server path: %w", err) + } + + toast.SetAppData(toast.AppData{ + AppID: appName, + GUID: appGUID, + IconPath: iconPath, + ActivationExe: exePath, + }) + + toast.SetActivationCallback(func(args string, data []toast.UserData) { + result := frontend.NotificationResult{} + + actionIdentifier, options, err := parseNotificationResponse(args) + + if err != nil { + result.Error = err + } else { + // Subtitle is retained but was not shown with the notification + response := frontend.NotificationResponse{ + ID: options.ID, + ActionIdentifier: actionIdentifier, + Title: options.Title, + Subtitle: options.Subtitle, + Body: options.Body, + CategoryID: options.CategoryID, + UserInfo: options.Data, + } + + if userText, found := getUserText(data); found { + response.UserText = userText + } + + result.Response = response + } + + handleNotificationResult(result) + }) + + // Register the COM class factory for toast activation. + // This is required for Windows to activate the app when users interact with notifications. + // The go-toast library's SetAppData and SetActivationCallback handle the callback setup, + // but the COM class factory registration is not exposed via public APIs, so we use + // go:linkname to access the internal registerClassFactory function. + if err := registerToastClassFactory(wintoast.ClassFactory); err != nil { + return fmt.Errorf("CoRegisterClassObject failed: %w", err) + } + + return loadCategoriesFromRegistry() +} + +// registerToastClassFactory registers the COM class factory required for Windows toast notification activation. +// This function uses go:linkname to access the unexported registerClassFactory function from go-toast. +// The class factory is necessary for Windows COM activation when users click notification actions. +// Without this registration, notification actions will not activate the application. +// +// This is a workaround until go-toast exports this functionality via a public API. +// See: https://git.sr.ht/~jackmordaunt/go-toast +// +//go:linkname registerToastClassFactory git.sr.ht/~jackmordaunt/go-toast/v2/wintoast.registerClassFactory +func registerToastClassFactory(factory *wintoast.IClassFactory) error + +// CleanupNotifications is a Windows stub that does nothing. +// (Linux-specific cleanup) +func (f *Frontend) CleanupNotifications() { + // No cleanup needed on Windows +} + +func (f *Frontend) IsNotificationAvailable() bool { + return true +} + +func (f *Frontend) RequestNotificationAuthorization() (bool, error) { + return true, nil +} + +func (f *Frontend) CheckNotificationAuthorization() (bool, error) { + return true, nil +} + +// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows. +// (subtitle is only available on macOS and Linux) +func (f *Frontend) SendNotification(options frontend.NotificationOptions) error { + if err := f.saveIconToDir(); err != nil { + f.logger.Warning("Error saving icon: %v", err) + } + + n := toast.Notification{ + Title: options.Title, + Body: options.Body, + ActivationType: toast.Foreground, + ActivationArguments: DefaultActionIdentifier, + } + + encodedPayload, err := encodePayload(DefaultActionIdentifier, options) + if err != nil { + return fmt.Errorf("failed to encode notification payload: %w", err) + } + n.ActivationArguments = encodedPayload + + return n.Push() +} + +// SendNotificationWithActions sends a notification with additional actions and inputs. +// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category. +// If a NotificationCategory is not registered a basic notification will be sent. +// (subtitle is only available on macOS and Linux) +func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error { + if err := f.saveIconToDir(); err != nil { + f.logger.Warning("Error saving icon: %v", err) + } + + categoriesLock.RLock() + nCategory, categoryExists := categories[options.CategoryID] + categoriesLock.RUnlock() + + if options.CategoryID == "" || !categoryExists { + f.logger.Warning("Category '%s' not found, sending basic notification without actions", options.CategoryID) + return f.SendNotification(options) + } + + n := toast.Notification{ + Title: options.Title, + Body: options.Body, + ActivationType: toast.Foreground, + ActivationArguments: DefaultActionIdentifier, + } + + for _, action := range nCategory.Actions { + n.Actions = append(n.Actions, toast.Action{ + Content: action.Title, + Arguments: action.ID, + }) + } + + if nCategory.HasReplyField { + n.Inputs = append(n.Inputs, toast.Input{ + ID: "userText", + Placeholder: nCategory.ReplyPlaceholder, + }) + + n.Actions = append(n.Actions, toast.Action{ + Content: nCategory.ReplyButtonTitle, + Arguments: "TEXT_REPLY", + InputID: "userText", + }) + } + + encodedPayload, err := encodePayload(n.ActivationArguments, options) + if err != nil { + return fmt.Errorf("failed to encode notification payload: %w", err) + } + n.ActivationArguments = encodedPayload + + for index := range n.Actions { + encodedPayload, err := encodePayload(n.Actions[index].Arguments, options) + if err != nil { + return fmt.Errorf("failed to encode notification payload: %w", err) + } + n.Actions[index].Arguments = encodedPayload + } + + return n.Push() +} + +// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions. +// Registering a category with the same name as a previously registered NotificationCategory will override it. +func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error { + categoriesLock.Lock() + defer categoriesLock.Unlock() + + categories[category.ID] = frontend.NotificationCategory{ + ID: category.ID, + Actions: category.Actions, + HasReplyField: category.HasReplyField, + ReplyPlaceholder: category.ReplyPlaceholder, + ReplyButtonTitle: category.ReplyButtonTitle, + } + + return saveCategoriesToRegistry() +} + +// RemoveNotificationCategory removes a previously registered NotificationCategory. +func (f *Frontend) RemoveNotificationCategory(categoryId string) error { + categoriesLock.Lock() + defer categoriesLock.Unlock() + + delete(categories, categoryId) + + return saveCategoriesToRegistry() +} + +// RemoveAllPendingNotifications is a Windows stub that always returns nil. +// (macOS and Linux only) +func (f *Frontend) RemoveAllPendingNotifications() error { + return nil +} + +// RemovePendingNotification is a Windows stub that always returns nil. +// (macOS and Linux only) +func (f *Frontend) RemovePendingNotification(_ string) error { + return nil +} + +// RemoveAllDeliveredNotifications is a Windows stub that always returns nil. +// (macOS and Linux only) +func (f *Frontend) RemoveAllDeliveredNotifications() error { + return nil +} + +// RemoveDeliveredNotification is a Windows stub that always returns nil. +// (macOS and Linux only) +func (f *Frontend) RemoveDeliveredNotification(_ string) error { + return nil +} + +// RemoveNotification is a Windows stub that always returns nil. +// (Linux-specific) +func (f *Frontend) RemoveNotification(identifier string) error { + return nil +} + +func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) { + callbackLock.Lock() + defer callbackLock.Unlock() + + notificationResultCallback = callback +} + +func (f *Frontend) saveIconToDir() error { + iconOnce.Do(func() { + hIcon := w32.ExtractIcon(exePath, 0) + if hIcon == 0 { + iconErr = fmt.Errorf("ExtractIcon failed for %s", exePath) + return + } + defer w32.DestroyIcon(hIcon) + iconErr = winc.SaveHIconAsPNG(hIcon, iconPath) + }) + return iconErr +} + +func saveCategoriesToRegistry() error { + // We assume lock is held by caller + + registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName) + + key, _, err := registry.CreateKey( + registry.CURRENT_USER, + registryPath, + registry.ALL_ACCESS, + ) + if err != nil { + return err + } + defer key.Close() + + data, err := json.Marshal(categories) + if err != nil { + return err + } + + return key.SetStringValue(NotificationCategoriesRegistryKey, string(data)) +} + +func loadCategoriesFromRegistry() error { + // We assume lock is held by caller + + registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName) + + key, err := registry.OpenKey( + registry.CURRENT_USER, + registryPath, + registry.QUERY_VALUE, + ) + if err != nil { + if err == registry.ErrNotExist { + // Not an error, no saved categories + return nil + } + return fmt.Errorf("failed to open registry key: %w", err) + } + defer key.Close() + + data, _, err := key.GetStringValue(NotificationCategoriesRegistryKey) + if err != nil { + if err == registry.ErrNotExist { + // No value yet, but key exists + return nil + } + return fmt.Errorf("failed to read categories from registry: %w", err) + } + + _categories := make(map[string]frontend.NotificationCategory) + if err := json.Unmarshal([]byte(data), &_categories); err != nil { + return fmt.Errorf("failed to parse notification categories from registry: %w", err) + } + + categories = _categories + + return nil +} + +func getUserText(data []toast.UserData) (string, bool) { + for _, d := range data { + if d.Key == "userText" { + return d.Value, true + } + } + return "", false +} + +// encodePayload combines an action ID and user data into a single encoded string +func encodePayload(actionID string, options frontend.NotificationOptions) (string, error) { + payload := NotificationPayload{ + Action: actionID, + Options: options, + } + + jsonData, err := json.Marshal(payload) + if err != nil { + return actionID, err + } + + encodedPayload := base64.StdEncoding.EncodeToString(jsonData) + return encodedPayload, nil +} + +// decodePayload extracts the action ID and user data from an encoded payload +func decodePayload(encodedString string) (string, frontend.NotificationOptions, error) { + jsonData, err := base64.StdEncoding.DecodeString(encodedString) + if err != nil { + return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to decode base64 payload: %w", err) + } + + var payload NotificationPayload + if err := json.Unmarshal(jsonData, &payload); err != nil { + return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to unmarshal notification payload: %w", err) + } + + return payload.Action, payload.Options, nil +} + +// parseNotificationResponse updated to use structured payload decoding +func parseNotificationResponse(response string) (action string, options frontend.NotificationOptions, err error) { + actionID, options, err := decodePayload(response) + + if err != nil { + log.Printf("Warning: Failed to decode notification response: %v", err) + return response, frontend.NotificationOptions{}, err + } + + return actionID, options, nil +} + +func handleNotificationResult(result frontend.NotificationResult) { + callbackLock.RLock() + callback := notificationResultCallback + callbackLock.RUnlock() + + if callback != nil { + go func() { + defer func() { + if r := recover(); r != nil { + // Log panic but don't crash the app + fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r) + } + }() + callback(result) + }() + } +} + +// Helper functions + +func getGUID() (string, error) { + keyPath := ToastRegistryPath + appName + + k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE) + if err == nil { + guid, _, err := k.GetStringValue(ToastRegistryGuidKey) + k.Close() + if err == nil && guid != "" { + return guid, nil + } + } + + guid := generateGUID() + + k, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.WRITE) + if err != nil { + return "", fmt.Errorf("failed to create registry key: %w", err) + } + defer k.Close() + + if err := k.SetStringValue(ToastRegistryGuidKey, guid); err != nil { + return "", fmt.Errorf("failed to write GUID to registry: %w", err) + } + + return guid, nil +} + +func generateGUID() string { + guid := uuid.New() + return fmt.Sprintf("{%s}", guid.String()) +} diff --git a/v2/internal/frontend/desktop/windows/winc/icon.go b/v2/internal/frontend/desktop/windows/winc/icon.go index 6a3e1a391..94e9198d6 100644 --- a/v2/internal/frontend/desktop/windows/winc/icon.go +++ b/v2/internal/frontend/desktop/windows/winc/icon.go @@ -10,11 +10,86 @@ package winc import ( "errors" "fmt" + "image" + "image/png" + "os" "syscall" + "unsafe" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" ) +var ( + user32 = syscall.NewLazyDLL("user32.dll") + gdi32 = syscall.NewLazyDLL("gdi32.dll") + procGetIconInfo = user32.NewProc("GetIconInfo") + procDeleteObject = gdi32.NewProc("DeleteObject") + procGetObject = gdi32.NewProc("GetObjectW") + procGetDIBits = gdi32.NewProc("GetDIBits") + procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC") + procSelectObject = gdi32.NewProc("SelectObject") + procDeleteDC = gdi32.NewProc("DeleteDC") +) + +func init() { + // Validate DLL loads at initialization time to surface missing APIs early + if err := user32.Load(); err != nil { + panic(fmt.Sprintf("failed to load user32.dll: %v", err)) + } + if err := gdi32.Load(); err != nil { + panic(fmt.Sprintf("failed to load gdi32.dll: %v", err)) + } +} + +// ICONINFO mirrors the Win32 ICONINFO struct +type ICONINFO struct { + FIcon int32 + XHotspot uint32 + YHotspot uint32 + HbmMask uintptr + HbmColor uintptr +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx +type BITMAPINFOHEADER struct { + BiSize uint32 + BiWidth int32 + BiHeight int32 + BiPlanes uint16 + BiBitCount uint16 + BiCompression uint32 + BiSizeImage uint32 + BiXPelsPerMeter int32 + BiYPelsPerMeter int32 + BiClrUsed uint32 + BiClrImportant uint32 +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx +type RGBQUAD struct { + RgbBlue byte + RgbGreen byte + RgbRed byte + RgbReserved byte +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx +type BITMAPINFO struct { + BmiHeader BITMAPINFOHEADER + BmiColors *RGBQUAD +} + +// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183371.aspx +type BITMAP struct { + BmType int32 + BmWidth int32 + BmHeight int32 + BmWidthBytes int32 + BmPlanes uint16 + BmBitsPixel uint16 + BmBits unsafe.Pointer +} + type Icon struct { handle w32.HICON } @@ -46,6 +121,95 @@ func ExtractIcon(fileName string, index int) (*Icon, error) { return ico, err } +func SaveHIconAsPNG(hIcon w32.HICON, filePath string) error { + // Get icon info + var iconInfo ICONINFO + ret, _, err := procGetIconInfo.Call( + uintptr(hIcon), + uintptr(unsafe.Pointer(&iconInfo)), + ) + if ret == 0 { + return err + } + defer procDeleteObject.Call(uintptr(iconInfo.HbmMask)) + defer procDeleteObject.Call(uintptr(iconInfo.HbmColor)) + + // Get bitmap info + var bmp BITMAP + ret, _, err = procGetObject.Call( + uintptr(iconInfo.HbmColor), + unsafe.Sizeof(bmp), + uintptr(unsafe.Pointer(&bmp)), + ) + if ret == 0 { + return err + } + + // Get screen DC for GetDIBits (bitmap must not be selected into a DC) + screenDC := w32.GetDC(0) + if screenDC == 0 { + return fmt.Errorf("failed to get screen DC") + } + defer w32.ReleaseDC(0, screenDC) + + // Prepare bitmap info header + var bi BITMAPINFO + bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader)) + bi.BmiHeader.BiWidth = bmp.BmWidth + bi.BmiHeader.BiHeight = bmp.BmHeight + bi.BmiHeader.BiPlanes = 1 + bi.BmiHeader.BiBitCount = 32 + bi.BmiHeader.BiCompression = w32.BI_RGB + + // Allocate memory for bitmap bits + width, height := int(bmp.BmWidth), int(bmp.BmHeight) + bufferSize := width * height * 4 + bits := make([]byte, bufferSize) + + // Get bitmap bits using screen DC (bitmap must not be selected into any DC) + ret, _, err = procGetDIBits.Call( + uintptr(screenDC), + uintptr(iconInfo.HbmColor), + 0, + uintptr(bmp.BmHeight), + uintptr(unsafe.Pointer(&bits[0])), + uintptr(unsafe.Pointer(&bi)), + w32.DIB_RGB_COLORS, + ) + if ret == 0 { + return fmt.Errorf("failed to get bitmap bits: %w", err) + } + + // Create Go image + img := image.NewRGBA(image.Rect(0, 0, width, height)) + + // Convert DIB to RGBA + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + // DIB is bottom-up, so we need to invert Y + dibIndex := ((height-1-y)*width + x) * 4 + // RGBA image is top-down + imgIndex := (y*width + x) * 4 + + // BGRA to RGBA + img.Pix[imgIndex] = bits[dibIndex+2] // R + img.Pix[imgIndex+1] = bits[dibIndex+1] // G + img.Pix[imgIndex+2] = bits[dibIndex] // B + img.Pix[imgIndex+3] = bits[dibIndex+3] // A + } + } + + // Create output file + outFile, err := os.Create(filePath) + if err != nil { + return err + } + defer outFile.Close() + + // Encode and save the image + return png.Encode(outFile, img) +} + func (ic *Icon) Destroy() bool { return w32.DestroyIcon(ic.handle) } diff --git a/v2/internal/frontend/dispatcher/systemcalls.go b/v2/internal/frontend/dispatcher/systemcalls.go index b090a416e..a13eb03b9 100644 --- a/v2/internal/frontend/dispatcher/systemcalls.go +++ b/v2/internal/frontend/dispatcher/systemcalls.go @@ -61,6 +61,102 @@ func (d *Dispatcher) processSystemCall(payload callMessage, sender frontend.Fron return false, err } return true, nil + case "InitializeNotifications": + err := sender.InitializeNotifications() + return nil, err + case "CleanupNotifications": + sender.CleanupNotifications() + return nil, nil + case "IsNotificationAvailable": + return sender.IsNotificationAvailable(), nil + case "RequestNotificationAuthorization": + authorized, err := sender.RequestNotificationAuthorization() + if err != nil { + return nil, err + } + return authorized, nil + case "CheckNotificationAuthorization": + authorized, err := sender.CheckNotificationAuthorization() + if err != nil { + return nil, err + } + return authorized, nil + case "SendNotification": + if len(payload.Args) < 1 { + return nil, errors.New("empty argument, cannot send notification") + } + var options frontend.NotificationOptions + if err := json.Unmarshal(payload.Args[0], &options); err != nil { + return nil, err + } + err := sender.SendNotification(options) + return nil, err + case "SendNotificationWithActions": + if len(payload.Args) < 1 { + return nil, errors.New("empty argument, cannot send notification") + } + var options frontend.NotificationOptions + if err := json.Unmarshal(payload.Args[0], &options); err != nil { + return nil, err + } + err := sender.SendNotificationWithActions(options) + return nil, err + case "RegisterNotificationCategory": + if len(payload.Args) < 1 { + return nil, errors.New("empty argument, cannot register category") + } + var category frontend.NotificationCategory + if err := json.Unmarshal(payload.Args[0], &category); err != nil { + return nil, err + } + err := sender.RegisterNotificationCategory(category) + return nil, err + case "RemoveNotificationCategory": + if len(payload.Args) < 1 { + return nil, errors.New("empty argument, cannot remove category") + } + var categoryId string + if err := json.Unmarshal(payload.Args[0], &categoryId); err != nil { + return nil, err + } + err := sender.RemoveNotificationCategory(categoryId) + return nil, err + case "RemoveAllPendingNotifications": + err := sender.RemoveAllPendingNotifications() + return nil, err + case "RemovePendingNotification": + if len(payload.Args) < 1 { + return nil, errors.New("empty argument, cannot remove notification") + } + var identifier string + if err := json.Unmarshal(payload.Args[0], &identifier); err != nil { + return nil, err + } + err := sender.RemovePendingNotification(identifier) + return nil, err + case "RemoveAllDeliveredNotifications": + err := sender.RemoveAllDeliveredNotifications() + return nil, err + case "RemoveDeliveredNotification": + if len(payload.Args) < 1 { + return nil, errors.New("empty argument, cannot remove notification") + } + var identifier string + if err := json.Unmarshal(payload.Args[0], &identifier); err != nil { + return nil, err + } + err := sender.RemoveDeliveredNotification(identifier) + return nil, err + case "RemoveNotification": + if len(payload.Args) < 1 { + return nil, errors.New("empty argument, cannot remove notification") + } + var identifier string + if err := json.Unmarshal(payload.Args[0], &identifier); err != nil { + return nil, err + } + err := sender.RemoveNotification(identifier) + return nil, err default: return nil, fmt.Errorf("unknown systemcall message: %s", payload.Name) } diff --git a/v2/internal/frontend/frontend.go b/v2/internal/frontend/frontend.go index 6b2ccbcae..873b61dc7 100644 --- a/v2/internal/frontend/frontend.go +++ b/v2/internal/frontend/frontend.go @@ -76,6 +76,51 @@ type MessageDialogOptions struct { Icon []byte } +// NotificationOptions contains configuration for a notification. +type NotificationOptions struct { + ID string `json:"id"` + Title string `json:"title"` + Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only) + Body string `json:"body,omitempty"` + CategoryID string `json:"categoryId,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` +} + +// NotificationAction represents an action button for a notification. +type NotificationAction struct { + ID string `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Destructive bool `json:"destructive,omitempty"` // (macOS-specific) +} + +// NotificationCategory groups actions for notifications. +type NotificationCategory struct { + ID string `json:"id,omitempty"` + Actions []NotificationAction `json:"actions,omitempty"` + HasReplyField bool `json:"hasReplyField,omitempty"` + ReplyPlaceholder string `json:"replyPlaceholder,omitempty"` + ReplyButtonTitle string `json:"replyButtonTitle,omitempty"` +} + +// NotificationResponse represents the response sent by interacting with a notification. +type NotificationResponse struct { + ID string `json:"id,omitempty"` + ActionIdentifier string `json:"actionIdentifier,omitempty"` + CategoryID string `json:"categoryId,omitempty"` // Consistent with NotificationOptions + Title string `json:"title,omitempty"` + Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only) + Body string `json:"body,omitempty"` + UserText string `json:"userText,omitempty"` + UserInfo map[string]interface{} `json:"userInfo,omitempty"` +} + +// NotificationResult represents the result of a notification response, +// returning the response or any errors that occurred. +type NotificationResult struct { + Response NotificationResponse + Error error +} + type Frontend interface { Run(ctx context.Context) error RunMainLoop() @@ -139,4 +184,21 @@ type Frontend interface { // Clipboard ClipboardGetText() (string, error) ClipboardSetText(text string) error + + // Notifications + InitializeNotifications() error + CleanupNotifications() + IsNotificationAvailable() bool + RequestNotificationAuthorization() (bool, error) + CheckNotificationAuthorization() (bool, error) + OnNotificationResponse(callback func(result NotificationResult)) + SendNotification(options NotificationOptions) error + SendNotificationWithActions(options NotificationOptions) error + RegisterNotificationCategory(category NotificationCategory) error + RemoveNotificationCategory(categoryId string) error + RemoveAllPendingNotifications() error + RemovePendingNotification(identifier string) error + RemoveAllDeliveredNotifications() error + RemoveDeliveredNotification(identifier string) error + RemoveNotification(identifier string) error } diff --git a/v2/internal/frontend/runtime/desktop/main.js b/v2/internal/frontend/runtime/desktop/main.js index 3fda7ef36..405d5f60d 100644 --- a/v2/internal/frontend/runtime/desktop/main.js +++ b/v2/internal/frontend/runtime/desktop/main.js @@ -27,6 +27,7 @@ import * as Browser from "./browser"; import * as Clipboard from "./clipboard"; import * as DragAndDrop from "./draganddrop"; import * as ContextMenu from "./contextmenu"; +import * as Notifications from "./notifications"; export function Quit() { window.WailsInvoke('Q'); @@ -52,6 +53,7 @@ window.runtime = { ...Screen, ...Clipboard, ...DragAndDrop, + ...Notifications, EventsOn, EventsOnce, EventsOnMultiple, diff --git a/v2/internal/frontend/runtime/desktop/notifications.js b/v2/internal/frontend/runtime/desktop/notifications.js new file mode 100644 index 000000000..25c01bb34 --- /dev/null +++ b/v2/internal/frontend/runtime/desktop/notifications.js @@ -0,0 +1,200 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ +/* jshint esversion: 9 */ + +import {Call} from "./calls"; + +/** + * Initialize the notification service for the application. + * This must be called before sending any notifications. + * On macOS, this also ensures the notification delegate is properly initialized. + * + * @export + * @return {Promise} + */ +export function InitializeNotifications() { + return Call(":wails:InitializeNotifications"); +} + +/** + * Clean up notification resources and release any held connections. + * This should be called when shutting down the application to properly release resources + * (primarily needed on Linux to close D-Bus connections). + * + * @export + * @return {Promise} + */ +export function CleanupNotifications() { + return Call(":wails:CleanupNotifications"); +} + +/** + * Check if notifications are available on the current platform. + * + * @export + * @return {Promise} True if notifications are available, false otherwise + */ +export function IsNotificationAvailable() { + return Call(":wails:IsNotificationAvailable"); +} + +/** + * Request notification authorization from the user. + * On macOS, this prompts the user to allow notifications. + * On other platforms, this always returns true. + * + * @export + * @return {Promise} True if authorization was granted, false otherwise + */ +export function RequestNotificationAuthorization() { + return Call(":wails:RequestNotificationAuthorization"); +} + +/** + * Check the current notification authorization status. + * On macOS, this checks if the app has notification permissions. + * On other platforms, this always returns true. + * + * @export + * @return {Promise} True if authorized, false otherwise + */ +export function CheckNotificationAuthorization() { + return Call(":wails:CheckNotificationAuthorization"); +} + +/** + * Send a basic notification with the given options. + * The notification will display with the provided title, subtitle (if supported), and body text. + * + * @export + * @param {Object} options - Notification options + * @param {string} options.id - Unique identifier for the notification + * @param {string} options.title - Notification title + * @param {string} [options.subtitle] - Notification subtitle (macOS and Linux only) + * @param {string} [options.body] - Notification body text + * @param {string} [options.categoryId] - Category ID for action buttons (requires SendNotificationWithActions) + * @param {Object} [options.data] - Additional user data to attach to the notification + * @return {Promise} + */ +export function SendNotification(options) { + return Call(":wails:SendNotification", [options]); +} + +/** + * Send a notification with action buttons. + * A NotificationCategory must be registered first using RegisterNotificationCategory. + * The options.categoryId must match a previously registered category ID. + * If the category is not found, a basic notification will be sent instead. + * + * @export + * @param {Object} options - Notification options + * @param {string} options.id - Unique identifier for the notification + * @param {string} options.title - Notification title + * @param {string} [options.subtitle] - Notification subtitle (macOS and Linux only) + * @param {string} [options.body] - Notification body text + * @param {string} options.categoryId - Category ID that matches a registered category + * @param {Object} [options.data] - Additional user data to attach to the notification + * @return {Promise} + */ +export function SendNotificationWithActions(options) { + return Call(":wails:SendNotificationWithActions", [options]); +} + +/** + * Register a notification category that can be used with SendNotificationWithActions. + * Categories define the action buttons and optional reply fields that will appear on notifications. + * Registering a category with the same ID as a previously registered category will override it. + * + * @export + * @param {Object} category - Notification category definition + * @param {string} category.id - Unique identifier for the category + * @param {Array} [category.actions] - Array of action buttons + * @param {string} category.actions[].id - Unique identifier for the action + * @param {string} category.actions[].title - Display title for the action button + * @param {boolean} [category.actions[].destructive] - Whether the action is destructive (macOS-specific) + * @param {boolean} [category.hasReplyField] - Whether to include a text input field for replies + * @param {string} [category.replyPlaceholder] - Placeholder text for the reply field (required if hasReplyField is true) + * @param {string} [category.replyButtonTitle] - Title for the reply button (required if hasReplyField is true) + * @return {Promise} + */ +export function RegisterNotificationCategory(category) { + return Call(":wails:RegisterNotificationCategory", [category]); +} + +/** + * Remove a previously registered notification category. + * + * @export + * @param {string} categoryId - The ID of the category to remove + * @return {Promise} + */ +export function RemoveNotificationCategory(categoryId) { + return Call(":wails:RemoveNotificationCategory", [categoryId]); +} + +/** + * Remove all pending notifications from the notification center. + * On Windows, this is a no-op as the platform manages notification lifecycle automatically. + * + * @export + * @return {Promise} + */ +export function RemoveAllPendingNotifications() { + return Call(":wails:RemoveAllPendingNotifications"); +} + +/** + * Remove a specific pending notification by its identifier. + * On Windows, this is a no-op as the platform manages notification lifecycle automatically. + * + * @export + * @param {string} identifier - The ID of the notification to remove + * @return {Promise} + */ +export function RemovePendingNotification(identifier) { + return Call(":wails:RemovePendingNotification", [identifier]); +} + +/** + * Remove all delivered notifications from the notification center. + * On Windows, this is a no-op as the platform manages notification lifecycle automatically. + * + * @export + * @return {Promise} + */ +export function RemoveAllDeliveredNotifications() { + return Call(":wails:RemoveAllDeliveredNotifications"); +} + +/** + * Remove a specific delivered notification by its identifier. + * On Windows, this is a no-op as the platform manages notification lifecycle automatically. + * + * @export + * @param {string} identifier - The ID of the notification to remove + * @return {Promise} + */ +export function RemoveDeliveredNotification(identifier) { + return Call(":wails:RemoveDeliveredNotification", [identifier]); +} + +/** + * Remove a notification by its identifier. + * This is a convenience function that works across platforms. + * On macOS, use the more specific RemovePendingNotification or RemoveDeliveredNotification functions. + * + * @export + * @param {string} identifier - The ID of the notification to remove + * @return {Promise} + */ +export function RemoveNotification(identifier) { + return Call(":wails:RemoveNotification", [identifier]); +} + diff --git a/v2/internal/frontend/runtime/ipc_websocket.js b/v2/internal/frontend/runtime/ipc_websocket.js index 1ca048df1..a0d6b4a70 100644 --- a/v2/internal/frontend/runtime/ipc_websocket.js +++ b/v2/internal/frontend/runtime/ipc_websocket.js @@ -1,9 +1,9 @@ (()=>{function D(t){console.log("%c wails dev %c "+t+" ","background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem","background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem")}function _(){}var A=t=>t;function N(t){return t()}function it(){return Object.create(null)}function b(t){t.forEach(N)}function w(t){return typeof t=="function"}function L(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function ot(t){return Object.keys(t).length===0}function rt(t,...e){if(t==null)return _;let n=t.subscribe(...e);return n.unsubscribe?()=>n.unsubscribe():n}function st(t,e,n){t.$$.on_destroy.push(rt(e,n))}var ct=typeof window!="undefined",Ot=ct?()=>window.performance.now():()=>Date.now(),P=ct?t=>requestAnimationFrame(t):_;var x=new Set;function lt(t){x.forEach(e=>{e.c(t)||(x.delete(e),e.f())}),x.size!==0&&P(lt)}function Dt(t){let e;return x.size===0&&P(lt),{promise:new Promise(n=>{x.add(e={c:t,f:n})}),abort(){x.delete(e)}}}var ut=!1;function At(){ut=!0}function Lt(){ut=!1}function Bt(t,e){t.appendChild(e)}function at(t,e,n){let i=R(t);if(!i.getElementById(e)){let o=B("style");o.id=e,o.textContent=n,ft(i,o)}}function R(t){if(!t)return document;let e=t.getRootNode?t.getRootNode():t.ownerDocument;return e&&e.host?e:t.ownerDocument}function Tt(t){let e=B("style");return ft(R(t),e),e.sheet}function ft(t,e){return Bt(t.head||t,e),e.sheet}function W(t,e,n){t.insertBefore(e,n||null)}function S(t){t.parentNode.removeChild(t)}function B(t){return document.createElement(t)}function Jt(t){return document.createTextNode(t)}function dt(){return Jt("")}function ht(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function zt(t){return Array.from(t.childNodes)}function Ht(t,e,{bubbles:n=!1,cancelable:i=!1}={}){let o=document.createEvent("CustomEvent");return o.initCustomEvent(t,n,i,e),o}var T=new Map,J=0;function Gt(t){let e=5381,n=t.length;for(;n--;)e=(e<<5)-e^t.charCodeAt(n);return e>>>0}function qt(t,e){let n={stylesheet:Tt(e),rules:{}};return T.set(t,n),n}function pt(t,e,n,i,o,c,s,l=0){let f=16.666/i,r=`{ `;for(let g=0;g<=1;g+=f){let F=e+(n-e)*c(g);r+=g*100+`%{${s(F,1-F)}} `}let y=r+`100% {${s(n,1-n)}} -}`,a=`__svelte_${Gt(y)}_${l}`,u=R(t),{stylesheet:h,rules:p}=T.get(u)||qt(u,t);p[a]||(p[a]=!0,h.insertRule(`@keyframes ${a} ${y}`,h.cssRules.length));let v=t.style.animation||"";return t.style.animation=`${v?`${v}, `:""}${a} ${i}ms linear ${o}ms 1 both`,J+=1,a}function Kt(t,e){let n=(t.style.animation||"").split(", "),i=n.filter(e?c=>c.indexOf(e)<0:c=>c.indexOf("__svelte")===-1),o=n.length-i.length;o&&(t.style.animation=i.join(", "),J-=o,J||Nt())}function Nt(){P(()=>{J||(T.forEach(t=>{let{ownerNode:e}=t.stylesheet;e&&S(e)}),T.clear())})}var V;function C(t){V=t}var k=[];var _t=[],z=[],mt=[],Pt=Promise.resolve(),U=!1;function Rt(){U||(U=!0,Pt.then(yt))}function $(t){z.push(t)}var X=new Set,H=0;function yt(){let t=V;do{for(;H{E=null})),E}function Z(t,e,n){t.dispatchEvent(Ht(`${e?"intro":"outro"}${n}`))}var G=new Set,m;function gt(){m={r:0,c:[],p:m}}function bt(){m.r||b(m.c),m=m.p}function I(t,e){t&&t.i&&(G.delete(t),t.i(e))}function Q(t,e,n,i){if(t&&t.o){if(G.has(t))return;G.add(t),m.c.push(()=>{G.delete(t),i&&(n&&t.d(1),i())}),t.o(e)}else i&&i()}var Ut={duration:0};function Y(t,e,n,i){let o=e(t,n),c=i?0:1,s=null,l=null,f=null;function r(){f&&Kt(t,f)}function y(u,h){let p=u.b-c;return h*=Math.abs(p),{a:c,b:u.b,d:p,duration:h,start:u.start,end:u.start+h,group:u.group}}function a(u){let{delay:h=0,duration:p=300,easing:v=A,tick:g=_,css:F}=o||Ut,K={start:Ot()+h,b:u};u||(K.group=m,m.r+=1),s||l?l=K:(F&&(r(),f=pt(t,c,u,p,h,v,F)),u&&g(0,1),s=y(K,p),$(()=>Z(t,u,"start")),Dt(O=>{if(l&&O>l.start&&(s=y(l,p),l=null,Z(t,s.b,"start"),F&&(r(),f=pt(t,c,s.b,s.duration,0,v,o.css))),s){if(O>=s.end)g(c=s.b,1-c),Z(t,s.b,"end"),l||(s.b?r():--s.group.r||b(s.group.c)),s=null;else if(O>=s.start){let jt=O-s.start;c=s.a+s.d*v(jt/s.duration),g(c,1-c)}}return!!(s||l)}))}return{run(u){w(o)?Vt().then(()=>{o=o(),a(u)}):a(u)},end(){r(),s=l=null}}}var le=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var ue=new Set(["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);function Xt(t,e,n,i){let{fragment:o,after_update:c}=t.$$;o&&o.m(e,n),i||$(()=>{let s=t.$$.on_mount.map(N).filter(w);t.$$.on_destroy?t.$$.on_destroy.push(...s):b(s),t.$$.on_mount=[]}),c.forEach($)}function wt(t,e){let n=t.$$;n.fragment!==null&&(b(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function Zt(t,e){t.$$.dirty[0]===-1&&(k.push(t),Rt(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let p=h.length?h[0]:u;return r.ctx&&o(r.ctx[a],r.ctx[a]=p)&&(!r.skip_bound&&r.bound[a]&&r.bound[a](p),y&&Zt(t,a)),u}):[],r.update(),y=!0,b(r.before_update),r.fragment=i?i(r.ctx):!1,e.target){if(e.hydrate){At();let a=zt(e.target);r.fragment&&r.fragment.l(a),a.forEach(S)}else r.fragment&&r.fragment.c();e.intro&&I(t.$$.fragment),Xt(t,e.target,e.anchor,e.customElement),Lt(),yt()}C(f)}var Qt;typeof HTMLElement=="function"&&(Qt=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(N).filter(w);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){b(this.$$.on_disconnect)}$destroy(){wt(this,1),this.$destroy=_}$on(t,e){if(!w(e))return _;let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}$set(t){this.$$set&&!ot(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var tt=class{$destroy(){wt(this,1),this.$destroy=_}$on(e,n){if(!w(n))return _;let i=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return i.push(n),()=>{let o=i.indexOf(n);o!==-1&&i.splice(o,1)}}$set(e){this.$$set&&!ot(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};var M=[];function Ft(t,e=_){let n,i=new Set;function o(l){if(L(t,l)&&(t=l,n)){let f=!M.length;for(let r of i)r[1](),M.push(r,t);if(f){for(let r=0;r{i.delete(r),i.size===0&&(n(),n=null)}}return{set:o,update:c,subscribe:s}}var q=Ft(!1);function xt(){q.set(!0)}function $t(){q.set(!1)}function et(t,{delay:e=0,duration:n=400,easing:i=A}={}){let o=+getComputedStyle(t).opacity;return{delay:e,duration:n,easing:i,css:c=>`opacity: ${c*o}`}}function Yt(t){at(t,"svelte-181h7z",`.wails-reconnect-overlay.svelte-181h7z{position:fixed;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(2px) saturate(0%) contrast(50%) brightness(25%);z-index:999999\r - }.wails-reconnect-overlay-content.svelte-181h7z{position:relative;top:50%;transform:translateY(-50%);margin:0;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center\r - }.wails-reconnect-overlay-loadingspinner.svelte-181h7z{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#f00 #eee0 #f00 #eee0;border-radius:50%;animation:svelte-181h7z-loadingspin 1s linear infinite;margin:auto;padding:2.5em\r +}`,a=`__svelte_${Gt(y)}_${l}`,u=R(t),{stylesheet:h,rules:p}=T.get(u)||qt(u,t);p[a]||(p[a]=!0,h.insertRule(`@keyframes ${a} ${y}`,h.cssRules.length));let v=t.style.animation||"";return t.style.animation=`${v?`${v}, `:""}${a} ${i}ms linear ${o}ms 1 both`,J+=1,a}function Kt(t,e){let n=(t.style.animation||"").split(", "),i=n.filter(e?c=>c.indexOf(e)<0:c=>c.indexOf("__svelte")===-1),o=n.length-i.length;o&&(t.style.animation=i.join(", "),J-=o,J||Nt())}function Nt(){P(()=>{J||(T.forEach(t=>{let{ownerNode:e}=t.stylesheet;e&&S(e)}),T.clear())})}var V;function C(t){V=t}var k=[];var _t=[],z=[],mt=[],Pt=Promise.resolve(),U=!1;function Rt(){U||(U=!0,Pt.then(yt))}function $(t){z.push(t)}var X=new Set,H=0;function yt(){let t=V;do{for(;H{E=null})),E}function Z(t,e,n){t.dispatchEvent(Ht(`${e?"intro":"outro"}${n}`))}var G=new Set,m;function gt(){m={r:0,c:[],p:m}}function bt(){m.r||b(m.c),m=m.p}function I(t,e){t&&t.i&&(G.delete(t),t.i(e))}function Q(t,e,n,i){if(t&&t.o){if(G.has(t))return;G.add(t),m.c.push(()=>{G.delete(t),i&&(n&&t.d(1),i())}),t.o(e)}else i&&i()}var Ut={duration:0};function Y(t,e,n,i){let o=e(t,n),c=i?0:1,s=null,l=null,f=null;function r(){f&&Kt(t,f)}function y(u,h){let p=u.b-c;return h*=Math.abs(p),{a:c,b:u.b,d:p,duration:h,start:u.start,end:u.start+h,group:u.group}}function a(u){let{delay:h=0,duration:p=300,easing:v=A,tick:g=_,css:F}=o||Ut,K={start:Ot()+h,b:u};u||(K.group=m,m.r+=1),s||l?l=K:(F&&(r(),f=pt(t,c,u,p,h,v,F)),u&&g(0,1),s=y(K,p),$(()=>Z(t,u,"start")),Dt(O=>{if(l&&O>l.start&&(s=y(l,p),l=null,Z(t,s.b,"start"),F&&(r(),f=pt(t,c,s.b,s.duration,0,v,o.css))),s){if(O>=s.end)g(c=s.b,1-c),Z(t,s.b,"end"),l||(s.b?r():--s.group.r||b(s.group.c)),s=null;else if(O>=s.start){let jt=O-s.start;c=s.a+s.d*v(jt/s.duration),g(c,1-c)}}return!!(s||l)}))}return{run(u){w(o)?Vt().then(()=>{o=o(),a(u)}):a(u)},end(){r(),s=l=null}}}var le=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var ue=new Set(["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);function Xt(t,e,n,i){let{fragment:o,after_update:c}=t.$$;o&&o.m(e,n),i||$(()=>{let s=t.$$.on_mount.map(N).filter(w);t.$$.on_destroy?t.$$.on_destroy.push(...s):b(s),t.$$.on_mount=[]}),c.forEach($)}function wt(t,e){let n=t.$$;n.fragment!==null&&(b(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function Zt(t,e){t.$$.dirty[0]===-1&&(k.push(t),Rt(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let p=h.length?h[0]:u;return r.ctx&&o(r.ctx[a],r.ctx[a]=p)&&(!r.skip_bound&&r.bound[a]&&r.bound[a](p),y&&Zt(t,a)),u}):[],r.update(),y=!0,b(r.before_update),r.fragment=i?i(r.ctx):!1,e.target){if(e.hydrate){At();let a=zt(e.target);r.fragment&&r.fragment.l(a),a.forEach(S)}else r.fragment&&r.fragment.c();e.intro&&I(t.$$.fragment),Xt(t,e.target,e.anchor,e.customElement),Lt(),yt()}C(f)}var Qt;typeof HTMLElement=="function"&&(Qt=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(N).filter(w);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){b(this.$$.on_disconnect)}$destroy(){wt(this,1),this.$destroy=_}$on(t,e){if(!w(e))return _;let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}$set(t){this.$$set&&!ot(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var tt=class{$destroy(){wt(this,1),this.$destroy=_}$on(e,n){if(!w(n))return _;let i=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return i.push(n),()=>{let o=i.indexOf(n);o!==-1&&i.splice(o,1)}}$set(e){this.$$set&&!ot(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};var M=[];function Ft(t,e=_){let n,i=new Set;function o(l){if(L(t,l)&&(t=l,n)){let f=!M.length;for(let r of i)r[1](),M.push(r,t);if(f){for(let r=0;r{i.delete(r),i.size===0&&(n(),n=null)}}return{set:o,update:c,subscribe:s}}var q=Ft(!1);function xt(){q.set(!0)}function $t(){q.set(!1)}function et(t,{delay:e=0,duration:n=400,easing:i=A}={}){let o=+getComputedStyle(t).opacity;return{delay:e,duration:n,easing:i,css:c=>`opacity: ${c*o}`}}function Yt(t){at(t,"svelte-181h7z",`.wails-reconnect-overlay.svelte-181h7z{position:fixed;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(2px) saturate(0%) contrast(50%) brightness(25%);z-index:999999 + }.wails-reconnect-overlay-content.svelte-181h7z{position:relative;top:50%;transform:translateY(-50%);margin:0;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center + }.wails-reconnect-overlay-loadingspinner.svelte-181h7z{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#f00 #eee0 #f00 #eee0;border-radius:50%;animation:svelte-181h7z-loadingspin 1s linear infinite;margin:auto;padding:2.5em }@keyframes svelte-181h7z-loadingspin{100%{transform:rotate(360deg)}}`)}function Mt(t){let e,n,i;return{c(){e=B("div"),e.innerHTML='
',ht(e,"class","wails-reconnect-overlay svelte-181h7z")},m(o,c){W(o,e,c),i=!0},i(o){i||($(()=>{n||(n=Y(e,et,{duration:300},!0)),n.run(1)}),i=!0)},o(o){n||(n=Y(e,et,{duration:300},!1)),n.run(0),i=!1},d(o){o&&S(e),o&&n&&n.end()}}}function te(t){let e,n,i=t[0]&&Mt(t);return{c(){i&&i.c(),e=dt()},m(o,c){i&&i.m(o,c),W(o,e,c),n=!0},p(o,[c]){o[0]?i?c&1&&I(i,1):(i=Mt(o),i.c(),I(i,1),i.m(e.parentNode,e)):i&&(gt(),Q(i,1,1,()=>{i=null}),bt())},i(o){n||(I(i),n=!0)},o(o){Q(i),n=!1},d(o){i&&i.d(o),o&&S(e)}}}function ee(t,e,n){let i;return st(t,q,o=>n(0,i=o)),[i]}var St=class extends tt{constructor(e){super();vt(this,e,ee,te,L,{},Yt)}},Ct=St;var ne={},nt=null,j=[];window.WailsInvoke=t=>{if(!nt){console.log("Queueing: "+t),j.push(t);return}nt(t)};window.addEventListener("DOMContentLoaded",()=>{ne.overlay=new Ct({target:document.body,anchor:document.querySelector("#wails-spinner")})});var d=null,kt;window.onbeforeunload=function(){d&&(d.onclose=function(){},d.close(),d=null)};It();function ie(){nt=t=>{d.send(t)};for(let t=0;t CheckNotificationAuthorization, + CleanupNotifications: () => CleanupNotifications, + InitializeNotifications: () => InitializeNotifications, + IsNotificationAvailable: () => IsNotificationAvailable, + RegisterNotificationCategory: () => RegisterNotificationCategory, + RemoveAllDeliveredNotifications: () => RemoveAllDeliveredNotifications, + RemoveAllPendingNotifications: () => RemoveAllPendingNotifications, + RemoveDeliveredNotification: () => RemoveDeliveredNotification, + RemoveNotification: () => RemoveNotification, + RemoveNotificationCategory: () => RemoveNotificationCategory, + RemovePendingNotification: () => RemovePendingNotification, + RequestNotificationAuthorization: () => RequestNotificationAuthorization, + SendNotification: () => SendNotification, + SendNotificationWithActions: () => SendNotificationWithActions + }); + function InitializeNotifications() { + return Call(":wails:InitializeNotifications"); + } + function CleanupNotifications() { + return Call(":wails:CleanupNotifications"); + } + function IsNotificationAvailable() { + return Call(":wails:IsNotificationAvailable"); + } + function RequestNotificationAuthorization() { + return Call(":wails:RequestNotificationAuthorization"); + } + function CheckNotificationAuthorization() { + return Call(":wails:CheckNotificationAuthorization"); + } + function SendNotification(options) { + return Call(":wails:SendNotification", [options]); + } + function SendNotificationWithActions(options) { + return Call(":wails:SendNotificationWithActions", [options]); + } + function RegisterNotificationCategory(category) { + return Call(":wails:RegisterNotificationCategory", [category]); + } + function RemoveNotificationCategory(categoryId) { + return Call(":wails:RemoveNotificationCategory", [categoryId]); + } + function RemoveAllPendingNotifications() { + return Call(":wails:RemoveAllPendingNotifications"); + } + function RemovePendingNotification(identifier) { + return Call(":wails:RemovePendingNotification", [identifier]); + } + function RemoveAllDeliveredNotifications() { + return Call(":wails:RemoveAllDeliveredNotifications"); + } + function RemoveDeliveredNotification(identifier) { + return Call(":wails:RemoveDeliveredNotification", [identifier]); + } + function RemoveNotification(identifier) { + return Call(":wails:RemoveNotification", [identifier]); + } + // desktop/main.js function Quit() { window.WailsInvoke("Q"); @@ -644,6 +705,7 @@ ...screen_exports, ...clipboard_exports, ...draganddrop_exports, + ...notifications_exports, EventsOn, EventsOnce, EventsOnMultiple, @@ -789,4 +851,4 @@ }); window.WailsInvoke("runtime:ready"); })(); -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9sb2cuanMiLCAiZGVza3RvcC9ldmVudHMuanMiLCAiZGVza3RvcC9jYWxscy5qcyIsICJkZXNrdG9wL2JpbmRpbmdzLmpzIiwgImRlc2t0b3Avd2luZG93LmpzIiwgImRlc2t0b3Avc2NyZWVuLmpzIiwgImRlc2t0b3AvYnJvd3Nlci5qcyIsICJkZXNrdG9wL2NsaXBib2FyZC5qcyIsICJkZXNrdG9wL2RyYWdhbmRkcm9wLmpzIiwgImRlc2t0b3AvY29udGV4dG1lbnUuanMiLCAiZGVza3RvcC9tYWluLmpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKlxyXG4gXyAgICAgICBfXyAgICAgIF8gX19cclxufCB8ICAgICAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA2ICovXHJcblxyXG4vKipcclxuICogU2VuZHMgYSBsb2cgbWVzc2FnZSB0byB0aGUgYmFja2VuZCB3aXRoIHRoZSBnaXZlbiBsZXZlbCArIG1lc3NhZ2VcclxuICpcclxuICogQHBhcmFtIHtzdHJpbmd9IGxldmVsXHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXHJcbiAqL1xyXG5mdW5jdGlvbiBzZW5kTG9nTWVzc2FnZShsZXZlbCwgbWVzc2FnZSkge1xyXG5cclxuXHQvLyBMb2cgTWVzc2FnZSBmb3JtYXQ6XHJcblx0Ly8gbFt0eXBlXVttZXNzYWdlXVxyXG5cdHdpbmRvdy5XYWlsc0ludm9rZSgnTCcgKyBsZXZlbCArIG1lc3NhZ2UpO1xyXG59XHJcblxyXG4vKipcclxuICogTG9nIHRoZSBnaXZlbiB0cmFjZSBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIExvZ1RyYWNlKG1lc3NhZ2UpIHtcclxuXHRzZW5kTG9nTWVzc2FnZSgnVCcsIG1lc3NhZ2UpO1xyXG59XHJcblxyXG4vKipcclxuICogTG9nIHRoZSBnaXZlbiBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIExvZ1ByaW50KG1lc3NhZ2UpIHtcclxuXHRzZW5kTG9nTWVzc2FnZSgnUCcsIG1lc3NhZ2UpO1xyXG59XHJcblxyXG4vKipcclxuICogTG9nIHRoZSBnaXZlbiBkZWJ1ZyBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIExvZ0RlYnVnKG1lc3NhZ2UpIHtcclxuXHRzZW5kTG9nTWVzc2FnZSgnRCcsIG1lc3NhZ2UpO1xyXG59XHJcblxyXG4vKipcclxuICogTG9nIHRoZSBnaXZlbiBpbmZvIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gTG9nSW5mbyhtZXNzYWdlKSB7XHJcblx0c2VuZExvZ01lc3NhZ2UoJ0knLCBtZXNzYWdlKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIExvZyB0aGUgZ2l2ZW4gd2FybmluZyBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIExvZ1dhcm5pbmcobWVzc2FnZSkge1xyXG5cdHNlbmRMb2dNZXNzYWdlKCdXJywgbWVzc2FnZSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBMb2cgdGhlIGdpdmVuIGVycm9yIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gTG9nRXJyb3IobWVzc2FnZSkge1xyXG5cdHNlbmRMb2dNZXNzYWdlKCdFJywgbWVzc2FnZSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBMb2cgdGhlIGdpdmVuIGZhdGFsIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gTG9nRmF0YWwobWVzc2FnZSkge1xyXG5cdHNlbmRMb2dNZXNzYWdlKCdGJywgbWVzc2FnZSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTZXRzIHRoZSBMb2cgbGV2ZWwgdG8gdGhlIGdpdmVuIGxvZyBsZXZlbFxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7bnVtYmVyfSBsb2dsZXZlbFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFNldExvZ0xldmVsKGxvZ2xldmVsKSB7XHJcblx0c2VuZExvZ01lc3NhZ2UoJ1MnLCBsb2dsZXZlbCk7XHJcbn1cclxuXHJcbi8vIExvZyBsZXZlbHNcclxuZXhwb3J0IGNvbnN0IExvZ0xldmVsID0ge1xyXG5cdFRSQUNFOiAxLFxyXG5cdERFQlVHOiAyLFxyXG5cdElORk86IDMsXHJcblx0V0FSTklORzogNCxcclxuXHRFUlJPUjogNSxcclxufTtcclxuIiwgIi8qXHJcbiBfICAgICAgIF9fICAgICAgXyBfX1xyXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuLyoganNoaW50IGVzdmVyc2lvbjogNiAqL1xyXG5cclxuLy8gRGVmaW5lcyBhIHNpbmdsZSBsaXN0ZW5lciB3aXRoIGEgbWF4aW11bSBudW1iZXIgb2YgdGltZXMgdG8gY2FsbGJhY2tcclxuXHJcbi8qKlxyXG4gKiBUaGUgTGlzdGVuZXIgY2xhc3MgZGVmaW5lcyBhIGxpc3RlbmVyISA6LSlcclxuICpcclxuICogQGNsYXNzIExpc3RlbmVyXHJcbiAqL1xyXG5jbGFzcyBMaXN0ZW5lciB7XHJcbiAgICAvKipcclxuICAgICAqIENyZWF0ZXMgYW4gaW5zdGFuY2Ugb2YgTGlzdGVuZXIuXHJcbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXHJcbiAgICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xyXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xyXG4gICAgICogQG1lbWJlcm9mIExpc3RlbmVyXHJcbiAgICAgKi9cclxuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcykge1xyXG4gICAgICAgIHRoaXMuZXZlbnROYW1lID0gZXZlbnROYW1lO1xyXG4gICAgICAgIC8vIERlZmF1bHQgb2YgLTEgbWVhbnMgaW5maW5pdGVcclxuICAgICAgICB0aGlzLm1heENhbGxiYWNrcyA9IG1heENhbGxiYWNrcyB8fCAtMTtcclxuICAgICAgICAvLyBDYWxsYmFjayBpbnZva2VzIHRoZSBjYWxsYmFjayB3aXRoIHRoZSBnaXZlbiBkYXRhXHJcbiAgICAgICAgLy8gUmV0dXJucyB0cnVlIGlmIHRoaXMgbGlzdGVuZXIgc2hvdWxkIGJlIGRlc3Ryb3llZFxyXG4gICAgICAgIHRoaXMuQ2FsbGJhY2sgPSAoZGF0YSkgPT4ge1xyXG4gICAgICAgICAgICBjYWxsYmFjay5hcHBseShudWxsLCBkYXRhKTtcclxuICAgICAgICAgICAgLy8gSWYgbWF4Q2FsbGJhY2tzIGlzIGluZmluaXRlLCByZXR1cm4gZmFsc2UgKGRvIG5vdCBkZXN0cm95KVxyXG4gICAgICAgICAgICBpZiAodGhpcy5tYXhDYWxsYmFja3MgPT09IC0xKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgLy8gRGVjcmVtZW50IG1heENhbGxiYWNrcy4gUmV0dXJuIHRydWUgaWYgbm93IDAsIG90aGVyd2lzZSBmYWxzZVxyXG4gICAgICAgICAgICB0aGlzLm1heENhbGxiYWNrcyAtPSAxO1xyXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5tYXhDYWxsYmFja3MgPT09IDA7XHJcbiAgICAgICAgfTtcclxuICAgIH1cclxufVxyXG5cclxuZXhwb3J0IGNvbnN0IGV2ZW50TGlzdGVuZXJzID0ge307XHJcblxyXG4vKipcclxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGBtYXhDYWxsYmFja3NgIHRpbWVzIGJlZm9yZSBiZWluZyBkZXN0cm95ZWRcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXHJcbiAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXHJcbiAqIEBwYXJhbSB7bnVtYmVyfSBtYXhDYWxsYmFja3NcclxuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcykge1xyXG4gICAgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXSA9IGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gfHwgW107XHJcbiAgICBjb25zdCB0aGlzTGlzdGVuZXIgPSBuZXcgTGlzdGVuZXIoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKTtcclxuICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0ucHVzaCh0aGlzTGlzdGVuZXIpO1xyXG4gICAgcmV0dXJuICgpID0+IGxpc3RlbmVyT2ZmKHRoaXNMaXN0ZW5lcik7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgZXZlcnkgdGltZSB0aGUgZXZlbnQgaXMgZW1pdHRlZFxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcclxuICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2tcclxuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNPbihldmVudE5hbWUsIGNhbGxiYWNrKSB7XHJcbiAgICByZXR1cm4gRXZlbnRzT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCAtMSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgb25jZSB0aGVuIGRlc3Ryb3llZFxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcclxuICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2tcclxuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNPbmNlKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcclxuICAgIHJldHVybiBFdmVudHNPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xyXG59XHJcblxyXG5mdW5jdGlvbiBub3RpZnlMaXN0ZW5lcnMoZXZlbnREYXRhKSB7XHJcblxyXG4gICAgLy8gR2V0IHRoZSBldmVudCBuYW1lXHJcbiAgICBsZXQgZXZlbnROYW1lID0gZXZlbnREYXRhLm5hbWU7XHJcblxyXG4gICAgLy8gS2VlcCBhIGxpc3Qgb2YgbGlzdGVuZXIgaW5kZXhlcyB0byBkZXN0cm95XHJcbiAgICBjb25zdCBuZXdFdmVudExpc3RlbmVyTGlzdCA9IGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0/LnNsaWNlKCkgfHwgW107XHJcblxyXG4gICAgLy8gQ2hlY2sgaWYgd2UgaGF2ZSBhbnkgbGlzdGVuZXJzIGZvciB0aGlzIGV2ZW50XHJcbiAgICBpZiAobmV3RXZlbnRMaXN0ZW5lckxpc3QubGVuZ3RoKSB7XHJcblxyXG4gICAgICAgIC8vIEl0ZXJhdGUgbGlzdGVuZXJzXHJcbiAgICAgICAgZm9yIChsZXQgY291bnQgPSBuZXdFdmVudExpc3RlbmVyTGlzdC5sZW5ndGggLSAxOyBjb3VudCA+PSAwOyBjb3VudCAtPSAxKSB7XHJcblxyXG4gICAgICAgICAgICAvLyBHZXQgbmV4dCBsaXN0ZW5lclxyXG4gICAgICAgICAgICBjb25zdCBsaXN0ZW5lciA9IG5ld0V2ZW50TGlzdGVuZXJMaXN0W2NvdW50XTtcclxuXHJcbiAgICAgICAgICAgIGxldCBkYXRhID0gZXZlbnREYXRhLmRhdGE7XHJcblxyXG4gICAgICAgICAgICAvLyBEbyB0aGUgY2FsbGJhY2tcclxuICAgICAgICAgICAgY29uc3QgZGVzdHJveSA9IGxpc3RlbmVyLkNhbGxiYWNrKGRhdGEpO1xyXG4gICAgICAgICAgICBpZiAoZGVzdHJveSkge1xyXG4gICAgICAgICAgICAgICAgLy8gaWYgdGhlIGxpc3RlbmVyIGluZGljYXRlZCB0byBkZXN0cm95IGl0c2VsZiwgYWRkIGl0IHRvIHRoZSBkZXN0cm95IGxpc3RcclxuICAgICAgICAgICAgICAgIG5ld0V2ZW50TGlzdGVuZXJMaXN0LnNwbGljZShjb3VudCwgMSk7XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIC8vIFVwZGF0ZSBjYWxsYmFja3Mgd2l0aCBuZXcgbGlzdCBvZiBsaXN0ZW5lcnNcclxuICAgICAgICBpZiAobmV3RXZlbnRMaXN0ZW5lckxpc3QubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSk7XHJcbiAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXSA9IG5ld0V2ZW50TGlzdGVuZXJMaXN0O1xyXG4gICAgICAgIH1cclxuICAgIH1cclxufVxyXG5cclxuLyoqXHJcbiAqIE5vdGlmeSBpbmZvcm1zIGZyb250ZW5kIGxpc3RlbmVycyB0aGF0IGFuIGV2ZW50IHdhcyBlbWl0dGVkIHdpdGggdGhlIGdpdmVuIGRhdGFcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gbm90aWZ5TWVzc2FnZSAtIGVuY29kZWQgbm90aWZpY2F0aW9uIG1lc3NhZ2VcclxuXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gRXZlbnRzTm90aWZ5KG5vdGlmeU1lc3NhZ2UpIHtcclxuICAgIC8vIFBhcnNlIHRoZSBtZXNzYWdlXHJcbiAgICBsZXQgbWVzc2FnZTtcclxuICAgIHRyeSB7XHJcbiAgICAgICAgbWVzc2FnZSA9IEpTT04ucGFyc2Uobm90aWZ5TWVzc2FnZSk7XHJcbiAgICB9IGNhdGNoIChlKSB7XHJcbiAgICAgICAgY29uc3QgZXJyb3IgPSAnSW52YWxpZCBKU09OIHBhc3NlZCB0byBOb3RpZnk6ICcgKyBub3RpZnlNZXNzYWdlO1xyXG4gICAgICAgIHRocm93IG5ldyBFcnJvcihlcnJvcik7XHJcbiAgICB9XHJcbiAgICBub3RpZnlMaXN0ZW5lcnMobWVzc2FnZSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBFbWl0IGFuIGV2ZW50IHdpdGggdGhlIGdpdmVuIG5hbWUgYW5kIGRhdGFcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gRXZlbnRzRW1pdChldmVudE5hbWUpIHtcclxuXHJcbiAgICBjb25zdCBwYXlsb2FkID0ge1xyXG4gICAgICAgIG5hbWU6IGV2ZW50TmFtZSxcclxuICAgICAgICBkYXRhOiBbXS5zbGljZS5hcHBseShhcmd1bWVudHMpLnNsaWNlKDEpLFxyXG4gICAgfTtcclxuXHJcbiAgICAvLyBOb3RpZnkgSlMgbGlzdGVuZXJzXHJcbiAgICBub3RpZnlMaXN0ZW5lcnMocGF5bG9hZCk7XHJcblxyXG4gICAgLy8gTm90aWZ5IEdvIGxpc3RlbmVyc1xyXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdFRScgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XHJcbn1cclxuXHJcbmZ1bmN0aW9uIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSkge1xyXG4gICAgLy8gUmVtb3ZlIGxvY2FsIGxpc3RlbmVyc1xyXG4gICAgZGVsZXRlIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV07XHJcblxyXG4gICAgLy8gTm90aWZ5IEdvIGxpc3RlbmVyc1xyXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdFWCcgKyBldmVudE5hbWUpO1xyXG59XHJcblxyXG4vKipcclxuICogT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT24sXHJcbiAqIG9wdGlvbmFsbHkgbXVsdGlwbGUgbGlzdGVuZXJlcyBjYW4gYmUgdW5yZWdpc3RlcmVkIHZpYSBgYWRkaXRpb25hbEV2ZW50TmFtZXNgXHJcbiAqXHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcclxuICogQHBhcmFtICB7Li4uc3RyaW5nfSBhZGRpdGlvbmFsRXZlbnROYW1lc1xyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09mZihldmVudE5hbWUsIC4uLmFkZGl0aW9uYWxFdmVudE5hbWVzKSB7XHJcbiAgICByZW1vdmVMaXN0ZW5lcihldmVudE5hbWUpXHJcblxyXG4gICAgaWYgKGFkZGl0aW9uYWxFdmVudE5hbWVzLmxlbmd0aCA+IDApIHtcclxuICAgICAgICBhZGRpdGlvbmFsRXZlbnROYW1lcy5mb3JFYWNoKGV2ZW50TmFtZSA9PiB7XHJcbiAgICAgICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSlcclxuICAgICAgICB9KVxyXG4gICAgfVxyXG59XHJcblxyXG4vKipcclxuICogT2ZmIHVucmVnaXN0ZXJzIGFsbCBldmVudCBsaXN0ZW5lcnMgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT25cclxuICovXHJcbiBleHBvcnQgZnVuY3Rpb24gRXZlbnRzT2ZmQWxsKCkge1xyXG4gICAgY29uc3QgZXZlbnROYW1lcyA9IE9iamVjdC5rZXlzKGV2ZW50TGlzdGVuZXJzKTtcclxuICAgIGV2ZW50TmFtZXMuZm9yRWFjaChldmVudE5hbWUgPT4ge1xyXG4gICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSlcclxuICAgIH0pXHJcbn1cclxuXHJcbi8qKlxyXG4gKiBsaXN0ZW5lck9mZiB1bnJlZ2lzdGVycyBhIGxpc3RlbmVyIHByZXZpb3VzbHkgcmVnaXN0ZXJlZCB3aXRoIEV2ZW50c09uXHJcbiAqXHJcbiAqIEBwYXJhbSB7TGlzdGVuZXJ9IGxpc3RlbmVyXHJcbiAqL1xyXG4gZnVuY3Rpb24gbGlzdGVuZXJPZmYobGlzdGVuZXIpIHtcclxuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcclxuICAgIGlmIChldmVudExpc3RlbmVyc1tldmVudE5hbWVdID09PSB1bmRlZmluZWQpIHJldHVybjtcclxuXHJcbiAgICAvLyBSZW1vdmUgbG9jYWwgbGlzdGVuZXJcclxuICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gPSBldmVudExpc3RlbmVyc1tldmVudE5hbWVdLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcclxuXHJcbiAgICAvLyBDbGVhbiB1cCBpZiB0aGVyZSBhcmUgbm8gZXZlbnQgbGlzdGVuZXJzIGxlZnRcclxuICAgIGlmIChldmVudExpc3RlbmVyc1tldmVudE5hbWVdLmxlbmd0aCA9PT0gMCkge1xyXG4gICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSk7XHJcbiAgICB9XHJcbn1cclxuIiwgIi8qXHJcbiBfICAgICAgIF9fICAgICAgXyBfX1xyXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuLyoganNoaW50IGVzdmVyc2lvbjogNiAqL1xyXG5cclxuZXhwb3J0IGNvbnN0IGNhbGxiYWNrcyA9IHt9O1xyXG5cclxuLyoqXHJcbiAqIFJldHVybnMgYSBudW1iZXIgZnJvbSB0aGUgbmF0aXZlIGJyb3dzZXIgcmFuZG9tIGZ1bmN0aW9uXHJcbiAqXHJcbiAqIEByZXR1cm5zIG51bWJlclxyXG4gKi9cclxuZnVuY3Rpb24gY3J5cHRvUmFuZG9tKCkge1xyXG5cdHZhciBhcnJheSA9IG5ldyBVaW50MzJBcnJheSgxKTtcclxuXHRyZXR1cm4gd2luZG93LmNyeXB0by5nZXRSYW5kb21WYWx1ZXMoYXJyYXkpWzBdO1xyXG59XHJcblxyXG4vKipcclxuICogUmV0dXJucyBhIG51bWJlciB1c2luZyBkYSBvbGQtc2tvb2wgTWF0aC5SYW5kb21cclxuICogSSBsaWtlcyB0byBjYWxsIGl0IExPTFJhbmRvbVxyXG4gKlxyXG4gKiBAcmV0dXJucyBudW1iZXJcclxuICovXHJcbmZ1bmN0aW9uIGJhc2ljUmFuZG9tKCkge1xyXG5cdHJldHVybiBNYXRoLnJhbmRvbSgpICogOTAwNzE5OTI1NDc0MDk5MTtcclxufVxyXG5cclxuLy8gUGljayBhIHJhbmRvbSBudW1iZXIgZnVuY3Rpb24gYmFzZWQgb24gYnJvd3NlciBjYXBhYmlsaXR5XHJcbnZhciByYW5kb21GdW5jO1xyXG5pZiAod2luZG93LmNyeXB0bykge1xyXG5cdHJhbmRvbUZ1bmMgPSBjcnlwdG9SYW5kb207XHJcbn0gZWxzZSB7XHJcblx0cmFuZG9tRnVuYyA9IGJhc2ljUmFuZG9tO1xyXG59XHJcblxyXG5cclxuLyoqXHJcbiAqIENhbGwgc2VuZHMgYSBtZXNzYWdlIHRvIHRoZSBiYWNrZW5kIHRvIGNhbGwgdGhlIGJpbmRpbmcgd2l0aCB0aGVcclxuICogZ2l2ZW4gZGF0YS4gQSBwcm9taXNlIGlzIHJldHVybmVkIGFuZCB3aWxsIGJlIGNvbXBsZXRlZCB3aGVuIHRoZVxyXG4gKiBiYWNrZW5kIHJlc3BvbmRzLiBUaGlzIHdpbGwgYmUgcmVzb2x2ZWQgd2hlbiB0aGUgY2FsbCB3YXMgc3VjY2Vzc2Z1bFxyXG4gKiBvciByZWplY3RlZCBpZiBhbiBlcnJvciBpcyBwYXNzZWQgYmFjay5cclxuICogVGhlcmUgaXMgYSB0aW1lb3V0IG1lY2hhbmlzbS4gSWYgdGhlIGNhbGwgZG9lc24ndCByZXNwb25kIGluIHRoZSBnaXZlblxyXG4gKiB0aW1lIChpbiBtaWxsaXNlY29uZHMpIHRoZW4gdGhlIHByb21pc2UgaXMgcmVqZWN0ZWQuXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtzdHJpbmd9IG5hbWVcclxuICogQHBhcmFtIHthbnk9fSBhcmdzXHJcbiAqIEBwYXJhbSB7bnVtYmVyPX0gdGltZW91dFxyXG4gKiBAcmV0dXJuc1xyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIENhbGwobmFtZSwgYXJncywgdGltZW91dCkge1xyXG5cclxuXHQvLyBUaW1lb3V0IGluZmluaXRlIGJ5IGRlZmF1bHRcclxuXHRpZiAodGltZW91dCA9PSBudWxsKSB7XHJcblx0XHR0aW1lb3V0ID0gMDtcclxuXHR9XHJcblxyXG5cdC8vIENyZWF0ZSBhIHByb21pc2VcclxuXHRyZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24gKHJlc29sdmUsIHJlamVjdCkge1xyXG5cclxuXHRcdC8vIENyZWF0ZSBhIHVuaXF1ZSBjYWxsYmFja0lEXHJcblx0XHR2YXIgY2FsbGJhY2tJRDtcclxuXHRcdGRvIHtcclxuXHRcdFx0Y2FsbGJhY2tJRCA9IG5hbWUgKyAnLScgKyByYW5kb21GdW5jKCk7XHJcblx0XHR9IHdoaWxlIChjYWxsYmFja3NbY2FsbGJhY2tJRF0pO1xyXG5cclxuXHRcdHZhciB0aW1lb3V0SGFuZGxlO1xyXG5cdFx0Ly8gU2V0IHRpbWVvdXRcclxuXHRcdGlmICh0aW1lb3V0ID4gMCkge1xyXG5cdFx0XHR0aW1lb3V0SGFuZGxlID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XHJcblx0XHRcdFx0cmVqZWN0KEVycm9yKCdDYWxsIHRvICcgKyBuYW1lICsgJyB0aW1lZCBvdXQuIFJlcXVlc3QgSUQ6ICcgKyBjYWxsYmFja0lEKSk7XHJcblx0XHRcdH0sIHRpbWVvdXQpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8vIFN0b3JlIGNhbGxiYWNrXHJcblx0XHRjYWxsYmFja3NbY2FsbGJhY2tJRF0gPSB7XHJcblx0XHRcdHRpbWVvdXRIYW5kbGU6IHRpbWVvdXRIYW5kbGUsXHJcblx0XHRcdHJlamVjdDogcmVqZWN0LFxyXG5cdFx0XHRyZXNvbHZlOiByZXNvbHZlXHJcblx0XHR9O1xyXG5cclxuXHRcdHRyeSB7XHJcblx0XHRcdGNvbnN0IHBheWxvYWQgPSB7XHJcblx0XHRcdFx0bmFtZSxcclxuXHRcdFx0XHRhcmdzLFxyXG5cdFx0XHRcdGNhbGxiYWNrSUQsXHJcblx0XHRcdH07XHJcblxyXG4gICAgICAgICAgICAvLyBNYWtlIHRoZSBjYWxsXHJcbiAgICAgICAgICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnQycgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XHJcbiAgICAgICAgfSBjYXRjaCAoZSkge1xyXG4gICAgICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmVcclxuICAgICAgICAgICAgY29uc29sZS5lcnJvcihlKTtcclxuICAgICAgICB9XHJcbiAgICB9KTtcclxufVxyXG5cclxud2luZG93Lk9iZnVzY2F0ZWRDYWxsID0gKGlkLCBhcmdzLCB0aW1lb3V0KSA9PiB7XHJcblxyXG4gICAgLy8gVGltZW91dCBpbmZpbml0ZSBieSBkZWZhdWx0XHJcbiAgICBpZiAodGltZW91dCA9PSBudWxsKSB7XHJcbiAgICAgICAgdGltZW91dCA9IDA7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQ3JlYXRlIGEgcHJvbWlzZVxyXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uIChyZXNvbHZlLCByZWplY3QpIHtcclxuXHJcbiAgICAgICAgLy8gQ3JlYXRlIGEgdW5pcXVlIGNhbGxiYWNrSURcclxuICAgICAgICB2YXIgY2FsbGJhY2tJRDtcclxuICAgICAgICBkbyB7XHJcbiAgICAgICAgICAgIGNhbGxiYWNrSUQgPSBpZCArICctJyArIHJhbmRvbUZ1bmMoKTtcclxuICAgICAgICB9IHdoaWxlIChjYWxsYmFja3NbY2FsbGJhY2tJRF0pO1xyXG5cclxuICAgICAgICB2YXIgdGltZW91dEhhbmRsZTtcclxuICAgICAgICAvLyBTZXQgdGltZW91dFxyXG4gICAgICAgIGlmICh0aW1lb3V0ID4gMCkge1xyXG4gICAgICAgICAgICB0aW1lb3V0SGFuZGxlID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICByZWplY3QoRXJyb3IoJ0NhbGwgdG8gbWV0aG9kICcgKyBpZCArICcgdGltZWQgb3V0LiBSZXF1ZXN0IElEOiAnICsgY2FsbGJhY2tJRCkpO1xyXG4gICAgICAgICAgICB9LCB0aW1lb3V0KTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIC8vIFN0b3JlIGNhbGxiYWNrXHJcbiAgICAgICAgY2FsbGJhY2tzW2NhbGxiYWNrSURdID0ge1xyXG4gICAgICAgICAgICB0aW1lb3V0SGFuZGxlOiB0aW1lb3V0SGFuZGxlLFxyXG4gICAgICAgICAgICByZWplY3Q6IHJlamVjdCxcclxuICAgICAgICAgICAgcmVzb2x2ZTogcmVzb2x2ZVxyXG4gICAgICAgIH07XHJcblxyXG4gICAgICAgIHRyeSB7XHJcbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSB7XHJcblx0XHRcdFx0aWQsXHJcblx0XHRcdFx0YXJncyxcclxuXHRcdFx0XHRjYWxsYmFja0lELFxyXG5cdFx0XHR9O1xyXG5cclxuICAgICAgICAgICAgLy8gTWFrZSB0aGUgY2FsbFxyXG4gICAgICAgICAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ2MnICsgSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkpO1xyXG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcclxuICAgICAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lXHJcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZSk7XHJcbiAgICAgICAgfVxyXG4gICAgfSk7XHJcbn07XHJcblxyXG5cclxuLyoqXHJcbiAqIENhbGxlZCBieSB0aGUgYmFja2VuZCB0byByZXR1cm4gZGF0YSB0byBhIHByZXZpb3VzbHkgY2FsbGVkXHJcbiAqIGJpbmRpbmcgaW52b2NhdGlvblxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7c3RyaW5nfSBpbmNvbWluZ01lc3NhZ2VcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBDYWxsYmFjayhpbmNvbWluZ01lc3NhZ2UpIHtcclxuXHQvLyBQYXJzZSB0aGUgbWVzc2FnZVxyXG5cdGxldCBtZXNzYWdlO1xyXG5cdHRyeSB7XHJcblx0XHRtZXNzYWdlID0gSlNPTi5wYXJzZShpbmNvbWluZ01lc3NhZ2UpO1xyXG5cdH0gY2F0Y2ggKGUpIHtcclxuXHRcdGNvbnN0IGVycm9yID0gYEludmFsaWQgSlNPTiBwYXNzZWQgdG8gY2FsbGJhY2s6ICR7ZS5tZXNzYWdlfS4gTWVzc2FnZTogJHtpbmNvbWluZ01lc3NhZ2V9YDtcclxuXHRcdHJ1bnRpbWUuTG9nRGVidWcoZXJyb3IpO1xyXG5cdFx0dGhyb3cgbmV3IEVycm9yKGVycm9yKTtcclxuXHR9XHJcblx0bGV0IGNhbGxiYWNrSUQgPSBtZXNzYWdlLmNhbGxiYWNraWQ7XHJcblx0bGV0IGNhbGxiYWNrRGF0YSA9IGNhbGxiYWNrc1tjYWxsYmFja0lEXTtcclxuXHRpZiAoIWNhbGxiYWNrRGF0YSkge1xyXG5cdFx0Y29uc3QgZXJyb3IgPSBgQ2FsbGJhY2sgJyR7Y2FsbGJhY2tJRH0nIG5vdCByZWdpc3RlcmVkISEhYDtcclxuXHRcdGNvbnNvbGUuZXJyb3IoZXJyb3IpOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lXHJcblx0XHR0aHJvdyBuZXcgRXJyb3IoZXJyb3IpO1xyXG5cdH1cclxuXHRjbGVhclRpbWVvdXQoY2FsbGJhY2tEYXRhLnRpbWVvdXRIYW5kbGUpO1xyXG5cclxuXHRkZWxldGUgY2FsbGJhY2tzW2NhbGxiYWNrSURdO1xyXG5cclxuXHRpZiAobWVzc2FnZS5lcnJvcikge1xyXG5cdFx0Y2FsbGJhY2tEYXRhLnJlamVjdChtZXNzYWdlLmVycm9yKTtcclxuXHR9IGVsc2Uge1xyXG5cdFx0Y2FsbGJhY2tEYXRhLnJlc29sdmUobWVzc2FnZS5yZXN1bHQpO1xyXG5cdH1cclxufVxyXG4iLCAiLypcclxuIF8gICAgICAgX18gICAgICBfIF9fICAgIFxyXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApIFxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy8gIFxyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuLyoganNoaW50IGVzdmVyc2lvbjogNiAqL1xyXG5cclxuaW1wb3J0IHtDYWxsfSBmcm9tICcuL2NhbGxzJztcclxuXHJcbi8vIFRoaXMgaXMgd2hlcmUgd2UgYmluZCBnbyBtZXRob2Qgd3JhcHBlcnNcclxud2luZG93LmdvID0ge307XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gU2V0QmluZGluZ3MoYmluZGluZ3NNYXApIHtcclxuXHR0cnkge1xyXG5cdFx0YmluZGluZ3NNYXAgPSBKU09OLnBhcnNlKGJpbmRpbmdzTWFwKTtcclxuXHR9IGNhdGNoIChlKSB7XHJcblx0XHRjb25zb2xlLmVycm9yKGUpO1xyXG5cdH1cclxuXHJcblx0Ly8gSW5pdGlhbGlzZSB0aGUgYmluZGluZ3MgbWFwXHJcblx0d2luZG93LmdvID0gd2luZG93LmdvIHx8IHt9O1xyXG5cclxuXHQvLyBJdGVyYXRlIHBhY2thZ2UgbmFtZXNcclxuXHRPYmplY3Qua2V5cyhiaW5kaW5nc01hcCkuZm9yRWFjaCgocGFja2FnZU5hbWUpID0+IHtcclxuXHJcblx0XHQvLyBDcmVhdGUgaW5uZXIgbWFwIGlmIGl0IGRvZXNuJ3QgZXhpc3RcclxuXHRcdHdpbmRvdy5nb1twYWNrYWdlTmFtZV0gPSB3aW5kb3cuZ29bcGFja2FnZU5hbWVdIHx8IHt9O1xyXG5cclxuXHRcdC8vIEl0ZXJhdGUgc3RydWN0IG5hbWVzXHJcblx0XHRPYmplY3Qua2V5cyhiaW5kaW5nc01hcFtwYWNrYWdlTmFtZV0pLmZvckVhY2goKHN0cnVjdE5hbWUpID0+IHtcclxuXHJcblx0XHRcdC8vIENyZWF0ZSBpbm5lciBtYXAgaWYgaXQgZG9lc24ndCBleGlzdFxyXG5cdFx0XHR3aW5kb3cuZ29bcGFja2FnZU5hbWVdW3N0cnVjdE5hbWVdID0gd2luZG93LmdvW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXSB8fCB7fTtcclxuXHJcblx0XHRcdE9iamVjdC5rZXlzKGJpbmRpbmdzTWFwW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXSkuZm9yRWFjaCgobWV0aG9kTmFtZSkgPT4ge1xyXG5cclxuXHRcdFx0XHR3aW5kb3cuZ29bcGFja2FnZU5hbWVdW3N0cnVjdE5hbWVdW21ldGhvZE5hbWVdID0gZnVuY3Rpb24gKCkge1xyXG5cclxuXHRcdFx0XHRcdC8vIE5vIHRpbWVvdXQgYnkgZGVmYXVsdFxyXG5cdFx0XHRcdFx0bGV0IHRpbWVvdXQgPSAwO1xyXG5cclxuXHRcdFx0XHRcdC8vIEFjdHVhbCBmdW5jdGlvblxyXG5cdFx0XHRcdFx0ZnVuY3Rpb24gZHluYW1pYygpIHtcclxuXHRcdFx0XHRcdFx0Y29uc3QgYXJncyA9IFtdLnNsaWNlLmNhbGwoYXJndW1lbnRzKTtcclxuXHRcdFx0XHRcdFx0cmV0dXJuIENhbGwoW3BhY2thZ2VOYW1lLCBzdHJ1Y3ROYW1lLCBtZXRob2ROYW1lXS5qb2luKCcuJyksIGFyZ3MsIHRpbWVvdXQpO1xyXG5cdFx0XHRcdFx0fVxyXG5cclxuXHRcdFx0XHRcdC8vIEFsbG93IHNldHRpbmcgdGltZW91dCB0byBmdW5jdGlvblxyXG5cdFx0XHRcdFx0ZHluYW1pYy5zZXRUaW1lb3V0ID0gZnVuY3Rpb24gKG5ld1RpbWVvdXQpIHtcclxuXHRcdFx0XHRcdFx0dGltZW91dCA9IG5ld1RpbWVvdXQ7XHJcblx0XHRcdFx0XHR9O1xyXG5cclxuXHRcdFx0XHRcdC8vIEFsbG93IGdldHRpbmcgdGltZW91dCB0byBmdW5jdGlvblxyXG5cdFx0XHRcdFx0ZHluYW1pYy5nZXRUaW1lb3V0ID0gZnVuY3Rpb24gKCkge1xyXG5cdFx0XHRcdFx0XHRyZXR1cm4gdGltZW91dDtcclxuXHRcdFx0XHRcdH07XHJcblxyXG5cdFx0XHRcdFx0cmV0dXJuIGR5bmFtaWM7XHJcblx0XHRcdFx0fSgpO1xyXG5cdFx0XHR9KTtcclxuXHRcdH0pO1xyXG5cdH0pO1xyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5cclxuaW1wb3J0IHtDYWxsfSBmcm9tIFwiLi9jYWxsc1wiO1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1JlbG9hZCgpIHtcclxuICAgIHdpbmRvdy5sb2NhdGlvbi5yZWxvYWQoKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1JlbG9hZEFwcCgpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1InKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFN5c3RlbURlZmF1bHRUaGVtZSgpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0FTRFQnKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldExpZ2h0VGhlbWUoKSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dBTFQnKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldERhcmtUaGVtZSgpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0FEVCcpO1xyXG59XHJcblxyXG4vKipcclxuICogUGxhY2UgdGhlIHdpbmRvdyBpbiB0aGUgY2VudGVyIG9mIHRoZSBzY3JlZW5cclxuICpcclxuICogQGV4cG9ydFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0NlbnRlcigpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV2MnKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFNldHMgdGhlIHdpbmRvdyB0aXRsZVxyXG4gKlxyXG4gKiBAcGFyYW0ge3N0cmluZ30gdGl0bGVcclxuICogQGV4cG9ydFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFRpdGxlKHRpdGxlKSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dUJyArIHRpdGxlKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIE1ha2VzIHRoZSB3aW5kb3cgZ28gZnVsbHNjcmVlblxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93RnVsbHNjcmVlbigpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0YnKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJldmVydHMgdGhlIHdpbmRvdyBmcm9tIGZ1bGxzY3JlZW5cclxuICpcclxuICogQGV4cG9ydFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1VuZnVsbHNjcmVlbigpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV2YnKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJldHVybnMgdGhlIHN0YXRlIG9mIHRoZSB3aW5kb3csIGkuZS4gd2hldGhlciB0aGUgd2luZG93IGlzIGluIGZ1bGwgc2NyZWVuIG1vZGUgb3Igbm90LlxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRoZSBzdGF0ZSBvZiB0aGUgd2luZG93XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SXNGdWxsc2NyZWVuKCkge1xyXG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93SXNGdWxsc2NyZWVuXCIpO1xyXG59XHJcblxyXG4vKipcclxuICogU2V0IHRoZSBTaXplIG9mIHRoZSB3aW5kb3dcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcGFyYW0ge251bWJlcn0gd2lkdGhcclxuICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFNpemUod2lkdGgsIGhlaWdodCkge1xyXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXczonICsgd2lkdGggKyAnOicgKyBoZWlnaHQpO1xyXG59XHJcblxyXG4vKipcclxuICogR2V0IHRoZSBTaXplIG9mIHRoZSB3aW5kb3dcclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAcmV0dXJuIHtQcm9taXNlPHt3OiBudW1iZXIsIGg6IG51bWJlcn0+fSBUaGUgc2l6ZSBvZiB0aGUgd2luZG93XHJcblxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0dldFNpemUoKSB7XHJcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dHZXRTaXplXCIpO1xyXG59XHJcblxyXG4vKipcclxuICogU2V0IHRoZSBtYXhpbXVtIHNpemUgb2YgdGhlIHdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7bnVtYmVyfSB3aWR0aFxyXG4gKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0TWF4U2l6ZSh3aWR0aCwgaGVpZ2h0KSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1daOicgKyB3aWR0aCArICc6JyArIGhlaWdodCk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBTZXQgdGhlIG1pbmltdW0gc2l6ZSBvZiB0aGUgd2luZG93XHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXHJcbiAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRNaW5TaXplKHdpZHRoLCBoZWlnaHQpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3o6JyArIHdpZHRoICsgJzonICsgaGVpZ2h0KTtcclxufVxyXG5cclxuXHJcblxyXG4vKipcclxuICogU2V0IHRoZSB3aW5kb3cgQWx3YXlzT25Ub3Agb3Igbm90IG9uIHRvcFxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0QWx3YXlzT25Ub3AoYikge1xyXG5cclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0FUUDonICsgKGIgPyAnMScgOiAnMCcpKTtcclxufVxyXG5cclxuXHJcblxyXG5cclxuLyoqXHJcbiAqIFNldCB0aGUgUG9zaXRpb24gb2YgdGhlIHdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7bnVtYmVyfSB4XHJcbiAqIEBwYXJhbSB7bnVtYmVyfSB5XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0UG9zaXRpb24oeCwgeSkge1xyXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXcDonICsgeCArICc6JyArIHkpO1xyXG59XHJcblxyXG4vKipcclxuICogR2V0IHRoZSBQb3NpdGlvbiBvZiB0aGUgd2luZG93XHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHJldHVybiB7UHJvbWlzZTx7eDogbnVtYmVyLCB5OiBudW1iZXJ9Pn0gVGhlIHBvc2l0aW9uIG9mIHRoZSB3aW5kb3dcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dHZXRQb3NpdGlvbigpIHtcclxuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOldpbmRvd0dldFBvc1wiKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIEhpZGUgdGhlIFdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SGlkZSgpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0gnKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFNob3cgdGhlIFdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2hvdygpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1MnKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIE1heGltaXNlIHRoZSBXaW5kb3dcclxuICpcclxuICogQGV4cG9ydFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd01heGltaXNlKCkge1xyXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXTScpO1xyXG59XHJcblxyXG4vKipcclxuICogVG9nZ2xlIHRoZSBNYXhpbWlzZSBvZiB0aGUgV2luZG93XHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dUb2dnbGVNYXhpbWlzZSgpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3QnKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFVubWF4aW1pc2UgdGhlIFdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93VW5tYXhpbWlzZSgpIHtcclxuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1UnKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJldHVybnMgdGhlIHN0YXRlIG9mIHRoZSB3aW5kb3csIGkuZS4gd2hldGhlciB0aGUgd2luZG93IGlzIG1heGltaXNlZCBvciBub3QuXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHJldHVybiB7UHJvbWlzZTxib29sZWFuPn0gVGhlIHN0YXRlIG9mIHRoZSB3aW5kb3dcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dJc01heGltaXNlZCgpIHtcclxuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOldpbmRvd0lzTWF4aW1pc2VkXCIpO1xyXG59XHJcblxyXG4vKipcclxuICogTWluaW1pc2UgdGhlIFdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93TWluaW1pc2UoKSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dtJyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBVbm1pbmltaXNlIHRoZSBXaW5kb3dcclxuICpcclxuICogQGV4cG9ydFxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1VubWluaW1pc2UoKSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1d1Jyk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBSZXR1cm5zIHRoZSBzdGF0ZSBvZiB0aGUgd2luZG93LCBpLmUuIHdoZXRoZXIgdGhlIHdpbmRvdyBpcyBtaW5pbWlzZWQgb3Igbm90LlxyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRoZSBzdGF0ZSBvZiB0aGUgd2luZG93XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SXNNaW5pbWlzZWQoKSB7XHJcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dJc01pbmltaXNlZFwiKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJldHVybnMgdGhlIHN0YXRlIG9mIHRoZSB3aW5kb3csIGkuZS4gd2hldGhlciB0aGUgd2luZG93IGlzIG5vcm1hbCBvciBub3QuXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHJldHVybiB7UHJvbWlzZTxib29sZWFuPn0gVGhlIHN0YXRlIG9mIHRoZSB3aW5kb3dcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dJc05vcm1hbCgpIHtcclxuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOldpbmRvd0lzTm9ybWFsXCIpO1xyXG59XHJcblxyXG4vKipcclxuICogU2V0cyB0aGUgYmFja2dyb3VuZCBjb2xvdXIgb2YgdGhlIHdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7bnVtYmVyfSBSIFJlZFxyXG4gKiBAcGFyYW0ge251bWJlcn0gRyBHcmVlblxyXG4gKiBAcGFyYW0ge251bWJlcn0gQiBCbHVlXHJcbiAqIEBwYXJhbSB7bnVtYmVyfSBBIEFscGhhXHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0QmFja2dyb3VuZENvbG91cihSLCBHLCBCLCBBKSB7XHJcbiAgICBsZXQgcmdiYSA9IEpTT04uc3RyaW5naWZ5KHtyOiBSIHx8IDAsIGc6IEcgfHwgMCwgYjogQiB8fCAwLCBhOiBBIHx8IDI1NX0pO1xyXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXcjonICsgcmdiYSk7XHJcbn1cclxuXHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcblxyXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXHJcblxyXG5cclxuaW1wb3J0IHtDYWxsfSBmcm9tIFwiLi9jYWxsc1wiO1xyXG5cclxuXHJcbi8qKlxyXG4gKiBHZXRzIHRoZSBhbGwgc2NyZWVucy4gQ2FsbCB0aGlzIGFuZXcgZWFjaCB0aW1lIHlvdSB3YW50IHRvIHJlZnJlc2ggZGF0YSBmcm9tIHRoZSB1bmRlcmx5aW5nIHdpbmRvd2luZyBzeXN0ZW0uXHJcbiAqIEBleHBvcnRcclxuICogQHR5cGVkZWYge2ltcG9ydCgnLi4vd3JhcHBlci9ydW50aW1lJykuU2NyZWVufSBTY3JlZW5cclxuICogQHJldHVybiB7UHJvbWlzZTx7U2NyZWVuW119Pn0gVGhlIHNjcmVlbnNcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBTY3JlZW5HZXRBbGwoKSB7XHJcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpTY3JlZW5HZXRBbGxcIik7XHJcbn1cclxuIiwgIi8qKlxyXG4gKiBAZGVzY3JpcHRpb246IFVzZSB0aGUgc3lzdGVtIGRlZmF1bHQgYnJvd3NlciB0byBvcGVuIHRoZSB1cmxcclxuICogQHBhcmFtIHtzdHJpbmd9IHVybCBcclxuICogQHJldHVybiB7dm9pZH1cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBCcm93c2VyT3BlblVSTCh1cmwpIHtcclxuICB3aW5kb3cuV2FpbHNJbnZva2UoJ0JPOicgKyB1cmwpO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7Q2FsbH0gZnJvbSBcIi4vY2FsbHNcIjtcclxuXHJcbi8qKlxyXG4gKiBTZXQgdGhlIFNpemUgb2YgdGhlIHdpbmRvd1xyXG4gKlxyXG4gKiBAZXhwb3J0XHJcbiAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0XHJcbiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gQ2xpcGJvYXJkU2V0VGV4dCh0ZXh0KSB7XHJcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpDbGlwYm9hcmRTZXRUZXh0XCIsIFt0ZXh0XSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBHZXQgdGhlIHRleHQgY29udGVudCBvZiB0aGUgY2xpcGJvYXJkXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHJldHVybiB7UHJvbWlzZTx7c3RyaW5nfT59IFRleHQgY29udGVudCBvZiB0aGUgY2xpcGJvYXJkXHJcblxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIENsaXBib2FyZEdldFRleHQoKSB7XHJcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpDbGlwYm9hcmRHZXRUZXh0XCIpO1xyXG59IiwgIi8qXHJcbiBfXHQgICBfX1x0ICBfIF9fXHJcbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXHJcbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cclxufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXHJcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xyXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXHJcbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcclxuKi9cclxuXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuXHJcbmltcG9ydCB7RXZlbnRzT24sIEV2ZW50c09mZn0gZnJvbSBcIi4vZXZlbnRzXCI7XHJcblxyXG5jb25zdCBmbGFncyA9IHtcclxuICAgIHJlZ2lzdGVyZWQ6IGZhbHNlLFxyXG4gICAgZGVmYXVsdFVzZURyb3BUYXJnZXQ6IHRydWUsXHJcbiAgICB1c2VEcm9wVGFyZ2V0OiB0cnVlLFxyXG4gICAgbmV4dERlYWN0aXZhdGU6IG51bGwsXHJcbiAgICBuZXh0RGVhY3RpdmF0ZVRpbWVvdXQ6IG51bGwsXHJcbn07XHJcblxyXG5jb25zdCBEUk9QX1RBUkdFVF9BQ1RJVkUgPSBcIndhaWxzLWRyb3AtdGFyZ2V0LWFjdGl2ZVwiO1xyXG5cclxuLyoqXHJcbiAqIGNoZWNrU3R5bGVEcm9wVGFyZ2V0IGNoZWNrcyBpZiB0aGUgc3R5bGUgaGFzIHRoZSBkcm9wIHRhcmdldCBhdHRyaWJ1dGVcclxuICogXHJcbiAqIEBwYXJhbSB7Q1NTU3R5bGVEZWNsYXJhdGlvbn0gc3R5bGUgXHJcbiAqIEByZXR1cm5zIFxyXG4gKi9cclxuZnVuY3Rpb24gY2hlY2tTdHlsZURyb3BUYXJnZXQoc3R5bGUpIHtcclxuICAgIGNvbnN0IGNzc0Ryb3BWYWx1ZSA9IHN0eWxlLmdldFByb3BlcnR5VmFsdWUod2luZG93LndhaWxzLmZsYWdzLmNzc0Ryb3BQcm9wZXJ0eSkudHJpbSgpO1xyXG4gICAgaWYgKGNzc0Ryb3BWYWx1ZSkge1xyXG4gICAgICAgIGlmIChjc3NEcm9wVmFsdWUgPT09IHdpbmRvdy53YWlscy5mbGFncy5jc3NEcm9wVmFsdWUpIHtcclxuICAgICAgICAgICAgcmV0dXJuIHRydWU7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIC8vIGlmIHRoZSBlbGVtZW50IGhhcyB0aGUgZHJvcCB0YXJnZXQgYXR0cmlidXRlLCBidXQgXHJcbiAgICAgICAgLy8gdGhlIHZhbHVlIGlzIG5vdCBjb3JyZWN0LCB0ZXJtaW5hdGUgZmluZGluZyBwcm9jZXNzLlxyXG4gICAgICAgIC8vIFRoaXMgY2FuIGJlIHVzZWZ1bCB0byBibG9jayBzb21lIGNoaWxkIGVsZW1lbnRzIGZyb20gYmVpbmcgZHJvcCB0YXJnZXRzLlxyXG4gICAgICAgIHJldHVybiBmYWxzZTtcclxuICAgIH1cclxuICAgIHJldHVybiBmYWxzZTtcclxufVxyXG5cclxuLyoqXHJcbiAqIG9uRHJhZ092ZXIgaXMgY2FsbGVkIHdoZW4gdGhlIGRyYWdvdmVyIGV2ZW50IGlzIGVtaXR0ZWQuXHJcbiAqIEBwYXJhbSB7RHJhZ0V2ZW50fSBlXHJcbiAqIEByZXR1cm5zXHJcbiAqL1xyXG5mdW5jdGlvbiBvbkRyYWdPdmVyKGUpIHtcclxuICAgIC8vIENoZWNrIGlmIHRoaXMgaXMgYW4gZXh0ZXJuYWwgZmlsZSBkcm9wIG9yIGludGVybmFsIEhUTUwgZHJhZ1xyXG4gICAgLy8gRXh0ZXJuYWwgZmlsZSBkcm9wcyB3aWxsIGhhdmUgXCJGaWxlc1wiIGluIHRoZSB0eXBlcyBhcnJheVxyXG4gICAgLy8gSW50ZXJuYWwgSFRNTCBkcmFncyB0eXBpY2FsbHkgaGF2ZSBcInRleHQvcGxhaW5cIiwgXCJ0ZXh0L2h0bWxcIiBvciBjdXN0b20gdHlwZXNcclxuICAgIGNvbnN0IGlzRmlsZURyb3AgPSBlLmRhdGFUcmFuc2Zlci50eXBlcy5pbmNsdWRlcyhcIkZpbGVzXCIpO1xyXG5cclxuICAgIC8vIE9ubHkgaGFuZGxlIGV4dGVybmFsIGZpbGUgZHJvcHMsIGxldCBpbnRlcm5hbCBIVE1MNSBkcmFnLWFuZC1kcm9wIHdvcmsgbm9ybWFsbHlcclxuICAgIGlmICghaXNGaWxlRHJvcCkge1xyXG4gICAgICAgIHJldHVybjtcclxuICAgIH1cclxuXHJcbiAgICAvLyBBTFdBWVMgcHJldmVudCBkZWZhdWx0IGZvciBmaWxlIGRyb3BzIHRvIHN0b3AgYnJvd3NlciBuYXZpZ2F0aW9uXHJcbiAgICBlLnByZXZlbnREZWZhdWx0KCk7XHJcbiAgICBlLmRhdGFUcmFuc2Zlci5kcm9wRWZmZWN0ID0gJ2NvcHknO1xyXG5cclxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVdhaWxzRHJhZ0FuZERyb3ApIHtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKCFmbGFncy51c2VEcm9wVGFyZ2V0KSB7XHJcbiAgICAgICAgcmV0dXJuO1xyXG4gICAgfVxyXG5cclxuICAgIGNvbnN0IGVsZW1lbnQgPSBlLnRhcmdldDtcclxuXHJcbiAgICAvLyBUcmlnZ2VyIGRlYm91bmNlIGZ1bmN0aW9uIHRvIGRlYWN0aXZhdGUgZHJvcCB0YXJnZXRzXHJcbiAgICBpZihmbGFncy5uZXh0RGVhY3RpdmF0ZSkgZmxhZ3MubmV4dERlYWN0aXZhdGUoKTtcclxuXHJcbiAgICAvLyBpZiB0aGUgZWxlbWVudCBpcyBudWxsIG9yIGVsZW1lbnQgaXMgbm90IGNoaWxkIG9mIGRyb3AgdGFyZ2V0IGVsZW1lbnRcclxuICAgIGlmICghZWxlbWVudCB8fCAhY2hlY2tTdHlsZURyb3BUYXJnZXQoZ2V0Q29tcHV0ZWRTdHlsZShlbGVtZW50KSkpIHtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgbGV0IGN1cnJlbnRFbGVtZW50ID0gZWxlbWVudDtcclxuICAgIHdoaWxlIChjdXJyZW50RWxlbWVudCkge1xyXG4gICAgICAgIC8vIGNoZWNrIGlmIGN1cnJlbnRFbGVtZW50IGlzIGRyb3AgdGFyZ2V0IGVsZW1lbnRcclxuICAgICAgICBpZiAoY2hlY2tTdHlsZURyb3BUYXJnZXQoZ2V0Q29tcHV0ZWRTdHlsZShjdXJyZW50RWxlbWVudCkpKSB7XHJcbiAgICAgICAgICAgIGN1cnJlbnRFbGVtZW50LmNsYXNzTGlzdC5hZGQoRFJPUF9UQVJHRVRfQUNUSVZFKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgY3VycmVudEVsZW1lbnQgPSBjdXJyZW50RWxlbWVudC5wYXJlbnRFbGVtZW50O1xyXG4gICAgfVxyXG59XHJcblxyXG4vKipcclxuICogb25EcmFnTGVhdmUgaXMgY2FsbGVkIHdoZW4gdGhlIGRyYWdsZWF2ZSBldmVudCBpcyBlbWl0dGVkLlxyXG4gKiBAcGFyYW0ge0RyYWdFdmVudH0gZVxyXG4gKiBAcmV0dXJuc1xyXG4gKi9cclxuZnVuY3Rpb24gb25EcmFnTGVhdmUoZSkge1xyXG4gICAgLy8gQ2hlY2sgaWYgdGhpcyBpcyBhbiBleHRlcm5hbCBmaWxlIGRyb3Agb3IgaW50ZXJuYWwgSFRNTCBkcmFnXHJcbiAgICBjb25zdCBpc0ZpbGVEcm9wID0gZS5kYXRhVHJhbnNmZXIudHlwZXMuaW5jbHVkZXMoXCJGaWxlc1wiKTtcclxuXHJcbiAgICAvLyBPbmx5IGhhbmRsZSBleHRlcm5hbCBmaWxlIGRyb3BzLCBsZXQgaW50ZXJuYWwgSFRNTDUgZHJhZy1hbmQtZHJvcCB3b3JrIG5vcm1hbGx5XHJcbiAgICBpZiAoIWlzRmlsZURyb3ApIHtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQUxXQVlTIHByZXZlbnQgZGVmYXVsdCBmb3IgZmlsZSBkcm9wcyB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxyXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xyXG5cclxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVdhaWxzRHJhZ0FuZERyb3ApIHtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKCFmbGFncy51c2VEcm9wVGFyZ2V0KSB7XHJcbiAgICAgICAgcmV0dXJuO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIEZpbmQgdGhlIGNsb3NlIGRyb3AgdGFyZ2V0IGVsZW1lbnRcclxuICAgIGlmICghZS50YXJnZXQgfHwgIWNoZWNrU3R5bGVEcm9wVGFyZ2V0KGdldENvbXB1dGVkU3R5bGUoZS50YXJnZXQpKSkge1xyXG4gICAgICAgIHJldHVybiBudWxsO1xyXG4gICAgfVxyXG5cclxuICAgIC8vIFRyaWdnZXIgZGVib3VuY2UgZnVuY3Rpb24gdG8gZGVhY3RpdmF0ZSBkcm9wIHRhcmdldHNcclxuICAgIGlmKGZsYWdzLm5leHREZWFjdGl2YXRlKSBmbGFncy5uZXh0RGVhY3RpdmF0ZSgpO1xyXG4gICAgXHJcbiAgICAvLyBVc2UgZGVib3VuY2UgdGVjaG5pcXVlIHRvIHRhY2xlIGRyYWdsZWF2ZSBldmVudHMgb24gb3ZlcmxhcHBpbmcgZWxlbWVudHMgYW5kIGRyb3AgdGFyZ2V0IGVsZW1lbnRzXHJcbiAgICBmbGFncy5uZXh0RGVhY3RpdmF0ZSA9ICgpID0+IHtcclxuICAgICAgICAvLyBEZWFjdGl2YXRlIGFsbCBkcm9wIHRhcmdldHMsIG5ldyBkcm9wIHRhcmdldCB3aWxsIGJlIGFjdGl2YXRlZCBvbiBuZXh0IGRyYWdvdmVyIGV2ZW50XHJcbiAgICAgICAgQXJyYXkuZnJvbShkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKERST1BfVEFSR0VUX0FDVElWRSkpLmZvckVhY2goZWwgPT4gZWwuY2xhc3NMaXN0LnJlbW92ZShEUk9QX1RBUkdFVF9BQ1RJVkUpKTtcclxuICAgICAgICAvLyBSZXNldCBuZXh0RGVhY3RpdmF0ZVxyXG4gICAgICAgIGZsYWdzLm5leHREZWFjdGl2YXRlID0gbnVsbDtcclxuICAgICAgICAvLyBDbGVhciB0aW1lb3V0XHJcbiAgICAgICAgaWYgKGZsYWdzLm5leHREZWFjdGl2YXRlVGltZW91dCkge1xyXG4gICAgICAgICAgICBjbGVhclRpbWVvdXQoZmxhZ3MubmV4dERlYWN0aXZhdGVUaW1lb3V0KTtcclxuICAgICAgICAgICAgZmxhZ3MubmV4dERlYWN0aXZhdGVUaW1lb3V0ID0gbnVsbDtcclxuICAgICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgLy8gU2V0IHRpbWVvdXQgdG8gZGVhY3RpdmF0ZSBkcm9wIHRhcmdldHMgaWYgbm90IHRyaWdnZXJlZCBieSBuZXh0IGRyYWcgZXZlbnRcclxuICAgIGZsYWdzLm5leHREZWFjdGl2YXRlVGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xyXG4gICAgICAgIGlmKGZsYWdzLm5leHREZWFjdGl2YXRlKSBmbGFncy5uZXh0RGVhY3RpdmF0ZSgpO1xyXG4gICAgfSwgNTApO1xyXG59XHJcblxyXG4vKipcclxuICogb25Ecm9wIGlzIGNhbGxlZCB3aGVuIHRoZSBkcm9wIGV2ZW50IGlzIGVtaXR0ZWQuXHJcbiAqIEBwYXJhbSB7RHJhZ0V2ZW50fSBlXHJcbiAqIEByZXR1cm5zXHJcbiAqL1xyXG5mdW5jdGlvbiBvbkRyb3AoZSkge1xyXG4gICAgLy8gQ2hlY2sgaWYgdGhpcyBpcyBhbiBleHRlcm5hbCBmaWxlIGRyb3Agb3IgaW50ZXJuYWwgSFRNTCBkcmFnXHJcbiAgICBjb25zdCBpc0ZpbGVEcm9wID0gZS5kYXRhVHJhbnNmZXIudHlwZXMuaW5jbHVkZXMoXCJGaWxlc1wiKTtcclxuXHJcbiAgICAvLyBPbmx5IGhhbmRsZSBleHRlcm5hbCBmaWxlIGRyb3BzLCBsZXQgaW50ZXJuYWwgSFRNTDUgZHJhZy1hbmQtZHJvcCB3b3JrIG5vcm1hbGx5XHJcbiAgICBpZiAoIWlzRmlsZURyb3ApIHtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgLy8gQUxXQVlTIHByZXZlbnQgZGVmYXVsdCBmb3IgZmlsZSBkcm9wcyB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxyXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xyXG5cclxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVdhaWxzRHJhZ0FuZERyb3ApIHtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKENhblJlc29sdmVGaWxlUGF0aHMoKSkge1xyXG4gICAgICAgIC8vIHByb2Nlc3MgZmlsZXNcclxuICAgICAgICBsZXQgZmlsZXMgPSBbXTtcclxuICAgICAgICBpZiAoZS5kYXRhVHJhbnNmZXIuaXRlbXMpIHtcclxuICAgICAgICAgICAgZmlsZXMgPSBbLi4uZS5kYXRhVHJhbnNmZXIuaXRlbXNdLm1hcCgoaXRlbSwgaSkgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKGl0ZW0ua2luZCA9PT0gJ2ZpbGUnKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGl0ZW0uZ2V0QXNGaWxlKCk7XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIH0pO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIGZpbGVzID0gWy4uLmUuZGF0YVRyYW5zZmVyLmZpbGVzXTtcclxuICAgICAgICB9XHJcbiAgICAgICAgd2luZG93LnJ1bnRpbWUuUmVzb2x2ZUZpbGVQYXRocyhlLngsIGUueSwgZmlsZXMpO1xyXG4gICAgfVxyXG5cclxuICAgIGlmICghZmxhZ3MudXNlRHJvcFRhcmdldCkge1xyXG4gICAgICAgIHJldHVybjtcclxuICAgIH1cclxuXHJcbiAgICAvLyBUcmlnZ2VyIGRlYm91bmNlIGZ1bmN0aW9uIHRvIGRlYWN0aXZhdGUgZHJvcCB0YXJnZXRzXHJcbiAgICBpZihmbGFncy5uZXh0RGVhY3RpdmF0ZSkgZmxhZ3MubmV4dERlYWN0aXZhdGUoKTtcclxuXHJcbiAgICAvLyBEZWFjdGl2YXRlIGFsbCBkcm9wIHRhcmdldHNcclxuICAgIEFycmF5LmZyb20oZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZShEUk9QX1RBUkdFVF9BQ1RJVkUpKS5mb3JFYWNoKGVsID0+IGVsLmNsYXNzTGlzdC5yZW1vdmUoRFJPUF9UQVJHRVRfQUNUSVZFKSk7XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBwb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0cyBjaGVja3MgdGhlIGJyb3dzZXIncyBjYXBhYmlsaXR5IG9mIHNlbmRpbmcgcG9zdE1lc3NhZ2VXaXRoQWRkaXRpb25hbE9iamVjdHNcclxuICpcclxuICogQHJldHVybnMge2Jvb2xlYW59XHJcbiAqIEBjb25zdHJ1Y3RvclxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIENhblJlc29sdmVGaWxlUGF0aHMoKSB7XHJcbiAgICByZXR1cm4gd2luZG93LmNocm9tZT8ud2Vidmlldz8ucG9zdE1lc3NhZ2VXaXRoQWRkaXRpb25hbE9iamVjdHMgIT0gbnVsbDtcclxufVxyXG5cclxuLyoqXHJcbiAqIFJlc29sdmVGaWxlUGF0aHMgc2VuZHMgZHJvcCBldmVudHMgdG8gdGhlIEdPIHNpZGUgdG8gcmVzb2x2ZSBmaWxlIHBhdGhzIG9uIHdpbmRvd3MuXHJcbiAqXHJcbiAqIEBwYXJhbSB7bnVtYmVyfSB4XHJcbiAqIEBwYXJhbSB7bnVtYmVyfSB5XHJcbiAqIEBwYXJhbSB7YW55W119IGZpbGVzXHJcbiAqIEBjb25zdHJ1Y3RvclxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIFJlc29sdmVGaWxlUGF0aHMoeCwgeSwgZmlsZXMpIHtcclxuICAgIC8vIE9ubHkgZm9yIHdpbmRvd3Mgd2VidmlldzIgPj0gMS4wLjE3NzQuMzBcclxuICAgIC8vIGh0dHBzOi8vbGVhcm4ubWljcm9zb2Z0LmNvbS9lbi11cy9taWNyb3NvZnQtZWRnZS93ZWJ2aWV3Mi9yZWZlcmVuY2Uvd2luMzIvaWNvcmV3ZWJ2aWV3MndlYm1lc3NhZ2VyZWNlaXZlZGV2ZW50YXJnczI/dmlldz13ZWJ2aWV3Mi0xLjAuMTgyMy4zMiNhcHBsaWVzLXRvXHJcbiAgICBpZiAod2luZG93LmNocm9tZT8ud2Vidmlldz8ucG9zdE1lc3NhZ2VXaXRoQWRkaXRpb25hbE9iamVjdHMpIHtcclxuICAgICAgICBjaHJvbWUud2Vidmlldy5wb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0cyhgZmlsZTpkcm9wOiR7eH06JHt5fWAsIGZpbGVzKTtcclxuICAgIH1cclxufVxyXG5cclxuLyoqXHJcbiAqIENhbGxiYWNrIGZvciBPbkZpbGVEcm9wIHJldHVybnMgYSBzbGljZSBvZiBmaWxlIHBhdGggc3RyaW5ncyB3aGVuIGEgZHJvcCBpcyBmaW5pc2hlZC5cclxuICpcclxuICogQGV4cG9ydFxyXG4gKiBAY2FsbGJhY2sgT25GaWxlRHJvcENhbGxiYWNrXHJcbiAqIEBwYXJhbSB7bnVtYmVyfSB4IC0geCBjb29yZGluYXRlIG9mIHRoZSBkcm9wXHJcbiAqIEBwYXJhbSB7bnVtYmVyfSB5IC0geSBjb29yZGluYXRlIG9mIHRoZSBkcm9wXHJcbiAqIEBwYXJhbSB7c3RyaW5nW119IHBhdGhzIC0gQSBsaXN0IG9mIGZpbGUgcGF0aHMuXHJcbiAqL1xyXG5cclxuLyoqXHJcbiAqIE9uRmlsZURyb3AgbGlzdGVucyB0byBkcmFnIGFuZCBkcm9wIGV2ZW50cyBhbmQgY2FsbHMgdGhlIGNhbGxiYWNrIHdpdGggdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSBkcm9wIGFuZCBhbiBhcnJheSBvZiBwYXRoIHN0cmluZ3MuXHJcbiAqXHJcbiAqIEBleHBvcnRcclxuICogQHBhcmFtIHtPbkZpbGVEcm9wQ2FsbGJhY2t9IGNhbGxiYWNrIC0gQ2FsbGJhY2sgZm9yIE9uRmlsZURyb3AgcmV0dXJucyBhIHNsaWNlIG9mIGZpbGUgcGF0aCBzdHJpbmdzIHdoZW4gYSBkcm9wIGlzIGZpbmlzaGVkLlxyXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFt1c2VEcm9wVGFyZ2V0PXRydWVdIC0gT25seSBjYWxsIHRoZSBjYWxsYmFjayB3aGVuIHRoZSBkcm9wIGZpbmlzaGVkIG9uIGFuIGVsZW1lbnQgdGhhdCBoYXMgdGhlIGRyb3AgdGFyZ2V0IHN0eWxlLiAoLS13YWlscy1kcm9wLXRhcmdldClcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBPbkZpbGVEcm9wKGNhbGxiYWNrLCB1c2VEcm9wVGFyZ2V0KSB7XHJcbiAgICBpZiAodHlwZW9mIGNhbGxiYWNrICE9PSBcImZ1bmN0aW9uXCIpIHtcclxuICAgICAgICBjb25zb2xlLmVycm9yKFwiRHJhZ0FuZERyb3BDYWxsYmFjayBpcyBub3QgYSBmdW5jdGlvblwiKTtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKGZsYWdzLnJlZ2lzdGVyZWQpIHtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcbiAgICBmbGFncy5yZWdpc3RlcmVkID0gdHJ1ZTtcclxuXHJcbiAgICBjb25zdCB1RFRQVCA9IHR5cGVvZiB1c2VEcm9wVGFyZ2V0O1xyXG4gICAgZmxhZ3MudXNlRHJvcFRhcmdldCA9IHVEVFBUID09PSBcInVuZGVmaW5lZFwiIHx8IHVEVFBUICE9PSBcImJvb2xlYW5cIiA/IGZsYWdzLmRlZmF1bHRVc2VEcm9wVGFyZ2V0IDogdXNlRHJvcFRhcmdldDtcclxuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdkcmFnb3ZlcicsIG9uRHJhZ092ZXIpO1xyXG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2RyYWdsZWF2ZScsIG9uRHJhZ0xlYXZlKTtcclxuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdkcm9wJywgb25Ecm9wKTtcclxuXHJcbiAgICBsZXQgY2IgPSBjYWxsYmFjaztcclxuICAgIGlmIChmbGFncy51c2VEcm9wVGFyZ2V0KSB7XHJcbiAgICAgICAgY2IgPSBmdW5jdGlvbiAoeCwgeSwgcGF0aHMpIHtcclxuICAgICAgICAgICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQoeCwgeSlcclxuICAgICAgICAgICAgLy8gaWYgdGhlIGVsZW1lbnQgaXMgbnVsbCBvciBlbGVtZW50IGlzIG5vdCBjaGlsZCBvZiBkcm9wIHRhcmdldCBlbGVtZW50LCByZXR1cm4gbnVsbFxyXG4gICAgICAgICAgICBpZiAoIWVsZW1lbnQgfHwgIWNoZWNrU3R5bGVEcm9wVGFyZ2V0KGdldENvbXB1dGVkU3R5bGUoZWxlbWVudCkpKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gbnVsbDtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICBjYWxsYmFjayh4LCB5LCBwYXRocyk7XHJcbiAgICAgICAgfVxyXG4gICAgfVxyXG5cclxuICAgIEV2ZW50c09uKFwid2FpbHM6ZmlsZS1kcm9wXCIsIGNiKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIE9uRmlsZURyb3BPZmYgcmVtb3ZlcyB0aGUgZHJhZyBhbmQgZHJvcCBsaXN0ZW5lcnMgYW5kIGhhbmRsZXJzLlxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIE9uRmlsZURyb3BPZmYoKSB7XHJcbiAgICB3aW5kb3cucmVtb3ZlRXZlbnRMaXN0ZW5lcignZHJhZ292ZXInLCBvbkRyYWdPdmVyKTtcclxuICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdkcmFnbGVhdmUnLCBvbkRyYWdMZWF2ZSk7XHJcbiAgICB3aW5kb3cucmVtb3ZlRXZlbnRMaXN0ZW5lcignZHJvcCcsIG9uRHJvcCk7XHJcbiAgICBFdmVudHNPZmYoXCJ3YWlsczpmaWxlLWRyb3BcIik7XHJcbiAgICBmbGFncy5yZWdpc3RlcmVkID0gZmFsc2U7XHJcbn1cclxuIiwgIi8qXHJcbi0tZGVmYXVsdC1jb250ZXh0bWVudTogYXV0bzsgKGRlZmF1bHQpIHdpbGwgc2hvdyB0aGUgZGVmYXVsdCBjb250ZXh0IG1lbnUgaWYgY29udGVudEVkaXRhYmxlIGlzIHRydWUgT1IgdGV4dCBoYXMgYmVlbiBzZWxlY3RlZCBPUiBlbGVtZW50IGlzIGlucHV0IG9yIHRleHRhcmVhXHJcbi0tZGVmYXVsdC1jb250ZXh0bWVudTogc2hvdzsgd2lsbCBhbHdheXMgc2hvdyB0aGUgZGVmYXVsdCBjb250ZXh0IG1lbnVcclxuLS1kZWZhdWx0LWNvbnRleHRtZW51OiBoaWRlOyB3aWxsIGFsd2F5cyBoaWRlIHRoZSBkZWZhdWx0IGNvbnRleHQgbWVudVxyXG5cclxuVGhpcyBydWxlIGlzIGluaGVyaXRlZCBsaWtlIG5vcm1hbCBDU1MgcnVsZXMsIHNvIG5lc3Rpbmcgd29ya3MgYXMgZXhwZWN0ZWRcclxuKi9cclxuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQpIHtcclxuICAgIC8vIFByb2Nlc3MgZGVmYXVsdCBjb250ZXh0IG1lbnVcclxuICAgIGNvbnN0IGVsZW1lbnQgPSBldmVudC50YXJnZXQ7XHJcbiAgICBjb25zdCBjb21wdXRlZFN0eWxlID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUoZWxlbWVudCk7XHJcbiAgICBjb25zdCBkZWZhdWx0Q29udGV4dE1lbnVBY3Rpb24gPSBjb21wdXRlZFN0eWxlLmdldFByb3BlcnR5VmFsdWUoXCItLWRlZmF1bHQtY29udGV4dG1lbnVcIikudHJpbSgpO1xyXG4gICAgc3dpdGNoIChkZWZhdWx0Q29udGV4dE1lbnVBY3Rpb24pIHtcclxuICAgICAgICBjYXNlIFwic2hvd1wiOlxyXG4gICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgY2FzZSBcImhpZGVcIjpcclxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgIGRlZmF1bHQ6XHJcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIGNvbnRlbnRFZGl0YWJsZSBpcyB0cnVlXHJcbiAgICAgICAgICAgIGlmIChlbGVtZW50LmlzQ29udGVudEVkaXRhYmxlKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIHRleHQgaGFzIGJlZW4gc2VsZWN0ZWQgYW5kIGFjdGlvbiBpcyBvbiB0aGUgc2VsZWN0ZWQgZWxlbWVudHNcclxuICAgICAgICAgICAgY29uc3Qgc2VsZWN0aW9uID0gd2luZG93LmdldFNlbGVjdGlvbigpO1xyXG4gICAgICAgICAgICBjb25zdCBoYXNTZWxlY3Rpb24gPSAoc2VsZWN0aW9uLnRvU3RyaW5nKCkubGVuZ3RoID4gMClcclxuICAgICAgICAgICAgaWYgKGhhc1NlbGVjdGlvbikge1xyXG4gICAgICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzZWxlY3Rpb24ucmFuZ2VDb3VudDsgaSsrKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgcmFuZ2UgPSBzZWxlY3Rpb24uZ2V0UmFuZ2VBdChpKTtcclxuICAgICAgICAgICAgICAgICAgICBjb25zdCByZWN0cyA9IHJhbmdlLmdldENsaWVudFJlY3RzKCk7XHJcbiAgICAgICAgICAgICAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCByZWN0cy5sZW5ndGg7IGorKykge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCByZWN0ID0gcmVjdHNbal07XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KHJlY3QubGVmdCwgcmVjdC50b3ApID09PSBlbGVtZW50KSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgLy8gQ2hlY2sgaWYgdGFnbmFtZSBpcyBpbnB1dCBvciB0ZXh0YXJlYVxyXG4gICAgICAgICAgICBpZiAoZWxlbWVudC50YWdOYW1lID09PSBcIklOUFVUXCIgfHwgZWxlbWVudC50YWdOYW1lID09PSBcIlRFWFRBUkVBXCIpIHtcclxuICAgICAgICAgICAgICAgIGlmIChoYXNTZWxlY3Rpb24gfHwgKCFlbGVtZW50LnJlYWRPbmx5ICYmICFlbGVtZW50LmRpc2FibGVkKSkge1xyXG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfVxyXG5cclxuICAgICAgICAgICAgLy8gaGlkZSBkZWZhdWx0IGNvbnRleHQgbWVudVxyXG4gICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xyXG4gICAgfVxyXG59XHJcbiIsICIvKlxyXG4gX1x0ICAgX19cdCAgXyBfX1xyXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xyXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXHJcbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxyXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cclxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xyXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XHJcbiovXHJcbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cclxuaW1wb3J0ICogYXMgTG9nIGZyb20gJy4vbG9nJztcclxuaW1wb3J0IHtcclxuICBldmVudExpc3RlbmVycyxcclxuICBFdmVudHNFbWl0LFxyXG4gIEV2ZW50c05vdGlmeSxcclxuICBFdmVudHNPZmYsXHJcbiAgRXZlbnRzT2ZmQWxsLFxyXG4gIEV2ZW50c09uLFxyXG4gIEV2ZW50c09uY2UsXHJcbiAgRXZlbnRzT25NdWx0aXBsZSxcclxufSBmcm9tIFwiLi9ldmVudHNcIjtcclxuaW1wb3J0IHsgQ2FsbCwgQ2FsbGJhY2ssIGNhbGxiYWNrcyB9IGZyb20gJy4vY2FsbHMnO1xyXG5pbXBvcnQgeyBTZXRCaW5kaW5ncyB9IGZyb20gXCIuL2JpbmRpbmdzXCI7XHJcbmltcG9ydCAqIGFzIFdpbmRvdyBmcm9tIFwiLi93aW5kb3dcIjtcclxuaW1wb3J0ICogYXMgU2NyZWVuIGZyb20gXCIuL3NjcmVlblwiO1xyXG5pbXBvcnQgKiBhcyBCcm93c2VyIGZyb20gXCIuL2Jyb3dzZXJcIjtcclxuaW1wb3J0ICogYXMgQ2xpcGJvYXJkIGZyb20gXCIuL2NsaXBib2FyZFwiO1xyXG5pbXBvcnQgKiBhcyBEcmFnQW5kRHJvcCBmcm9tIFwiLi9kcmFnYW5kZHJvcFwiO1xyXG5pbXBvcnQgKiBhcyBDb250ZXh0TWVudSBmcm9tIFwiLi9jb250ZXh0bWVudVwiO1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIFF1aXQoKSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1EnKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIFNob3coKSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1MnKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIEhpZGUoKSB7XHJcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ0gnKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIEVudmlyb25tZW50KCkge1xyXG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6RW52aXJvbm1lbnRcIik7XHJcbn1cclxuXHJcbi8vIFRoZSBKUyBydW50aW1lXHJcbndpbmRvdy5ydW50aW1lID0ge1xyXG4gICAgLi4uTG9nLFxyXG4gICAgLi4uV2luZG93LFxyXG4gICAgLi4uQnJvd3NlcixcclxuICAgIC4uLlNjcmVlbixcclxuICAgIC4uLkNsaXBib2FyZCxcclxuICAgIC4uLkRyYWdBbmREcm9wLFxyXG4gICAgRXZlbnRzT24sXHJcbiAgICBFdmVudHNPbmNlLFxyXG4gICAgRXZlbnRzT25NdWx0aXBsZSxcclxuICAgIEV2ZW50c0VtaXQsXHJcbiAgICBFdmVudHNPZmYsXHJcbiAgICBFdmVudHNPZmZBbGwsXHJcbiAgICBFbnZpcm9ubWVudCxcclxuICAgIFNob3csXHJcbiAgICBIaWRlLFxyXG4gICAgUXVpdFxyXG59O1xyXG5cclxuLy8gSW50ZXJuYWwgd2FpbHMgZW5kcG9pbnRzXHJcbndpbmRvdy53YWlscyA9IHtcclxuICAgIENhbGxiYWNrLFxyXG4gICAgRXZlbnRzTm90aWZ5LFxyXG4gICAgU2V0QmluZGluZ3MsXHJcbiAgICBldmVudExpc3RlbmVycyxcclxuICAgIGNhbGxiYWNrcyxcclxuICAgIGZsYWdzOiB7XHJcbiAgICAgICAgZGlzYWJsZVNjcm9sbGJhckRyYWc6IGZhbHNlLFxyXG4gICAgICAgIGRpc2FibGVEZWZhdWx0Q29udGV4dE1lbnU6IGZhbHNlLFxyXG4gICAgICAgIGVuYWJsZVJlc2l6ZTogZmFsc2UsXHJcbiAgICAgICAgZGVmYXVsdEN1cnNvcjogbnVsbCxcclxuICAgICAgICBib3JkZXJUaGlja25lc3M6IDYsXHJcbiAgICAgICAgc2hvdWxkRHJhZzogZmFsc2UsXHJcbiAgICAgICAgZGVmZXJEcmFnVG9Nb3VzZU1vdmU6IHRydWUsXHJcbiAgICAgICAgY3NzRHJhZ1Byb3BlcnR5OiBcIi0td2FpbHMtZHJhZ2dhYmxlXCIsXHJcbiAgICAgICAgY3NzRHJhZ1ZhbHVlOiBcImRyYWdcIixcclxuICAgICAgICBjc3NEcm9wUHJvcGVydHk6IFwiLS13YWlscy1kcm9wLXRhcmdldFwiLFxyXG4gICAgICAgIGNzc0Ryb3BWYWx1ZTogXCJkcm9wXCIsXHJcbiAgICAgICAgZW5hYmxlV2FpbHNEcmFnQW5kRHJvcDogZmFsc2UsXHJcbiAgICB9XHJcbn07XHJcblxyXG4vLyBTZXQgdGhlIGJpbmRpbmdzXHJcbmlmICh3aW5kb3cud2FpbHNiaW5kaW5ncykge1xyXG4gICAgd2luZG93LndhaWxzLlNldEJpbmRpbmdzKHdpbmRvdy53YWlsc2JpbmRpbmdzKTtcclxuICAgIGRlbGV0ZSB3aW5kb3cud2FpbHMuU2V0QmluZGluZ3M7XHJcbn1cclxuXHJcbi8vIChib29sKSBUaGlzIGlzIGV2YWx1YXRlZCBhdCBidWlsZCB0aW1lIGluIHBhY2thZ2UuanNvblxyXG5pZiAoIURFQlVHKSB7XHJcbiAgICBkZWxldGUgd2luZG93LndhaWxzYmluZGluZ3M7XHJcbn1cclxuXHJcbmxldCBkcmFnVGVzdCA9IGZ1bmN0aW9uKGUpIHtcclxuICAgIHZhciB2YWwgPSB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShlLnRhcmdldCkuZ2V0UHJvcGVydHlWYWx1ZSh3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1Byb3BlcnR5KTtcclxuICAgIGlmICh2YWwpIHtcclxuICAgICAgICB2YWwgPSB2YWwudHJpbSgpO1xyXG4gICAgfVxyXG5cclxuICAgIGlmICh2YWwgIT09IHdpbmRvdy53YWlscy5mbGFncy5jc3NEcmFnVmFsdWUpIHtcclxuICAgICAgICByZXR1cm4gZmFsc2U7XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKGUuYnV0dG9ucyAhPT0gMSkge1xyXG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnZ2luZyBpZiBub3QgdGhlIHByaW1hcnkgYnV0dG9uIGhhcyBiZWVuIGNsaWNrZWQuXHJcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xyXG4gICAgfVxyXG5cclxuICAgIGlmIChlLmRldGFpbCAhPT0gMSkge1xyXG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnZ2luZyBpZiBtb3JlIHRoYW4gb25jZSBoYXMgYmVlbiBjbGlja2VkLCBlLmcuIHdoZW4gZG91YmxlIGNsaWNraW5nXHJcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xyXG4gICAgfVxyXG5cclxuICAgIHJldHVybiB0cnVlO1xyXG59O1xyXG5cclxud2luZG93LndhaWxzLnNldENTU0RyYWdQcm9wZXJ0aWVzID0gZnVuY3Rpb24ocHJvcGVydHksIHZhbHVlKSB7XHJcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1Byb3BlcnR5ID0gcHJvcGVydHk7XHJcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1ZhbHVlID0gdmFsdWU7XHJcbn1cclxuXHJcbndpbmRvdy53YWlscy5zZXRDU1NEcm9wUHJvcGVydGllcyA9IGZ1bmN0aW9uKHByb3BlcnR5LCB2YWx1ZSkge1xyXG4gICAgd2luZG93LndhaWxzLmZsYWdzLmNzc0Ryb3BQcm9wZXJ0eSA9IHByb3BlcnR5O1xyXG4gICAgd2luZG93LndhaWxzLmZsYWdzLmNzc0Ryb3BWYWx1ZSA9IHZhbHVlO1xyXG59XHJcblxyXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2Vkb3duJywgKGUpID0+IHtcclxuICAgIC8vIENoZWNrIGZvciByZXNpemluZ1xyXG4gICAgaWYgKHdpbmRvdy53YWlscy5mbGFncy5yZXNpemVFZGdlKSB7XHJcbiAgICAgICAgd2luZG93LldhaWxzSW52b2tlKFwicmVzaXplOlwiICsgd2luZG93LndhaWxzLmZsYWdzLnJlc2l6ZUVkZ2UpO1xyXG4gICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcclxuICAgICAgICByZXR1cm47XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKGRyYWdUZXN0KGUpKSB7XHJcbiAgICAgICAgaWYgKHdpbmRvdy53YWlscy5mbGFncy5kaXNhYmxlU2Nyb2xsYmFyRHJhZykge1xyXG4gICAgICAgICAgICAvLyBUaGlzIGNoZWNrcyBmb3IgY2xpY2tzIG9uIHRoZSBzY3JvbGwgYmFyXHJcbiAgICAgICAgICAgIGlmIChlLm9mZnNldFggPiBlLnRhcmdldC5jbGllbnRXaWR0aCB8fCBlLm9mZnNldFkgPiBlLnRhcmdldC5jbGllbnRIZWlnaHQpIHtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgICAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLmRlZmVyRHJhZ1RvTW91c2VNb3ZlKSB7XHJcbiAgICAgICAgICAgIHdpbmRvdy53YWlscy5mbGFncy5zaG91bGREcmFnID0gdHJ1ZTtcclxuICAgICAgICB9IGVsc2Uge1xyXG4gICAgICAgICAgICBlLnByZXZlbnREZWZhdWx0KClcclxuICAgICAgICAgICAgd2luZG93LldhaWxzSW52b2tlKFwiZHJhZ1wiKTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuO1xyXG4gICAgfSBlbHNlIHtcclxuICAgICAgICB3aW5kb3cud2FpbHMuZmxhZ3Muc2hvdWxkRHJhZyA9IGZhbHNlO1xyXG4gICAgfVxyXG59KTtcclxuXHJcbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZXVwJywgKCkgPT4ge1xyXG4gICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcclxufSk7XHJcblxyXG5mdW5jdGlvbiBzZXRSZXNpemUoY3Vyc29yKSB7XHJcbiAgICBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gY3Vyc29yIHx8IHdpbmRvdy53YWlscy5mbGFncy5kZWZhdWx0Q3Vyc29yO1xyXG4gICAgd2luZG93LndhaWxzLmZsYWdzLnJlc2l6ZUVkZ2UgPSBjdXJzb3I7XHJcbn1cclxuXHJcbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZW1vdmUnLCBmdW5jdGlvbihlKSB7XHJcbiAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcpIHtcclxuICAgICAgICB3aW5kb3cud2FpbHMuZmxhZ3Muc2hvdWxkRHJhZyA9IGZhbHNlO1xyXG4gICAgICAgIGxldCBtb3VzZVByZXNzZWQgPSBlLmJ1dHRvbnMgIT09IHVuZGVmaW5lZCA/IGUuYnV0dG9ucyA6IGUud2hpY2g7XHJcbiAgICAgICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcclxuICAgICAgICAgICAgd2luZG93LldhaWxzSW52b2tlKFwiZHJhZ1wiKTtcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG4gICAgICAgIH1cclxuICAgIH1cclxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVJlc2l6ZSkge1xyXG4gICAgICAgIHJldHVybjtcclxuICAgIH1cclxuICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MuZGVmYXVsdEN1cnNvciA9PSBudWxsKSB7XHJcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLmRlZmF1bHRDdXJzb3IgPSBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yO1xyXG4gICAgfVxyXG4gICAgaWYgKHdpbmRvdy5vdXRlcldpZHRoIC0gZS5jbGllbnRYIDwgd2luZG93LndhaWxzLmZsYWdzLmJvcmRlclRoaWNrbmVzcyAmJiB3aW5kb3cub3V0ZXJIZWlnaHQgLSBlLmNsaWVudFkgPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzKSB7XHJcbiAgICAgICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IFwic2UtcmVzaXplXCI7XHJcbiAgICB9XHJcbiAgICBsZXQgcmlnaHRCb3JkZXIgPSB3aW5kb3cub3V0ZXJXaWR0aCAtIGUuY2xpZW50WCA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XHJcbiAgICBsZXQgbGVmdEJvcmRlciA9IGUuY2xpZW50WCA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XHJcbiAgICBsZXQgdG9wQm9yZGVyID0gZS5jbGllbnRZIDwgd2luZG93LndhaWxzLmZsYWdzLmJvcmRlclRoaWNrbmVzcztcclxuICAgIGxldCBib3R0b21Cb3JkZXIgPSB3aW5kb3cub3V0ZXJIZWlnaHQgLSBlLmNsaWVudFkgPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzO1xyXG5cclxuICAgIC8vIElmIHdlIGFyZW4ndCBvbiBhbiBlZGdlLCBidXQgd2VyZSwgcmVzZXQgdGhlIGN1cnNvciB0byBkZWZhdWx0XHJcbiAgICBpZiAoIWxlZnRCb3JkZXIgJiYgIXJpZ2h0Qm9yZGVyICYmICF0b3BCb3JkZXIgJiYgIWJvdHRvbUJvcmRlciAmJiB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSAhPT0gdW5kZWZpbmVkKSB7XHJcbiAgICAgICAgc2V0UmVzaXplKCk7XHJcbiAgICB9IGVsc2UgaWYgKHJpZ2h0Qm9yZGVyICYmIGJvdHRvbUJvcmRlcikgc2V0UmVzaXplKFwic2UtcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAobGVmdEJvcmRlciAmJiBib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInN3LXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKGxlZnRCb3JkZXIgJiYgdG9wQm9yZGVyKSBzZXRSZXNpemUoXCJudy1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmICh0b3BCb3JkZXIgJiYgcmlnaHRCb3JkZXIpIHNldFJlc2l6ZShcIm5lLXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKGxlZnRCb3JkZXIpIHNldFJlc2l6ZShcInctcmVzaXplXCIpO1xyXG4gICAgZWxzZSBpZiAodG9wQm9yZGVyKSBzZXRSZXNpemUoXCJuLXJlc2l6ZVwiKTtcclxuICAgIGVsc2UgaWYgKGJvdHRvbUJvcmRlcikgc2V0UmVzaXplKFwicy1yZXNpemVcIik7XHJcbiAgICBlbHNlIGlmIChyaWdodEJvcmRlcikgc2V0UmVzaXplKFwiZS1yZXNpemVcIik7XHJcblxyXG59KTtcclxuXHJcbi8vIFNldHVwIGNvbnRleHQgbWVudSBob29rXHJcbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdjb250ZXh0bWVudScsIGZ1bmN0aW9uKGUpIHtcclxuICAgIC8vIGFsd2F5cyBzaG93IHRoZSBjb250ZXh0bWVudSBpbiBkZWJ1ZyAmIGRldlxyXG4gICAgaWYgKERFQlVHKSByZXR1cm47XHJcblxyXG4gICAgaWYgKHdpbmRvdy53YWlscy5mbGFncy5kaXNhYmxlRGVmYXVsdENvbnRleHRNZW51KSB7XHJcbiAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xyXG4gICAgfSBlbHNlIHtcclxuICAgICAgICBDb250ZXh0TWVudS5wcm9jZXNzRGVmYXVsdENvbnRleHRNZW51KGUpO1xyXG4gICAgfVxyXG59KTtcclxuXHJcbndpbmRvdy5XYWlsc0ludm9rZShcInJ1bnRpbWU6cmVhZHlcIik7Il0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFrQkEsV0FBUyxlQUFlLE9BQU8sU0FBUztBQUl2QyxXQUFPLFlBQVksTUFBTSxRQUFRLE9BQU87QUFBQSxFQUN6QztBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxTQUFTLFNBQVM7QUFDakMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsUUFBUSxTQUFTO0FBQ2hDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxXQUFXLFNBQVM7QUFDbkMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxZQUFZLFVBQVU7QUFDckMsbUJBQWUsS0FBSyxRQUFRO0FBQUEsRUFDN0I7QUFHTyxNQUFNLFdBQVc7QUFBQSxJQUN2QixPQUFPO0FBQUEsSUFDUCxPQUFPO0FBQUEsSUFDUCxNQUFNO0FBQUEsSUFDTixTQUFTO0FBQUEsSUFDVCxPQUFPO0FBQUEsRUFDUjs7O0FDOUZBLE1BQU0sV0FBTixNQUFlO0FBQUEsSUFRWCxZQUFZLFdBQVcsVUFBVSxjQUFjO0FBQzNDLFdBQUssWUFBWTtBQUVqQixXQUFLLGVBQWUsZ0JBQWdCO0FB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 +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9sb2cuanMiLCAiZGVza3RvcC9ldmVudHMuanMiLCAiZGVza3RvcC9jYWxscy5qcyIsICJkZXNrdG9wL2JpbmRpbmdzLmpzIiwgImRlc2t0b3Avd2luZG93LmpzIiwgImRlc2t0b3Avc2NyZWVuLmpzIiwgImRlc2t0b3AvYnJvd3Nlci5qcyIsICJkZXNrdG9wL2NsaXBib2FyZC5qcyIsICJkZXNrdG9wL2RyYWdhbmRkcm9wLmpzIiwgImRlc2t0b3AvY29udGV4dG1lbnUuanMiLCAiZGVza3RvcC9ub3RpZmljYXRpb25zLmpzIiwgImRlc2t0b3AvbWFpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfICAgICAgIF9fICAgICAgXyBfX1xufCB8ICAgICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDYgKi9cblxuLyoqXG4gKiBTZW5kcyBhIGxvZyBtZXNzYWdlIHRvIHRoZSBiYWNrZW5kIHdpdGggdGhlIGdpdmVuIGxldmVsICsgbWVzc2FnZVxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBsZXZlbFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZnVuY3Rpb24gc2VuZExvZ01lc3NhZ2UobGV2ZWwsIG1lc3NhZ2UpIHtcblxuXHQvLyBMb2cgTWVzc2FnZSBmb3JtYXQ6XG5cdC8vIGxbdHlwZV1bbWVzc2FnZV1cblx0d2luZG93LldhaWxzSW52b2tlKCdMJyArIGxldmVsICsgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogTG9nIHRoZSBnaXZlbiB0cmFjZSBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxuICovXG5leHBvcnQgZnVuY3Rpb24gTG9nVHJhY2UobWVzc2FnZSkge1xuXHRzZW5kTG9nTWVzc2FnZSgnVCcsIG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIExvZyB0aGUgZ2l2ZW4gbWVzc2FnZSB3aXRoIHRoZSBiYWNrZW5kXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZ1ByaW50KG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ1AnLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIGRlYnVnIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dEZWJ1ZyhtZXNzYWdlKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdEJywgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogTG9nIHRoZSBnaXZlbiBpbmZvIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dJbmZvKG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ0knLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIHdhcm5pbmcgbWVzc2FnZSB3aXRoIHRoZSBiYWNrZW5kXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZ1dhcm5pbmcobWVzc2FnZSkge1xuXHRzZW5kTG9nTWVzc2FnZSgnVycsIG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIExvZyB0aGUgZ2l2ZW4gZXJyb3IgbWVzc2FnZSB3aXRoIHRoZSBiYWNrZW5kXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZ0Vycm9yKG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ0UnLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIGZhdGFsIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dGYXRhbChtZXNzYWdlKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdGJywgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogU2V0cyB0aGUgTG9nIGxldmVsIHRvIHRoZSBnaXZlbiBsb2cgbGV2ZWxcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge251bWJlcn0gbG9nbGV2ZWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNldExvZ0xldmVsKGxvZ2xldmVsKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdTJywgbG9nbGV2ZWwpO1xufVxuXG4vLyBMb2cgbGV2ZWxzXG5leHBvcnQgY29uc3QgTG9nTGV2ZWwgPSB7XG5cdFRSQUNFOiAxLFxuXHRERUJVRzogMixcblx0SU5GTzogMyxcblx0V0FSTklORzogNCxcblx0RVJST1I6IDUsXG59O1xuIiwgIi8qXG4gXyAgICAgICBfXyAgICAgIF8gX19cbnwgfCAgICAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuLyoganNoaW50IGVzdmVyc2lvbjogNiAqL1xuXG4vLyBEZWZpbmVzIGEgc2luZ2xlIGxpc3RlbmVyIHdpdGggYSBtYXhpbXVtIG51bWJlciBvZiB0aW1lcyB0byBjYWxsYmFja1xuXG4vKipcbiAqIFRoZSBMaXN0ZW5lciBjbGFzcyBkZWZpbmVzIGEgbGlzdGVuZXIhIDotKVxuICpcbiAqIEBjbGFzcyBMaXN0ZW5lclxuICovXG5jbGFzcyBMaXN0ZW5lciB7XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhbiBpbnN0YW5jZSBvZiBMaXN0ZW5lci5cbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gICAgICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2tcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXG4gICAgICogQG1lbWJlcm9mIExpc3RlbmVyXG4gICAgICovXG4gICAgY29uc3RydWN0b3IoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XG4gICAgICAgIHRoaXMuZXZlbnROYW1lID0gZXZlbnROYW1lO1xuICAgICAgICAvLyBEZWZhdWx0IG9mIC0xIG1lYW5zIGluZmluaXRlXG4gICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzID0gbWF4Q2FsbGJhY2tzIHx8IC0xO1xuICAgICAgICAvLyBDYWxsYmFjayBpbnZva2VzIHRoZSBjYWxsYmFjayB3aXRoIHRoZSBnaXZlbiBkYXRhXG4gICAgICAgIC8vIFJldHVybnMgdHJ1ZSBpZiB0aGlzIGxpc3RlbmVyIHNob3VsZCBiZSBkZXN0cm95ZWRcbiAgICAgICAgdGhpcy5DYWxsYmFjayA9IChkYXRhKSA9PiB7XG4gICAgICAgICAgICBjYWxsYmFjay5hcHBseShudWxsLCBkYXRhKTtcbiAgICAgICAgICAgIC8vIElmIG1heENhbGxiYWNrcyBpcyBpbmZpbml0ZSwgcmV0dXJuIGZhbHNlIChkbyBub3QgZGVzdHJveSlcbiAgICAgICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBEZWNyZW1lbnQgbWF4Q2FsbGJhY2tzLiBSZXR1cm4gdHJ1ZSBpZiBub3cgMCwgb3RoZXJ3aXNlIGZhbHNlXG4gICAgICAgICAgICB0aGlzLm1heENhbGxiYWNrcyAtPSAxO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMubWF4Q2FsbGJhY2tzID09PSAwO1xuICAgICAgICB9O1xuICAgIH1cbn1cblxuZXhwb3J0IGNvbnN0IGV2ZW50TGlzdGVuZXJzID0ge307XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGBtYXhDYWxsYmFja3NgIHRpbWVzIGJlZm9yZSBiZWluZyBkZXN0cm95ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xuICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XG4gICAgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXSA9IGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gfHwgW107XG4gICAgY29uc3QgdGhpc0xpc3RlbmVyID0gbmV3IExpc3RlbmVyKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcyk7XG4gICAgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXS5wdXNoKHRoaXNMaXN0ZW5lcik7XG4gICAgcmV0dXJuICgpID0+IGxpc3RlbmVyT2ZmKHRoaXNMaXN0ZW5lcik7XG59XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGV2ZXJ5IHRpbWUgdGhlIGV2ZW50IGlzIGVtaXR0ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09uKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcbiAgICByZXR1cm4gRXZlbnRzT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCAtMSk7XG59XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIG9uY2UgdGhlbiBkZXN0cm95ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09uY2UoZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBFdmVudHNPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xufVxuXG5mdW5jdGlvbiBub3RpZnlMaXN0ZW5lcnMoZXZlbnREYXRhKSB7XG5cbiAgICAvLyBHZXQgdGhlIGV2ZW50IG5hbWVcbiAgICBsZXQgZXZlbnROYW1lID0gZXZlbnREYXRhLm5hbWU7XG5cbiAgICAvLyBLZWVwIGEgbGlzdCBvZiBsaXN0ZW5lciBpbmRleGVzIHRvIGRlc3Ryb3lcbiAgICBjb25zdCBuZXdFdmVudExpc3RlbmVyTGlzdCA9IGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0/LnNsaWNlKCkgfHwgW107XG5cbiAgICAvLyBDaGVjayBpZiB3ZSBoYXZlIGFueSBsaXN0ZW5lcnMgZm9yIHRoaXMgZXZlbnRcbiAgICBpZiAobmV3RXZlbnRMaXN0ZW5lckxpc3QubGVuZ3RoKSB7XG5cbiAgICAgICAgLy8gSXRlcmF0ZSBsaXN0ZW5lcnNcbiAgICAgICAgZm9yIChsZXQgY291bnQgPSBuZXdFdmVudExpc3RlbmVyTGlzdC5sZW5ndGggLSAxOyBjb3VudCA+PSAwOyBjb3VudCAtPSAxKSB7XG5cbiAgICAgICAgICAgIC8vIEdldCBuZXh0IGxpc3RlbmVyXG4gICAgICAgICAgICBjb25zdCBsaXN0ZW5lciA9IG5ld0V2ZW50TGlzdGVuZXJMaXN0W2NvdW50XTtcblxuICAgICAgICAgICAgbGV0IGRhdGEgPSBldmVudERhdGEuZGF0YTtcblxuICAgICAgICAgICAgLy8gRG8gdGhlIGNhbGxiYWNrXG4gICAgICAgICAgICBjb25zdCBkZXN0cm95ID0gbGlzdGVuZXIuQ2FsbGJhY2soZGF0YSk7XG4gICAgICAgICAgICBpZiAoZGVzdHJveSkge1xuICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBsaXN0ZW5lciBpbmRpY2F0ZWQgdG8gZGVzdHJveSBpdHNlbGYsIGFkZCBpdCB0byB0aGUgZGVzdHJveSBsaXN0XG4gICAgICAgICAgICAgICAgbmV3RXZlbnRMaXN0ZW5lckxpc3Quc3BsaWNlKGNvdW50LCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFVwZGF0ZSBjYWxsYmFja3Mgd2l0aCBuZXcgbGlzdCBvZiBsaXN0ZW5lcnNcbiAgICAgICAgaWYgKG5ld0V2ZW50TGlzdGVuZXJMaXN0Lmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnROYW1lKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gPSBuZXdFdmVudExpc3RlbmVyTGlzdDtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLyoqXG4gKiBOb3RpZnkgaW5mb3JtcyBmcm9udGVuZCBsaXN0ZW5lcnMgdGhhdCBhbiBldmVudCB3YXMgZW1pdHRlZCB3aXRoIHRoZSBnaXZlbiBkYXRhXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG5vdGlmeU1lc3NhZ2UgLSBlbmNvZGVkIG5vdGlmaWNhdGlvbiBtZXNzYWdlXG5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c05vdGlmeShub3RpZnlNZXNzYWdlKSB7XG4gICAgLy8gUGFyc2UgdGhlIG1lc3NhZ2VcbiAgICBsZXQgbWVzc2FnZTtcbiAgICB0cnkge1xuICAgICAgICBtZXNzYWdlID0gSlNPTi5wYXJzZShub3RpZnlNZXNzYWdlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnN0IGVycm9yID0gJ0ludmFsaWQgSlNPTiBwYXNzZWQgdG8gTm90aWZ5OiAnICsgbm90aWZ5TWVzc2FnZTtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGVycm9yKTtcbiAgICB9XG4gICAgbm90aWZ5TGlzdGVuZXJzKG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIEVtaXQgYW4gZXZlbnQgd2l0aCB0aGUgZ2l2ZW4gbmFtZSBhbmQgZGF0YVxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c0VtaXQoZXZlbnROYW1lKSB7XG5cbiAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICBuYW1lOiBldmVudE5hbWUsXG4gICAgICAgIGRhdGE6IFtdLnNsaWNlLmFwcGx5KGFyZ3VtZW50cykuc2xpY2UoMSksXG4gICAgfTtcblxuICAgIC8vIE5vdGlmeSBKUyBsaXN0ZW5lcnNcbiAgICBub3RpZnlMaXN0ZW5lcnMocGF5bG9hZCk7XG5cbiAgICAvLyBOb3RpZnkgR28gbGlzdGVuZXJzXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdFRScgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XG59XG5cbmZ1bmN0aW9uIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSkge1xuICAgIC8vIFJlbW92ZSBsb2NhbCBsaXN0ZW5lcnNcbiAgICBkZWxldGUgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXTtcblxuICAgIC8vIE5vdGlmeSBHbyBsaXN0ZW5lcnNcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ0VYJyArIGV2ZW50TmFtZSk7XG59XG5cbi8qKlxuICogT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT24sXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVyZXMgY2FuIGJlIHVucmVnaXN0ZXJlZCB2aWEgYGFkZGl0aW9uYWxFdmVudE5hbWVzYFxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSAgey4uLnN0cmluZ30gYWRkaXRpb25hbEV2ZW50TmFtZXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09mZihldmVudE5hbWUsIC4uLmFkZGl0aW9uYWxFdmVudE5hbWVzKSB7XG4gICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnROYW1lKVxuXG4gICAgaWYgKGFkZGl0aW9uYWxFdmVudE5hbWVzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgYWRkaXRpb25hbEV2ZW50TmFtZXMuZm9yRWFjaChldmVudE5hbWUgPT4ge1xuICAgICAgICAgICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnROYW1lKVxuICAgICAgICB9KVxuICAgIH1cbn1cblxuLyoqXG4gKiBPZmYgdW5yZWdpc3RlcnMgYWxsIGV2ZW50IGxpc3RlbmVycyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgd2l0aCBPblxuICovXG4gZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09mZkFsbCgpIHtcbiAgICBjb25zdCBldmVudE5hbWVzID0gT2JqZWN0LmtleXMoZXZlbnRMaXN0ZW5lcnMpO1xuICAgIGV2ZW50TmFtZXMuZm9yRWFjaChldmVudE5hbWUgPT4ge1xuICAgICAgICByZW1vdmVMaXN0ZW5lcihldmVudE5hbWUpXG4gICAgfSlcbn1cblxuLyoqXG4gKiBsaXN0ZW5lck9mZiB1bnJlZ2lzdGVycyBhIGxpc3RlbmVyIHByZXZpb3VzbHkgcmVnaXN0ZXJlZCB3aXRoIEV2ZW50c09uXG4gKlxuICogQHBhcmFtIHtMaXN0ZW5lcn0gbGlzdGVuZXJcbiAqL1xuIGZ1bmN0aW9uIGxpc3RlbmVyT2ZmKGxpc3RlbmVyKSB7XG4gICAgY29uc3QgZXZlbnROYW1lID0gbGlzdGVuZXIuZXZlbnROYW1lO1xuICAgIGlmIChldmVudExpc3RlbmVyc1tldmVudE5hbWVdID09PSB1bmRlZmluZWQpIHJldHVybjtcblxuICAgIC8vIFJlbW92ZSBsb2NhbCBsaXN0ZW5lclxuICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gPSBldmVudExpc3RlbmVyc1tldmVudE5hbWVdLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcblxuICAgIC8vIENsZWFuIHVwIGlmIHRoZXJlIGFyZSBubyBldmVudCBsaXN0ZW5lcnMgbGVmdFxuICAgIGlmIChldmVudExpc3RlbmVyc1tldmVudE5hbWVdLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICByZW1vdmVMaXN0ZW5lcihldmVudE5hbWUpO1xuICAgIH1cbn1cbiIsICIvKlxuIF8gICAgICAgX18gICAgICBfIF9fXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDYgKi9cblxuZXhwb3J0IGNvbnN0IGNhbGxiYWNrcyA9IHt9O1xuXG4vKipcbiAqIFJldHVybnMgYSBudW1iZXIgZnJvbSB0aGUgbmF0aXZlIGJyb3dzZXIgcmFuZG9tIGZ1bmN0aW9uXG4gKlxuICogQHJldHVybnMgbnVtYmVyXG4gKi9cbmZ1bmN0aW9uIGNyeXB0b1JhbmRvbSgpIHtcblx0dmFyIGFycmF5ID0gbmV3IFVpbnQzMkFycmF5KDEpO1xuXHRyZXR1cm4gd2luZG93LmNyeXB0by5nZXRSYW5kb21WYWx1ZXMoYXJyYXkpWzBdO1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBudW1iZXIgdXNpbmcgZGEgb2xkLXNrb29sIE1hdGguUmFuZG9tXG4gKiBJIGxpa2VzIHRvIGNhbGwgaXQgTE9MUmFuZG9tXG4gKlxuICogQHJldHVybnMgbnVtYmVyXG4gKi9cbmZ1bmN0aW9uIGJhc2ljUmFuZG9tKCkge1xuXHRyZXR1cm4gTWF0aC5yYW5kb20oKSAqIDkwMDcxOTkyNTQ3NDA5OTE7XG59XG5cbi8vIFBpY2sgYSByYW5kb20gbnVtYmVyIGZ1bmN0aW9uIGJhc2VkIG9uIGJyb3dzZXIgY2FwYWJpbGl0eVxudmFyIHJhbmRvbUZ1bmM7XG5pZiAod2luZG93LmNyeXB0bykge1xuXHRyYW5kb21GdW5jID0gY3J5cHRvUmFuZG9tO1xufSBlbHNlIHtcblx0cmFuZG9tRnVuYyA9IGJhc2ljUmFuZG9tO1xufVxuXG5cbi8qKlxuICogQ2FsbCBzZW5kcyBhIG1lc3NhZ2UgdG8gdGhlIGJhY2tlbmQgdG8gY2FsbCB0aGUgYmluZGluZyB3aXRoIHRoZVxuICogZ2l2ZW4gZGF0YS4gQSBwcm9taXNlIGlzIHJldHVybmVkIGFuZCB3aWxsIGJlIGNvbXBsZXRlZCB3aGVuIHRoZVxuICogYmFja2VuZCByZXNwb25kcy4gVGhpcyB3aWxsIGJlIHJlc29sdmVkIHdoZW4gdGhlIGNhbGwgd2FzIHN1Y2Nlc3NmdWxcbiAqIG9yIHJlamVjdGVkIGlmIGFuIGVycm9yIGlzIHBhc3NlZCBiYWNrLlxuICogVGhlcmUgaXMgYSB0aW1lb3V0IG1lY2hhbmlzbS4gSWYgdGhlIGNhbGwgZG9lc24ndCByZXNwb25kIGluIHRoZSBnaXZlblxuICogdGltZSAoaW4gbWlsbGlzZWNvbmRzKSB0aGVuIHRoZSBwcm9taXNlIGlzIHJlamVjdGVkLlxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lXG4gKiBAcGFyYW0ge2FueT19IGFyZ3NcbiAqIEBwYXJhbSB7bnVtYmVyPX0gdGltZW91dFxuICogQHJldHVybnNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENhbGwobmFtZSwgYXJncywgdGltZW91dCkge1xuXG5cdC8vIFRpbWVvdXQgaW5maW5pdGUgYnkgZGVmYXVsdFxuXHRpZiAodGltZW91dCA9PSBudWxsKSB7XG5cdFx0dGltZW91dCA9IDA7XG5cdH1cblxuXHQvLyBDcmVhdGUgYSBwcm9taXNlXG5cdHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbiAocmVzb2x2ZSwgcmVqZWN0KSB7XG5cblx0XHQvLyBDcmVhdGUgYSB1bmlxdWUgY2FsbGJhY2tJRFxuXHRcdHZhciBjYWxsYmFja0lEO1xuXHRcdGRvIHtcblx0XHRcdGNhbGxiYWNrSUQgPSBuYW1lICsgJy0nICsgcmFuZG9tRnVuYygpO1xuXHRcdH0gd2hpbGUgKGNhbGxiYWNrc1tjYWxsYmFja0lEXSk7XG5cblx0XHR2YXIgdGltZW91dEhhbmRsZTtcblx0XHQvLyBTZXQgdGltZW91dFxuXHRcdGlmICh0aW1lb3V0ID4gMCkge1xuXHRcdFx0dGltZW91dEhhbmRsZSA9IHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuXHRcdFx0XHRyZWplY3QoRXJyb3IoJ0NhbGwgdG8gJyArIG5hbWUgKyAnIHRpbWVkIG91dC4gUmVxdWVzdCBJRDogJyArIGNhbGxiYWNrSUQpKTtcblx0XHRcdH0sIHRpbWVvdXQpO1xuXHRcdH1cblxuXHRcdC8vIFN0b3JlIGNhbGxiYWNrXG5cdFx0Y2FsbGJhY2tzW2NhbGxiYWNrSURdID0ge1xuXHRcdFx0dGltZW91dEhhbmRsZTogdGltZW91dEhhbmRsZSxcblx0XHRcdHJlamVjdDogcmVqZWN0LFxuXHRcdFx0cmVzb2x2ZTogcmVzb2x2ZVxuXHRcdH07XG5cblx0XHR0cnkge1xuXHRcdFx0Y29uc3QgcGF5bG9hZCA9IHtcblx0XHRcdFx0bmFtZSxcblx0XHRcdFx0YXJncyxcblx0XHRcdFx0Y2FsbGJhY2tJRCxcblx0XHRcdH07XG5cbiAgICAgICAgICAgIC8vIE1ha2UgdGhlIGNhbGxcbiAgICAgICAgICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnQycgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZVxuICAgICAgICAgICAgY29uc29sZS5lcnJvcihlKTtcbiAgICAgICAgfVxuICAgIH0pO1xufVxuXG53aW5kb3cuT2JmdXNjYXRlZENhbGwgPSAoaWQsIGFyZ3MsIHRpbWVvdXQpID0+IHtcblxuICAgIC8vIFRpbWVvdXQgaW5maW5pdGUgYnkgZGVmYXVsdFxuICAgIGlmICh0aW1lb3V0ID09IG51bGwpIHtcbiAgICAgICAgdGltZW91dCA9IDA7XG4gICAgfVxuXG4gICAgLy8gQ3JlYXRlIGEgcHJvbWlzZVxuICAgIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbiAocmVzb2x2ZSwgcmVqZWN0KSB7XG5cbiAgICAgICAgLy8gQ3JlYXRlIGEgdW5pcXVlIGNhbGxiYWNrSURcbiAgICAgICAgdmFyIGNhbGxiYWNrSUQ7XG4gICAgICAgIGRvIHtcbiAgICAgICAgICAgIGNhbGxiYWNrSUQgPSBpZCArICctJyArIHJhbmRvbUZ1bmMoKTtcbiAgICAgICAgfSB3aGlsZSAoY2FsbGJhY2tzW2NhbGxiYWNrSURdKTtcblxuICAgICAgICB2YXIgdGltZW91dEhhbmRsZTtcbiAgICAgICAgLy8gU2V0IHRpbWVvdXRcbiAgICAgICAgaWYgKHRpbWVvdXQgPiAwKSB7XG4gICAgICAgICAgICB0aW1lb3V0SGFuZGxlID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKCdDYWxsIHRvIG1ldGhvZCAnICsgaWQgKyAnIHRpbWVkIG91dC4gUmVxdWVzdCBJRDogJyArIGNhbGxiYWNrSUQpKTtcbiAgICAgICAgICAgIH0sIHRpbWVvdXQpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gU3RvcmUgY2FsbGJhY2tcbiAgICAgICAgY2FsbGJhY2tzW2NhbGxiYWNrSURdID0ge1xuICAgICAgICAgICAgdGltZW91dEhhbmRsZTogdGltZW91dEhhbmRsZSxcbiAgICAgICAgICAgIHJlamVjdDogcmVqZWN0LFxuICAgICAgICAgICAgcmVzb2x2ZTogcmVzb2x2ZVxuICAgICAgICB9O1xuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuXHRcdFx0XHRpZCxcblx0XHRcdFx0YXJncyxcblx0XHRcdFx0Y2FsbGJhY2tJRCxcblx0XHRcdH07XG5cbiAgICAgICAgICAgIC8vIE1ha2UgdGhlIGNhbGxcbiAgICAgICAgICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnYycgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZVxuICAgICAgICAgICAgY29uc29sZS5lcnJvcihlKTtcbiAgICAgICAgfVxuICAgIH0pO1xufTtcblxuXG4vKipcbiAqIENhbGxlZCBieSB0aGUgYmFja2VuZCB0byByZXR1cm4gZGF0YSB0byBhIHByZXZpb3VzbHkgY2FsbGVkXG4gKiBiaW5kaW5nIGludm9jYXRpb25cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gaW5jb21pbmdNZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDYWxsYmFjayhpbmNvbWluZ01lc3NhZ2UpIHtcblx0Ly8gUGFyc2UgdGhlIG1lc3NhZ2Vcblx0bGV0IG1lc3NhZ2U7XG5cdHRyeSB7XG5cdFx0bWVzc2FnZSA9IEpTT04ucGFyc2UoaW5jb21pbmdNZXNzYWdlKTtcblx0fSBjYXRjaCAoZSkge1xuXHRcdGNvbnN0IGVycm9yID0gYEludmFsaWQgSlNPTiBwYXNzZWQgdG8gY2FsbGJhY2s6ICR7ZS5tZXNzYWdlfS4gTWVzc2FnZTogJHtpbmNvbWluZ01lc3NhZ2V9YDtcblx0XHRydW50aW1lLkxvZ0RlYnVnKGVycm9yKTtcblx0XHR0aHJvdyBuZXcgRXJyb3IoZXJyb3IpO1xuXHR9XG5cdGxldCBjYWxsYmFja0lEID0gbWVzc2FnZS5jYWxsYmFja2lkO1xuXHRsZXQgY2FsbGJhY2tEYXRhID0gY2FsbGJhY2tzW2NhbGxiYWNrSURdO1xuXHRpZiAoIWNhbGxiYWNrRGF0YSkge1xuXHRcdGNvbnN0IGVycm9yID0gYENhbGxiYWNrICcke2NhbGxiYWNrSUR9JyBub3QgcmVnaXN0ZXJlZCEhIWA7XG5cdFx0Y29uc29sZS5lcnJvcihlcnJvcik7IC8vIGVzbGludC1kaXNhYmxlLWxpbmVcblx0XHR0aHJvdyBuZXcgRXJyb3IoZXJyb3IpO1xuXHR9XG5cdGNsZWFyVGltZW91dChjYWxsYmFja0RhdGEudGltZW91dEhhbmRsZSk7XG5cblx0ZGVsZXRlIGNhbGxiYWNrc1tjYWxsYmFja0lEXTtcblxuXHRpZiAobWVzc2FnZS5lcnJvcikge1xuXHRcdGNhbGxiYWNrRGF0YS5yZWplY3QobWVzc2FnZS5lcnJvcik7XG5cdH0gZWxzZSB7XG5cdFx0Y2FsbGJhY2tEYXRhLnJlc29sdmUobWVzc2FnZS5yZXN1bHQpO1xuXHR9XG59XG4iLCAiLypcbiBfICAgICAgIF9fICAgICAgXyBfXyAgICBcbnwgfCAgICAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKSBcbnxfXy98X18vXFxfXyxfL18vXy9fX19fLyAgXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuLyoganNoaW50IGVzdmVyc2lvbjogNiAqL1xuXG5pbXBvcnQge0NhbGx9IGZyb20gJy4vY2FsbHMnO1xuXG4vLyBUaGlzIGlzIHdoZXJlIHdlIGJpbmQgZ28gbWV0aG9kIHdyYXBwZXJzXG53aW5kb3cuZ28gPSB7fTtcblxuZXhwb3J0IGZ1bmN0aW9uIFNldEJpbmRpbmdzKGJpbmRpbmdzTWFwKSB7XG5cdHRyeSB7XG5cdFx0YmluZGluZ3NNYXAgPSBKU09OLnBhcnNlKGJpbmRpbmdzTWFwKTtcblx0fSBjYXRjaCAoZSkge1xuXHRcdGNvbnNvbGUuZXJyb3IoZSk7XG5cdH1cblxuXHQvLyBJbml0aWFsaXNlIHRoZSBiaW5kaW5ncyBtYXBcblx0d2luZG93LmdvID0gd2luZG93LmdvIHx8IHt9O1xuXG5cdC8vIEl0ZXJhdGUgcGFja2FnZSBuYW1lc1xuXHRPYmplY3Qua2V5cyhiaW5kaW5nc01hcCkuZm9yRWFjaCgocGFja2FnZU5hbWUpID0+IHtcblxuXHRcdC8vIENyZWF0ZSBpbm5lciBtYXAgaWYgaXQgZG9lc24ndCBleGlzdFxuXHRcdHdpbmRvdy5nb1twYWNrYWdlTmFtZV0gPSB3aW5kb3cuZ29bcGFja2FnZU5hbWVdIHx8IHt9O1xuXG5cdFx0Ly8gSXRlcmF0ZSBzdHJ1Y3QgbmFtZXNcblx0XHRPYmplY3Qua2V5cyhiaW5kaW5nc01hcFtwYWNrYWdlTmFtZV0pLmZvckVhY2goKHN0cnVjdE5hbWUpID0+IHtcblxuXHRcdFx0Ly8gQ3JlYXRlIGlubmVyIG1hcCBpZiBpdCBkb2Vzbid0IGV4aXN0XG5cdFx0XHR3aW5kb3cuZ29bcGFja2FnZU5hbWVdW3N0cnVjdE5hbWVdID0gd2luZG93LmdvW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXSB8fCB7fTtcblxuXHRcdFx0T2JqZWN0LmtleXMoYmluZGluZ3NNYXBbcGFja2FnZU5hbWVdW3N0cnVjdE5hbWVdKS5mb3JFYWNoKChtZXRob2ROYW1lKSA9PiB7XG5cblx0XHRcdFx0d2luZG93LmdvW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXVttZXRob2ROYW1lXSA9IGZ1bmN0aW9uICgpIHtcblxuXHRcdFx0XHRcdC8vIE5vIHRpbWVvdXQgYnkgZGVmYXVsdFxuXHRcdFx0XHRcdGxldCB0aW1lb3V0ID0gMDtcblxuXHRcdFx0XHRcdC8vIEFjdHVhbCBmdW5jdGlvblxuXHRcdFx0XHRcdGZ1bmN0aW9uIGR5bmFtaWMoKSB7XG5cdFx0XHRcdFx0XHRjb25zdCBhcmdzID0gW10uc2xpY2UuY2FsbChhcmd1bWVudHMpO1xuXHRcdFx0XHRcdFx0cmV0dXJuIENhbGwoW3BhY2thZ2VOYW1lLCBzdHJ1Y3ROYW1lLCBtZXRob2ROYW1lXS5qb2luKCcuJyksIGFyZ3MsIHRpbWVvdXQpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIEFsbG93IHNldHRpbmcgdGltZW91dCB0byBmdW5jdGlvblxuXHRcdFx0XHRcdGR5bmFtaWMuc2V0VGltZW91dCA9IGZ1bmN0aW9uIChuZXdUaW1lb3V0KSB7XG5cdFx0XHRcdFx0XHR0aW1lb3V0ID0gbmV3VGltZW91dDtcblx0XHRcdFx0XHR9O1xuXG5cdFx0XHRcdFx0Ly8gQWxsb3cgZ2V0dGluZyB0aW1lb3V0IHRvIGZ1bmN0aW9uXG5cdFx0XHRcdFx0ZHluYW1pYy5nZXRUaW1lb3V0ID0gZnVuY3Rpb24gKCkge1xuXHRcdFx0XHRcdFx0cmV0dXJuIHRpbWVvdXQ7XG5cdFx0XHRcdFx0fTtcblxuXHRcdFx0XHRcdHJldHVybiBkeW5hbWljO1xuXHRcdFx0XHR9KCk7XG5cdFx0XHR9KTtcblx0XHR9KTtcblx0fSk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuXG5pbXBvcnQge0NhbGx9IGZyb20gXCIuL2NhbGxzXCI7XG5cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dSZWxvYWQoKSB7XG4gICAgd2luZG93LmxvY2F0aW9uLnJlbG9hZCgpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gV2luZG93UmVsb2FkQXBwKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1InKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFN5c3RlbURlZmF1bHRUaGVtZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dBU0RUJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRMaWdodFRoZW1lKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0FMVCcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0RGFya1RoZW1lKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0FEVCcpO1xufVxuXG4vKipcbiAqIFBsYWNlIHRoZSB3aW5kb3cgaW4gdGhlIGNlbnRlciBvZiB0aGUgc2NyZWVuXG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93Q2VudGVyKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV2MnKTtcbn1cblxuLyoqXG4gKiBTZXRzIHRoZSB3aW5kb3cgdGl0bGVcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gdGl0bGVcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFRpdGxlKHRpdGxlKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXVCcgKyB0aXRsZSk7XG59XG5cbi8qKlxuICogTWFrZXMgdGhlIHdpbmRvdyBnbyBmdWxsc2NyZWVuXG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93RnVsbHNjcmVlbigpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dGJyk7XG59XG5cbi8qKlxuICogUmV2ZXJ0cyB0aGUgd2luZG93IGZyb20gZnVsbHNjcmVlblxuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1VuZnVsbHNjcmVlbigpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dmJyk7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgc3RhdGUgb2YgdGhlIHdpbmRvdywgaS5lLiB3aGV0aGVyIHRoZSB3aW5kb3cgaXMgaW4gZnVsbCBzY3JlZW4gbW9kZSBvciBub3QuXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTxib29sZWFuPn0gVGhlIHN0YXRlIG9mIHRoZSB3aW5kb3dcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0lzRnVsbHNjcmVlbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dJc0Z1bGxzY3JlZW5cIik7XG59XG5cbi8qKlxuICogU2V0IHRoZSBTaXplIG9mIHRoZSB3aW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge251bWJlcn0gd2lkdGhcbiAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFNpemUod2lkdGgsIGhlaWdodCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3M6JyArIHdpZHRoICsgJzonICsgaGVpZ2h0KTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIFNpemUgb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8e3c6IG51bWJlciwgaDogbnVtYmVyfT59IFRoZSBzaXplIG9mIHRoZSB3aW5kb3dcblxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93R2V0U2l6ZSgpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dHZXRTaXplXCIpO1xufVxuXG4vKipcbiAqIFNldCB0aGUgbWF4aW11bSBzaXplIG9mIHRoZSB3aW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge251bWJlcn0gd2lkdGhcbiAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldE1heFNpemUod2lkdGgsIGhlaWdodCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1o6JyArIHdpZHRoICsgJzonICsgaGVpZ2h0KTtcbn1cblxuLyoqXG4gKiBTZXQgdGhlIG1pbmltdW0gc2l6ZSBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRNaW5TaXplKHdpZHRoLCBoZWlnaHQpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1d6OicgKyB3aWR0aCArICc6JyArIGhlaWdodCk7XG59XG5cblxuXG4vKipcbiAqIFNldCB0aGUgd2luZG93IEFsd2F5c09uVG9wIG9yIG5vdCBvbiB0b3BcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRBbHdheXNPblRvcChiKSB7XG5cbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dBVFA6JyArIChiID8gJzEnIDogJzAnKSk7XG59XG5cblxuXG5cbi8qKlxuICogU2V0IHRoZSBQb3NpdGlvbiBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IHhcbiAqIEBwYXJhbSB7bnVtYmVyfSB5XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRQb3NpdGlvbih4LCB5KSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXcDonICsgeCArICc6JyArIHkpO1xufVxuXG4vKipcbiAqIEdldCB0aGUgUG9zaXRpb24gb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8e3g6IG51bWJlciwgeTogbnVtYmVyfT59IFRoZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dHZXRQb3NpdGlvbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dHZXRQb3NcIik7XG59XG5cbi8qKlxuICogSGlkZSB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SGlkZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dIJyk7XG59XG5cbi8qKlxuICogU2hvdyB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2hvdygpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dTJyk7XG59XG5cbi8qKlxuICogTWF4aW1pc2UgdGhlIFdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd01heGltaXNlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV00nKTtcbn1cblxuLyoqXG4gKiBUb2dnbGUgdGhlIE1heGltaXNlIG9mIHRoZSBXaW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dUb2dnbGVNYXhpbWlzZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1d0Jyk7XG59XG5cbi8qKlxuICogVW5tYXhpbWlzZSB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93VW5tYXhpbWlzZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dVJyk7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgc3RhdGUgb2YgdGhlIHdpbmRvdywgaS5lLiB3aGV0aGVyIHRoZSB3aW5kb3cgaXMgbWF4aW1pc2VkIG9yIG5vdC5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPGJvb2xlYW4+fSBUaGUgc3RhdGUgb2YgdGhlIHdpbmRvd1xuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SXNNYXhpbWlzZWQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93SXNNYXhpbWlzZWRcIik7XG59XG5cbi8qKlxuICogTWluaW1pc2UgdGhlIFdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd01pbmltaXNlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV20nKTtcbn1cblxuLyoqXG4gKiBVbm1pbmltaXNlIHRoZSBXaW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dVbm1pbmltaXNlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3UnKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBzdGF0ZSBvZiB0aGUgd2luZG93LCBpLmUuIHdoZXRoZXIgdGhlIHdpbmRvdyBpcyBtaW5pbWlzZWQgb3Igbm90LlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRoZSBzdGF0ZSBvZiB0aGUgd2luZG93XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dJc01pbmltaXNlZCgpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dJc01pbmltaXNlZFwiKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBzdGF0ZSBvZiB0aGUgd2luZG93LCBpLmUuIHdoZXRoZXIgdGhlIHdpbmRvdyBpcyBub3JtYWwgb3Igbm90LlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRoZSBzdGF0ZSBvZiB0aGUgd2luZG93XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dJc05vcm1hbCgpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dJc05vcm1hbFwiKTtcbn1cblxuLyoqXG4gKiBTZXRzIHRoZSBiYWNrZ3JvdW5kIGNvbG91ciBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IFIgUmVkXG4gKiBAcGFyYW0ge251bWJlcn0gRyBHcmVlblxuICogQHBhcmFtIHtudW1iZXJ9IEIgQmx1ZVxuICogQHBhcmFtIHtudW1iZXJ9IEEgQWxwaGFcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldEJhY2tncm91bmRDb2xvdXIoUiwgRywgQiwgQSkge1xuICAgIGxldCByZ2JhID0gSlNPTi5zdHJpbmdpZnkoe3I6IFIgfHwgMCwgZzogRyB8fCAwLCBiOiBCIHx8IDAsIGE6IEEgfHwgMjU1fSk7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXcjonICsgcmdiYSk7XG59XG5cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5cbmltcG9ydCB7Q2FsbH0gZnJvbSBcIi4vY2FsbHNcIjtcblxuXG4vKipcbiAqIEdldHMgdGhlIGFsbCBzY3JlZW5zLiBDYWxsIHRoaXMgYW5ldyBlYWNoIHRpbWUgeW91IHdhbnQgdG8gcmVmcmVzaCBkYXRhIGZyb20gdGhlIHVuZGVybHlpbmcgd2luZG93aW5nIHN5c3RlbS5cbiAqIEBleHBvcnRcbiAqIEB0eXBlZGVmIHtpbXBvcnQoJy4uL3dyYXBwZXIvcnVudGltZScpLlNjcmVlbn0gU2NyZWVuXG4gKiBAcmV0dXJuIHtQcm9taXNlPHtTY3JlZW5bXX0+fSBUaGUgc2NyZWVuc1xuICovXG5leHBvcnQgZnVuY3Rpb24gU2NyZWVuR2V0QWxsKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlNjcmVlbkdldEFsbFwiKTtcbn1cbiIsICIvKipcbiAqIEBkZXNjcmlwdGlvbjogVXNlIHRoZSBzeXN0ZW0gZGVmYXVsdCBicm93c2VyIHRvIG9wZW4gdGhlIHVybFxuICogQHBhcmFtIHtzdHJpbmd9IHVybCBcbiAqIEByZXR1cm4ge3ZvaWR9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCcm93c2VyT3BlblVSTCh1cmwpIHtcbiAgd2luZG93LldhaWxzSW52b2tlKCdCTzonICsgdXJsKTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtDYWxsfSBmcm9tIFwiLi9jYWxsc1wiO1xuXG4vKipcbiAqIFNldCB0aGUgU2l6ZSBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IHRleHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENsaXBib2FyZFNldFRleHQodGV4dCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOkNsaXBib2FyZFNldFRleHRcIiwgW3RleHRdKTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIHRleHQgY29udGVudCBvZiB0aGUgY2xpcGJvYXJkXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTx7c3RyaW5nfT59IFRleHQgY29udGVudCBvZiB0aGUgY2xpcGJvYXJkXG5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENsaXBib2FyZEdldFRleHQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6Q2xpcGJvYXJkR2V0VGV4dFwiKTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtFdmVudHNPbiwgRXZlbnRzT2ZmfSBmcm9tIFwiLi9ldmVudHNcIjtcblxuY29uc3QgZmxhZ3MgPSB7XG4gICAgcmVnaXN0ZXJlZDogZmFsc2UsXG4gICAgZGVmYXVsdFVzZURyb3BUYXJnZXQ6IHRydWUsXG4gICAgdXNlRHJvcFRhcmdldDogdHJ1ZSxcbiAgICBuZXh0RGVhY3RpdmF0ZTogbnVsbCxcbiAgICBuZXh0RGVhY3RpdmF0ZVRpbWVvdXQ6IG51bGwsXG59O1xuXG5jb25zdCBEUk9QX1RBUkdFVF9BQ1RJVkUgPSBcIndhaWxzLWRyb3AtdGFyZ2V0LWFjdGl2ZVwiO1xuXG4vKipcbiAqIGNoZWNrU3R5bGVEcm9wVGFyZ2V0IGNoZWNrcyBpZiB0aGUgc3R5bGUgaGFzIHRoZSBkcm9wIHRhcmdldCBhdHRyaWJ1dGVcbiAqIFxuICogQHBhcmFtIHtDU1NTdHlsZURlY2xhcmF0aW9ufSBzdHlsZSBcbiAqIEByZXR1cm5zIFxuICovXG5mdW5jdGlvbiBjaGVja1N0eWxlRHJvcFRhcmdldChzdHlsZSkge1xuICAgIGNvbnN0IGNzc0Ryb3BWYWx1ZSA9IHN0eWxlLmdldFByb3BlcnR5VmFsdWUod2luZG93LndhaWxzLmZsYWdzLmNzc0Ryb3BQcm9wZXJ0eSkudHJpbSgpO1xuICAgIGlmIChjc3NEcm9wVmFsdWUpIHtcbiAgICAgICAgaWYgKGNzc0Ryb3BWYWx1ZSA9PT0gd2luZG93LndhaWxzLmZsYWdzLmNzc0Ryb3BWYWx1ZSkge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gaWYgdGhlIGVsZW1lbnQgaGFzIHRoZSBkcm9wIHRhcmdldCBhdHRyaWJ1dGUsIGJ1dCBcbiAgICAgICAgLy8gdGhlIHZhbHVlIGlzIG5vdCBjb3JyZWN0LCB0ZXJtaW5hdGUgZmluZGluZyBwcm9jZXNzLlxuICAgICAgICAvLyBUaGlzIGNhbiBiZSB1c2VmdWwgdG8gYmxvY2sgc29tZSBjaGlsZCBlbGVtZW50cyBmcm9tIGJlaW5nIGRyb3AgdGFyZ2V0cy5cbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG59XG5cbi8qKlxuICogb25EcmFnT3ZlciBpcyBjYWxsZWQgd2hlbiB0aGUgZHJhZ292ZXIgZXZlbnQgaXMgZW1pdHRlZC5cbiAqIEBwYXJhbSB7RHJhZ0V2ZW50fSBlXG4gKiBAcmV0dXJuc1xuICovXG5mdW5jdGlvbiBvbkRyYWdPdmVyKGUpIHtcbiAgICAvLyBDaGVjayBpZiB0aGlzIGlzIGFuIGV4dGVybmFsIGZpbGUgZHJvcCBvciBpbnRlcm5hbCBIVE1MIGRyYWdcbiAgICAvLyBFeHRlcm5hbCBmaWxlIGRyb3BzIHdpbGwgaGF2ZSBcIkZpbGVzXCIgaW4gdGhlIHR5cGVzIGFycmF5XG4gICAgLy8gSW50ZXJuYWwgSFRNTCBkcmFncyB0eXBpY2FsbHkgaGF2ZSBcInRleHQvcGxhaW5cIiwgXCJ0ZXh0L2h0bWxcIiBvciBjdXN0b20gdHlwZXNcbiAgICBjb25zdCBpc0ZpbGVEcm9wID0gZS5kYXRhVHJhbnNmZXIudHlwZXMuaW5jbHVkZXMoXCJGaWxlc1wiKTtcblxuICAgIC8vIE9ubHkgaGFuZGxlIGV4dGVybmFsIGZpbGUgZHJvcHMsIGxldCBpbnRlcm5hbCBIVE1MNSBkcmFnLWFuZC1kcm9wIHdvcmsgbm9ybWFsbHlcbiAgICBpZiAoIWlzRmlsZURyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIEFMV0FZUyBwcmV2ZW50IGRlZmF1bHQgZm9yIGZpbGUgZHJvcHMgdG8gc3RvcCBicm93c2VyIG5hdmlnYXRpb25cbiAgICBlLnByZXZlbnREZWZhdWx0KCk7XG4gICAgZS5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdjb3B5JztcblxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVdhaWxzRHJhZ0FuZERyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmICghZmxhZ3MudXNlRHJvcFRhcmdldCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgZWxlbWVudCA9IGUudGFyZ2V0O1xuXG4gICAgLy8gVHJpZ2dlciBkZWJvdW5jZSBmdW5jdGlvbiB0byBkZWFjdGl2YXRlIGRyb3AgdGFyZ2V0c1xuICAgIGlmKGZsYWdzLm5leHREZWFjdGl2YXRlKSBmbGFncy5uZXh0RGVhY3RpdmF0ZSgpO1xuXG4gICAgLy8gaWYgdGhlIGVsZW1lbnQgaXMgbnVsbCBvciBlbGVtZW50IGlzIG5vdCBjaGlsZCBvZiBkcm9wIHRhcmdldCBlbGVtZW50XG4gICAgaWYgKCFlbGVtZW50IHx8ICFjaGVja1N0eWxlRHJvcFRhcmdldChnZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpKSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGV0IGN1cnJlbnRFbGVtZW50ID0gZWxlbWVudDtcbiAgICB3aGlsZSAoY3VycmVudEVsZW1lbnQpIHtcbiAgICAgICAgLy8gY2hlY2sgaWYgY3VycmVudEVsZW1lbnQgaXMgZHJvcCB0YXJnZXQgZWxlbWVudFxuICAgICAgICBpZiAoY2hlY2tTdHlsZURyb3BUYXJnZXQoZ2V0Q29tcHV0ZWRTdHlsZShjdXJyZW50RWxlbWVudCkpKSB7XG4gICAgICAgICAgICBjdXJyZW50RWxlbWVudC5jbGFzc0xpc3QuYWRkKERST1BfVEFSR0VUX0FDVElWRSk7XG4gICAgICAgIH1cbiAgICAgICAgY3VycmVudEVsZW1lbnQgPSBjdXJyZW50RWxlbWVudC5wYXJlbnRFbGVtZW50O1xuICAgIH1cbn1cblxuLyoqXG4gKiBvbkRyYWdMZWF2ZSBpcyBjYWxsZWQgd2hlbiB0aGUgZHJhZ2xlYXZlIGV2ZW50IGlzIGVtaXR0ZWQuXG4gKiBAcGFyYW0ge0RyYWdFdmVudH0gZVxuICogQHJldHVybnNcbiAqL1xuZnVuY3Rpb24gb25EcmFnTGVhdmUoZSkge1xuICAgIC8vIENoZWNrIGlmIHRoaXMgaXMgYW4gZXh0ZXJuYWwgZmlsZSBkcm9wIG9yIGludGVybmFsIEhUTUwgZHJhZ1xuICAgIGNvbnN0IGlzRmlsZURyb3AgPSBlLmRhdGFUcmFuc2Zlci50eXBlcy5pbmNsdWRlcyhcIkZpbGVzXCIpO1xuXG4gICAgLy8gT25seSBoYW5kbGUgZXh0ZXJuYWwgZmlsZSBkcm9wcywgbGV0IGludGVybmFsIEhUTUw1IGRyYWctYW5kLWRyb3Agd29yayBub3JtYWxseVxuICAgIGlmICghaXNGaWxlRHJvcCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gQUxXQVlTIHByZXZlbnQgZGVmYXVsdCBmb3IgZmlsZSBkcm9wcyB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxuICAgIGUucHJldmVudERlZmF1bHQoKTtcblxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVdhaWxzRHJhZ0FuZERyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmICghZmxhZ3MudXNlRHJvcFRhcmdldCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gRmluZCB0aGUgY2xvc2UgZHJvcCB0YXJnZXQgZWxlbWVudFxuICAgIGlmICghZS50YXJnZXQgfHwgIWNoZWNrU3R5bGVEcm9wVGFyZ2V0KGdldENvbXB1dGVkU3R5bGUoZS50YXJnZXQpKSkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICAvLyBUcmlnZ2VyIGRlYm91bmNlIGZ1bmN0aW9uIHRvIGRlYWN0aXZhdGUgZHJvcCB0YXJnZXRzXG4gICAgaWYoZmxhZ3MubmV4dERlYWN0aXZhdGUpIGZsYWdzLm5leHREZWFjdGl2YXRlKCk7XG4gICAgXG4gICAgLy8gVXNlIGRlYm91bmNlIHRlY2huaXF1ZSB0byB0YWNsZSBkcmFnbGVhdmUgZXZlbnRzIG9uIG92ZXJsYXBwaW5nIGVsZW1lbnRzIGFuZCBkcm9wIHRhcmdldCBlbGVtZW50c1xuICAgIGZsYWdzLm5leHREZWFjdGl2YXRlID0gKCkgPT4ge1xuICAgICAgICAvLyBEZWFjdGl2YXRlIGFsbCBkcm9wIHRhcmdldHMsIG5ldyBkcm9wIHRhcmdldCB3aWxsIGJlIGFjdGl2YXRlZCBvbiBuZXh0IGRyYWdvdmVyIGV2ZW50XG4gICAgICAgIEFycmF5LmZyb20oZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZShEUk9QX1RBUkdFVF9BQ1RJVkUpKS5mb3JFYWNoKGVsID0+IGVsLmNsYXNzTGlzdC5yZW1vdmUoRFJPUF9UQVJHRVRfQUNUSVZFKSk7XG4gICAgICAgIC8vIFJlc2V0IG5leHREZWFjdGl2YXRlXG4gICAgICAgIGZsYWdzLm5leHREZWFjdGl2YXRlID0gbnVsbDtcbiAgICAgICAgLy8gQ2xlYXIgdGltZW91dFxuICAgICAgICBpZiAoZmxhZ3MubmV4dERlYWN0aXZhdGVUaW1lb3V0KSB7XG4gICAgICAgICAgICBjbGVhclRpbWVvdXQoZmxhZ3MubmV4dERlYWN0aXZhdGVUaW1lb3V0KTtcbiAgICAgICAgICAgIGZsYWdzLm5leHREZWFjdGl2YXRlVGltZW91dCA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBTZXQgdGltZW91dCB0byBkZWFjdGl2YXRlIGRyb3AgdGFyZ2V0cyBpZiBub3QgdHJpZ2dlcmVkIGJ5IG5leHQgZHJhZyBldmVudFxuICAgIGZsYWdzLm5leHREZWFjdGl2YXRlVGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICBpZihmbGFncy5uZXh0RGVhY3RpdmF0ZSkgZmxhZ3MubmV4dERlYWN0aXZhdGUoKTtcbiAgICB9LCA1MCk7XG59XG5cbi8qKlxuICogb25Ecm9wIGlzIGNhbGxlZCB3aGVuIHRoZSBkcm9wIGV2ZW50IGlzIGVtaXR0ZWQuXG4gKiBAcGFyYW0ge0RyYWdFdmVudH0gZVxuICogQHJldHVybnNcbiAqL1xuZnVuY3Rpb24gb25Ecm9wKGUpIHtcbiAgICAvLyBDaGVjayBpZiB0aGlzIGlzIGFuIGV4dGVybmFsIGZpbGUgZHJvcCBvciBpbnRlcm5hbCBIVE1MIGRyYWdcbiAgICBjb25zdCBpc0ZpbGVEcm9wID0gZS5kYXRhVHJhbnNmZXIudHlwZXMuaW5jbHVkZXMoXCJGaWxlc1wiKTtcblxuICAgIC8vIE9ubHkgaGFuZGxlIGV4dGVybmFsIGZpbGUgZHJvcHMsIGxldCBpbnRlcm5hbCBIVE1MNSBkcmFnLWFuZC1kcm9wIHdvcmsgbm9ybWFsbHlcbiAgICBpZiAoIWlzRmlsZURyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIEFMV0FZUyBwcmV2ZW50IGRlZmF1bHQgZm9yIGZpbGUgZHJvcHMgdG8gc3RvcCBicm93c2VyIG5hdmlnYXRpb25cbiAgICBlLnByZXZlbnREZWZhdWx0KCk7XG5cbiAgICBpZiAoIXdpbmRvdy53YWlscy5mbGFncy5lbmFibGVXYWlsc0RyYWdBbmREcm9wKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAoQ2FuUmVzb2x2ZUZpbGVQYXRocygpKSB7XG4gICAgICAgIC8vIHByb2Nlc3MgZmlsZXNcbiAgICAgICAgbGV0IGZpbGVzID0gW107XG4gICAgICAgIGlmIChlLmRhdGFUcmFuc2Zlci5pdGVtcykge1xuICAgICAgICAgICAgZmlsZXMgPSBbLi4uZS5kYXRhVHJhbnNmZXIuaXRlbXNdLm1hcCgoaXRlbSwgaSkgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChpdGVtLmtpbmQgPT09ICdmaWxlJykge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gaXRlbS5nZXRBc0ZpbGUoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGZpbGVzID0gWy4uLmUuZGF0YVRyYW5zZmVyLmZpbGVzXTtcbiAgICAgICAgfVxuICAgICAgICB3aW5kb3cucnVudGltZS5SZXNvbHZlRmlsZVBhdGhzKGUueCwgZS55LCBmaWxlcyk7XG4gICAgfVxuXG4gICAgaWYgKCFmbGFncy51c2VEcm9wVGFyZ2V0KSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBUcmlnZ2VyIGRlYm91bmNlIGZ1bmN0aW9uIHRvIGRlYWN0aXZhdGUgZHJvcCB0YXJnZXRzXG4gICAgaWYoZmxhZ3MubmV4dERlYWN0aXZhdGUpIGZsYWdzLm5leHREZWFjdGl2YXRlKCk7XG5cbiAgICAvLyBEZWFjdGl2YXRlIGFsbCBkcm9wIHRhcmdldHNcbiAgICBBcnJheS5mcm9tKGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoRFJPUF9UQVJHRVRfQUNUSVZFKSkuZm9yRWFjaChlbCA9PiBlbC5jbGFzc0xpc3QucmVtb3ZlKERST1BfVEFSR0VUX0FDVElWRSkpO1xufVxuXG4vKipcbiAqIHBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzIGNoZWNrcyB0aGUgYnJvd3NlcidzIGNhcGFiaWxpdHkgb2Ygc2VuZGluZyBwb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0c1xuICpcbiAqIEByZXR1cm5zIHtib29sZWFufVxuICogQGNvbnN0cnVjdG9yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDYW5SZXNvbHZlRmlsZVBhdGhzKCkge1xuICAgIHJldHVybiB3aW5kb3cuY2hyb21lPy53ZWJ2aWV3Py5wb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0cyAhPSBudWxsO1xufVxuXG4vKipcbiAqIFJlc29sdmVGaWxlUGF0aHMgc2VuZHMgZHJvcCBldmVudHMgdG8gdGhlIEdPIHNpZGUgdG8gcmVzb2x2ZSBmaWxlIHBhdGhzIG9uIHdpbmRvd3MuXG4gKlxuICogQHBhcmFtIHtudW1iZXJ9IHhcbiAqIEBwYXJhbSB7bnVtYmVyfSB5XG4gKiBAcGFyYW0ge2FueVtdfSBmaWxlc1xuICogQGNvbnN0cnVjdG9yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZXNvbHZlRmlsZVBhdGhzKHgsIHksIGZpbGVzKSB7XG4gICAgLy8gT25seSBmb3Igd2luZG93cyB3ZWJ2aWV3MiA+PSAxLjAuMTc3NC4zMFxuICAgIC8vIGh0dHBzOi8vbGVhcm4ubWljcm9zb2Z0LmNvbS9lbi11cy9taWNyb3NvZnQtZWRnZS93ZWJ2aWV3Mi9yZWZlcmVuY2Uvd2luMzIvaWNvcmV3ZWJ2aWV3MndlYm1lc3NhZ2VyZWNlaXZlZGV2ZW50YXJnczI/dmlldz13ZWJ2aWV3Mi0xLjAuMTgyMy4zMiNhcHBsaWVzLXRvXG4gICAgaWYgKHdpbmRvdy5jaHJvbWU/LndlYnZpZXc/LnBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzKSB7XG4gICAgICAgIGNocm9tZS53ZWJ2aWV3LnBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzKGBmaWxlOmRyb3A6JHt4fToke3l9YCwgZmlsZXMpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBDYWxsYmFjayBmb3IgT25GaWxlRHJvcCByZXR1cm5zIGEgc2xpY2Ugb2YgZmlsZSBwYXRoIHN0cmluZ3Mgd2hlbiBhIGRyb3AgaXMgZmluaXNoZWQuXG4gKlxuICogQGV4cG9ydFxuICogQGNhbGxiYWNrIE9uRmlsZURyb3BDYWxsYmFja1xuICogQHBhcmFtIHtudW1iZXJ9IHggLSB4IGNvb3JkaW5hdGUgb2YgdGhlIGRyb3BcbiAqIEBwYXJhbSB7bnVtYmVyfSB5IC0geSBjb29yZGluYXRlIG9mIHRoZSBkcm9wXG4gKiBAcGFyYW0ge3N0cmluZ1tdfSBwYXRocyAtIEEgbGlzdCBvZiBmaWxlIHBhdGhzLlxuICovXG5cbi8qKlxuICogT25GaWxlRHJvcCBsaXN0ZW5zIHRvIGRyYWcgYW5kIGRyb3AgZXZlbnRzIGFuZCBjYWxscyB0aGUgY2FsbGJhY2sgd2l0aCB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIGRyb3AgYW5kIGFuIGFycmF5IG9mIHBhdGggc3RyaW5ncy5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge09uRmlsZURyb3BDYWxsYmFja30gY2FsbGJhY2sgLSBDYWxsYmFjayBmb3IgT25GaWxlRHJvcCByZXR1cm5zIGEgc2xpY2Ugb2YgZmlsZSBwYXRoIHN0cmluZ3Mgd2hlbiBhIGRyb3AgaXMgZmluaXNoZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFt1c2VEcm9wVGFyZ2V0PXRydWVdIC0gT25seSBjYWxsIHRoZSBjYWxsYmFjayB3aGVuIHRoZSBkcm9wIGZpbmlzaGVkIG9uIGFuIGVsZW1lbnQgdGhhdCBoYXMgdGhlIGRyb3AgdGFyZ2V0IHN0eWxlLiAoLS13YWlscy1kcm9wLXRhcmdldClcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uRmlsZURyb3AoY2FsbGJhY2ssIHVzZURyb3BUYXJnZXQpIHtcbiAgICBpZiAodHlwZW9mIGNhbGxiYWNrICE9PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihcIkRyYWdBbmREcm9wQ2FsbGJhY2sgaXMgbm90IGEgZnVuY3Rpb25cIik7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAoZmxhZ3MucmVnaXN0ZXJlZCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGZsYWdzLnJlZ2lzdGVyZWQgPSB0cnVlO1xuXG4gICAgY29uc3QgdURUUFQgPSB0eXBlb2YgdXNlRHJvcFRhcmdldDtcbiAgICBmbGFncy51c2VEcm9wVGFyZ2V0ID0gdURUUFQgPT09IFwidW5kZWZpbmVkXCIgfHwgdURUUFQgIT09IFwiYm9vbGVhblwiID8gZmxhZ3MuZGVmYXVsdFVzZURyb3BUYXJnZXQgOiB1c2VEcm9wVGFyZ2V0O1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdkcmFnb3ZlcicsIG9uRHJhZ092ZXIpO1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdkcmFnbGVhdmUnLCBvbkRyYWdMZWF2ZSk7XG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2Ryb3AnLCBvbkRyb3ApO1xuXG4gICAgbGV0IGNiID0gY2FsbGJhY2s7XG4gICAgaWYgKGZsYWdzLnVzZURyb3BUYXJnZXQpIHtcbiAgICAgICAgY2IgPSBmdW5jdGlvbiAoeCwgeSwgcGF0aHMpIHtcbiAgICAgICAgICAgIGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KHgsIHkpXG4gICAgICAgICAgICAvLyBpZiB0aGUgZWxlbWVudCBpcyBudWxsIG9yIGVsZW1lbnQgaXMgbm90IGNoaWxkIG9mIGRyb3AgdGFyZ2V0IGVsZW1lbnQsIHJldHVybiBudWxsXG4gICAgICAgICAgICBpZiAoIWVsZW1lbnQgfHwgIWNoZWNrU3R5bGVEcm9wVGFyZ2V0KGdldENvbXB1dGVkU3R5bGUoZWxlbWVudCkpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjYWxsYmFjayh4LCB5LCBwYXRocyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBFdmVudHNPbihcIndhaWxzOmZpbGUtZHJvcFwiLCBjYik7XG59XG5cbi8qKlxuICogT25GaWxlRHJvcE9mZiByZW1vdmVzIHRoZSBkcmFnIGFuZCBkcm9wIGxpc3RlbmVycyBhbmQgaGFuZGxlcnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPbkZpbGVEcm9wT2ZmKCkge1xuICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdkcmFnb3ZlcicsIG9uRHJhZ092ZXIpO1xuICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdkcmFnbGVhdmUnLCBvbkRyYWdMZWF2ZSk7XG4gICAgd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ2Ryb3AnLCBvbkRyb3ApO1xuICAgIEV2ZW50c09mZihcIndhaWxzOmZpbGUtZHJvcFwiKTtcbiAgICBmbGFncy5yZWdpc3RlcmVkID0gZmFsc2U7XG59XG4iLCAiLypcbi0tZGVmYXVsdC1jb250ZXh0bWVudTogYXV0bzsgKGRlZmF1bHQpIHdpbGwgc2hvdyB0aGUgZGVmYXVsdCBjb250ZXh0IG1lbnUgaWYgY29udGVudEVkaXRhYmxlIGlzIHRydWUgT1IgdGV4dCBoYXMgYmVlbiBzZWxlY3RlZCBPUiBlbGVtZW50IGlzIGlucHV0IG9yIHRleHRhcmVhXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IHNob3c7IHdpbGwgYWx3YXlzIHNob3cgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGhpZGU7IHdpbGwgYWx3YXlzIGhpZGUgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XG5cblRoaXMgcnVsZSBpcyBpbmhlcml0ZWQgbGlrZSBub3JtYWwgQ1NTIHJ1bGVzLCBzbyBuZXN0aW5nIHdvcmtzIGFzIGV4cGVjdGVkXG4qL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQpIHtcbiAgICAvLyBQcm9jZXNzIGRlZmF1bHQgY29udGV4dCBtZW51XG4gICAgY29uc3QgZWxlbWVudCA9IGV2ZW50LnRhcmdldDtcbiAgICBjb25zdCBjb21wdXRlZFN0eWxlID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUoZWxlbWVudCk7XG4gICAgY29uc3QgZGVmYXVsdENvbnRleHRNZW51QWN0aW9uID0gY29tcHV0ZWRTdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1kZWZhdWx0LWNvbnRleHRtZW51XCIpLnRyaW0oKTtcbiAgICBzd2l0Y2ggKGRlZmF1bHRDb250ZXh0TWVudUFjdGlvbikge1xuICAgICAgICBjYXNlIFwic2hvd1wiOlxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjYXNlIFwiaGlkZVwiOlxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIGNvbnRlbnRFZGl0YWJsZSBpcyB0cnVlXG4gICAgICAgICAgICBpZiAoZWxlbWVudC5pc0NvbnRlbnRFZGl0YWJsZSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gQ2hlY2sgaWYgdGV4dCBoYXMgYmVlbiBzZWxlY3RlZCBhbmQgYWN0aW9uIGlzIG9uIHRoZSBzZWxlY3RlZCBlbGVtZW50c1xuICAgICAgICAgICAgY29uc3Qgc2VsZWN0aW9uID0gd2luZG93LmdldFNlbGVjdGlvbigpO1xuICAgICAgICAgICAgY29uc3QgaGFzU2VsZWN0aW9uID0gKHNlbGVjdGlvbi50b1N0cmluZygpLmxlbmd0aCA+IDApXG4gICAgICAgICAgICBpZiAoaGFzU2VsZWN0aW9uKSB7XG4gICAgICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzZWxlY3Rpb24ucmFuZ2VDb3VudDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJhbmdlID0gc2VsZWN0aW9uLmdldFJhbmdlQXQoaSk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJlY3RzID0gcmFuZ2UuZ2V0Q2xpZW50UmVjdHMoKTtcbiAgICAgICAgICAgICAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCByZWN0cy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdCA9IHJlY3RzW2pdO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQocmVjdC5sZWZ0LCByZWN0LnRvcCkgPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBDaGVjayBpZiB0YWduYW1lIGlzIGlucHV0IG9yIHRleHRhcmVhXG4gICAgICAgICAgICBpZiAoZWxlbWVudC50YWdOYW1lID09PSBcIklOUFVUXCIgfHwgZWxlbWVudC50YWdOYW1lID09PSBcIlRFWFRBUkVBXCIpIHtcbiAgICAgICAgICAgICAgICBpZiAoaGFzU2VsZWN0aW9uIHx8ICghZWxlbWVudC5yZWFkT25seSAmJiAhZWxlbWVudC5kaXNhYmxlZCkpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gaGlkZSBkZWZhdWx0IGNvbnRleHQgbWVudVxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICB9XG59XG4iLCAiLypcbiBfICAgICAgIF9fICAgICAgXyBfX1xufCB8ICAgICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXG5cbmltcG9ydCB7Q2FsbH0gZnJvbSBcIi4vY2FsbHNcIjtcblxuLyoqXG4gKiBJbml0aWFsaXplIHRoZSBub3RpZmljYXRpb24gc2VydmljZSBmb3IgdGhlIGFwcGxpY2F0aW9uLlxuICogVGhpcyBtdXN0IGJlIGNhbGxlZCBiZWZvcmUgc2VuZGluZyBhbnkgbm90aWZpY2F0aW9ucy5cbiAqIE9uIG1hY09TLCB0aGlzIGFsc28gZW5zdXJlcyB0aGUgbm90aWZpY2F0aW9uIGRlbGVnYXRlIGlzIHByb3Blcmx5IGluaXRpYWxpemVkLlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJbml0aWFsaXplTm90aWZpY2F0aW9ucygpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpJbml0aWFsaXplTm90aWZpY2F0aW9uc1wiKTtcbn1cblxuLyoqXG4gKiBDbGVhbiB1cCBub3RpZmljYXRpb24gcmVzb3VyY2VzIGFuZCByZWxlYXNlIGFueSBoZWxkIGNvbm5lY3Rpb25zLlxuICogVGhpcyBzaG91bGQgYmUgY2FsbGVkIHdoZW4gc2h1dHRpbmcgZG93biB0aGUgYXBwbGljYXRpb24gdG8gcHJvcGVybHkgcmVsZWFzZSByZXNvdXJjZXNcbiAqIChwcmltYXJpbHkgbmVlZGVkIG9uIExpbnV4IHRvIGNsb3NlIEQtQnVzIGNvbm5lY3Rpb25zKS5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gQ2xlYW51cE5vdGlmaWNhdGlvbnMoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6Q2xlYW51cE5vdGlmaWNhdGlvbnNcIik7XG59XG5cbi8qKlxuICogQ2hlY2sgaWYgbm90aWZpY2F0aW9ucyBhcmUgYXZhaWxhYmxlIG9uIHRoZSBjdXJyZW50IHBsYXRmb3JtLlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRydWUgaWYgbm90aWZpY2F0aW9ucyBhcmUgYXZhaWxhYmxlLCBmYWxzZSBvdGhlcndpc2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzTm90aWZpY2F0aW9uQXZhaWxhYmxlKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOklzTm90aWZpY2F0aW9uQXZhaWxhYmxlXCIpO1xufVxuXG4vKipcbiAqIFJlcXVlc3Qgbm90aWZpY2F0aW9uIGF1dGhvcml6YXRpb24gZnJvbSB0aGUgdXNlci5cbiAqIE9uIG1hY09TLCB0aGlzIHByb21wdHMgdGhlIHVzZXIgdG8gYWxsb3cgbm90aWZpY2F0aW9ucy5cbiAqIE9uIG90aGVyIHBsYXRmb3JtcywgdGhpcyBhbHdheXMgcmV0dXJucyB0cnVlLlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRydWUgaWYgYXV0aG9yaXphdGlvbiB3YXMgZ3JhbnRlZCwgZmFsc2Ugb3RoZXJ3aXNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZXF1ZXN0Tm90aWZpY2F0aW9uQXV0aG9yaXphdGlvbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpSZXF1ZXN0Tm90aWZpY2F0aW9uQXV0aG9yaXphdGlvblwiKTtcbn1cblxuLyoqXG4gKiBDaGVjayB0aGUgY3VycmVudCBub3RpZmljYXRpb24gYXV0aG9yaXphdGlvbiBzdGF0dXMuXG4gKiBPbiBtYWNPUywgdGhpcyBjaGVja3MgaWYgdGhlIGFwcCBoYXMgbm90aWZpY2F0aW9uIHBlcm1pc3Npb25zLlxuICogT24gb3RoZXIgcGxhdGZvcm1zLCB0aGlzIGFsd2F5cyByZXR1cm5zIHRydWUuXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTxib29sZWFuPn0gVHJ1ZSBpZiBhdXRob3JpemVkLCBmYWxzZSBvdGhlcndpc2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENoZWNrTm90aWZpY2F0aW9uQXV0aG9yaXphdGlvbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpDaGVja05vdGlmaWNhdGlvbkF1dGhvcml6YXRpb25cIik7XG59XG5cbi8qKlxuICogU2VuZCBhIGJhc2ljIG5vdGlmaWNhdGlvbiB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxuICogVGhlIG5vdGlmaWNhdGlvbiB3aWxsIGRpc3BsYXkgd2l0aCB0aGUgcHJvdmlkZWQgdGl0bGUsIHN1YnRpdGxlIChpZiBzdXBwb3J0ZWQpLCBhbmQgYm9keSB0ZXh0LlxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7T2JqZWN0fSBvcHRpb25zIC0gTm90aWZpY2F0aW9uIG9wdGlvbnNcbiAqIEBwYXJhbSB7c3RyaW5nfSBvcHRpb25zLmlkIC0gVW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoZSBub3RpZmljYXRpb25cbiAqIEBwYXJhbSB7c3RyaW5nfSBvcHRpb25zLnRpdGxlIC0gTm90aWZpY2F0aW9uIHRpdGxlXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMuc3VidGl0bGVdIC0gTm90aWZpY2F0aW9uIHN1YnRpdGxlIChtYWNPUyBhbmQgTGludXggb25seSlcbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5ib2R5XSAtIE5vdGlmaWNhdGlvbiBib2R5IHRleHRcbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5jYXRlZ29yeUlkXSAtIENhdGVnb3J5IElEIGZvciBhY3Rpb24gYnV0dG9ucyAocmVxdWlyZXMgU2VuZE5vdGlmaWNhdGlvbldpdGhBY3Rpb25zKVxuICogQHBhcmFtIHtPYmplY3Q8c3RyaW5nLCBhbnk+fSBbb3B0aW9ucy5kYXRhXSAtIEFkZGl0aW9uYWwgdXNlciBkYXRhIHRvIGF0dGFjaCB0byB0aGUgbm90aWZpY2F0aW9uXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gU2VuZE5vdGlmaWNhdGlvbihvcHRpb25zKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6U2VuZE5vdGlmaWNhdGlvblwiLCBbb3B0aW9uc10pO1xufVxuXG4vKipcbiAqIFNlbmQgYSBub3RpZmljYXRpb24gd2l0aCBhY3Rpb24gYnV0dG9ucy5cbiAqIEEgTm90aWZpY2F0aW9uQ2F0ZWdvcnkgbXVzdCBiZSByZWdpc3RlcmVkIGZpcnN0IHVzaW5nIFJlZ2lzdGVyTm90aWZpY2F0aW9uQ2F0ZWdvcnkuXG4gKiBUaGUgb3B0aW9ucy5jYXRlZ29yeUlkIG11c3QgbWF0Y2ggYSBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgY2F0ZWdvcnkgSUQuXG4gKiBJZiB0aGUgY2F0ZWdvcnkgaXMgbm90IGZvdW5kLCBhIGJhc2ljIG5vdGlmaWNhdGlvbiB3aWxsIGJlIHNlbnQgaW5zdGVhZC5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge09iamVjdH0gb3B0aW9ucyAtIE5vdGlmaWNhdGlvbiBvcHRpb25zXG4gKiBAcGFyYW0ge3N0cmluZ30gb3B0aW9ucy5pZCAtIFVuaXF1ZSBpZGVudGlmaWVyIGZvciB0aGUgbm90aWZpY2F0aW9uXG4gKiBAcGFyYW0ge3N0cmluZ30gb3B0aW9ucy50aXRsZSAtIE5vdGlmaWNhdGlvbiB0aXRsZVxuICogQHBhcmFtIHtzdHJpbmd9IFtvcHRpb25zLnN1YnRpdGxlXSAtIE5vdGlmaWNhdGlvbiBzdWJ0aXRsZSAobWFjT1MgYW5kIExpbnV4IG9ubHkpXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMuYm9keV0gLSBOb3RpZmljYXRpb24gYm9keSB0ZXh0XG4gKiBAcGFyYW0ge3N0cmluZ30gb3B0aW9ucy5jYXRlZ29yeUlkIC0gQ2F0ZWdvcnkgSUQgdGhhdCBtYXRjaGVzIGEgcmVnaXN0ZXJlZCBjYXRlZ29yeVxuICogQHBhcmFtIHtPYmplY3Q8c3RyaW5nLCBhbnk+fSBbb3B0aW9ucy5kYXRhXSAtIEFkZGl0aW9uYWwgdXNlciBkYXRhIHRvIGF0dGFjaCB0byB0aGUgbm90aWZpY2F0aW9uXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gU2VuZE5vdGlmaWNhdGlvbldpdGhBY3Rpb25zKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpTZW5kTm90aWZpY2F0aW9uV2l0aEFjdGlvbnNcIiwgW29wdGlvbnNdKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlciBhIG5vdGlmaWNhdGlvbiBjYXRlZ29yeSB0aGF0IGNhbiBiZSB1c2VkIHdpdGggU2VuZE5vdGlmaWNhdGlvbldpdGhBY3Rpb25zLlxuICogQ2F0ZWdvcmllcyBkZWZpbmUgdGhlIGFjdGlvbiBidXR0b25zIGFuZCBvcHRpb25hbCByZXBseSBmaWVsZHMgdGhhdCB3aWxsIGFwcGVhciBvbiBub3RpZmljYXRpb25zLlxuICogUmVnaXN0ZXJpbmcgYSBjYXRlZ29yeSB3aXRoIHRoZSBzYW1lIElEIGFzIGEgcHJldmlvdXNseSByZWdpc3RlcmVkIGNhdGVnb3J5IHdpbGwgb3ZlcnJpZGUgaXQuXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtPYmplY3R9IGNhdGVnb3J5IC0gTm90aWZpY2F0aW9uIGNhdGVnb3J5IGRlZmluaXRpb25cbiAqIEBwYXJhbSB7c3RyaW5nfSBjYXRlZ29yeS5pZCAtIFVuaXF1ZSBpZGVudGlmaWVyIGZvciB0aGUgY2F0ZWdvcnlcbiAqIEBwYXJhbSB7QXJyYXk8T2JqZWN0Pn0gW2NhdGVnb3J5LmFjdGlvbnNdIC0gQXJyYXkgb2YgYWN0aW9uIGJ1dHRvbnNcbiAqIEBwYXJhbSB7c3RyaW5nfSBjYXRlZ29yeS5hY3Rpb25zW10uaWQgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIGFjdGlvblxuICogQHBhcmFtIHtzdHJpbmd9IGNhdGVnb3J5LmFjdGlvbnNbXS50aXRsZSAtIERpc3BsYXkgdGl0bGUgZm9yIHRoZSBhY3Rpb24gYnV0dG9uXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtjYXRlZ29yeS5hY3Rpb25zW10uZGVzdHJ1Y3RpdmVdIC0gV2hldGhlciB0aGUgYWN0aW9uIGlzIGRlc3RydWN0aXZlIChtYWNPUy1zcGVjaWZpYylcbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2NhdGVnb3J5Lmhhc1JlcGx5RmllbGRdIC0gV2hldGhlciB0byBpbmNsdWRlIGEgdGV4dCBpbnB1dCBmaWVsZCBmb3IgcmVwbGllc1xuICogQHBhcmFtIHtzdHJpbmd9IFtjYXRlZ29yeS5yZXBseVBsYWNlaG9sZGVyXSAtIFBsYWNlaG9sZGVyIHRleHQgZm9yIHRoZSByZXBseSBmaWVsZCAocmVxdWlyZWQgaWYgaGFzUmVwbHlGaWVsZCBpcyB0cnVlKVxuICogQHBhcmFtIHtzdHJpbmd9IFtjYXRlZ29yeS5yZXBseUJ1dHRvblRpdGxlXSAtIFRpdGxlIGZvciB0aGUgcmVwbHkgYnV0dG9uIChyZXF1aXJlZCBpZiBoYXNSZXBseUZpZWxkIGlzIHRydWUpXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gUmVnaXN0ZXJOb3RpZmljYXRpb25DYXRlZ29yeShjYXRlZ29yeSkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlZ2lzdGVyTm90aWZpY2F0aW9uQ2F0ZWdvcnlcIiwgW2NhdGVnb3J5XSk7XG59XG5cbi8qKlxuICogUmVtb3ZlIGEgcHJldmlvdXNseSByZWdpc3RlcmVkIG5vdGlmaWNhdGlvbiBjYXRlZ29yeS5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gY2F0ZWdvcnlJZCAtIFRoZSBJRCBvZiB0aGUgY2F0ZWdvcnkgdG8gcmVtb3ZlXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gUmVtb3ZlTm90aWZpY2F0aW9uQ2F0ZWdvcnkoY2F0ZWdvcnlJZCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZU5vdGlmaWNhdGlvbkNhdGVnb3J5XCIsIFtjYXRlZ29yeUlkXSk7XG59XG5cbi8qKlxuICogUmVtb3ZlIGFsbCBwZW5kaW5nIG5vdGlmaWNhdGlvbnMgZnJvbSB0aGUgbm90aWZpY2F0aW9uIGNlbnRlci5cbiAqIE9uIFdpbmRvd3MsIHRoaXMgaXMgYSBuby1vcCBhcyB0aGUgcGxhdGZvcm0gbWFuYWdlcyBub3RpZmljYXRpb24gbGlmZWN5Y2xlIGF1dG9tYXRpY2FsbHkuXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlbW92ZUFsbFBlbmRpbmdOb3RpZmljYXRpb25zKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZUFsbFBlbmRpbmdOb3RpZmljYXRpb25zXCIpO1xufVxuXG4vKipcbiAqIFJlbW92ZSBhIHNwZWNpZmljIHBlbmRpbmcgbm90aWZpY2F0aW9uIGJ5IGl0cyBpZGVudGlmaWVyLlxuICogT24gV2luZG93cywgdGhpcyBpcyBhIG5vLW9wIGFzIHRoZSBwbGF0Zm9ybSBtYW5hZ2VzIG5vdGlmaWNhdGlvbiBsaWZlY3ljbGUgYXV0b21hdGljYWxseS5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gaWRlbnRpZmllciAtIFRoZSBJRCBvZiB0aGUgbm90aWZpY2F0aW9uIHRvIHJlbW92ZVxuICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlbW92ZVBlbmRpbmdOb3RpZmljYXRpb24oaWRlbnRpZmllcikge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZVBlbmRpbmdOb3RpZmljYXRpb25cIiwgW2lkZW50aWZpZXJdKTtcbn1cblxuLyoqXG4gKiBSZW1vdmUgYWxsIGRlbGl2ZXJlZCBub3RpZmljYXRpb25zIGZyb20gdGhlIG5vdGlmaWNhdGlvbiBjZW50ZXIuXG4gKiBPbiBXaW5kb3dzLCB0aGlzIGlzIGEgbm8tb3AgYXMgdGhlIHBsYXRmb3JtIG1hbmFnZXMgbm90aWZpY2F0aW9uIGxpZmVjeWNsZSBhdXRvbWF0aWNhbGx5LlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZW1vdmVBbGxEZWxpdmVyZWROb3RpZmljYXRpb25zKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZUFsbERlbGl2ZXJlZE5vdGlmaWNhdGlvbnNcIik7XG59XG5cbi8qKlxuICogUmVtb3ZlIGEgc3BlY2lmaWMgZGVsaXZlcmVkIG5vdGlmaWNhdGlvbiBieSBpdHMgaWRlbnRpZmllci5cbiAqIE9uIFdpbmRvd3MsIHRoaXMgaXMgYSBuby1vcCBhcyB0aGUgcGxhdGZvcm0gbWFuYWdlcyBub3RpZmljYXRpb24gbGlmZWN5Y2xlIGF1dG9tYXRpY2FsbHkuXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IGlkZW50aWZpZXIgLSBUaGUgSUQgb2YgdGhlIG5vdGlmaWNhdGlvbiB0byByZW1vdmVcbiAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZW1vdmVEZWxpdmVyZWROb3RpZmljYXRpb24oaWRlbnRpZmllcikge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZURlbGl2ZXJlZE5vdGlmaWNhdGlvblwiLCBbaWRlbnRpZmllcl0pO1xufVxuXG4vKipcbiAqIFJlbW92ZSBhIG5vdGlmaWNhdGlvbiBieSBpdHMgaWRlbnRpZmllci5cbiAqIFRoaXMgaXMgYSBjb252ZW5pZW5jZSBmdW5jdGlvbiB0aGF0IHdvcmtzIGFjcm9zcyBwbGF0Zm9ybXMuXG4gKiBPbiBtYWNPUywgdXNlIHRoZSBtb3JlIHNwZWNpZmljIFJlbW92ZVBlbmRpbmdOb3RpZmljYXRpb24gb3IgUmVtb3ZlRGVsaXZlcmVkTm90aWZpY2F0aW9uIGZ1bmN0aW9ucy5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gaWRlbnRpZmllciAtIFRoZSBJRCBvZiB0aGUgbm90aWZpY2F0aW9uIHRvIHJlbW92ZVxuICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlbW92ZU5vdGlmaWNhdGlvbihpZGVudGlmaWVyKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6UmVtb3ZlTm90aWZpY2F0aW9uXCIsIFtpZGVudGlmaWVyXSk7XG59XG5cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cbmltcG9ydCAqIGFzIExvZyBmcm9tICcuL2xvZyc7XG5pbXBvcnQge1xuICBldmVudExpc3RlbmVycyxcbiAgRXZlbnRzRW1pdCxcbiAgRXZlbnRzTm90aWZ5LFxuICBFdmVudHNPZmYsXG4gIEV2ZW50c09mZkFsbCxcbiAgRXZlbnRzT24sXG4gIEV2ZW50c09uY2UsXG4gIEV2ZW50c09uTXVsdGlwbGUsXG59IGZyb20gXCIuL2V2ZW50c1wiO1xuaW1wb3J0IHsgQ2FsbCwgQ2FsbGJhY2ssIGNhbGxiYWNrcyB9IGZyb20gJy4vY2FsbHMnO1xuaW1wb3J0IHsgU2V0QmluZGluZ3MgfSBmcm9tIFwiLi9iaW5kaW5nc1wiO1xuaW1wb3J0ICogYXMgV2luZG93IGZyb20gXCIuL3dpbmRvd1wiO1xuaW1wb3J0ICogYXMgU2NyZWVuIGZyb20gXCIuL3NjcmVlblwiO1xuaW1wb3J0ICogYXMgQnJvd3NlciBmcm9tIFwiLi9icm93c2VyXCI7XG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSBcIi4vY2xpcGJvYXJkXCI7XG5pbXBvcnQgKiBhcyBEcmFnQW5kRHJvcCBmcm9tIFwiLi9kcmFnYW5kZHJvcFwiO1xuaW1wb3J0ICogYXMgQ29udGV4dE1lbnUgZnJvbSBcIi4vY29udGV4dG1lbnVcIjtcbmltcG9ydCAqIGFzIE5vdGlmaWNhdGlvbnMgZnJvbSBcIi4vbm90aWZpY2F0aW9uc1wiO1xuXG5leHBvcnQgZnVuY3Rpb24gUXVpdCgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1EnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFNob3coKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdTJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBIaWRlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnSCcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gRW52aXJvbm1lbnQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6RW52aXJvbm1lbnRcIik7XG59XG5cbi8vIFRoZSBKUyBydW50aW1lXG53aW5kb3cucnVudGltZSA9IHtcbiAgICAuLi5Mb2csXG4gICAgLi4uV2luZG93LFxuICAgIC4uLkJyb3dzZXIsXG4gICAgLi4uU2NyZWVuLFxuICAgIC4uLkNsaXBib2FyZCxcbiAgICAuLi5EcmFnQW5kRHJvcCxcbiAgICAuLi5Ob3RpZmljYXRpb25zLFxuICAgIEV2ZW50c09uLFxuICAgIEV2ZW50c09uY2UsXG4gICAgRXZlbnRzT25NdWx0aXBsZSxcbiAgICBFdmVudHNFbWl0LFxuICAgIEV2ZW50c09mZixcbiAgICBFdmVudHNPZmZBbGwsXG4gICAgRW52aXJvbm1lbnQsXG4gICAgU2hvdyxcbiAgICBIaWRlLFxuICAgIFF1aXRcbn07XG5cbi8vIEludGVybmFsIHdhaWxzIGVuZHBvaW50c1xud2luZG93LndhaWxzID0ge1xuICAgIENhbGxiYWNrLFxuICAgIEV2ZW50c05vdGlmeSxcbiAgICBTZXRCaW5kaW5ncyxcbiAgICBldmVudExpc3RlbmVycyxcbiAgICBjYWxsYmFja3MsXG4gICAgZmxhZ3M6IHtcbiAgICAgICAgZGlzYWJsZVNjcm9sbGJhckRyYWc6IGZhbHNlLFxuICAgICAgICBkaXNhYmxlRGVmYXVsdENvbnRleHRNZW51OiBmYWxzZSxcbiAgICAgICAgZW5hYmxlUmVzaXplOiBmYWxzZSxcbiAgICAgICAgZGVmYXVsdEN1cnNvcjogbnVsbCxcbiAgICAgICAgYm9yZGVyVGhpY2tuZXNzOiA2LFxuICAgICAgICBzaG91bGREcmFnOiBmYWxzZSxcbiAgICAgICAgZGVmZXJEcmFnVG9Nb3VzZU1vdmU6IHRydWUsXG4gICAgICAgIGNzc0RyYWdQcm9wZXJ0eTogXCItLXdhaWxzLWRyYWdnYWJsZVwiLFxuICAgICAgICBjc3NEcmFnVmFsdWU6IFwiZHJhZ1wiLFxuICAgICAgICBjc3NEcm9wUHJvcGVydHk6IFwiLS13YWlscy1kcm9wLXRhcmdldFwiLFxuICAgICAgICBjc3NEcm9wVmFsdWU6IFwiZHJvcFwiLFxuICAgICAgICBlbmFibGVXYWlsc0RyYWdBbmREcm9wOiBmYWxzZSxcbiAgICB9XG59O1xuXG4vLyBTZXQgdGhlIGJpbmRpbmdzXG5pZiAod2luZG93LndhaWxzYmluZGluZ3MpIHtcbiAgICB3aW5kb3cud2FpbHMuU2V0QmluZGluZ3Mod2luZG93LndhaWxzYmluZGluZ3MpO1xuICAgIGRlbGV0ZSB3aW5kb3cud2FpbHMuU2V0QmluZGluZ3M7XG59XG5cbi8vIChib29sKSBUaGlzIGlzIGV2YWx1YXRlZCBhdCBidWlsZCB0aW1lIGluIHBhY2thZ2UuanNvblxuaWYgKCFERUJVRykge1xuICAgIGRlbGV0ZSB3aW5kb3cud2FpbHNiaW5kaW5ncztcbn1cblxubGV0IGRyYWdUZXN0ID0gZnVuY3Rpb24oZSkge1xuICAgIHZhciB2YWwgPSB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShlLnRhcmdldCkuZ2V0UHJvcGVydHlWYWx1ZSh3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1Byb3BlcnR5KTtcbiAgICBpZiAodmFsKSB7XG4gICAgICAgIHZhbCA9IHZhbC50cmltKCk7XG4gICAgfVxuXG4gICAgaWYgKHZhbCAhPT0gd2luZG93LndhaWxzLmZsYWdzLmNzc0RyYWdWYWx1ZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgaWYgKGUuYnV0dG9ucyAhPT0gMSkge1xuICAgICAgICAvLyBEbyBub3Qgc3RhcnQgZHJhZ2dpbmcgaWYgbm90IHRoZSBwcmltYXJ5IGJ1dHRvbiBoYXMgYmVlbiBjbGlja2VkLlxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgaWYgKGUuZGV0YWlsICE9PSAxKSB7XG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnZ2luZyBpZiBtb3JlIHRoYW4gb25jZSBoYXMgYmVlbiBjbGlja2VkLCBlLmcuIHdoZW4gZG91YmxlIGNsaWNraW5nXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbn07XG5cbndpbmRvdy53YWlscy5zZXRDU1NEcmFnUHJvcGVydGllcyA9IGZ1bmN0aW9uKHByb3BlcnR5LCB2YWx1ZSkge1xuICAgIHdpbmRvdy53YWlscy5mbGFncy5jc3NEcmFnUHJvcGVydHkgPSBwcm9wZXJ0eTtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1ZhbHVlID0gdmFsdWU7XG59XG5cbndpbmRvdy53YWlscy5zZXRDU1NEcm9wUHJvcGVydGllcyA9IGZ1bmN0aW9uKHByb3BlcnR5LCB2YWx1ZSkge1xuICAgIHdpbmRvdy53YWlscy5mbGFncy5jc3NEcm9wUHJvcGVydHkgPSBwcm9wZXJ0eTtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJvcFZhbHVlID0gdmFsdWU7XG59XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZWRvd24nLCAoZSkgPT4ge1xuICAgIC8vIENoZWNrIGZvciByZXNpemluZ1xuICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSkge1xuICAgICAgICB3aW5kb3cuV2FpbHNJbnZva2UoXCJyZXNpemU6XCIgKyB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSk7XG4gICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChkcmFnVGVzdChlKSkge1xuICAgICAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLmRpc2FibGVTY3JvbGxiYXJEcmFnKSB7XG4gICAgICAgICAgICAvLyBUaGlzIGNoZWNrcyBmb3IgY2xpY2tzIG9uIHRoZSBzY3JvbGwgYmFyXG4gICAgICAgICAgICBpZiAoZS5vZmZzZXRYID4gZS50YXJnZXQuY2xpZW50V2lkdGggfHwgZS5vZmZzZXRZID4gZS50YXJnZXQuY2xpZW50SGVpZ2h0KSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MuZGVmZXJEcmFnVG9Nb3VzZU1vdmUpIHtcbiAgICAgICAgICAgIHdpbmRvdy53YWlscy5mbGFncy5zaG91bGREcmFnID0gdHJ1ZTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgICAgICAgICAgd2luZG93LldhaWxzSW52b2tlKFwiZHJhZ1wiKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgfSBlbHNlIHtcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbiAgICB9XG59KTtcblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNldXAnLCAoKSA9PiB7XG4gICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbn0pO1xuXG5mdW5jdGlvbiBzZXRSZXNpemUoY3Vyc29yKSB7XG4gICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IGN1cnNvciB8fCB3aW5kb3cud2FpbHMuZmxhZ3MuZGVmYXVsdEN1cnNvcjtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSA9IGN1cnNvcjtcbn1cblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlbW92ZScsIGZ1bmN0aW9uKGUpIHtcbiAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcpIHtcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbiAgICAgICAgbGV0IG1vdXNlUHJlc3NlZCA9IGUuYnV0dG9ucyAhPT0gdW5kZWZpbmVkID8gZS5idXR0b25zIDogZS53aGljaDtcbiAgICAgICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcbiAgICAgICAgICAgIHdpbmRvdy5XYWlsc0ludm9rZShcImRyYWdcIik7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKCF3aW5kb3cud2FpbHMuZmxhZ3MuZW5hYmxlUmVzaXplKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHdpbmRvdy53YWlscy5mbGFncy5kZWZhdWx0Q3Vyc29yID09IG51bGwpIHtcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLmRlZmF1bHRDdXJzb3IgPSBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yO1xuICAgIH1cbiAgICBpZiAod2luZG93Lm91dGVyV2lkdGggLSBlLmNsaWVudFggPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzICYmIHdpbmRvdy5vdXRlckhlaWdodCAtIGUuY2xpZW50WSA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3MpIHtcbiAgICAgICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IFwic2UtcmVzaXplXCI7XG4gICAgfVxuICAgIGxldCByaWdodEJvcmRlciA9IHdpbmRvdy5vdXRlcldpZHRoIC0gZS5jbGllbnRYIDwgd2luZG93LndhaWxzLmZsYWdzLmJvcmRlclRoaWNrbmVzcztcbiAgICBsZXQgbGVmdEJvcmRlciA9IGUuY2xpZW50WCA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XG4gICAgbGV0IHRvcEJvcmRlciA9IGUuY2xpZW50WSA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XG4gICAgbGV0IGJvdHRvbUJvcmRlciA9IHdpbmRvdy5vdXRlckhlaWdodCAtIGUuY2xpZW50WSA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XG5cbiAgICAvLyBJZiB3ZSBhcmVuJ3Qgb24gYW4gZWRnZSwgYnV0IHdlcmUsIHJlc2V0IHRoZSBjdXJzb3IgdG8gZGVmYXVsdFxuICAgIGlmICghbGVmdEJvcmRlciAmJiAhcmlnaHRCb3JkZXIgJiYgIXRvcEJvcmRlciAmJiAhYm90dG9tQm9yZGVyICYmIHdpbmRvdy53YWlscy5mbGFncy5yZXNpemVFZGdlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgc2V0UmVzaXplKCk7XG4gICAgfSBlbHNlIGlmIChyaWdodEJvcmRlciAmJiBib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChsZWZ0Qm9yZGVyICYmIGJvdHRvbUJvcmRlcikgc2V0UmVzaXplKFwic3ctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKGxlZnRCb3JkZXIgJiYgdG9wQm9yZGVyKSBzZXRSZXNpemUoXCJudy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQm9yZGVyICYmIHJpZ2h0Qm9yZGVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAobGVmdEJvcmRlcikgc2V0UmVzaXplKFwidy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQm9yZGVyKSBzZXRSZXNpemUoXCJuLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHJpZ2h0Qm9yZGVyKSBzZXRSZXNpemUoXCJlLXJlc2l6ZVwiKTtcblxufSk7XG5cbi8vIFNldHVwIGNvbnRleHQgbWVudSBob29rXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBmdW5jdGlvbihlKSB7XG4gICAgLy8gYWx3YXlzIHNob3cgdGhlIGNvbnRleHRtZW51IGluIGRlYnVnICYgZGV2XG4gICAgaWYgKERFQlVHKSByZXR1cm47XG5cbiAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLmRpc2FibGVEZWZhdWx0Q29udGV4dE1lbnUpIHtcbiAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIENvbnRleHRNZW51LnByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZSk7XG4gICAgfVxufSk7XG5cbndpbmRvdy5XYWlsc0ludm9rZShcInJ1bnRpbWU6cmVhZHlcIik7Il0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFrQkEsV0FBUyxlQUFlLE9BQU8sU0FBUztBQUl2QyxXQUFPLFlBQVksTUFBTSxRQUFRLE9BQU87QUFBQSxFQUN6QztBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxTQUFTLFNBQVM7QUFDakMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsUUFBUSxTQUFTO0FBQ2hDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxXQUFXLFNBQVM7QUFDbkMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxZQUFZLFVBQVU7QUFDckMsbUJBQWUsS0FBSyxRQUFRO0FBQUEsRUFDN0I7QUFHTyxNQUFNLFdBQVc7QUFBQSxJQUN2QixPQUFPO0FBQUEsSUFDUCxPQUFPO0FBQUEsSUFDUCxNQUFNO0FB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 diff --git a/v2/internal/frontend/runtime/runtime_prod_desktop.js b/v2/internal/frontend/runtime/runtime_prod_desktop.js index 3d38924f7..c285fa642 100644 --- a/v2/internal/frontend/runtime/runtime_prod_desktop.js +++ b/v2/internal/frontend/runtime/runtime_prod_desktop.js @@ -1 +1 @@ -(()=>{var j=Object.defineProperty;var p=(e,t)=>{for(var n in t)j(e,n,{get:t[n],enumerable:!0})};var b={};p(b,{LogDebug:()=>$,LogError:()=>Q,LogFatal:()=>_,LogInfo:()=>Y,LogLevel:()=>K,LogPrint:()=>X,LogTrace:()=>J,LogWarning:()=>q,SetLogLevel:()=>Z});function u(e,t){window.WailsInvoke("L"+e+t)}function J(e){u("T",e)}function X(e){u("P",e)}function $(e){u("D",e)}function Y(e){u("I",e)}function q(e){u("W",e)}function Q(e){u("E",e)}function _(e){u("F",e)}function Z(e){u("S",e)}var K={TRACE:1,DEBUG:2,INFO:3,WARNING:4,ERROR:5};var y=class{constructor(t,n,o){this.eventName=t,this.maxCallbacks=o||-1,this.Callback=i=>(n.apply(null,i),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},w={};function v(e,t,n){w[e]=w[e]||[];let o=new y(e,t,n);return w[e].push(o),()=>ee(o)}function W(e,t){return v(e,t,-1)}function A(e,t){return v(e,t,1)}function P(e){let t=e.name,n=w[t]?.slice()||[];if(n.length){for(let o=n.length-1;o>=0;o-=1){let i=n[o],r=e.data;i.Callback(r)&&n.splice(o,1)}n.length===0?g(t):w[t]=n}}function F(e){let t;try{t=JSON.parse(e)}catch{let o="Invalid JSON passed to Notify: "+e;throw new Error(o)}P(t)}function R(e){let t={name:e,data:[].slice.apply(arguments).slice(1)};P(t),window.WailsInvoke("EE"+JSON.stringify(t))}function g(e){delete w[e],window.WailsInvoke("EX"+e)}function x(e,...t){g(e),t.length>0&&t.forEach(n=>{g(n)})}function M(){Object.keys(w).forEach(t=>{g(t)})}function ee(e){let t=e.eventName;w[t]!==void 0&&(w[t]=w[t].filter(n=>n!==e),w[t].length===0&&g(t))}var c={};function te(){var e=new Uint32Array(1);return window.crypto.getRandomValues(e)[0]}function ne(){return Math.random()*9007199254740991}var D;window.crypto?D=te:D=ne;function a(e,t,n){return n==null&&(n=0),new Promise(function(o,i){var r;do r=e+"-"+D();while(c[r]);var l;n>0&&(l=setTimeout(function(){i(Error("Call to "+e+" timed out. Request ID: "+r))},n)),c[r]={timeoutHandle:l,reject:i,resolve:o};try{let d={name:e,args:t,callbackID:r};window.WailsInvoke("C"+JSON.stringify(d))}catch(d){console.error(d)}})}window.ObfuscatedCall=(e,t,n)=>(n==null&&(n=0),new Promise(function(o,i){var r;do r=e+"-"+D();while(c[r]);var l;n>0&&(l=setTimeout(function(){i(Error("Call to method "+e+" timed out. Request ID: "+r))},n)),c[r]={timeoutHandle:l,reject:i,resolve:o};try{let d={id:e,args:t,callbackID:r};window.WailsInvoke("c"+JSON.stringify(d))}catch(d){console.error(d)}}));function z(e){let t;try{t=JSON.parse(e)}catch(i){let r=`Invalid JSON passed to callback: ${i.message}. Message: ${e}`;throw runtime.LogDebug(r),new Error(r)}let n=t.callbackid,o=c[n];if(!o){let i=`Callback '${n}' not registered!!!`;throw console.error(i),new Error(i)}clearTimeout(o.timeoutHandle),delete c[n],t.error?o.reject(t.error):o.resolve(t.result)}window.go={};function B(e){try{e=JSON.parse(e)}catch(t){console.error(t)}window.go=window.go||{},Object.keys(e).forEach(t=>{window.go[t]=window.go[t]||{},Object.keys(e[t]).forEach(n=>{window.go[t][n]=window.go[t][n]||{},Object.keys(e[t][n]).forEach(o=>{window.go[t][n][o]=function(){let i=0;function r(){let l=[].slice.call(arguments);return a([t,n,o].join("."),l,i)}return r.setTimeout=function(l){i=l},r.getTimeout=function(){return i},r}()})})})}var T={};p(T,{WindowCenter:()=>ae,WindowFullscreen:()=>de,WindowGetPosition:()=>xe,WindowGetSize:()=>pe,WindowHide:()=>De,WindowIsFullscreen:()=>ue,WindowIsMaximised:()=>Te,WindowIsMinimised:()=>Ce,WindowIsNormal:()=>Ie,WindowMaximise:()=>Ee,WindowMinimise:()=>Se,WindowReload:()=>oe,WindowReloadApp:()=>ie,WindowSetAlwaysOnTop:()=>ve,WindowSetBackgroundColour:()=>Oe,WindowSetDarkTheme:()=>le,WindowSetLightTheme:()=>se,WindowSetMaxSize:()=>ge,WindowSetMinSize:()=>me,WindowSetPosition:()=>We,WindowSetSize:()=>ce,WindowSetSystemDefaultTheme:()=>re,WindowSetTitle:()=>we,WindowShow:()=>he,WindowToggleMaximise:()=>be,WindowUnfullscreen:()=>fe,WindowUnmaximise:()=>ye,WindowUnminimise:()=>ke});function oe(){window.location.reload()}function ie(){window.WailsInvoke("WR")}function re(){window.WailsInvoke("WASDT")}function se(){window.WailsInvoke("WALT")}function le(){window.WailsInvoke("WADT")}function ae(){window.WailsInvoke("Wc")}function we(e){window.WailsInvoke("WT"+e)}function de(){window.WailsInvoke("WF")}function fe(){window.WailsInvoke("Wf")}function ue(){return a(":wails:WindowIsFullscreen")}function ce(e,t){window.WailsInvoke("Ws:"+e+":"+t)}function pe(){return a(":wails:WindowGetSize")}function ge(e,t){window.WailsInvoke("WZ:"+e+":"+t)}function me(e,t){window.WailsInvoke("Wz:"+e+":"+t)}function ve(e){window.WailsInvoke("WATP:"+(e?"1":"0"))}function We(e,t){window.WailsInvoke("Wp:"+e+":"+t)}function xe(){return a(":wails:WindowGetPos")}function De(){window.WailsInvoke("WH")}function he(){window.WailsInvoke("WS")}function Ee(){window.WailsInvoke("WM")}function be(){window.WailsInvoke("Wt")}function ye(){window.WailsInvoke("WU")}function Te(){return a(":wails:WindowIsMaximised")}function Se(){window.WailsInvoke("Wm")}function ke(){window.WailsInvoke("Wu")}function Ce(){return a(":wails:WindowIsMinimised")}function Ie(){return a(":wails:WindowIsNormal")}function Oe(e,t,n,o){let i=JSON.stringify({r:e||0,g:t||0,b:n||0,a:o||255});window.WailsInvoke("Wr:"+i)}var S={};p(S,{ScreenGetAll:()=>Le});function Le(){return a(":wails:ScreenGetAll")}var k={};p(k,{BrowserOpenURL:()=>Ae});function Ae(e){window.WailsInvoke("BO:"+e)}var C={};p(C,{ClipboardGetText:()=>Fe,ClipboardSetText:()=>Pe});function Pe(e){return a(":wails:ClipboardSetText",[e])}function Fe(){return a(":wails:ClipboardGetText")}var I={};p(I,{CanResolveFilePaths:()=>V,OnFileDrop:()=>Me,OnFileDropOff:()=>ze,ResolveFilePaths:()=>Re});var s={registered:!1,defaultUseDropTarget:!0,useDropTarget:!0,nextDeactivate:null,nextDeactivateTimeout:null},m="wails-drop-target-active";function h(e){let t=e.getPropertyValue(window.wails.flags.cssDropProperty).trim();return t?t===window.wails.flags.cssDropValue:!1}function G(e){if(!e.dataTransfer.types.includes("Files")||(e.preventDefault(),e.dataTransfer.dropEffect="copy",!window.wails.flags.enableWailsDragAndDrop)||!s.useDropTarget)return;let n=e.target;if(s.nextDeactivate&&s.nextDeactivate(),!n||!h(getComputedStyle(n)))return;let o=n;for(;o;)h(getComputedStyle(o))&&o.classList.add(m),o=o.parentElement}function H(e){if(!!e.dataTransfer.types.includes("Files")&&(e.preventDefault(),!!window.wails.flags.enableWailsDragAndDrop&&!!s.useDropTarget)){if(!e.target||!h(getComputedStyle(e.target)))return null;s.nextDeactivate&&s.nextDeactivate(),s.nextDeactivate=()=>{Array.from(document.getElementsByClassName(m)).forEach(n=>n.classList.remove(m)),s.nextDeactivate=null,s.nextDeactivateTimeout&&(clearTimeout(s.nextDeactivateTimeout),s.nextDeactivateTimeout=null)},s.nextDeactivateTimeout=setTimeout(()=>{s.nextDeactivate&&s.nextDeactivate()},50)}}function U(e){if(!!e.dataTransfer.types.includes("Files")&&(e.preventDefault(),!!window.wails.flags.enableWailsDragAndDrop)){if(V()){let n=[];e.dataTransfer.items?n=[...e.dataTransfer.items].map((o,i)=>{if(o.kind==="file")return o.getAsFile()}):n=[...e.dataTransfer.files],window.runtime.ResolveFilePaths(e.x,e.y,n)}!s.useDropTarget||(s.nextDeactivate&&s.nextDeactivate(),Array.from(document.getElementsByClassName(m)).forEach(n=>n.classList.remove(m)))}}function V(){return window.chrome?.webview?.postMessageWithAdditionalObjects!=null}function Re(e,t,n){window.chrome?.webview?.postMessageWithAdditionalObjects&&chrome.webview.postMessageWithAdditionalObjects(`file:drop:${e}:${t}`,n)}function Me(e,t){if(typeof e!="function"){console.error("DragAndDropCallback is not a function");return}if(s.registered)return;s.registered=!0;let n=typeof t;s.useDropTarget=n==="undefined"||n!=="boolean"?s.defaultUseDropTarget:t,window.addEventListener("dragover",G),window.addEventListener("dragleave",H),window.addEventListener("drop",U);let o=e;s.useDropTarget&&(o=function(i,r,l){let d=document.elementFromPoint(i,r);if(!d||!h(getComputedStyle(d)))return null;e(i,r,l)}),W("wails:file-drop",o)}function ze(){window.removeEventListener("dragover",G),window.removeEventListener("dragleave",H),window.removeEventListener("drop",U),x("wails:file-drop"),s.registered=!1}function N(e){let t=e.target;switch(window.getComputedStyle(t).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":e.preventDefault();return;default:if(t.isContentEditable)return;let i=window.getSelection(),r=i.toString().length>0;if(r)for(let l=0;l{if(window.wails.flags.resizeEdge){window.WailsInvoke("resize:"+window.wails.flags.resizeEdge),e.preventDefault();return}if(Ne(e)){if(window.wails.flags.disableScrollbarDrag&&(e.offsetX>e.target.clientWidth||e.offsetY>e.target.clientHeight))return;window.wails.flags.deferDragToMouseMove?window.wails.flags.shouldDrag=!0:(e.preventDefault(),window.WailsInvoke("drag"));return}else window.wails.flags.shouldDrag=!1});window.addEventListener("mouseup",()=>{window.wails.flags.shouldDrag=!1});function f(e){document.documentElement.style.cursor=e||window.wails.flags.defaultCursor,window.wails.flags.resizeEdge=e}window.addEventListener("mousemove",function(e){if(window.wails.flags.shouldDrag&&(window.wails.flags.shouldDrag=!1,(e.buttons!==void 0?e.buttons:e.which)>0)){window.WailsInvoke("drag");return}if(!window.wails.flags.enableResize)return;window.wails.flags.defaultCursor==null&&(window.wails.flags.defaultCursor=document.documentElement.style.cursor),window.outerWidth-e.clientX{var J=Object.defineProperty;var p=(e,t)=>{for(var n in t)J(e,n,{get:t[n],enumerable:!0})};var y={};p(y,{LogDebug:()=>q,LogError:()=>_,LogFatal:()=>Z,LogInfo:()=>Y,LogLevel:()=>ee,LogPrint:()=>$,LogTrace:()=>X,LogWarning:()=>Q,SetLogLevel:()=>K});function u(e,t){window.WailsInvoke("L"+e+t)}function X(e){u("T",e)}function $(e){u("P",e)}function q(e){u("D",e)}function Y(e){u("I",e)}function Q(e){u("W",e)}function _(e){u("E",e)}function Z(e){u("F",e)}function K(e){u("S",e)}var ee={TRACE:1,DEBUG:2,INFO:3,WARNING:4,ERROR:5};var b=class{constructor(t,n,i){this.eventName=t,this.maxCallbacks=i||-1,this.Callback=o=>(n.apply(null,o),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},f={};function v(e,t,n){f[e]=f[e]||[];let i=new b(e,t,n);return f[e].push(i),()=>te(i)}function x(e,t){return v(e,t,-1)}function N(e,t){return v(e,t,1)}function L(e){let t=e.name,n=f[t]?.slice()||[];if(n.length){for(let i=n.length-1;i>=0;i-=1){let o=n[i],s=e.data;o.Callback(s)&&n.splice(i,1)}n.length===0?g(t):f[t]=n}}function P(e){let t;try{t=JSON.parse(e)}catch{let i="Invalid JSON passed to Notify: "+e;throw new Error(i)}L(t)}function z(e){let t={name:e,data:[].slice.apply(arguments).slice(1)};L(t),window.WailsInvoke("EE"+JSON.stringify(t))}function g(e){delete f[e],window.WailsInvoke("EX"+e)}function D(e,...t){g(e),t.length>0&&t.forEach(n=>{g(n)})}function F(){Object.keys(f).forEach(t=>{g(t)})}function te(e){let t=e.eventName;f[t]!==void 0&&(f[t]=f[t].filter(n=>n!==e),f[t].length===0&&g(t))}var c={};function ne(){var e=new Uint32Array(1);return window.crypto.getRandomValues(e)[0]}function ie(){return Math.random()*9007199254740991}var W;window.crypto?W=ne:W=ie;function r(e,t,n){return n==null&&(n=0),new Promise(function(i,o){var s;do s=e+"-"+W();while(c[s]);var l;n>0&&(l=setTimeout(function(){o(Error("Call to "+e+" timed out. Request ID: "+s))},n)),c[s]={timeoutHandle:l,reject:o,resolve:i};try{let w={name:e,args:t,callbackID:s};window.WailsInvoke("C"+JSON.stringify(w))}catch(w){console.error(w)}})}window.ObfuscatedCall=(e,t,n)=>(n==null&&(n=0),new Promise(function(i,o){var s;do s=e+"-"+W();while(c[s]);var l;n>0&&(l=setTimeout(function(){o(Error("Call to method "+e+" timed out. Request ID: "+s))},n)),c[s]={timeoutHandle:l,reject:o,resolve:i};try{let w={id:e,args:t,callbackID:s};window.WailsInvoke("c"+JSON.stringify(w))}catch(w){console.error(w)}}));function M(e){let t;try{t=JSON.parse(e)}catch(o){let s=`Invalid JSON passed to callback: ${o.message}. Message: ${e}`;throw runtime.LogDebug(s),new Error(s)}let n=t.callbackid,i=c[n];if(!i){let o=`Callback '${n}' not registered!!!`;throw console.error(o),new Error(o)}clearTimeout(i.timeoutHandle),delete c[n],t.error?i.reject(t.error):i.resolve(t.result)}window.go={};function B(e){try{e=JSON.parse(e)}catch(t){console.error(t)}window.go=window.go||{},Object.keys(e).forEach(t=>{window.go[t]=window.go[t]||{},Object.keys(e[t]).forEach(n=>{window.go[t][n]=window.go[t][n]||{},Object.keys(e[t][n]).forEach(i=>{window.go[t][n][i]=function(){let o=0;function s(){let l=[].slice.call(arguments);return r([t,n,i].join("."),l,o)}return s.setTimeout=function(l){o=l},s.getTimeout=function(){return o},s}()})})})}var C={};p(C,{WindowCenter:()=>fe,WindowFullscreen:()=>de,WindowGetPosition:()=>We,WindowGetSize:()=>ge,WindowHide:()=>he,WindowIsFullscreen:()=>ce,WindowIsMaximised:()=>Se,WindowIsMinimised:()=>Ie,WindowIsNormal:()=>Ae,WindowMaximise:()=>ye,WindowMinimise:()=>Te,WindowReload:()=>oe,WindowReloadApp:()=>re,WindowSetAlwaysOnTop:()=>xe,WindowSetBackgroundColour:()=>Oe,WindowSetDarkTheme:()=>le,WindowSetLightTheme:()=>ae,WindowSetMaxSize:()=>me,WindowSetMinSize:()=>ve,WindowSetPosition:()=>De,WindowSetSize:()=>pe,WindowSetSystemDefaultTheme:()=>se,WindowSetTitle:()=>we,WindowShow:()=>Ee,WindowToggleMaximise:()=>be,WindowUnfullscreen:()=>ue,WindowUnmaximise:()=>Ce,WindowUnminimise:()=>ke});function oe(){window.location.reload()}function re(){window.WailsInvoke("WR")}function se(){window.WailsInvoke("WASDT")}function ae(){window.WailsInvoke("WALT")}function le(){window.WailsInvoke("WADT")}function fe(){window.WailsInvoke("Wc")}function we(e){window.WailsInvoke("WT"+e)}function de(){window.WailsInvoke("WF")}function ue(){window.WailsInvoke("Wf")}function ce(){return r(":wails:WindowIsFullscreen")}function pe(e,t){window.WailsInvoke("Ws:"+e+":"+t)}function ge(){return r(":wails:WindowGetSize")}function me(e,t){window.WailsInvoke("WZ:"+e+":"+t)}function ve(e,t){window.WailsInvoke("Wz:"+e+":"+t)}function xe(e){window.WailsInvoke("WATP:"+(e?"1":"0"))}function De(e,t){window.WailsInvoke("Wp:"+e+":"+t)}function We(){return r(":wails:WindowGetPos")}function he(){window.WailsInvoke("WH")}function Ee(){window.WailsInvoke("WS")}function ye(){window.WailsInvoke("WM")}function be(){window.WailsInvoke("Wt")}function Ce(){window.WailsInvoke("WU")}function Se(){return r(":wails:WindowIsMaximised")}function Te(){window.WailsInvoke("Wm")}function ke(){window.WailsInvoke("Wu")}function Ie(){return r(":wails:WindowIsMinimised")}function Ae(){return r(":wails:WindowIsNormal")}function Oe(e,t,n,i){let o=JSON.stringify({r:e||0,g:t||0,b:n||0,a:i||255});window.WailsInvoke("Wr:"+o)}var S={};p(S,{ScreenGetAll:()=>Re});function Re(){return r(":wails:ScreenGetAll")}var T={};p(T,{BrowserOpenURL:()=>Ne});function Ne(e){window.WailsInvoke("BO:"+e)}var k={};p(k,{ClipboardGetText:()=>Pe,ClipboardSetText:()=>Le});function Le(e){return r(":wails:ClipboardSetText",[e])}function Pe(){return r(":wails:ClipboardGetText")}var I={};p(I,{CanResolveFilePaths:()=>V,OnFileDrop:()=>Fe,OnFileDropOff:()=>Me,ResolveFilePaths:()=>ze});var a={registered:!1,defaultUseDropTarget:!0,useDropTarget:!0,nextDeactivate:null,nextDeactivateTimeout:null},m="wails-drop-target-active";function h(e){let t=e.getPropertyValue(window.wails.flags.cssDropProperty).trim();return t?t===window.wails.flags.cssDropValue:!1}function G(e){if(!e.dataTransfer.types.includes("Files")||(e.preventDefault(),e.dataTransfer.dropEffect="copy",!window.wails.flags.enableWailsDragAndDrop)||!a.useDropTarget)return;let n=e.target;if(a.nextDeactivate&&a.nextDeactivate(),!n||!h(getComputedStyle(n)))return;let i=n;for(;i;)h(getComputedStyle(i))&&i.classList.add(m),i=i.parentElement}function H(e){if(!!e.dataTransfer.types.includes("Files")&&(e.preventDefault(),!!window.wails.flags.enableWailsDragAndDrop&&!!a.useDropTarget)){if(!e.target||!h(getComputedStyle(e.target)))return null;a.nextDeactivate&&a.nextDeactivate(),a.nextDeactivate=()=>{Array.from(document.getElementsByClassName(m)).forEach(n=>n.classList.remove(m)),a.nextDeactivate=null,a.nextDeactivateTimeout&&(clearTimeout(a.nextDeactivateTimeout),a.nextDeactivateTimeout=null)},a.nextDeactivateTimeout=setTimeout(()=>{a.nextDeactivate&&a.nextDeactivate()},50)}}function U(e){if(!!e.dataTransfer.types.includes("Files")&&(e.preventDefault(),!!window.wails.flags.enableWailsDragAndDrop)){if(V()){let n=[];e.dataTransfer.items?n=[...e.dataTransfer.items].map((i,o)=>{if(i.kind==="file")return i.getAsFile()}):n=[...e.dataTransfer.files],window.runtime.ResolveFilePaths(e.x,e.y,n)}!a.useDropTarget||(a.nextDeactivate&&a.nextDeactivate(),Array.from(document.getElementsByClassName(m)).forEach(n=>n.classList.remove(m)))}}function V(){return window.chrome?.webview?.postMessageWithAdditionalObjects!=null}function ze(e,t,n){window.chrome?.webview?.postMessageWithAdditionalObjects&&chrome.webview.postMessageWithAdditionalObjects(`file:drop:${e}:${t}`,n)}function Fe(e,t){if(typeof e!="function"){console.error("DragAndDropCallback is not a function");return}if(a.registered)return;a.registered=!0;let n=typeof t;a.useDropTarget=n==="undefined"||n!=="boolean"?a.defaultUseDropTarget:t,window.addEventListener("dragover",G),window.addEventListener("dragleave",H),window.addEventListener("drop",U);let i=e;a.useDropTarget&&(i=function(o,s,l){let w=document.elementFromPoint(o,s);if(!w||!h(getComputedStyle(w)))return null;e(o,s,l)}),x("wails:file-drop",i)}function Me(){window.removeEventListener("dragover",G),window.removeEventListener("dragleave",H),window.removeEventListener("drop",U),D("wails:file-drop"),a.registered=!1}function j(e){let t=e.target;switch(window.getComputedStyle(t).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":e.preventDefault();return;default:if(t.isContentEditable)return;let o=window.getSelection(),s=o.toString().length>0;if(s)for(let l=0;lje,CleanupNotifications:()=>He,InitializeNotifications:()=>Ge,IsNotificationAvailable:()=>Ue,RegisterNotificationCategory:()=>$e,RemoveAllDeliveredNotifications:()=>_e,RemoveAllPendingNotifications:()=>Ye,RemoveDeliveredNotification:()=>Ze,RemoveNotification:()=>Ke,RemoveNotificationCategory:()=>qe,RemovePendingNotification:()=>Qe,RequestNotificationAuthorization:()=>Ve,SendNotification:()=>Je,SendNotificationWithActions:()=>Xe});function Ge(){return r(":wails:InitializeNotifications")}function He(){return r(":wails:CleanupNotifications")}function Ue(){return r(":wails:IsNotificationAvailable")}function Ve(){return r(":wails:RequestNotificationAuthorization")}function je(){return r(":wails:CheckNotificationAuthorization")}function Je(e){return r(":wails:SendNotification",[e])}function Xe(e){return r(":wails:SendNotificationWithActions",[e])}function $e(e){return r(":wails:RegisterNotificationCategory",[e])}function qe(e){return r(":wails:RemoveNotificationCategory",[e])}function Ye(){return r(":wails:RemoveAllPendingNotifications")}function Qe(e){return r(":wails:RemovePendingNotification",[e])}function _e(){return r(":wails:RemoveAllDeliveredNotifications")}function Ze(e){return r(":wails:RemoveDeliveredNotification",[e])}function Ke(e){return r(":wails:RemoveNotification",[e])}function et(){window.WailsInvoke("Q")}function tt(){window.WailsInvoke("S")}function nt(){window.WailsInvoke("H")}function it(){return r(":wails:Environment")}window.runtime={...y,...C,...T,...S,...k,...I,...A,EventsOn:x,EventsOnce:N,EventsOnMultiple:v,EventsEmit:z,EventsOff:D,EventsOffAll:F,Environment:it,Show:tt,Hide:nt,Quit:et};window.wails={Callback:M,EventsNotify:P,SetBindings:B,eventListeners:f,callbacks:c,flags:{disableScrollbarDrag:!1,disableDefaultContextMenu:!1,enableResize:!1,defaultCursor:null,borderThickness:6,shouldDrag:!1,deferDragToMouseMove:!0,cssDragProperty:"--wails-draggable",cssDragValue:"drag",cssDropProperty:"--wails-drop-target",cssDropValue:"drop",enableWailsDragAndDrop:!1}};window.wailsbindings&&(window.wails.SetBindings(window.wailsbindings),delete window.wails.SetBindings);delete window.wailsbindings;var ot=function(e){var t=window.getComputedStyle(e.target).getPropertyValue(window.wails.flags.cssDragProperty);return t&&(t=t.trim()),!(t!==window.wails.flags.cssDragValue||e.buttons!==1||e.detail!==1)};window.wails.setCSSDragProperties=function(e,t){window.wails.flags.cssDragProperty=e,window.wails.flags.cssDragValue=t};window.wails.setCSSDropProperties=function(e,t){window.wails.flags.cssDropProperty=e,window.wails.flags.cssDropValue=t};window.addEventListener("mousedown",e=>{if(window.wails.flags.resizeEdge){window.WailsInvoke("resize:"+window.wails.flags.resizeEdge),e.preventDefault();return}if(ot(e)){if(window.wails.flags.disableScrollbarDrag&&(e.offsetX>e.target.clientWidth||e.offsetY>e.target.clientHeight))return;window.wails.flags.deferDragToMouseMove?window.wails.flags.shouldDrag=!0:(e.preventDefault(),window.WailsInvoke("drag"));return}else window.wails.flags.shouldDrag=!1});window.addEventListener("mouseup",()=>{window.wails.flags.shouldDrag=!1});function d(e){document.documentElement.style.cursor=e||window.wails.flags.defaultCursor,window.wails.flags.resizeEdge=e}window.addEventListener("mousemove",function(e){if(window.wails.flags.shouldDrag&&(window.wails.flags.shouldDrag=!1,(e.buttons!==void 0?e.buttons:e.which)>0)){window.WailsInvoke("drag");return}if(!window.wails.flags.enableResize)return;window.wails.flags.defaultCursor==null&&(window.wails.flags.defaultCursor=document.documentElement.style.cursor),window.outerWidth-e.clientX; + +// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications) +// Cleans up notification resources and releases any held connections. +export function CleanupNotifications(): Promise; + +// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable) +// Checks if notifications are available on the current platform. +export function IsNotificationAvailable(): Promise; + +// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization) +// Requests notification authorization from the user (macOS only). +export function RequestNotificationAuthorization(): Promise; + +// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization) +// Checks the current notification authorization status (macOS only). +export function CheckNotificationAuthorization(): Promise; + +// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification) +// Sends a basic notification with the given options. +export function SendNotification(options: NotificationOptions): Promise; + +// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions) +// Sends a notification with action buttons. Requires a registered category. +export function SendNotificationWithActions(options: NotificationOptions): Promise; + +// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory) +// Registers a notification category that can be used with SendNotificationWithActions. +export function RegisterNotificationCategory(category: NotificationCategory): Promise; + +// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory) +// Removes a previously registered notification category. +export function RemoveNotificationCategory(categoryId: string): Promise; + +// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications) +// Removes all pending notifications from the notification center. +export function RemoveAllPendingNotifications(): Promise; + +// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification) +// Removes a specific pending notification by its identifier. +export function RemovePendingNotification(identifier: string): Promise; + +// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications) +// Removes all delivered notifications from the notification center. +export function RemoveAllDeliveredNotifications(): Promise; + +// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification) +// Removes a specific delivered notification by its identifier. +export function RemoveDeliveredNotification(identifier: string): Promise; + +// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification) +// Removes a notification by its identifier (cross-platform convenience function). +export function RemoveNotification(identifier: string): Promise; \ No newline at end of file diff --git a/v2/internal/frontend/runtime/wrapper/runtime.js b/v2/internal/frontend/runtime/wrapper/runtime.js index 7cb89d750..556621eeb 100644 --- a/v2/internal/frontend/runtime/wrapper/runtime.js +++ b/v2/internal/frontend/runtime/wrapper/runtime.js @@ -239,4 +239,60 @@ export function CanResolveFilePaths() { export function ResolveFilePaths(files) { return window.runtime.ResolveFilePaths(files); +} + +export function InitializeNotifications() { + return window.runtime.InitializeNotifications(); +} + +export function CleanupNotifications() { + return window.runtime.CleanupNotifications(); +} + +export function IsNotificationAvailable() { + return window.runtime.IsNotificationAvailable(); +} + +export function RequestNotificationAuthorization() { + return window.runtime.RequestNotificationAuthorization(); +} + +export function CheckNotificationAuthorization() { + return window.runtime.CheckNotificationAuthorization(); +} + +export function SendNotification(options) { + return window.runtime.SendNotification(options); +} + +export function SendNotificationWithActions(options) { + return window.runtime.SendNotificationWithActions(options); +} + +export function RegisterNotificationCategory(category) { + return window.runtime.RegisterNotificationCategory(category); +} + +export function RemoveNotificationCategory(categoryId) { + return window.runtime.RemoveNotificationCategory(categoryId); +} + +export function RemoveAllPendingNotifications() { + return window.runtime.RemoveAllPendingNotifications(); +} + +export function RemovePendingNotification(identifier) { + return window.runtime.RemovePendingNotification(identifier); +} + +export function RemoveAllDeliveredNotifications() { + return window.runtime.RemoveAllDeliveredNotifications(); +} + +export function RemoveDeliveredNotification(identifier) { + return window.runtime.RemoveDeliveredNotification(identifier); +} + +export function RemoveNotification(identifier) { + return window.runtime.RemoveNotification(identifier); } \ No newline at end of file diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go index 7263f63ae..491a57801 100644 --- a/v2/pkg/commands/build/build.go +++ b/v2/pkg/commands/build/build.go @@ -3,6 +3,7 @@ package build import ( "fmt" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -357,6 +358,16 @@ func execBuildApplication(builder Builder, options *Options) (string, error) { pterm.Println("Done.") } + if runtime.GOOS == "darwin" && options.Platform == "darwin" { + // On macOS, self-sign the .app bundle so notifications work + printBulletPoint("Self-signing application: ") + cmd := exec.Command("/usr/bin/codesign", "--force", "--deep", "--sign", "-", options.CompiledBinary) + if out, err := cmd.CombinedOutput(); err != nil { + return "", fmt.Errorf("codesign failed: %v – %s", err, out) + } + pterm.Println("Done.") + } + if options.Platform == "windows" { const nativeWebView2Loader = "native_webview2loader" diff --git a/v2/pkg/runtime/notifications.go b/v2/pkg/runtime/notifications.go new file mode 100644 index 000000000..46ae09fac --- /dev/null +++ b/v2/pkg/runtime/notifications.go @@ -0,0 +1,136 @@ +package runtime + +import ( + "context" + + "github.com/wailsapp/wails/v2/internal/frontend" +) + +// NotificationOptions contains configuration for a notification. +type NotificationOptions = frontend.NotificationOptions + +// NotificationAction represents an action button for a notification. +type NotificationAction = frontend.NotificationAction + +// NotificationCategory groups actions for notifications. +type NotificationCategory = frontend.NotificationCategory + +// NotificationResponse represents the response sent by interacting with a notification. +type NotificationResponse = frontend.NotificationResponse + +// NotificationResult represents the result of a notification response, +// returning the response or any errors that occurred. +type NotificationResult = frontend.NotificationResult + +// InitializeNotifications initializes the notification service for the application. +// This must be called before sending any notifications. On macOS, this also ensures +// the notification delegate is properly initialized. +func InitializeNotifications(ctx context.Context) error { + fe := getFrontend(ctx) + return fe.InitializeNotifications() +} + +// CleanupNotifications cleans up notification resources and releases any held connections. +// This should be called when shutting down the application to properly release resources +// (primarily needed on Linux to close D-Bus connections). +func CleanupNotifications(ctx context.Context) { + fe := getFrontend(ctx) + fe.CleanupNotifications() +} + +// IsNotificationAvailable checks if notifications are available on the current platform. +func IsNotificationAvailable(ctx context.Context) bool { + fe := getFrontend(ctx) + return fe.IsNotificationAvailable() +} + +// RequestNotificationAuthorization requests notification authorization from the user. +// On macOS, this prompts the user to allow notifications. On other platforms, this +// always returns true. Returns true if authorization was granted, false otherwise. +func RequestNotificationAuthorization(ctx context.Context) (bool, error) { + fe := getFrontend(ctx) + return fe.RequestNotificationAuthorization() +} + +// CheckNotificationAuthorization checks the current notification authorization status. +// On macOS, this checks if the app has notification permissions. On other platforms, +// this always returns true. +func CheckNotificationAuthorization(ctx context.Context) (bool, error) { + fe := getFrontend(ctx) + return fe.CheckNotificationAuthorization() +} + +// SendNotification sends a basic notification with the given options. +// The notification will display with the provided title, subtitle (if supported), +// and body text. +func SendNotification(ctx context.Context, options NotificationOptions) error { + fe := getFrontend(ctx) + return fe.SendNotification(options) +} + +// SendNotificationWithActions sends a notification with action buttons. +// A NotificationCategory must be registered first using RegisterNotificationCategory. +// The options.CategoryID must match a previously registered category ID. +// If the category is not found, a basic notification will be sent instead. +func SendNotificationWithActions(ctx context.Context, options NotificationOptions) error { + fe := getFrontend(ctx) + return fe.SendNotificationWithActions(options) +} + +// RegisterNotificationCategory registers a notification category that can be used +// with SendNotificationWithActions. Categories define the action buttons and optional +// reply fields that will appear on notifications. +func RegisterNotificationCategory(ctx context.Context, category NotificationCategory) error { + fe := getFrontend(ctx) + return fe.RegisterNotificationCategory(category) +} + +// RemoveNotificationCategory removes a previously registered notification category. +func RemoveNotificationCategory(ctx context.Context, categoryId string) error { + fe := getFrontend(ctx) + return fe.RemoveNotificationCategory(categoryId) +} + +// RemoveAllPendingNotifications removes all pending notifications from the notification center. +// On Windows, this is a no-op as the platform manages notification lifecycle automatically. +func RemoveAllPendingNotifications(ctx context.Context) error { + fe := getFrontend(ctx) + return fe.RemoveAllPendingNotifications() +} + +// RemovePendingNotification removes a specific pending notification by its identifier. +// On Windows, this is a no-op as the platform manages notification lifecycle automatically. +func RemovePendingNotification(ctx context.Context, identifier string) error { + fe := getFrontend(ctx) + return fe.RemovePendingNotification(identifier) +} + +// RemoveAllDeliveredNotifications removes all delivered notifications from the notification center. +// On Windows, this is a no-op as the platform manages notification lifecycle automatically. +func RemoveAllDeliveredNotifications(ctx context.Context) error { + fe := getFrontend(ctx) + return fe.RemoveAllDeliveredNotifications() +} + +// RemoveDeliveredNotification removes a specific delivered notification by its identifier. +// On Windows, this is a no-op as the platform manages notification lifecycle automatically. +func RemoveDeliveredNotification(ctx context.Context, identifier string) error { + fe := getFrontend(ctx) + return fe.RemoveDeliveredNotification(identifier) +} + +// RemoveNotification removes a notification by its identifier. +// This is a convenience function that works across platforms. On macOS, use the +// more specific RemovePendingNotification or RemoveDeliveredNotification functions. +func RemoveNotification(ctx context.Context, identifier string) error { + fe := getFrontend(ctx) + return fe.RemoveNotification(identifier) +} + +// OnNotificationResponse registers a callback function that will be invoked when +// a user interacts with a notification (e.g., clicks an action button or the notification itself). +// The callback receives a NotificationResult containing the response details or any errors. +func OnNotificationResponse(ctx context.Context, callback func(result NotificationResult)) { + fe := getFrontend(ctx) + fe.OnNotificationResponse(callback) +} diff --git a/website/docs/guides/notifications.mdx b/website/docs/guides/notifications.mdx new file mode 100644 index 000000000..50d16cb50 --- /dev/null +++ b/website/docs/guides/notifications.mdx @@ -0,0 +1,233 @@ +# Notifications + +Wails provides a comprehensive cross-platform notification system for desktop applications. This runtime allows you to display native system notifications with support for interactive elements like action buttons and text input fields. + +:::info JavaScript + +Notifications are currently unsupported in the JS runtime. + +::: + +## Basic Usage + +### Initializing Notifications + +First, initialize the notification system. This should be called during app startup (typically in `OnStartup`): + +```go +err := runtime.InitializeNotifications(a.ctx) +if err != nil { + // Handle initialization error + // On macOS, this may fail if bundle identifier is not set +} +``` + +Then, check if notifications are available on the current platform: + +```go +if runtime.IsNotificationAvailable(a.ctx) { + // Notifications are supported + // On macOS, this checks for macOS 10.14+ + // On Windows and Linux, this always returns true +} +``` + +On macOS, you'll need to request permission before sending notifications: + +```go +authorized, err := runtime.CheckNotificationAuthorization(a.ctx) +if err != nil { + // Handle authorization error +} + +if !authorized { + authorized, err = runtime.RequestNotificationAuthorization(a.ctx) + if err != nil || !authorized { + // Handle permission denial + } +} +``` + +On Windows and Linux, authorization is not required as these platforms don't have permission systems. + +### Sending Basic Notifications + +Send a basic notification with a unique ID, title, optional subtitle (macOS and Linux), and body text: + +```go +err := runtime.SendNotification(a.ctx, runtime.NotificationOptions{ + ID: "calendar-invite-001", + Title: "New Calendar Invite", + Subtitle: "From: Jane Doe", // Optional - macOS and Linux only + Body: "Tap to view the event", +}) +if err != nil { + // Handle error +} +``` + +## Interactive Notifications + +Interactive notifications allow users to respond with button actions or text input. You must first register a notification category that defines the available actions. + +### Creating Notification Categories + +Define a category with action buttons and optional text input: + +```go +categoryID := "message-category" + +category := runtime.NotificationCategory{ + ID: categoryID, + Actions: []runtime.NotificationAction{ + { + ID: "OPEN", + Title: "Open", + }, + { + ID: "ARCHIVE", + Title: "Archive", + Destructive: true, // macOS-specific - shows as red button + }, + }, + HasReplyField: true, + ReplyPlaceholder: "Type your reply...", + ReplyButtonTitle: "Reply", +} + +err := runtime.RegisterNotificationCategory(a.ctx, category) +if err != nil { + // Handle error +} +``` + +### Sending Interactive Notifications + +Send an interactive notification using the registered category. If the category is not found or `CategoryID` is empty, a basic notification will be sent instead: + +```go +err := runtime.SendNotificationWithActions(a.ctx, runtime.NotificationOptions{ + ID: "message-001", + Title: "New Message", + Subtitle: "From: John Smith", // Optional - macOS and Linux only + Body: "Hey, are you free for lunch?", + CategoryID: categoryID, +}) +if err != nil { + // Handle error +} +``` + +## Handling Notification Responses + +Listen for user interactions with notifications by registering a callback: + +```go +runtime.OnNotificationResponse(a.ctx, func(result runtime.NotificationResult) { + if result.Error != nil { + // Handle response error + return + } + + response := result.Response + fmt.Printf("Notification %s was actioned with: %s\n", + response.ID, response.ActionIdentifier) + + if response.ActionIdentifier == "TEXT_REPLY" { + fmt.Printf("User replied: %s\n", response.UserText) + } + + // You can also emit events to the frontend + runtime.EventsEmit(a.ctx, "notification", response) +}) +``` + +## Adding Custom Data + +Basic and interactive notifications can include custom data that will be returned in the response: + +```go +err := runtime.SendNotification(a.ctx, runtime.NotificationOptions{ + ID: "event-001", + Title: "Team Meeting", + Subtitle: "In 30 minutes", + Body: "Don't forget your presentation materials!", + Data: map[string]interface{}{ + "eventId": "meeting-123", + "startTime": "2024-01-15T14:00:00Z", + "attendees": []string{"john@company.com", "jane@company.com"}, + "priority": "high", + }, +}) + +// In the response handler: +runtime.OnNotificationResponse(a.ctx, func(result runtime.NotificationResult) { + response := result.Response + if eventId, ok := response.UserInfo["eventId"].(string); ok { + fmt.Printf("Event ID: %s\n", eventId) + } +}) +``` + +## Managing Notifications + +### Removing Notification Categories + +Remove a previously registered notification category: + +```go +err := runtime.RemoveNotificationCategory(a.ctx, "message-category") +``` + +### Managing Notifications Lifecycle + +Control notification visibility: + +```go +// Remove a specific pending notification (macOS and Linux only) +err := runtime.RemovePendingNotification(a.ctx, "notification-id") + +// Remove all pending notifications (macOS and Linux only) +err = runtime.RemoveAllPendingNotifications(a.ctx) + +// Remove a specific delivered notification (macOS and Linux only) +err = runtime.RemoveDeliveredNotification(a.ctx, "notification-id") + +// Remove all delivered notifications (macOS and Linux only) +err = runtime.RemoveAllDeliveredNotifications(a.ctx) + +// Remove a notification (Linux-specific) +err = runtime.RemoveNotification(a.ctx, "notification-id") +``` + +## Platform Considerations + +### macOS + +- **Authorization Required**: Apps must request notification permission +- **Notarization**: Required for app distribution on macOS +- **Features**: Supports subtitles, user text input, destructive actions, dark/light mode +- **Behavior**: Notifications appear in the system notification center + +### Windows + +- **No Authorization**: No permission system required +- **Features**: Supports user text input, high DPI displays, Windows theme adaptation +- **Limitations**: Does not support subtitles +- **Behavior**: Uses Windows toast notifications + +### Linux + +- **Desktop Environment Dependent**: Behavior varies by DE (GNOME, KDE, etc.) +- **Features**: Supports subtitles and themes +- **Limitations**: Does not support user text input +- **Behavior**: Uses native notification system when available + +## Best Practices + +1. **Check Platform Support**: Always verify notifications are available before using them +2. **Handle Authorization**: Properly request and check permissions on macOS +3. **Use Descriptive Content**: Provide clear titles, subtitles, and action button labels +4. **Handle Responses**: Always implement proper error handling for notification responses +5. **Test Across Platforms**: Verify functionality on your target platforms +6. **Clean Up**: Remove old notification categories when they're no longer needed \ No newline at end of file diff --git a/website/docs/reference/runtime/notification.mdx b/website/docs/reference/runtime/notification.mdx new file mode 100644 index 000000000..e9890ce59 --- /dev/null +++ b/website/docs/reference/runtime/notification.mdx @@ -0,0 +1,601 @@ +--- +sidebar_position: 6 +--- + +# Notification + +This part of the runtime provides access to native system notifications with support for interactive elements like action buttons and text input fields. + +### InitializeNotifications + +Initializes the notification system. It should be called during app startup. + +**Go:** `InitializeNotifications(ctx context.Context) error` + +**JavaScript:** `InitializeNotifications(): Promise` + +Returns: Error if initialization fails + +**Example:** +```go +err := runtime.InitializeNotifications(ctx) +if err != nil { + log.Fatal(err) +} +``` + +```javascript +await runtime.InitializeNotifications(); +``` + +### IsNotificationAvailable + +Checks if notifications are supported on the current platform. + +**Go:** `IsNotificationAvailable(ctx context.Context) bool` + +**JavaScript:** `IsNotificationAvailable(): Promise` + +Returns: `true` if notifications are supported, `false` otherwise + +**Example:** +```go +if !runtime.IsNotificationAvailable(ctx) { + log.Println("Notifications not available on this platform") +} +``` + +```javascript +const available = await runtime.IsNotificationAvailable(); +if (!available) { + console.log("Notifications not available on this platform"); +} +``` + +### RequestNotificationAuthorization + +Requests permission to display notifications (macOS only). On Windows and Linux, this always returns `true`. + +**Go:** `RequestNotificationAuthorization(ctx context.Context) (bool, error)` + +**JavaScript:** `RequestNotificationAuthorization(): Promise` + +Returns: Authorization status and error + +**Example:** +```go +authorized, err := runtime.RequestNotificationAuthorization(ctx) +``` + +```javascript +const authorized = await runtime.RequestNotificationAuthorization(); +``` + +### CheckNotificationAuthorization + +Checks the current notification authorization status (macOS only). On Windows and Linux, this always returns `true`. + +**Go:** `CheckNotificationAuthorization(ctx context.Context) (bool, error)` + +**JavaScript:** `CheckNotificationAuthorization(): Promise` + +Returns: Authorization status and error + +**Example:** +```go +authorized, err := runtime.CheckNotificationAuthorization(ctx) +``` + +```javascript +const authorized = await runtime.CheckNotificationAuthorization(); +``` + +### CleanupNotifications + +Cleans up notification resources and releases any held connections. This should be called when shutting down the application, particularly on Linux where it closes the D-Bus connection. + +**Go:** `CleanupNotifications(ctx context.Context)` + +**JavaScript:** `CleanupNotifications(): Promise` + +**Example:** +```go +runtime.CleanupNotifications(ctx) +``` + +```javascript +await runtime.CleanupNotifications(); +``` + +### SendNotification + +Sends a basic notification to the system. + +**Go:** `SendNotification(ctx context.Context, options NotificationOptions) error` + +**JavaScript:** `SendNotification(options: NotificationOptions): Promise` + +Returns: Error if the notification fails to send + +**Example:** +```go +err := runtime.SendNotification(ctx, runtime.NotificationOptions{ + ID: "notif-1", + Title: "Hello", + Body: "This is a notification", +}) +``` + +```javascript +await runtime.SendNotification({ + id: "notif-1", + title: "Hello", + body: "This is a notification" +}); +``` + +### SendNotificationWithActions + +Sends an interactive notification with predefined actions. Requires a registered notification category. If the category is not found or `CategoryID` is empty, a basic notification will be sent instead. + +**Go:** `SendNotificationWithActions(ctx context.Context, options NotificationOptions) error` + +**JavaScript:** `SendNotificationWithActions(options: NotificationOptions): Promise` + +Returns: Error if the notification fails to send + +**Example:** +```go +err := runtime.SendNotificationWithActions(ctx, runtime.NotificationOptions{ + ID: "notif-2", + Title: "Task Reminder", + Body: "Complete your task", + CategoryID: "TASK_CATEGORY", +}) +``` + +```javascript +await runtime.SendNotificationWithActions({ + id: "notif-2", + title: "Task Reminder", + body: "Complete your task", + categoryId: "TASK_CATEGORY" +}); +``` + +### RegisterNotificationCategory + +Registers a notification category that can be used with interactive notifications. Registering a category with the same ID as a previously registered category will override it. + +**Go:** `RegisterNotificationCategory(ctx context.Context, category NotificationCategory) error` + +**JavaScript:** `RegisterNotificationCategory(category: NotificationCategory): Promise` + +Returns: Error if registration fails + +**Example:** +```go +err := runtime.RegisterNotificationCategory(ctx, runtime.NotificationCategory{ + ID: "TASK_CATEGORY", + Actions: []runtime.NotificationAction{ + {ID: "COMPLETE", Title: "Complete"}, + {ID: "CANCEL", Title: "Cancel"}, + }, +}) +``` + +```javascript +await runtime.RegisterNotificationCategory({ + id: "TASK_CATEGORY", + actions: [ + {id: "COMPLETE", title: "Complete"}, + {id: "CANCEL", title: "Cancel"} + ] +}); +``` + +### RemoveNotificationCategory + +Removes a previously registered notification category. + +**Go:** `RemoveNotificationCategory(ctx context.Context, categoryId string) error` + +**JavaScript:** `RemoveNotificationCategory(categoryId: string): Promise` + +Returns: Error if removal fails + +**Example:** +```go +err := runtime.RemoveNotificationCategory(ctx, "TASK_CATEGORY") +``` + +```javascript +await runtime.RemoveNotificationCategory("TASK_CATEGORY"); +``` + +### RemoveAllPendingNotifications + +Removes all pending notifications (macOS and Linux only). + +**Go:** `RemoveAllPendingNotifications(ctx context.Context) error` + +**JavaScript:** `RemoveAllPendingNotifications(): Promise` + +Returns: Error if removal fails + +**Example:** +```go +err := runtime.RemoveAllPendingNotifications(ctx) +``` + +```javascript +await runtime.RemoveAllPendingNotifications(); +``` + +### RemovePendingNotification + +Removes a specific pending notification (macOS and Linux only). + +**Go:** `RemovePendingNotification(ctx context.Context, identifier string) error` + +**JavaScript:** `RemovePendingNotification(identifier: string): Promise` + +Returns: Error if removal fails + +**Example:** +```go +err := runtime.RemovePendingNotification(ctx, "notif-1") +``` + +```javascript +await runtime.RemovePendingNotification("notif-1"); +``` + +### RemoveAllDeliveredNotifications + +Removes all delivered notifications (macOS and Linux only). + +**Go:** `RemoveAllDeliveredNotifications(ctx context.Context) error` + +**JavaScript:** `RemoveAllDeliveredNotifications(): Promise` + +Returns: Error if removal fails + +**Example:** +```go +err := runtime.RemoveAllDeliveredNotifications(ctx) +``` + +```javascript +await runtime.RemoveAllDeliveredNotifications(); +``` + +### RemoveDeliveredNotification + +Removes a specific delivered notification (macOS and Linux only). + +**Go:** `RemoveDeliveredNotification(ctx context.Context, identifier string) error` + +**JavaScript:** `RemoveDeliveredNotification(identifier: string): Promise` + +Returns: Error if removal fails + +**Example:** +```go +err := runtime.RemoveDeliveredNotification(ctx, "notif-1") +``` + +```javascript +await runtime.RemoveDeliveredNotification("notif-1"); +``` + +### RemoveNotification + +Removes a notification by identifier (Linux only). On macOS and Windows, this is a stub that always returns `nil`. + +**Go:** `RemoveNotification(ctx context.Context, identifier string) error` + +**JavaScript:** `RemoveNotification(identifier: string): Promise` + +Returns: Error if removal fails + +**Example:** +```go +err := runtime.RemoveNotification(ctx, "notif-1") +``` + +```javascript +await runtime.RemoveNotification("notif-1"); +``` + +### OnNotificationResponse + +Registers a callback function to handle notification responses when users interact with notifications. + +**Go:** `OnNotificationResponse(ctx context.Context, callback func(result NotificationResult))` + +:::note JavaScript + +`OnNotificationResponse` is not available in the JavaScript runtime. Instead, JavaScript applications should use the [Events API](/docs/reference/runtime/events) to listen for notification responses. From your Go callback, emit an event that your JavaScript code can listen to. + +**Example:** +```go +runtime.OnNotificationResponse(ctx, func(result runtime.NotificationResult) { + if result.Error != nil { + return + } + // Emit an event that JavaScript can listen to + runtime.EventsEmit(ctx, "notification-response", result.Response) +}) +``` + +```javascript +runtime.EventsOn("notification-response", (response) => { + console.log("Notification response:", response); + switch (response.actionIdentifier) { + case "COMPLETE": + // Handle complete action + break; + case "CANCEL": + // Handle cancel action + break; + } +}); +``` + +::: + +## Options + +### NotificationOptions + +**Go:** +```go +type NotificationOptions struct { + ID string `json:"id"` + Title string `json:"title"` + Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only) + Body string `json:"body,omitempty"` + CategoryID string `json:"categoryId,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` +} +``` + +**TypeScript:** +```typescript +interface NotificationOptions { + id: string; + title: string; + subtitle?: string; // macOS and Linux only + body?: string; + categoryId?: string; + data?: { [key: string]: any }; +} +``` + +| Field | Description | Win | Mac | Lin | +|-------------|------------------------------------------------|-----|-----|-----| +| ID | Unique identifier for the notification | ✅ | ✅ | ✅ | +| Title | Main notification title | ✅ | ✅ | ✅ | +| Subtitle | Subtitle text (macOS and Linux only) | | ✅ | ✅ | +| Body | Main notification content | ✅ | ✅ | ✅ | +| CategoryID | Category identifier for interactive notifications | ✅ | ✅ | ✅ | +| Data | Custom data to associate with the notification | ✅ | ✅ | ✅ | + +### NotificationCategory + +**Go:** +```go +type NotificationCategory struct { + ID string `json:"id,omitempty"` + Actions []NotificationAction `json:"actions,omitempty"` + HasReplyField bool `json:"hasReplyField,omitempty"` + ReplyPlaceholder string `json:"replyPlaceholder,omitempty"` + ReplyButtonTitle string `json:"replyButtonTitle,omitempty"` +} +``` + +**TypeScript:** +```typescript +interface NotificationCategory { + id?: string; + actions?: NotificationAction[]; + hasReplyField?: boolean; + replyPlaceholder?: string; + replyButtonTitle?: string; +} +``` + +| Field | Description | Win | Mac | Lin | +|------------------|------------------------------------------------|-----|-----|-----| +| ID | Unique identifier for the category | ✅ | ✅ | ✅ | +| Actions | Array of action buttons | ✅ | ✅ | ✅ | +| HasReplyField | Whether to include a text input field | ✅ | ✅ | | +| ReplyPlaceholder | Placeholder text for the input field | ✅ | ✅ | | +| ReplyButtonTitle | Text for the reply button | ✅ | ✅ | | + +### NotificationAction + +**Go:** +```go +type NotificationAction struct { + ID string `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Destructive bool `json:"destructive,omitempty"` // (macOS-specific) +} +``` + +**TypeScript:** +```typescript +interface NotificationAction { + id?: string; + title?: string; + destructive?: boolean; // macOS-specific +} +``` + +| Field | Description | Win | Mac | Lin | +|-------------|------------------------------------------------|----------------|-----|-----| +| ID | Unique identifier for the action | ✅ | ✅ | ✅ | +| Title | Button text | ✅ | ✅ | ✅ | +| Destructive | Whether the action is destructive (macOS-only) | | ✅ | | + +#### macOS-specific Behavior + +On macOS, the `Destructive` flag causes the action button to appear in red, indicating it's a destructive action (like delete or cancel). On Windows and Linux, this flag is ignored. + +Example: +```go +actions := []runtime.NotificationAction{ + {ID: "SAVE", Title: "Save"}, + {ID: "DELETE", Title: "Delete", Destructive: true}, // Shows as red button on macOS +} +``` + +### NotificationResponse + +```go +type NotificationResponse struct { + ID string `json:"id,omitempty"` + ActionIdentifier string `json:"actionIdentifier,omitempty"` + CategoryID string `json:"categoryId,omitempty"` // Consistent with NotificationOptions + Title string `json:"title,omitempty"` + Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only) + Body string `json:"body,omitempty"` + UserText string `json:"userText,omitempty"` + UserInfo map[string]interface{} `json:"userInfo,omitempty"` +} +``` + +| Field | Description | Win | Mac | Lin | +|------------------|------------------------------------------------|-----|-----|-----| +| ID | Notification identifier | ✅ | ✅ | ✅ | +| ActionIdentifier | Action that was triggered | ✅ | ✅ | ✅ | +| CategoryID | Category of the notification | ✅ | ✅ | ✅ | +| Title | Title of the notification | ✅ | ✅ | ✅ | +| Subtitle | Subtitle of the notification (macOS and Linux only) | | ✅ | ✅ | +| Body | Body text of the notification | ✅ | ✅ | ✅ | +| UserText | Text entered by the user | ✅ | ✅ | | +| UserInfo | Custom data from the notification | ✅ | ✅ | ✅ | + +### NotificationResult + +```go +type NotificationResult struct { + Response NotificationResponse + Error error +} +``` + +| Field | Description | +|----------|------------------------------------------------| +| Response | The notification response data | +| Error | Any error that occurred during the interaction | + +## Platform-Specific Behavior + +### macOS + +- **Authorization Required**: Apps must request notification permission before sending notifications +- **Notarization**: Apps must be notarized for distribution +- **Features**: All features supported including subtitles, text input, and destructive actions +- **Styling**: Automatically adapts to system dark/light mode +- **Center**: Notifications appear in macOS Notification Center + +**Example:** +```go +// Check and request authorization +authorized, err := runtime.CheckNotificationAuthorization(ctx) +if err != nil { + return err +} + +if !authorized { + authorized, err = runtime.RequestNotificationAuthorization(ctx) + if err != nil || !authorized { + return fmt.Errorf("notification authorization denied") + } +} + +// Now send notifications +``` + +```javascript +// Check and request authorization +let authorized = await runtime.CheckNotificationAuthorization(); +if (!authorized) { + authorized = await runtime.RequestNotificationAuthorization(); + if (!authorized) { + throw new Error("Notification authorization denied"); + } +} + +// Now send notifications +``` + +### Windows + +- **No Authorization**: Permission system not required +- **Features**: Supports text input and high DPI displays +- **Limitations**: Subtitle not supported +- **Styling**: Adapts to Windows theme settings +- **Behavior**: Uses Windows toast notification system + +### Linux + +- **Desktop Environment Dependent**: Behavior varies by DE (GNOME, KDE, XFCE, etc.) +- **Features**: Supports subtitles +- **Limitations**: User text input not supported +- **Styling**: Follows desktop environment theme +- **Behavior**: Uses native notification system when available + +**Example:** +```go +// Check system support +if !runtime.IsNotificationAvailable(ctx) { + return fmt.Errorf("notifications not supported on this Linux desktop") +} + +// Linux notifications may not support text input +// Only use actions that don't require user text +``` + +```javascript +// Check system support +const available = await runtime.IsNotificationAvailable(); +if (!available) { + throw new Error("Notifications not supported on this Linux desktop"); +} + +// Linux notifications may not support text input +// Only use actions that don't require user text +``` + +## Action Identifiers + +When handling notification responses, these special action identifiers may be present: + +- `DEFAULT_ACTION`: Triggered when the user clicks the notification itself (not an action button) +- `TEXT_REPLY`: Triggered when the user submits text via the reply field + +Example response handling: +```go +runtime.OnNotificationResponse(ctx, func(result runtime.NotificationResult) { + if result.Error != nil { + fmt.Printf("Response error: %v\n", result.Error) + return + } + + response := result.Response + switch response.ActionIdentifier { + case "DEFAULT_ACTION": + fmt.Println("User clicked the notification") + case "TEXT_REPLY": + fmt.Printf("User replied: %s\n", response.UserText) + case "COMPLETE": + fmt.Println("User clicked Complete button") + case "CANCEL": + fmt.Println("User clicked Cancel button") + } +}) +``` diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 50208109b..ef0052c1c 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `build:tags` to project specification for automatically adding compilation tags by @symball in [PR](https://github.com/wailsapp/wails/pull/4439) - Support for binding generics in [PR](https://github.dev/wailsapp/wails/pull/3626) by @ktsivkov - Add universal link support for macOS by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4693) +- Notifications API in [PR](https://github.com/wailsapp/wails/pull/4256) by @popaprozac ### Fixed - Added url validation for BrowserOpenURL by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4484)