diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 442b4c60c093..000000000000 --- a/.cursorrules +++ /dev/null @@ -1,132 +0,0 @@ -# Cursor Rules for Keybase Client Repository - -## Downloading CI Test Failure Logs (citigo logs) - -When test failures occur, you'll see suggested commands like: - -``` -curl -s -o - https://ci-fail-logs.s3.amazonaws.com/citogo-PR_28719-4-kbfs_libdokan-TestStatRoot-puco3td7mech4ilrcdhabhsklm25vgk2.gz | zcat -d | less -``` - -### ⚠️ COMMON PITFALLS TO AVOID: - -1. **DO NOT run curl commands in the sandbox** - Use `required_permissions: ["all"]` to bypass SSL certificate access restrictions -2. **DO NOT stream logs to stdout/less** - Save them locally first to avoid repeated downloads -3. **DO NOT use `-k` flag** - Use proper permissions instead to maintain SSL security - -### ✅ CORRECT WORKFLOW: - -#### Step 1: Extract the Log Information - -When you see a test failure with a fetch command, identify: - -- The full URL (e.g., `https://ci-fail-logs.s3.amazonaws.com/citogo-PR_28719-4-...`) -- The test name (e.g., `TestStatRoot`) - -#### Step 2: Download and Save Locally - -Use `run_terminal_cmd` with **all permissions** to bypass sandbox SSL certificate restrictions: - -```bash -# Download the .gz file locally first -curl -s -o /path/to/repo/test-failure-TestName.gz https://ci-fail-logs.s3.amazonaws.com/citigo-... -``` - -**Important**: - -- **ALWAYS use `required_permissions: ["all"]`** - The sandbox blocks access to `/etc/ssl/cert.pem`, causing SSL verification to fail -- This allows curl to properly verify SSL certificates without using `-k` (insecure mode) -- Save files to the repository root with descriptive names -- Keep the .gz extension for the downloaded file -- For multiple logs, chain commands with `&&` to download all at once - -#### Step 3: Decompress Locally - -Once downloaded, decompress without network access: - -```bash -# Decompress the file -gunzip -c test-failure-TestName.gz > test-failure-TestName.log -``` - -Or in one step if the file is already downloaded: - -```bash -zcat test-failure-TestName.gz > test-failure-TestName.log -``` - -#### Step 4: Read the Decompressed Log - -Use the `read_file` tool to read the decompressed `.log` file directly. - -### EXAMPLE COMPLETE WORKFLOW: - -``` -User reports: "TestStatRoot failed, logs at: curl -s -o - https://ci-fail-logs.s3.amazonaws.com/citogo-PR_28719-4-kbfs_libdokan-TestStatRoot-puco3td7mech4ilrcdhabhsklm25vgk2.gz | zcat -d | less" - -Step 1: Download bypassing sandbox (required for SSL cert access) -run_terminal_cmd( - command: "curl -s -o TestStatRoot-failure.gz https://ci-fail-logs.s3.amazonaws.com/citogo-PR_28719-4-kbfs_libdokan-TestStatRoot-puco3td7mech4ilrcdhabhsklm25vgk2.gz", - required_permissions: ["all"] -) - -Step 2: Decompress locally (no special permissions needed) -run_terminal_cmd( - command: "gunzip -c TestStatRoot-failure.gz > TestStatRoot-failure.log" -) - -Step 3: Read the log file -read_file(target_file: "TestStatRoot-failure.log") -``` - -### EXAMPLE: Multiple Logs at Once - -``` -For multiple test failures, download all logs in one command: - -run_terminal_cmd( - command: "cd /path/to/repo && curl -s -o Test1.gz URL1 && curl -s -o Test2.gz URL2 && curl -s -o Test3.gz URL3", - required_permissions: ["all"] -) - -Then decompress all at once: -run_terminal_cmd( - command: "cd /path/to/repo && gunzip -c Test1.gz > Test1.log && gunzip -c Test2.gz > Test2.log && gunzip -c Test3.gz > Test3.log" -) -``` - -### TROUBLESHOOTING: - -**If you get certificate errors (exit code 77):** - -- Root cause: Sandbox blocks access to `/etc/ssl/cert.pem` (system CA certificates) -- Solution: Use `required_permissions: ["all"]` to bypass sandbox restrictions -- This allows curl to access system SSL certificates for proper verification -- Alternative (not recommended): Use `-k` flag to skip certificate verification entirely - -**If sandbox errors occur:** - -- Ensure you're using `required_permissions: ["all"]` for curl commands -- The sandbox allows network access with `["network"]` but still blocks system cert access - -**If the file is corrupted:** - -- Re-download with verbose output: `curl -v -o file.gz https://...` -- Check file size: `ls -lh file.gz` - -### FILE NAMING CONVENTION: - -Use descriptive names that include the test name: - -- ✅ `TestStatRoot-failure.log` -- ✅ `test-fail-PR28719-TestStatRoot.gz` -- ❌ `test-fail.gz` (too generic) -- ❌ `temp.log` (not descriptive) - -### CLEANUP: - -After analyzing logs, consider cleaning up large log files: - -```bash -rm *.gz *.log -``` diff --git a/Jenkinsfile b/Jenkinsfile index c6fbbd5e0a45..dbe456d6356d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -203,6 +203,7 @@ helpers.rootLinuxNode(env, { sh "go install mvdan.cc/gofumpt" } dir ('protocol') { + sh "yarn cache clean avdl-compiler" sh "yarn --frozen-lockfile" sh "make clean" sh "make" diff --git a/go/bind/keybase.go b/go/bind/keybase.go index 4c94b7f80d7d..aeaf9dba40a0 100644 --- a/go/bind/keybase.go +++ b/go/bind/keybase.go @@ -59,13 +59,29 @@ var ( connMutex sync.Mutex // Protects conn operations ) -// log writes to kbCtx.Log if available, otherwise falls back to fmt.Printf +func describeConn(c net.Conn) string { + if c == nil { + return "" + } + return fmt.Sprintf("%T@%p", c, c) +} + +func appStateForLog() string { + if kbCtx == nil || kbCtx.MobileAppState == nil { + return "" + } + return fmt.Sprintf("%v", kbCtx.MobileAppState.State()) +} + +// log writes to kbCtx.Log if available, otherwise falls back to stderr. +// Stderr is captured in crash logs and the Xcode console, making early Init +// messages (before kbCtx.Log is set up by Configure) visible in diagnostics. func log(format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) if kbCtx != nil && kbCtx.Log != nil { kbCtx.Log.Info(msg) } else { - fmt.Printf("%s\n", msg) + fmt.Fprintf(os.Stderr, "keybase: %s\n", msg) } } @@ -469,9 +485,13 @@ func WriteArr(b []byte) (err error) { n, err := currentConn.Write(bytes) if err != nil { + log("Go: WriteArr error conn=%s len=%d appState=%s err=%v", + describeConn(currentConn), len(bytes), appStateForLog(), err) return fmt.Errorf("Write error: %s", err) } if n != len(bytes) { + log("Go: WriteArr short write conn=%s wrote=%d expected=%d appState=%s", + describeConn(currentConn), n, len(bytes), appStateForLog()) return errors.New("Did not write all the data") } return nil @@ -525,18 +545,37 @@ func ReadArr() (data []byte, err error) { // Must be called with connMutex held. func ensureConnection() error { if !isInited() { + log("ensureConnection: keybase not initialized") return errors.New("keybase not initialized") } if kbCtx == nil || kbCtx.LoopbackListener == nil { + log("ensureConnection: loopback listener not initialized (kbCtx nil: %v)", kbCtx == nil) return errors.New("loopback listener not initialized") } var err error conn, err = kbCtx.LoopbackListener.Dial() if err != nil { - return fmt.Errorf("Failed to dial loopback listener: %s", err) + // The listener was closed (isClosed=true, returns syscall.EINVAL). Recreate it and + // start a new ListenLoop goroutine, then retry the dial once. + log("ensureConnection: Dial failed (%v), restarting loopback server", err) + l, rerr := kbCtx.MakeLoopbackServer() + if rerr != nil { + log("ensureConnection: MakeLoopbackServer failed: %v", rerr) + return fmt.Errorf("failed to restart loopback server: %s", rerr) + } + go func() { _ = kbSvc.ListenLoop(l) }() + conn, err = kbCtx.LoopbackListener.Dial() + if err != nil { + log("ensureConnection: Dial failed after restart: %v", err) + return fmt.Errorf("failed to dial after loopback restart: %s", err) + } + log("ensureConnection: loopback server restarted successfully conn=%s appState=%s", + describeConn(conn), appStateForLog()) + return nil } - log("Go: Established loopback connection") + log("Go: Established loopback connection conn=%s appState=%s", + describeConn(conn), appStateForLog()) return nil } @@ -545,24 +584,28 @@ func Reset() error { connMutex.Lock() defer connMutex.Unlock() + log("Go: Reset start conn=%s appState=%s", describeConn(conn), appStateForLog()) if conn != nil { conn.Close() conn = nil } if kbCtx == nil || kbCtx.LoopbackListener == nil { + log("Go: Reset complete without listener appState=%s", appStateForLog()) return nil } // Connection will be re-established lazily on next read/write - log("Go: Connection reset, will reconnect on next operation") + log("Go: Connection reset, will reconnect on next operation appState=%s", appStateForLog()) return nil } // NotifyJSReady signals that the JavaScript side is ready to send/receive RPCs. // This unblocks the ReadArr loop and allows bidirectional communication. +// jsReadyCh is closed once and stays closed — repeated calls from engine resets are no-ops. func NotifyJSReady() { jsReadyOnce.Do(func() { - log("Go: JS signaled ready, unblocking RPC communication") + log("Go: JS signaled ready, unblocking RPC communication appState=%s conn=%s", + appStateForLog(), describeConn(conn)) close(jsReadyCh) }) } @@ -590,6 +633,12 @@ func IsAppStateForeground() bool { return kbCtx.MobileAppState.State() == keybase1.MobileAppState_FOREGROUND } +// FlushLogs synchronously flushes any buffered log data to disk. Call this +// before background suspension and after foreground resume to prevent log loss. +func FlushLogs() { + logger.FlushLogFile() +} + func SetAppStateForeground() { if !isInited() { return @@ -639,36 +688,39 @@ func waitForInit(maxDur time.Duration) error { } } -func BackgroundSync() { +// BackgroundSync runs a short background sync pulse. Returns a non-empty status +// string on early exit or error so Swift can log it via NSLog. +func BackgroundSync() string { // On Android there is a race where this function can be called before Init when starting up in the // background. Let's wait a little bit here for Init to get run, and bail out if it never does. if err := waitForInit(5 * time.Second); err != nil { - return + return fmt.Sprintf("waitForInit timeout: %v", err) } defer kbCtx.Trace("BackgroundSync", nil)() // Skip the sync if we aren't in the background if state := kbCtx.MobileAppState.State(); state != keybase1.MobileAppState_BACKGROUND { - kbCtx.Log.Debug("BackgroundSync: skipping, app not in background state: %v", state) - return + msg := fmt.Sprintf("skipping, app not in background state: %v", state) + kbCtx.Log.Debug("BackgroundSync: %s", msg) + return msg } nextState := keybase1.MobileAppState_BACKGROUNDACTIVE kbCtx.MobileAppState.Update(nextState) - doneCh := make(chan struct{}) + resultCh := make(chan string, 1) go func() { - defer func() { close(doneCh) }() select { case state := <-kbCtx.MobileAppState.NextUpdate(&nextState): // if literally anything happens, let's get out of here - kbCtx.Log.Debug("BackgroundSync: bailing out early, appstate change: %v", state) - return + msg := fmt.Sprintf("bailing out early, appstate change: %v", state) + kbCtx.Log.Debug("BackgroundSync: %s", msg) + resultCh <- msg case <-time.After(10 * time.Second): kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND) - return + resultCh <- "completed 10s window" } }() - <-doneCh + return <-resultCh } // pushPendingMessageFailure sends at most one notification that a message diff --git a/go/chat/localizer.go b/go/chat/localizer.go index af3aa42fb48b..444624b75473 100644 --- a/go/chat/localizer.go +++ b/go/chat/localizer.go @@ -845,8 +845,9 @@ func (s *localizerPipeline) localizeConversation(ctx context.Context, uid gregor var maxValidID chat1.MessageID s.Debug(ctx, "localizing %d max msgs", len(maxMsgs)) for _, mm := range maxMsgs { - if mm.IsValid() && - utils.IsSnippetChatMessageType(mm.GetMessageType()) && + isValidSnippet := mm.IsValid() && utils.IsSnippetChatMessageType(mm.GetMessageType()) + isEphemeralErr := mm.IsError() && mm.Error().IsEphemeral + if (isValidSnippet || isEphemeralErr) && (conversationLocal.Info.SnippetMsg == nil || conversationLocal.Info.SnippetMsg.GetMessageID() < mm.GetMessageID()) { conversationLocal.Info.SnippetMsg = new(chat1.MessageUnboxed) diff --git a/go/chat/utils/utils.go b/go/chat/utils/utils.go index e65d478aa612..36e1042b8723 100644 --- a/go/chat/utils/utils.go +++ b/go/chat/utils/utils.go @@ -1116,10 +1116,13 @@ func formatDuration(dur time.Duration) string { func getMsgSnippetDecoration(msg chat1.MessageUnboxed) chat1.SnippetDecoration { var msgBody chat1.MessageBody - if msg.IsValid() { + switch { + case msg.IsValid(): msgBody = msg.Valid().MessageBody - } else { + case msg.IsOutbox(): msgBody = msg.Outbox().Msg.MessageBody + default: + return chat1.SnippetDecoration_NONE } switch msg.GetMessageType() { case chat1.MessageType_ATTACHMENT: @@ -1220,14 +1223,20 @@ func GetMsgSnippetBody(ctx context.Context, g *globals.Context, uid gregor1.UID, func GetMsgSnippet(ctx context.Context, g *globals.Context, uid gregor1.UID, msg chat1.MessageUnboxed, conv chat1.ConversationLocal, currentUsername string, ) (decoration chat1.SnippetDecoration, snippet string, snippetDecorated string) { - if !msg.IsValid() && !msg.IsOutbox() { - return chat1.SnippetDecoration_NONE, "", "" - } defer func() { if len(snippetDecorated) == 0 { snippetDecorated = snippet } }() + if !msg.IsValid() && !msg.IsOutbox() { + if msg.IsError() && msg.Error().IsEphemeral { + if msg.Error().IsEphemeralExpired(time.Now()) { + return chat1.SnippetDecoration_EXPLODED_MESSAGE, "Message exploded.", "" + } + return chat1.SnippetDecoration_EXPLODING_MESSAGE, msg.Error().ErrMsg, "" + } + return chat1.SnippetDecoration_NONE, "", "" + } var senderUsername string if msg.IsValid() { diff --git a/go/chat/utils/utils_test.go b/go/chat/utils/utils_test.go index a8b0acdabc0c..e39b1ac63060 100644 --- a/go/chat/utils/utils_test.go +++ b/go/chat/utils/utils_test.go @@ -1086,6 +1086,36 @@ func TestSearchableRemoteConversationName(t *testing.T) { searchableRemoteConversationNameFromStr("joshblum,zoommikem,mikem,zoomua,mikem", "mikem")) } +func TestGetMsgSnippetEphemeralError(t *testing.T) { + ctx := context.Background() + conv := chat1.ConversationLocal{} + errMsg := "This exploding message is not available because this device was created after the message was sent" + + // Non-expired ephemeral error: should surface the error message with EXPLODING_MESSAGE decoration. + msg := chat1.NewMessageUnboxedWithError(chat1.MessageUnboxedError{ + ErrType: chat1.MessageUnboxedErrorType_EPHEMERAL, + ErrMsg: errMsg, + IsEphemeral: true, + Etime: gregor1.ToTime(time.Now().Add(time.Hour)), + MessageType: chat1.MessageType_TEXT, + }) + decoration, snippet, _ := GetMsgSnippet(ctx, nil, gregor1.UID{}, msg, conv, "alice") + require.Equal(t, chat1.SnippetDecoration_EXPLODING_MESSAGE, decoration) + require.Equal(t, errMsg, snippet) + + // Expired ephemeral error: should show "Message exploded." with EXPLODED_MESSAGE decoration. + msg = chat1.NewMessageUnboxedWithError(chat1.MessageUnboxedError{ + ErrType: chat1.MessageUnboxedErrorType_EPHEMERAL, + ErrMsg: errMsg, + IsEphemeral: true, + Etime: gregor1.ToTime(time.Now().Add(-time.Hour)), + MessageType: chat1.MessageType_TEXT, + }) + decoration, snippet, _ = GetMsgSnippet(ctx, nil, gregor1.UID{}, msg, conv, "alice") + require.Equal(t, chat1.SnippetDecoration_EXPLODED_MESSAGE, decoration) + require.Equal(t, "Message exploded.", snippet) +} + func TestStripUsernameFromConvName(t *testing.T) { // Only the username as a complete segment is removed; "mikem" inside "zoommikem" must not be stripped require.Equal(t, "joshblum,zoommikem,zoomua", diff --git a/go/engine/bootstrap.go b/go/engine/bootstrap.go index a41e089e5cc9..d6ede82d3da2 100644 --- a/go/engine/bootstrap.go +++ b/go/engine/bootstrap.go @@ -44,6 +44,7 @@ func (e *Bootstrap) SubConsumers() []libkb.UIConsumer { } func (e *Bootstrap) lookupFullname(m libkb.MetaContext, uv keybase1.UserVersion) { + defer m.Trace("Bootstrap.lookupFullname", nil)() pkgs, err := m.G().UIDMapper.MapUIDsToUsernamePackagesOffline(m.Ctx(), m.G(), []keybase1.UID{uv.Uid}, time.Duration(0)) if err != nil { m.Warning("UID -> Username failed lookup: %s", err) @@ -62,7 +63,8 @@ func (e *Bootstrap) lookupFullname(m libkb.MetaContext, uv keybase1.UserVersion) } // Run starts the engine. -func (e *Bootstrap) Run(m libkb.MetaContext) error { +func (e *Bootstrap) Run(m libkb.MetaContext) (err error) { + defer m.Trace("Bootstrap.Run", &err)() e.status.Registered = e.signedUp(m) // if any Login engine worked previously, then ActiveDevice will diff --git a/go/ephemeral/common_test.go b/go/ephemeral/common_test.go index dd7473975259..eb2eefdb72be 100644 --- a/go/ephemeral/common_test.go +++ b/go/ephemeral/common_test.go @@ -132,7 +132,10 @@ func TestEphemeralPluralization(t *testing.T) { require.Equal(t, humanMsg, pluralized) pluralized = PluralizeErrorMessage(humanMsg, 2) - require.Equal(t, "2 exploding messages are not available, because this device was created after it was sent", pluralized) + require.Equal(t, "2 exploding messages are not available because this device was created after the messages were sent", pluralized) + + pluralized = PluralizeErrorMessage(humanMsgWithPrefix(MemberAfterEKErrMsg), 3) + require.Equal(t, "3 exploding messages are not available because you joined the team after the messages were sent", pluralized) pluralized = PluralizeErrorMessage(DefaultHumanErrMsg, 2) require.Equal(t, "2 exploding messages are not available", pluralized) diff --git a/go/ephemeral/errors.go b/go/ephemeral/errors.go index 29f0b1ebd009..5bab64893c2b 100644 --- a/go/ephemeral/errors.go +++ b/go/ephemeral/errors.go @@ -82,10 +82,10 @@ func newTransientEphemeralKeyError(err EphemeralKeyError) EphemeralKeyError { const ( DefaultHumanErrMsg = "This exploding message is not available" DefaultPluralHumanErrMsg = "%d exploding messages are not available" - DeviceCloneErrMsg = "cloned devices do not support exploding messages" - DeviceCloneWithOneshotErrMsg = "to support exploding messages in `oneshot` mode, you need a separate paper key for each running instance" - DeviceAfterEKErrMsg = "because this device was created after it was sent" - MemberAfterEKErrMsg = "because you joined the team after it was sent" + DeviceCloneErrMsg = "because this device has been cloned" + DeviceCloneWithOneshotErrMsg = "because this device is running in `oneshot` mode; to support exploding messages in `oneshot` mode, you need a separate paper key for each running instance" + DeviceAfterEKErrMsg = "because this device was created after the message was sent" + MemberAfterEKErrMsg = "because you joined the team after the message was sent" DeviceStaleErrMsg = "because this device wasn't online to generate an exploding key" UserStaleErrMsg = "because you weren't online to generate new exploding keys" ) @@ -159,7 +159,7 @@ func humanMsgWithPrefix(humanMsg string) string { if humanMsg == "" { humanMsg = DefaultHumanErrMsg } else if !strings.Contains(humanMsg, DefaultHumanErrMsg) { - humanMsg = fmt.Sprintf("%s, %s", DefaultHumanErrMsg, humanMsg) + humanMsg = fmt.Sprintf("%s %s", DefaultHumanErrMsg, humanMsg) } return humanMsg } diff --git a/go/go.mod b/go/go.mod index 1c0c4af9b7d4..d63e916b519e 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,13 +1,13 @@ module github.com/keybase/client/go -go 1.24.0 +go 1.25.0 toolchain go1.25.5 require ( bazil.org/fuse v0.0.0-20200424023519-3c101025617f camlistore.org v0.0.0-20161205184337-c55c8602d3ce - github.com/PuerkitoBio/goquery v1.5.1 + github.com/PuerkitoBio/goquery v1.12.0 github.com/akavel/rsrc v0.2.1-0.20151103204339-ba14da1f8271 github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e github.com/blang/semver v3.5.1+incompatible @@ -25,7 +25,7 @@ require ( github.com/gammazero/workerpool v0.0.0-20181230203049-86a96b5d5d92 github.com/go-errors/errors v1.4.2 github.com/go-sql-driver/mysql v1.9.3 - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 github.com/golang/mock v1.6.0 github.com/hashicorp/golang-lru v0.5.4 github.com/josephspurrier/goversioninfo v0.0.0-20160622020813-53f6213da3d7 @@ -71,13 +71,13 @@ require ( github.com/urfave/cli v1.22.1 github.com/vividcortex/ewma v1.1.2-0.20170804035156-43880d236f69 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.46.0 - golang.org/x/image v0.34.0 + golang.org/x/crypto v0.49.0 + golang.org/x/image v0.38.0 golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294 // indirect - golang.org/x/net v0.48.0 - golang.org/x/sync v0.19.0 - golang.org/x/sys v0.39.0 - golang.org/x/text v0.32.0 + golang.org/x/net v0.52.0 + golang.org/x/sync v0.20.0 + golang.org/x/sys v0.42.0 + golang.org/x/text v0.35.0 golang.org/x/time v0.14.0 gopkg.in/src-d/go-billy.v4 v4.3.2 gopkg.in/src-d/go-git.v4 v4.13.1 @@ -91,7 +91,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.28.6 github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 - github.com/gocolly/colly/v2 v2.1.1-0.20231020184023-3c987f1982ed + github.com/gocolly/colly/v2 v2.3.0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a gopkg.in/alecthomas/kingpin.v2 v2.2.6 @@ -104,11 +104,11 @@ require ( github.com/StackExchange/wmi v1.2.1 // indirect github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 // indirect github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect - github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/andybalholm/cascadia v1.3.3 // indirect github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect - github.com/antchfx/htmlquery v1.2.3 // indirect - github.com/antchfx/xmlquery v1.3.4 // indirect - github.com/antchfx/xpath v1.1.10 // indirect + github.com/antchfx/htmlquery v1.3.6 // indirect + github.com/antchfx/xmlquery v1.5.0 // indirect + github.com/antchfx/xpath v1.3.6 // indirect github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect @@ -125,7 +125,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect github.com/aws/smithy-go v1.22.1 // indirect - github.com/bits-and-blooms/bitset v1.2.2-0.20220111210104-dfa3e347c392 // indirect + github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/blevesearch/blevex v0.0.0-20190916190636-152f0fe5c040 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/segment v0.8.0 // indirect @@ -142,7 +142,7 @@ require ( github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -156,14 +156,14 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect github.com/nf/cr2 v0.0.0-20140528043846-05d46fef4f2f // indirect - github.com/nlnwa/whatwg-url v0.1.2 // indirect + github.com/nlnwa/whatwg-url v0.6.2 // indirect github.com/onsi/gomega v1.36.2 // indirect github.com/pelletier/go-buffruneio v0.3.0 // indirect github.com/philhofer/fwd v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/rwcarlsen/goexif v0.0.0-20150520140647-709fab3d192d // indirect - github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/simplereach/timeutils v1.2.0 // indirect @@ -174,7 +174,7 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/strib/gomounts v0.0.0-20180215003523-d9ea4eaa52ca // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect - github.com/temoto/robotstxt v1.1.1 // indirect + github.com/temoto/robotstxt v1.1.2 // indirect github.com/tinylib/msgp v1.1.0 // indirect github.com/willf/bitset v1.1.11-0.20190404145324-77892cd8d53f // indirect github.com/xanzy/ssh-agent v0.2.0 // indirect @@ -182,12 +182,12 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go4.org v0.0.0-20161118210015-09d86de304dc // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect - golang.org/x/tools v0.40.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect + golang.org/x/tools v0.42.0 // indirect golang.org/x/vuln v1.1.4 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.36.5 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 // indirect diff --git a/go/go.sum b/go/go.sum index 98b6b4b68162..67bfd5283537 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,4 +1,3 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.2.1-0.20160717150709-99064174e013/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -6,8 +5,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Masterminds/squirrel v0.0.0-20161115235646-20f192218cf5/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM= -github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo= +github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ= github.com/RoaringBitmap/roaring v0.4.22-0.20191112221735-4d53b29a8f7d h1:AaTEP55Sj0H3etCU0/Hr4uJNh42T5jRlU/r1dteKH8I= github.com/RoaringBitmap/roaring v0.4.22-0.20191112221735-4d53b29a8f7d/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= @@ -23,18 +22,17 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= -github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= +github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M= -github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0= -github.com/antchfx/xmlquery v1.3.4 h1:RuhsI4AA5Ma4XoXhaAr2VjJxU0Xp0W2zy/f9ZIpsF4s= -github.com/antchfx/xmlquery v1.3.4/go.mod h1:64w0Xesg2sTaawIdNqMB+7qaW/bSqkQm+ssPaCMWNnc= -github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= -github.com/antchfx/xpath v1.1.10 h1:cJ0pOvEdN/WvYXxvRrzQH9x5QWKpzHacYO8qzCcDYAg= -github.com/antchfx/xpath v1.1.10/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/antchfx/htmlquery v1.3.6 h1:RNHHL7YehO5XdO8IM8CynwLKONwRHWkrghbYhQIk9ag= +github.com/antchfx/htmlquery v1.3.6/go.mod h1:kcVUqancxPygm26X2rceEcagZFFVkLEE7xgLkGSDl/4= +github.com/antchfx/xmlquery v1.5.0 h1:uAi+mO40ZWfyU6mlUBxRVvL6uBNZ6LMU4M3+mQIBV4c= +github.com/antchfx/xmlquery v1.5.0/go.mod h1:lJfWRXzYMK1ss32zm1GQV3gMIW/HFey3xDZmkP1SuNc= +github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/xpath v1.3.6 h1:s0y+ElRRtTQdfHP609qFu0+c6bglDv20pqOViQjjdPI= +github.com/antchfx/xpath v1.3.6/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e h1:s05JG2GwtJMHaPcXDpo4V35TFgyYZzNsmBlSkHPEbeg= github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -79,8 +77,9 @@ github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/bits-and-blooms/bitset v1.2.2-0.20220111210104-dfa3e347c392 h1:9d7ak0NpT8/bhFM5ZkQuLpeS8Ey9zDY9OJJcOYqYV4c= -github.com/bits-and-blooms/bitset v1.2.2-0.20220111210104-dfa3e347c392/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blevesearch/bleve v0.8.2-0.20191030071327-189ee421f71e h1:JCcMFXeEvelJX6uSfSNiReo//1ukxugA/yQT2J2iXuM= @@ -95,11 +94,9 @@ github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoA github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -135,8 +132,6 @@ github.com/edsrzf/mmap-go v1.0.1-0.20190108065903-904c4ced31cd/go.mod h1:W3m91qe github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/emirpasic/gods v1.12.1-0.20181020102604-7c131f671417 h1:YxPhcuk2uFv51gUaZnDTK4za30/5kY6IJJ2GSR7e/LY= github.com/emirpasic/gods v1.12.1-0.20181020102604-7c131f671417/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/inject v0.0.0-20161006174721-cc1aa653e50f/go.mod h1:oO8UHw+fDHjDsk4CTy/E96WDzFUYozAtBAaGNoVL0+c= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= @@ -176,44 +171,39 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1 github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/packr v1.12.1/go.mod h1:H2dZhQFqHeZwr/5A/uGQkBp7xYuMGuzXFeKhYdcz5No= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe h1:zn8tqiUbec4wR94o7Qj3LZCAT6uGobhEgnDRg6isG5U= github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gocolly/colly/v2 v2.1.1-0.20231020184023-3c987f1982ed h1:JBVpXGF611yz+zSXdRbWOqZ4C6gs7YpthVvm752yO4Q= -github.com/gocolly/colly/v2 v2.1.1-0.20231020184023-3c987f1982ed/go.mod h1:bpukTX2Y+tFDoVBr4gAh7osKn/IbhWTgdmL1sMP0u0c= +github.com/gocolly/colly/v2 v2.3.0 h1:HSFh0ckbgVd2CSGRE+Y/iA4goUhGROJwyQDCMXGFBWM= +github.com/gocolly/colly/v2 v2.3.0/go.mod h1:Qp54s/kQbwCQvFVx8KzKCSTXVJ1wWT4QeAKEu33x1q8= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 h1:oERTZ1buOUYlpmKaqlO5fYmz8cZ1rYu5DieJzF4ZVmU= @@ -238,7 +228,6 @@ github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/C github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jarcoal/httpmock v0.0.0-20161210151336-4442edb3db31 h1:Aw95BEvxJ3K6o9GGv5ppCd1P8hkeIeEJ30FO+OhOJpM= github.com/jarcoal/httpmock v0.0.0-20161210151336-4442edb3db31/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= -github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg= 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/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -365,8 +354,8 @@ github.com/nf/cr2 v0.0.0-20140528043846-05d46fef4f2f h1:nyKdx+jcykIdxGNrbgo/TGjd github.com/nf/cr2 v0.0.0-20140528043846-05d46fef4f2f/go.mod h1:HazDB3gS/i//QXMMRmTAV7Ni9gAi4mDNTH2HjZ5aVgU= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/nlnwa/whatwg-url v0.1.2 h1:BqqsIVG6xv71wOoMAoFDmV6OK6/2sXn7BJdOsTkBl88= -github.com/nlnwa/whatwg-url v0.1.2/go.mod h1:b0r+dEyM/KztLMDSVY6ApcO9Fmzgq+e9+Ugq20UBYck= +github.com/nlnwa/whatwg-url v0.6.2 h1:jU61lU2ig4LANydbEJmA2nPrtCGiKdtgT0rmMd2VZ/Q= +github.com/nlnwa/whatwg-url v0.6.2/go.mod h1:x0FPXJzzOEieQtsBT/AKvbiBbQ46YlL6Xa7m02M1ECk= github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077/go.mod h1:sZZi9x5aHXGZ/RRp7Ne5rkvtDxZb7pd7vgVA+gmE35A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -399,7 +388,6 @@ github.com/pkg/xattr v0.2.2 h1:aOMVxbdr71YJkqrPXbwZRtKNbxvIOiDl3OjzAQbMTT0= github.com/pkg/xattr v0.2.2/go.mod h1:Y9LTXzFU+ntVswypGeJ916t7Haa8ao/kfcTKFEUX/Cs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/qrtz/nativemessaging v0.0.0-20161221035708-f4769a80e040 h1:iKwSrA9BD8ywmJGJy2JbiscEaRrtVY+m/U+ikacZazI= github.com/qrtz/nativemessaging v0.0.0-20161221035708-f4769a80e040/go.mod h1:oJXjZgmJiNwg5XtEvky9JCZqd7CZI7iz+mwmTxjIpak= github.com/rcrowley/go-metrics v0.0.0-20160113235030-51425a2415d2/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -420,8 +408,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20150520140647-709fab3d192d h1:0/OVHHIrQqGZxHRbrX3tJx95MkIbzV44KE66ZGkhH+0= github.com/rwcarlsen/goexif v0.0.0-20150520140647-709fab3d192d/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= -github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sebest/xff v0.0.0-20150611211316-7a36e3a787b5/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 h1:S4OC0+OBKz6mJnzuHioeEat74PuQ4Sgvbf8eus695sc= github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2/go.mod h1:8zLRYR5npGjaOXgPSKat5+oOh+UHd8OdbS18iqX9F6Y= @@ -463,7 +451,6 @@ github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible/g github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 h1:JNEGSiWg6D3lcBCMCBqN3ELniXujt+0QNHLhNnO0w3s= github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2/go.mod h1:mjqs7N0Q6m5HpR7QfXVBZXZWSqTjQLeTujjA/xUp2uw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -478,8 +465,8 @@ github.com/strib/gomounts v0.0.0-20180215003523-d9ea4eaa52ca h1:wVJqmi/uGy9ZBcMl github.com/strib/gomounts v0.0.0-20180215003523-d9ea4eaa52ca/go.mod h1:kyoAB93nwIsDbBftMJ8L2vIIGTdNORUr9hwjX83liiA= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= -github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA= -github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= +github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg= +github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tyler-smith/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= @@ -536,63 +523,62 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= -golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294 h1:Cr6kbEvA6nqvdHynE4CtVKlqpZB9dS1Jva/6IsHA19g= golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294/go.mod h1:RdZ+3sb4CVgpCFnzv+I4haEpwqFfsfzlLHs3L7ok+e0= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -623,36 +609,45 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= -golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= +golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -660,8 +655,10 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -673,31 +670,19 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -728,8 +713,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= perkeep.org v0.0.0-20161205184337-c55c8602d3ce h1:xy8y59WZpw0hhw4yw28PeIKmKorPHjbtPQoHnslwNZQ= diff --git a/go/kbfs/kbfsgit/runner.go b/go/kbfs/kbfsgit/runner.go index f9a60c26d9f0..c4551957f393 100644 --- a/go/kbfs/kbfsgit/runner.go +++ b/go/kbfs/kbfsgit/runner.go @@ -682,6 +682,21 @@ func (r *runner) waitForJournal(ctx context.Context) error { r.printStageEndIfNeeded) } +// bestBranchFromCandidates selects the best branch for HEAD from a set of +// candidate branch names (prefer main > master > alphabetically first). +func bestBranchFromCandidates(best plumbing.ReferenceName, candidate plumbing.ReferenceName) plumbing.ReferenceName { + switch { + case candidate == "refs/heads/main": + return candidate + case candidate == "refs/heads/master" && best != "refs/heads/main": + return candidate + case best == "" || (best != "refs/heads/main" && best != "refs/heads/master" && candidate < best): + return candidate + default: + return best + } +} + // handleList: From https://git-scm.com/docs/git-remote-helpers // // Lists the refs, one per line, in the format " [ @@ -708,9 +723,18 @@ func (r *runner) handleList(ctx context.Context, args []string) (err error) { if err != nil { return err } + defer refs.Close() - var symRefs []string + type symRefInfo struct { + name plumbing.ReferenceName + target plumbing.ReferenceName + } + var symRefs []symRefInfo + hashRefNames := make(map[plumbing.ReferenceName]bool) hashesSeen := false + // Track the best fallback branch for HEAD in case its target + // doesn't exist (prefer main > master > alphabetically first). + var bestBranch plumbing.ReferenceName for { ref, err := refs.Next() if errors.Cause(err) == io.EOF { @@ -725,6 +749,11 @@ func (r *runner) handleList(ctx context.Context, args []string) (err error) { case plumbing.HashReference: value = ref.Hash().String() hashesSeen = true + hashRefNames[ref.Name()] = true + // Track best branch for fallback HEAD. + if strings.HasPrefix(ref.Name().String(), "refs/heads/") { + bestBranch = bestBranchFromCandidates(bestBranch, ref.Name()) + } case plumbing.SymbolicReference: value = "@" + ref.Target().String() default: @@ -737,7 +766,10 @@ func (r *runner) handleList(ctx context.Context, args []string) (err error) { // cloning an empty repo will result in an error because // the HEAD symbolic ref points to a ref that doesn't // exist. - symRefs = append(symRefs, refStr) + symRefs = append(symRefs, symRefInfo{ + name: ref.Name(), + target: ref.Target(), + }) continue } r.log.CDebugf(ctx, "Listing ref %s", refStr) @@ -748,7 +780,30 @@ func (r *runner) handleList(ctx context.Context, args []string) (err error) { } if hashesSeen && !forPush { - for _, refStr := range symRefs { + for _, sr := range symRefs { + target := sr.target + // If the symref target doesn't exist among hash refs, + // rewrite it to point to the best available branch, + // but only for HEAD. Other symrefs are emitted as-is. + if !hashRefNames[target] { + if sr.name == plumbing.HEAD { + if bestBranch == "" { + r.log.CDebugf(ctx, + "Skipping HEAD symref %s -> %s (no branches available)", + sr.name, target) + continue + } + r.log.CDebugf(ctx, + "Rewriting HEAD symref from %s to %s", + target, bestBranch) + target = bestBranch + } else { + r.log.CDebugf(ctx, + "Emitting non-HEAD symref %s -> %s with unknown target", + sr.name, target) + } + } + refStr := "@" + target.String() + " " + sr.name.String() + "\n" r.log.CDebugf(ctx, "Listing symbolic ref %s", refStr) _, err = r.output.Write([]byte(refStr)) if err != nil { @@ -1830,6 +1885,53 @@ func (r *runner) handlePushBatch(ctx context.Context, args [][]string) ( return nil, err } + // If HEAD points to a nonexistent ref, update it to point to the + // best available branch in the repo (prefer main > master > alphabetical). + // We intentionally repair HEAD even when the target was explicitly + // deleted in this batch, because a broken HEAD causes clone failures. + // This must happen before waitForJournal so the HEAD update is + // included in the same flush. + head, headErr := repo.Storer.Reference(plumbing.HEAD) + if headErr == nil && head.Type() == plumbing.SymbolicReference { + _, targetErr := repo.Storer.Reference(head.Target()) + var bestBranch plumbing.ReferenceName + if targetErr == plumbing.ErrReferenceNotFound { + allRefs, refsErr := repo.References() + if refsErr == nil { + defer allRefs.Close() + for { + ref, nextErr := allRefs.Next() + if errors.Cause(nextErr) == io.EOF { + break + } + if nextErr != nil { + break + } + if ref.Type() != plumbing.HashReference { + continue + } + if !strings.HasPrefix(ref.Name().String(), "refs/heads/") { + continue + } + bestBranch = bestBranchFromCandidates(bestBranch, ref.Name()) + } + } + } + if bestBranch != "" { + newHead := plumbing.NewSymbolicReference( + plumbing.HEAD, bestBranch) + if setErr := repo.Storer.SetReference( + newHead); setErr != nil { + r.log.CDebugf(ctx, + "Error updating HEAD to %s: %+v", + bestBranch, setErr) + } else { + r.log.CDebugf(ctx, + "Updated HEAD to point to %s", bestBranch) + } + } + } + err = r.waitForJournal(ctx) if err != nil { return nil, err diff --git a/go/kbfs/kbfsgit/runner_test.go b/go/kbfs/kbfsgit/runner_test.go index 982994338f1c..3648bb44dbb2 100644 --- a/go/kbfs/kbfsgit/runner_test.go +++ b/go/kbfs/kbfsgit/runner_test.go @@ -27,6 +27,7 @@ import ( "github.com/keybase/client/go/protocol/keybase1" "github.com/stretchr/testify/require" gogitcfg "gopkg.in/src-d/go-git.v4/config" + "gopkg.in/src-d/go-git.v4/plumbing" ) type testErrput struct { @@ -156,7 +157,7 @@ func makeLocalRepoWithOneFileCustomCommitMsg(t *testing.T, gitExec(t, dotgit, gitDir, "init") if branch != "" { - gitExec(t, dotgit, gitDir, "checkout", "-b", branch) + gitExec(t, dotgit, gitDir, "checkout", "-B", branch) } gitExec(t, dotgit, gitDir, "add", filename) @@ -1289,3 +1290,42 @@ func TestRunnerLFS(t *testing.T) { require.NoError(t, err) require.Equal(t, lfsData, buf) } + +// Test that when only a non-master branch (e.g. "main") is pushed, +// HEAD correctly points to that branch instead of the nonexistent +// "refs/heads/master". +func TestRunnerListNonMasterDefault(t *testing.T) { + ctx, config, tempdir := initConfigForRunner(t) + defer func() { _ = os.RemoveAll(tempdir) }() + defer libkbfs.CheckConfigAndShutdown(ctx, t, config) + + gitDir, err := os.MkdirTemp(os.TempDir(), "kbfsgittest") + require.NoError(t, err) + defer func() { _ = os.RemoveAll(gitDir) }() + + makeLocalRepoWithOneFile(t, gitDir, "foo", "hello", "main") + + h, err := tlfhandle.ParseHandle( + ctx, config.KBPKI(), config.MDOps(), config, "user1", tlf.Private) + require.NoError(t, err) + _, err = libgit.CreateRepoAndID(ctx, config, h, "test") + require.NoError(t, err) + + testPush(ctx, t, config, gitDir, + "refs/heads/main:refs/heads/main") + + // Verify the underlying KBFS repo HEAD symref was actually updated. + fs, _, err := libgit.GetRepoAndID(ctx, config, h, "test", "") + require.NoError(t, err) + storage, err := libgit.NewGitConfigWithoutRemotesStorer(fs) + require.NoError(t, err) + headRef, err := storage.Reference(plumbing.HEAD) + require.NoError(t, err) + require.Equal(t, plumbing.SymbolicReference, headRef.Type()) + require.Equal(t, plumbing.ReferenceName("refs/heads/main"), headRef.Target()) + + // List refs and verify HEAD points to refs/heads/main. + heads := testListAndGetHeads(ctx, t, config, gitDir, + []string{"refs/heads/main", "HEAD"}) + require.Equal(t, "@refs/heads/main", heads[1]) +} diff --git a/go/kbfs/libgit/browser.go b/go/kbfs/libgit/browser.go index 8f4c7dd7160c..4b3b9fb5bf22 100644 --- a/go/kbfs/libgit/browser.go +++ b/go/kbfs/libgit/browser.go @@ -57,10 +57,12 @@ type Browser struct { var _ billy.Filesystem = (*Browser)(nil) // NewBrowser makes a new Browser instance, browsing the given branch -// of the given repo. If `gitBranchName` is empty, -// "refs/heads/master" is used. If `gitBranchName` is not empty, but -// it doesn't begin with "refs/", then "refs/heads/" is prepended to -// it. +// of the given repo. If `gitBranchName` is empty, HEAD is resolved +// to determine the default branch; if HEAD is missing or points to a +// nonexistent ref, NewBrowser falls back to "refs/heads/master" if it +// exists, otherwise the repo is treated as empty. If `gitBranchName` +// is not empty but doesn't begin with "refs/", then "refs/heads/" is +// prepended to it. func NewBrowser( repoFS *libfs.FS, clock libkbfs.Clock, gitBranchName plumbing.ReferenceName, @@ -73,6 +75,7 @@ func NewBrowser( } const masterBranch = "refs/heads/master" + branchWasEmpty := gitBranchName == "" if gitBranchName == "" { gitBranchName = masterBranch } else if !strings.HasPrefix(string(gitBranchName), "refs/") { @@ -98,9 +101,23 @@ func NewBrowser( return nil, err } + // If no branch was specified, try to resolve HEAD to find the + // default branch instead of hardcoding master. + if branchWasEmpty { + headRef, headErr := repo.Reference(plumbing.HEAD, false) + if headErr == nil && headRef.Type() == plumbing.SymbolicReference { + gitBranchName = headRef.Target() + } + } + ref, err := repo.Reference(gitBranchName, true) - if err == plumbing.ErrReferenceNotFound && gitBranchName == masterBranch { - // This branch has no commits, so pretend it's empty. + if err == plumbing.ErrReferenceNotFound && branchWasEmpty && gitBranchName != masterBranch { + // HEAD points to a nonexistent ref; fall back to master. + gitBranchName = masterBranch + ref, err = repo.Reference(gitBranchName, true) + } + if err == plumbing.ErrReferenceNotFound && (gitBranchName == masterBranch || branchWasEmpty) { + // No commits on this branch, so pretend it's empty. return &Browser{ root: string(gitBranchName), sharedCache: sharedCache, diff --git a/go/kbfs/libgit/browser_test.go b/go/kbfs/libgit/browser_test.go index 480d9abd8841..5c2120f1333d 100644 --- a/go/kbfs/libgit/browser_test.go +++ b/go/kbfs/libgit/browser_test.go @@ -19,6 +19,7 @@ import ( "github.com/keybase/client/go/protocol/keybase1" "github.com/stretchr/testify/require" gogit "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" ) @@ -176,3 +177,68 @@ func TestBrowserWithCache(t *testing.T) { require.NoError(t, err) testBrowser(t, cache) } + +func TestBrowserHeadResolution(t *testing.T) { + ctx, config, cancel, tempdir := initConfigForAutogit(t) + defer cancel() + defer func() { _ = os.RemoveAll(tempdir) }() + defer libkbfs.CheckConfigAndShutdown(ctx, t, config) + + h, err := tlfhandle.ParseHandle( + ctx, config.KBPKI(), config.MDOps(), nil, "user1", tlf.Private) + require.NoError(t, err) + rootFS, err := libfs.NewFS( + ctx, config, h, data.MasterBranch, "", "", keybase1.MDPriorityNormal) + require.NoError(t, err) + + t.Log("Init a new repo directly into KBFS.") + dotgitFS, _, err := GetOrCreateRepoAndID(ctx, config, h, "test-head", "") + require.NoError(t, err) + + err = rootFS.MkdirAll("worktree-head", 0o600) + require.NoError(t, err) + worktreeFS, err := rootFS.Chroot("worktree-head") + require.NoError(t, err) + dotgitStorage, err := NewGitConfigWithoutRemotesStorer(dotgitFS) + require.NoError(t, err) + repo, err := gogit.Init(dotgitStorage, worktreeFS) + require.NoError(t, err) + + t.Log("Set HEAD to point to refs/heads/main instead of master.") + newHead := plumbing.NewSymbolicReference( + plumbing.HEAD, "refs/heads/main") + err = repo.Storer.SetReference(newHead) + require.NoError(t, err) + + t.Log("Commit a file — go-git creates the main branch since HEAD points there.") + addFileToWorktreeAndCommit( + ctx, t, config, h, repo, worktreeFS, "hello.txt", "world") + + t.Log("NewBrowser with empty branch should resolve HEAD to main.") + b, err := NewBrowser(dotgitFS, config.Clock(), "", noopSharedInBrowserCache{}) + require.NoError(t, err) + require.NotNil(t, b.tree, "browser should have a non-nil tree") + fi, err := b.Stat("hello.txt") + require.NoError(t, err) + require.Equal(t, "hello.txt", fi.Name()) + + f, err := b.Open("hello.txt") + require.NoError(t, err) + defer func() { _ = f.Close() }() + fileData, err := io.ReadAll(f) + require.NoError(t, err) + require.Equal(t, "world", string(fileData)) + + t.Log("Test stale HEAD: point HEAD to a nonexistent ref.") + staleHead := plumbing.NewSymbolicReference( + plumbing.HEAD, "refs/heads/nonexistent") + err = repo.Storer.SetReference(staleHead) + require.NoError(t, err) + + t.Log("NewBrowser should return an empty browser without error.") + b, err = NewBrowser(dotgitFS, config.Clock(), "", noopSharedInBrowserCache{}) + require.NoError(t, err) + fis, err := b.ReadDir("") + require.NoError(t, err) + require.Len(t, fis, 0) +} diff --git a/go/libkb/loopback.go b/go/libkb/loopback.go index 00368cc18590..b264eb396bdc 100644 --- a/go/libkb/loopback.go +++ b/go/libkb/loopback.go @@ -102,8 +102,10 @@ func (ll *LoopbackListener) Close() (err error) { ll.mutex.Lock() defer ll.mutex.Unlock() if ll.isClosed { + ll.logCtx.GetLog().Debug("LoopbackListener.Close: already closed") return syscall.EINVAL } + ll.logCtx.GetLog().Debug("LoopbackListener.Close: closing") ll.isClosed = true close(ll.ch) return diff --git a/go/libkb/secret_store.go b/go/libkb/secret_store.go index 8427edc54ca5..942d66748968 100644 --- a/go/libkb/secret_store.go +++ b/go/libkb/secret_store.go @@ -112,7 +112,8 @@ func NewSecretStore(m MetaContext, username NormalizedUsername) SecretStore { return nil } -func GetConfiguredAccountsFromProvisionedUsernames(m MetaContext, s SecretStoreAll, currentUsername NormalizedUsername, allUsernames []NormalizedUsername) ([]keybase1.ConfiguredAccount, error) { +func GetConfiguredAccountsFromProvisionedUsernames(m MetaContext, s SecretStoreAll, currentUsername NormalizedUsername, allUsernames []NormalizedUsername) (_ []keybase1.ConfiguredAccount, err error) { + defer m.Trace("GetConfiguredAccountsFromProvisionedUsernames", &err)() if !currentUsername.IsNil() { allUsernames = append(allUsernames, currentUsername) } @@ -321,7 +322,8 @@ func (s *SecretStoreLocked) ClearSecret(m MetaContext, username NormalizedUserna return s.disk.ClearSecret(m, username) } -func (s *SecretStoreLocked) GetUsersWithStoredSecrets(m MetaContext) ([]string, error) { +func (s *SecretStoreLocked) GetUsersWithStoredSecrets(m MetaContext) (_ []string, err error) { + defer m.Trace("SecretStoreLocked.GetUsersWithStoredSecrets", &err)() if s == nil || s.isNil() { return nil, nil } diff --git a/go/libkb/secret_store_darwin.go b/go/libkb/secret_store_darwin.go index e0b09aaa2703..fa3ee171684b 100644 --- a/go/libkb/secret_store_darwin.go +++ b/go/libkb/secret_store_darwin.go @@ -217,7 +217,8 @@ func HasSecretStore() bool { return true } -func (k KeychainSecretStore) GetUsersWithStoredSecrets(mctx MetaContext) ([]string, error) { +func (k KeychainSecretStore) GetUsersWithStoredSecrets(mctx MetaContext) (_ []string, err error) { + defer mctx.Trace("KeychainSecretStore.GetUsersWithStoredSecrets", &err)() accounts, err := keychain.GetAccountsForService(k.serviceName(mctx)) if err != nil { mctx.Debug("KeychainSecretStore.GetUsersWithStoredSecrets() error: %s", err) diff --git a/go/libkb/socket.go b/go/libkb/socket.go index 6a668c1a40fb..2361c6341b50 100644 --- a/go/libkb/socket.go +++ b/go/libkb/socket.go @@ -42,6 +42,11 @@ type SocketWrapper struct { func (g *GlobalContext) MakeLoopbackServer() (l net.Listener, err error) { g.socketWrapperMu.Lock() defer g.socketWrapperMu.Unlock() + if g.LoopbackListener != nil { + g.Log.Debug("MakeLoopbackServer: replacing existing loopback listener (old isClosed=%v)", g.LoopbackListener.isClosed) + } else { + g.Log.Debug("MakeLoopbackServer: creating new loopback listener") + } g.LoopbackListener = NewLoopbackListener(g) l = g.LoopbackListener return l, err diff --git a/go/libkb/version.go b/go/libkb/version.go index d8b23ce1ad0e..4740e6d87f04 100644 --- a/go/libkb/version.go +++ b/go/libkb/version.go @@ -4,4 +4,4 @@ package libkb // Version is the current version (should be MAJOR.MINOR.PATCH) -const Version = "6.6.0" +const Version = "6.6.2" diff --git a/go/logger/buffer.go b/go/logger/buffer.go index 819bd702837e..91f6cab9e14b 100644 --- a/go/logger/buffer.go +++ b/go/logger/buffer.go @@ -81,12 +81,7 @@ func (writer *autoFlushingBufferedWriter) backgroundFlush() { select { case <-writer.timer.C: // Swap out active and backup writers - writer.lock.Lock() - writer.bufferedWriter, writer.backupWriter = writer. - backupWriter, writer.bufferedWriter - writer.lock.Unlock() - - writer.backupWriter.Flush() + writer.Flush() case <-writer.shutdown: writer.timer.shutdownCh <- struct{}{} writer.bufferedWriter.Flush() @@ -127,6 +122,15 @@ func NewAutoFlushingBufferedWriter(baseWriter io.Writer, return result, result.shutdown, result.doneShutdown } +// Flush synchronously flushes any buffered data to the underlying writer. +// Safe to call concurrently with Write. +func (writer *autoFlushingBufferedWriter) Flush() { + writer.lock.Lock() + writer.bufferedWriter, writer.backupWriter = writer.backupWriter, writer.bufferedWriter + writer.lock.Unlock() + _ = writer.backupWriter.Flush() +} + func (writer *autoFlushingBufferedWriter) Write(p []byte) (int, error) { // The locked resource here, the pointer bufferedWriter, is only being read // even though this function is Write. diff --git a/go/logger/file.go b/go/logger/file.go index e59f796d1028..4ec2f4af44fe 100644 --- a/go/logger/file.go +++ b/go/logger/file.go @@ -62,6 +62,7 @@ func SetLogFileConfig(lfc *LogFileConfig, blc *BufferedLoggerConfig) error { if first { buf, shutdown, _ := NewAutoFlushingBufferedWriter(w, blc) w.stopFlushing = shutdown + currentBufferedWriter = buf.(*autoFlushingBufferedWriter) fileBackend := logging.NewLogBackend(buf, "", 0) logging.SetBackend(fileBackend) @@ -105,6 +106,18 @@ func (lfw *LogFileWriter) Open(at time.Time) error { return nil } +// FlushLogFile synchronously flushes any buffered log data to disk. +// Call this before the app is suspended or after resuming from background +// to ensure logs are not lost if the process is killed. +func FlushLogFile() { + globalLock.Lock() + buf := currentBufferedWriter + globalLock.Unlock() + if buf != nil { + buf.Flush() + } +} + func (lfw *LogFileWriter) Close() error { if lfw == nil { return nil diff --git a/go/logger/global.go b/go/logger/global.go index fb8d947362dc..fb25add2f78b 100644 --- a/go/logger/global.go +++ b/go/logger/global.go @@ -12,6 +12,7 @@ var ( globalLock sync.Mutex stderrIsTerminal = isatty.IsTerminal(os.Stderr.Fd()) currentLogFileWriter *LogFileWriter + currentBufferedWriter *autoFlushingBufferedWriter stdErrLoggingShutdown chan<- struct{} stdErrLoggingShutdownDone <-chan struct{} ) diff --git a/go/service/config.go b/go/service/config.go index 0a6ddb4cfcde..2e9d0a1aabca 100644 --- a/go/service/config.go +++ b/go/service/config.go @@ -348,22 +348,22 @@ func (h ConfigHandler) WaitForClient(_ context.Context, arg keybase1.WaitForClie return h.G().ConnectionManager.WaitForClientType(arg.ClientType, arg.Timeout.Duration()), nil } -func (h ConfigHandler) GetBootstrapStatus(ctx context.Context, sessionID int) (keybase1.BootstrapStatus, error) { +func (h ConfigHandler) GetBootstrapStatus(ctx context.Context, sessionID int) (res keybase1.BootstrapStatus, err error) { + m := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG") + defer m.Trace("GetBootstrapStatus", &err)() eng := engine.NewBootstrap(h.G()) - m := libkb.NewMetaContext(ctx, h.G()) - if err := engine.RunEngine2(m, eng); err != nil { - return keybase1.BootstrapStatus{}, err + if err = engine.RunEngine2(m, eng); err != nil { + return res, err } - status := eng.Status() - h.G().Log.CDebugf(ctx, "GetBootstrapStatus: attempting to get HTTP server address") + res = eng.Status() + m.Debug("GetBootstrapStatus: attempting to get HTTP server address") for i := 0; i < 40; i++ { // wait at most 2 seconds - addr, err := h.svc.httpSrv.Addr() - if err != nil { - h.G().Log.CDebugf(ctx, "GetBootstrapStatus: failed to get HTTP server address: %s", err) + addr, addrErr := h.svc.httpSrv.Addr() + if addrErr != nil { + m.Debug("GetBootstrapStatus: failed to get HTTP server address: %s", addrErr) } else { - h.G().Log.CDebugf(ctx, "GetBootstrapStatus: http server: addr: %s token: %s", addr, - h.svc.httpSrv.Token()) - status.HttpSrvInfo = &keybase1.HttpSrvInfo{ + m.Debug("GetBootstrapStatus: http server: addr: %s token: %s", addr, h.svc.httpSrv.Token()) + res.HttpSrvInfo = &keybase1.HttpSrvInfo{ Address: addr, Token: h.svc.httpSrv.Token(), } @@ -371,10 +371,10 @@ func (h ConfigHandler) GetBootstrapStatus(ctx context.Context, sessionID int) (k } time.Sleep(50 * time.Millisecond) } - if status.HttpSrvInfo == nil { - h.G().Log.CDebugf(ctx, "GetBootstrapStatus: failed to get HTTP srv info after max attempts") + if res.HttpSrvInfo == nil { + m.Debug("GetBootstrapStatus: failed to get HTTP srv info after max attempts") } - return status, nil + return res, nil } func (h ConfigHandler) RequestFollowingAndUnverifiedFollowers(ctx context.Context, sessionID int) error { diff --git a/go/status/status.go b/go/status/status.go index eee83f10ae7c..1c1f7b8ba621 100644 --- a/go/status/status.go +++ b/go/status/status.go @@ -119,11 +119,22 @@ func GetExtendedStatus(mctx libkb.MetaContext) (res keybase1.ExtendedStatus, err res.DeviceEkNames = dekNames } - res.LocalDbStats = strings.Split(g.LocalDb.Stats(), "\n") - res.LocalChatDbStats = strings.Split(g.LocalChatDb.Stats(), "\n") - if cacheSizeInfo, err := CacheSizeInfo(g); err == nil { - res.CacheDirSizeInfo = cacheSizeInfo - } + func() { + defer mctx.Trace("LocalDb.Stats", nil)() + res.LocalDbStats = strings.Split(g.LocalDb.Stats(), "\n") + }() + + func() { + defer mctx.Trace("LocalChatDb.Stats", nil)() + res.LocalChatDbStats = strings.Split(g.LocalChatDb.Stats(), "\n") + }() + + func() { + defer mctx.Trace("CacheSizeInfo", nil)() + if cacheSizeInfo, err := CacheSizeInfo(g); err == nil { + res.CacheDirSizeInfo = cacheSizeInfo + } + }() if g.ConnectionManager != nil { xp := g.ConnectionManager.LookupByClientType(keybase1.ClientType_KBFS) @@ -134,13 +145,16 @@ func GetExtendedStatus(mctx libkb.MetaContext) (res keybase1.ExtendedStatus, err Cli: rpc.NewClient( xp, libkb.NewContextifiedErrorUnwrapper(g), nil), } - stats, err := cli.SimpleFSGetStats(mctx.Ctx()) - if err != nil { - mctx.Debug("| KBFS stats error: %+v", err) - } else { - res.LocalBlockCacheDbStats = stats.BlockCacheDbStats - res.LocalSyncCacheDbStats = stats.SyncCacheDbStats - } + func() { + defer mctx.Trace("SimpleFSGetStats", nil)() + stats, err := cli.SimpleFSGetStats(mctx.Ctx()) + if err != nil { + mctx.Debug("| KBFS stats error: %+v", err) + } else { + res.LocalBlockCacheDbStats = stats.BlockCacheDbStats + res.LocalSyncCacheDbStats = stats.SyncCacheDbStats + } + }() } } diff --git a/go/uidmap/uidmap.go b/go/uidmap/uidmap.go index c2a8594d2b5a..24894383ab70 100644 --- a/go/uidmap/uidmap.go +++ b/go/uidmap/uidmap.go @@ -355,6 +355,7 @@ func (u *UIDMap) MapUIDsToUsernamePackages(ctx context.Context, g libkb.UIDMappe uids []keybase1.UID, fullNameFreshness, networkTimeBudget time.Duration, forceNetworkForFullNames bool, ) (res []libkb.UsernamePackage, err error) { + defer libkb.CTrace(ctx, g.GetLog(), "UIDMap.MapUIDsToUsernamePackages", &err, g.GetClock())() u.Lock() defer u.Unlock() diff --git a/packaging/linux/tuxbot/bot/chatbot/chatbot.go b/packaging/linux/tuxbot/bot/chatbot/chatbot.go index 8081e305365b..51534ace3538 100644 --- a/packaging/linux/tuxbot/bot/chatbot/chatbot.go +++ b/packaging/linux/tuxbot/bot/chatbot/chatbot.go @@ -121,13 +121,13 @@ func (l ChatLogger) VDebug(format string, args ...interface{}) { func (l ChatLogger) Debug(format string, args ...interface{}) { msg := l.msg(format, args...) fmt.Println(msg) - if _, err := l.API.SendMessage(l.DebugChannel, msg); err != nil { + if _, err := l.API.SendMessage(l.DebugChannel, "%s", msg); err != nil { fmt.Printf("unable to SendMessage: %v", err) } } func (l ChatLogger) Info(format string, args ...interface{}) { - if _, err := l.API.SendMessage(l.InfoChannel, l.msg(format, args...)); err != nil { + if _, err := l.API.SendMessage(l.InfoChannel, "%s", l.msg(format, args...)); err != nil { fmt.Printf("unable to SendMessage: %v", err) } if l.InfoChannel.Name != l.DebugChannel.Name || l.InfoChannel.TopicName != l.DebugChannel.TopicName { diff --git a/packaging/linux/tuxbot/bot/common/acl.go b/packaging/linux/tuxbot/bot/common/acl.go index 3e7e72ae4bec..81902dfe0d2a 100644 --- a/packaging/linux/tuxbot/bot/common/acl.go +++ b/packaging/linux/tuxbot/bot/common/acl.go @@ -7,8 +7,10 @@ import ( var self access.Username = "tuxbot" -var tuxbotAdmins = []access.Username{self, "max", "mikem", "modalduality", "cjb", "jzila", - "patrick", "songgao", "strib", "joshblum", "mlsteele"} +var tuxbotAdmins = []access.Username{ + self, "mikem", "modalduality", + "patrick", "songgao", "joshblum", +} func SimpleTuxbotACL(infoChannel chat1.ChatChannel) access.ACL { return access.NewConstantACL(map[chat1.ChatChannel][]access.Username{ diff --git a/packaging/linux/tuxbot/bot/tuxbot/tuxbot.go b/packaging/linux/tuxbot/bot/tuxbot/tuxbot.go index d5a3c4b0dc18..1ca3e62df1dd 100644 --- a/packaging/linux/tuxbot/bot/tuxbot/tuxbot.go +++ b/packaging/linux/tuxbot/bot/tuxbot/tuxbot.go @@ -113,12 +113,12 @@ func (c Tuxbot) Dispatch(msg chat1.MsgSummary, args []string) (err error) { xzCmd := exec.Command("xz") archiveName := fmt.Sprintf("/keybase/team/%s/keybase-%s.tar.xz", c.archiveTeam, revision) - archiveHandle, err := os.OpenFile(archiveName, os.O_RDWR|os.O_CREATE, 0644) + archiveHandle, err := os.OpenFile(archiveName, os.O_RDWR|os.O_CREATE, 0o644) if err != nil { return err } defer archiveHandle.Close() - sigHandle, err := os.OpenFile(archiveName+".sig", os.O_RDWR|os.O_CREATE, 0644) + sigHandle, err := os.OpenFile(archiveName+".sig", os.O_RDWR|os.O_CREATE, 0o644) if err != nil { return err } @@ -228,7 +228,7 @@ func (c Tuxbot) Dispatch(msg chat1.MsgSummary, args []string) (err error) { return } - if _, err := c.API().SendMessage(c.sendChannel, "!build-docker "+strings.Join(args, " ")); err != nil { + if _, err := c.API().SendMessage(c.sendChannel, "%s", "!build-docker "+strings.Join(args, " ")); err != nil { c.Debug("unable to SendMessage: %v", err) } }() @@ -444,7 +444,7 @@ func (c Tuxbot) Dispatch(msg chat1.MsgSummary, args []string) (err error) { } func execToFile(filename string, cmd *exec.Cmd) error { - handle, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) + handle, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o644) if err != nil { return err } @@ -463,6 +463,61 @@ func execToFile(filename string, cmd *exec.Cmd) error { return nil } +func (c Tuxbot) advertisement() kbchat.Advertisement { + commands := []chat1.UserBotCommandInput{ + { + Name: "release", + Description: "Build Linux release packages from HEAD or a specified revision.", + }, + { + Name: "nightly", + Description: "Build nightly Linux packages from HEAD or a specified revision.", + }, + { + Name: "test", + Description: "Build Linux test packages from HEAD or a specified revision.", + }, + { + Name: "archive", + Description: "Create and sign Linux source release artifacts for a revision.", + }, + { + Name: "build-docker", + Description: "Build and push Docker images for HEAD or a specified revision.", + }, + { + Name: "release-docker", + Description: "Promote an existing Docker tag as a release.", + }, + { + Name: "tuxjournal", + Description: "Write the recent tuxbot journal to the archive team folder.", + }, + { + Name: "journal", + Description: "Write the recent system journal to the archive team folder.", + }, + { + Name: "cleanup", + Description: "Run the local cleanup helper on the tuxbot host.", + }, + { + Name: "restartdocker", + Description: "Restart Docker on the tuxbot host.", + }, + } + + return kbchat.Advertisement{ + Alias: c.Name, + Advertisements: []chat1.AdvertiseCommandAPIParam{ + { + Typ: "public", + Commands: commands, + }, + }, + } +} + func main() { err := gotenv.Load(fmt.Sprintf("/keybase/team/%s/.kbfs_autogit/%s/tuxbot.env", os.Getenv("SECRETS_TEAM"), os.Getenv("SECRETS_REPO"))) if err != nil { @@ -510,6 +565,10 @@ func main() { archiveTeam: *infoTeam, } + if _, err := tuxbot.API().AdvertiseCommands(tuxbot.advertisement()); err != nil { + tuxbot.Info("unable to advertise commands: %v", err) + } + if err := chatbot.Listen(tuxbot); err != nil { panic(err) } diff --git a/protocol/package.json b/protocol/package.json index bd5b7938feb8..0452f4851000 100644 --- a/protocol/package.json +++ b/protocol/package.json @@ -17,7 +17,7 @@ }, "homepage": "https://github.com/keybase/protocol", "dependencies": { - "avdl-compiler": "github:keybase/node-avdl-compiler#master", + "avdl-compiler": "github:keybase/node-avdl-compiler#7fe780ac783fa214e2d0f54d8f48ea490e8c1612", "avdl2json": "2.2.5", "camelcase": "6.3.0", "iced-coffee-script": "108.0.14", diff --git a/protocol/yarn.lock b/protocol/yarn.lock index 26b2a5cbad88..3777ca14c466 100644 --- a/protocol/yarn.lock +++ b/protocol/yarn.lock @@ -17,7 +17,7 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= -"avdl-compiler@github:keybase/node-avdl-compiler#master": +"avdl-compiler@github:keybase/node-avdl-compiler#7fe780ac783fa214e2d0f54d8f48ea490e8c1612": version "1.4.10" resolved "https://codeload.github.com/keybase/node-avdl-compiler/tar.gz/7fe780ac783fa214e2d0f54d8f48ea490e8c1612" dependencies: diff --git a/rnmodules/kb-common/src/ItemProviderHelper.swift b/rnmodules/kb-common/src/ItemProviderHelper.swift index 41273e67ffbc..3dca9745c613 100644 --- a/rnmodules/kb-common/src/ItemProviderHelper.swift +++ b/rnmodules/kb-common/src/ItemProviderHelper.swift @@ -4,7 +4,6 @@ import Foundation import UIKit import UniformTypeIdentifiers - @objc(ItemProviderHelper) public class ItemProviderHelper: NSObject { private var itemArrs: [Any] @@ -14,12 +13,12 @@ public class ItemProviderHelper: NSObject { private var typeToArray: [String: [[String: Any]]] = [:] private var completionHandler: () -> Void private var unprocessed: Int = 0 - + @objc public var manifest: [[String: Any]] { // reconcile what we're sending over. types=text, url, video, image, file, error var toWrite: [[String: Any]] = [] let urls = typeToArray["url"] - + // We treat all text that has http in it a url, we take the longest one as its // likely most descriptive if let urls = urls { @@ -31,14 +30,14 @@ public class ItemProviderHelper: NSObject { } toWrite.append([ "type": "text", - "content": content + "content": content, ]) } else { let images = typeToArray["image"] ?? [] let videos = typeToArray["video"] ?? [] let files = typeToArray["file"] ?? [] let texts = typeToArray["text"] ?? [] - + // If we have media, ignore text, we want to attach stuff and not also // inject into the input box if !images.isEmpty || !videos.isEmpty || !files.isEmpty { @@ -50,17 +49,19 @@ public class ItemProviderHelper: NSObject { toWrite.append(texts[0]) } } - + return toWrite } - - @objc public init(forShare isShare: Bool, withItems itemArrs: [Any], completionHandler: @escaping () -> Void) { + + @objc public init( + forShare isShare: Bool, withItems itemArrs: [Any], completionHandler: @escaping () -> Void + ) { self.isShare = isShare self.itemArrs = itemArrs self.completionHandler = completionHandler self.payloadFolderURL = Self.makePayloadFolder() } - + private func completeProcessingItemAlreadyInMainThread() { // more to process objc_sync_enter(self) @@ -70,7 +71,7 @@ public class ItemProviderHelper: NSObject { return } objc_sync_exit(self) - + // done if !done { done = true @@ -80,76 +81,82 @@ public class ItemProviderHelper: NSObject { // already done? } } - + private static func getIncomingShareFolder() -> URL { - let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.keybase")! + let containerURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: "group.keybase")! // Use the cache URL so if we fail to clean up payloads they can be deleted by // the OS. let cacheURL = containerURL.appendingPathComponent("Library", isDirectory: true) .appendingPathComponent("Caches", isDirectory: true) - let incomingShareFolderURL = cacheURL.appendingPathComponent("incoming-shares", isDirectory: true) + let incomingShareFolderURL = cacheURL.appendingPathComponent( + "incoming-shares", isDirectory: true) return incomingShareFolderURL } - + private static func makePayloadFolder() -> URL { let incomingShareFolderURL = getIncomingShareFolder() let guid = ProcessInfo.processInfo.globallyUniqueString let payloadFolderURL = incomingShareFolderURL.appendingPathComponent(guid, isDirectory: true) - try? FileManager.default.createDirectory(at: payloadFolderURL, withIntermediateDirectories: true, attributes: nil) + try? FileManager.default.createDirectory( + at: payloadFolderURL, withIntermediateDirectories: true, attributes: nil) return payloadFolderURL } - + private func getPayloadURL(from url: URL?) -> URL { let guid = ProcessInfo.processInfo.globallyUniqueString - return url != nil ? payloadFolderURL.appendingPathComponent(url!.lastPathComponent) - : payloadFolderURL.appendingPathComponent(guid) + return url != nil + ? payloadFolderURL.appendingPathComponent(url!.lastPathComponent) + : payloadFolderURL.appendingPathComponent(guid) } - + private func getPayloadURL(withExtension ext: String?) -> URL { let guid = ProcessInfo.processInfo.globallyUniqueString - return ext != nil ? payloadFolderURL.appendingPathComponent(guid).appendingPathExtension(ext!) - : payloadFolderURL.appendingPathComponent(guid) + return ext != nil + ? payloadFolderURL.appendingPathComponent(guid).appendingPathExtension(ext!) + : payloadFolderURL.appendingPathComponent(guid) } - + private func getManifestFileURL() -> URL { let incomingShareFolderURL = Self.getIncomingShareFolder() - try? FileManager.default.createDirectory(at: incomingShareFolderURL, withIntermediateDirectories: true, attributes: nil) + try? FileManager.default.createDirectory( + at: incomingShareFolderURL, withIntermediateDirectories: true, attributes: nil) return incomingShareFolderURL.appendingPathComponent("manifest.json") } - + private func ensureArray(ofType type: String) -> [[String: Any]] { if typeToArray[type] == nil { typeToArray[type] = [] } return typeToArray[type]! } - + private func completeItemAndAppendManifest(type: String, originalFileURL: URL) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } var arr = self.ensureArray(ofType: type) arr.append([ "type": type, - "originalPath": originalFileURL.absoluteURL.path + "originalPath": originalFileURL.absoluteURL.path, ]) self.typeToArray[type] = arr self.completeProcessingItemAlreadyInMainThread() } } - + private func completeItemAndAppendManifest(type: String, content: String) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } var arr = self.ensureArray(ofType: type) arr.append([ "type": type, - "content": content + "content": content, ]) self.typeToArray[type] = arr self.completeProcessingItemAlreadyInMainThread() } } - + private func completeItemAndAppendManifest(type: String, originalFileURL: URL, content: String) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -157,27 +164,28 @@ public class ItemProviderHelper: NSObject { arr.append([ "type": type, "originalPath": originalFileURL.absoluteURL.path, - "content": content + "content": content, ]) self.typeToArray[type] = arr self.completeProcessingItemAlreadyInMainThread() } } - - private func completeItemAndAppendManifest(type: String, originalFileURL: URL, scaledFileURL: URL) { + + private func completeItemAndAppendManifest(type: String, originalFileURL: URL, scaledFileURL: URL) + { DispatchQueue.main.async { [weak self] in guard let self = self else { return } var arr = self.ensureArray(ofType: type) arr.append([ "type": type, "originalPath": originalFileURL.absoluteURL.path, - "scaledPath": scaledFileURL.absoluteURL.path + "scaledPath": scaledFileURL.absoluteURL.path, ]) self.typeToArray[type] = arr self.completeProcessingItemAlreadyInMainThread() } } - + private func completeItemAndAppendManifestAndLogError(text: String, error: Error?) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -189,7 +197,7 @@ public class ItemProviderHelper: NSObject { self.completeProcessingItemAlreadyInMainThread() } } - + private func writeManifest() { let toWrite = manifest let fileURL = getManifestFileURL() @@ -200,58 +208,63 @@ public class ItemProviderHelper: NSObject { JSONSerialization.writeJSONObject(toWrite, to: outputStream, options: [], error: nil) } } - + private func handleAndCompleteMediaFile(_ url: URL, isVideo: Bool) { if isVideo { MediaUtils.processVideo(fromOriginal: url) { error, scaled in if let error = error { - self.completeItemAndAppendManifestAndLogError(text: "handleAndCompleteMediaFile", error: error) + self.completeItemAndAppendManifestAndLogError( + text: "handleAndCompleteMediaFile", error: error) return } - self.completeItemAndAppendManifest(type: isVideo ? "video" : "image", - originalFileURL: url, - scaledFileURL: scaled!) + self.completeItemAndAppendManifest( + type: isVideo ? "video" : "image", + originalFileURL: url, + scaledFileURL: scaled!) } } else { MediaUtils.processImage(fromOriginal: url) { error, scaled in if let error = error { - self.completeItemAndAppendManifestAndLogError(text: "handleAndCompleteMediaFile", error: error) + self.completeItemAndAppendManifestAndLogError( + text: "handleAndCompleteMediaFile", error: error) return } - self.completeItemAndAppendManifest(type: isVideo ? "video" : "image", - originalFileURL: url, - scaledFileURL: scaled!) + self.completeItemAndAppendManifest( + type: isVideo ? "video" : "image", + originalFileURL: url, + scaledFileURL: scaled!) } } } - + private func sendText(_ text: String) { if text.isEmpty { completeItemAndAppendManifestAndLogError(text: "sendText: empty?", error: nil) return } - + if text.count < 1000 { let isURL = text.range(of: "http", options: .caseInsensitive) != nil completeItemAndAppendManifest(type: isURL ? "url" : "text", content: text) return } - + let originalFileURL = getPayloadURL(withExtension: "txt") do { try text.write(to: originalFileURL, atomically: true, encoding: .utf8) completeItemAndAppendManifest(type: "text", originalFileURL: originalFileURL) } catch { - completeItemAndAppendManifestAndLogError(text: "sendText: unable to write payload file", error: error) + completeItemAndAppendManifestAndLogError( + text: "sendText: unable to write payload file", error: error) } } - + private func sendFile(_ url: URL?) { guard let url = url else { completeItemAndAppendManifestAndLogError(text: "sendFile: unable to decode share", error: nil) return } - + let filePayloadURL = getPayloadURL(from: url) do { try FileManager.default.copyItem(at: url, to: filePayloadURL) @@ -260,45 +273,47 @@ public class ItemProviderHelper: NSObject { completeItemAndAppendManifestAndLogError(text: "fileHandlerSimple: copy error", error: error) } } - + private func sendContact(_ vCardData: Data) { do { let contacts = try CNContactVCardSerialization.contacts(with: vCardData) let addressFormatter = CNPostalAddressFormatter() var contents: [String] = [] - + for contact in contacts { var content: [String] = [] - if let fullName = CNContactFormatter.string(from: contact, style: .fullName), !fullName.isEmpty { + if let fullName = CNContactFormatter.string(from: contact, style: .fullName), + !fullName.isEmpty + { content.append(fullName) } - + // For NSString properties, we need to check length if (contact.organizationName as NSString).length > 0 { content.append("Organization: \(contact.organizationName)") } - + for phoneNumber in contact.phoneNumbers { let label = CNLabeledValue.localizedString(forLabel: phoneNumber.label ?? "") let number = phoneNumber.value.stringValue - + if (label as NSString).length > 0 && (number as NSString).length > 0 { content.append("\(label): \(number)") } else if (number as NSString).length > 0 { content.append(number) } } - + // Handle email addresses and URLs var misc: [Any] = [] misc.append(contentsOf: contact.emailAddresses) misc.append(contentsOf: contact.urlAddresses) - + for m in misc { if let labeledValue = m as? CNLabeledValue { let label = CNLabeledValue.localizedString(forLabel: labeledValue.label ?? "") let val = labeledValue.value as String - + if !label.isEmpty && !val.isEmpty { content.append("\(label): \(val)") } else if !val.isEmpty { @@ -306,39 +321,41 @@ public class ItemProviderHelper: NSObject { } } } - + // Handle postal addresses for postalAddress in contact.postalAddresses { let label = CNLabeledValue.localizedString(forLabel: postalAddress.label ?? "") let val = addressFormatter.string(from: postalAddress.value) - + if !label.isEmpty && !val.isEmpty { content.append("\(label): \(val)") } else if !val.isEmpty { content.append(val) } } - + if !content.isEmpty { contents.append(content.joined(separator: "\n")) } } - + if !contents.isEmpty { let text = contents.joined(separator: "\n\n") completeItemAndAppendManifest(type: "text", content: text) } else { - completeItemAndAppendManifestAndLogError(text: "vcardHandler: unable to decode share", error: nil) + completeItemAndAppendManifestAndLogError( + text: "vcardHandler: unable to decode share", error: nil) } } catch { - completeItemAndAppendManifestAndLogError(text: "vcardHandler: error processing vcard", error: error) + completeItemAndAppendManifestAndLogError( + text: "vcardHandler: error processing vcard", error: error) } } - + private func sendMovie(_ url: URL?) { var filePayloadURL: URL? var error: Error? - + if let url = url { filePayloadURL = getPayloadURL(from: url) do { @@ -347,14 +364,15 @@ public class ItemProviderHelper: NSObject { error = copyError } } - + if let filePayloadURL = filePayloadURL, error == nil { handleAndCompleteMediaFile(filePayloadURL, isVideo: true) } else { - completeItemAndAppendManifestAndLogError(text: "movieFileHandlerSimple2: copy error", error: error) + completeItemAndAppendManifestAndLogError( + text: "movieFileHandlerSimple2: copy error", error: error) } } - + private func sendImage(_ imgData: Data?) { if let imgData = imgData { let originalFileURL = getPayloadURL(withExtension: "jpg") @@ -364,152 +382,162 @@ public class ItemProviderHelper: NSObject { return } } - - completeItemAndAppendManifestAndLogError(text: "coerceImageHandlerSimple2: unable to decode share", error: nil) + + completeItemAndAppendManifestAndLogError( + text: "coerceImageHandlerSimple2: unable to decode share", error: nil) } - + private func incrementUnprocessed() { objc_sync_enter(self) unprocessed += 1 objc_sync_exit(self) } - + @objc public func startProcessing() { // Handlers for different types let fileHandler: @Sendable (URL?, Error?) -> Void = { url, error in - self.sendFile(url) + self.sendFile(url) } - + let movieHandler: @Sendable (URL?, Error?) -> Void = { url, error in - self.sendMovie(error == nil ? url : nil) + self.sendMovie(error == nil ? url : nil) } - + let imageHandler: @Sendable (NSSecureCoding?, Error?) -> Void = { item, error in - var imgData: Data? // Changed to var so we can modify it - if error == nil { - if let url = item as? URL { - imgData = try? Data(contentsOf: url) - if let data = imgData { - let image = UIImage(data: data) - imgData = image?.jpegData(compressionQuality: 0.85) - } - } else if let image = item as? UIImage { - imgData = image.jpegData(compressionQuality: 0.85) - } + var imgData: Data? // Changed to var so we can modify it + if error == nil { + if let url = item as? URL { + imgData = try? Data(contentsOf: url) + if let data = imgData { + let image = UIImage(data: data) + imgData = image?.jpegData(compressionQuality: 0.85) + } + } else if let image = item as? UIImage { + imgData = image.jpegData(compressionQuality: 0.85) } - self.sendImage(imgData) + } + self.sendImage(imgData) } - + let contactHandler: @Sendable (Data?, Error?) -> Void = { data, error in - if let data = data { - self.sendContact(data) - } else { - self.completeItemAndAppendManifestAndLogError( - text: "vcardHandler: unable to decode share", - error: nil - ) - } + if let data = data { + self.sendContact(data) + } else { + self.completeItemAndAppendManifestAndLogError( + text: "vcardHandler: unable to decode share", + error: nil + ) + } } - + let secureTextHandler: @Sendable (NSSecureCoding?, Error?) -> Void = { item, error in - var text: String? // Changed to var so we can modify it - if error == nil { - if let str = item as? String { - text = str - } else if let url = item as? URL { - text = url.absoluteString - if text?.hasPrefix("file://") == true { - let d = try? Data(contentsOf: url) - text = d != nil ? String(data: d!, encoding: .utf8) : nil - } - } else if let data = item as? Data { - text = String(data: data, encoding: .utf8) - } + var text: String? // Changed to var so we can modify it + if error == nil { + if let str = item as? String { + text = str + } else if let url = item as? URL { + text = url.absoluteString + if text?.hasPrefix("file://") == true { + let d = try? Data(contentsOf: url) + text = d != nil ? String(data: d!, encoding: .utf8) : nil + } + } else if let data = item as? Data { + text = String(data: data, encoding: .utf8) } - self.sendText(text ?? "") + } + self.sendText(text ?? "") } // Rest of the method remains the same... for items in itemArrs as! [[NSItemProvider]] { - // Only handle one from itemArrs if we're already processing - objc_sync_enter(self) - if unprocessed > 0 { - objc_sync_exit(self) - break - } + // Only handle one from itemArrs if we're already processing + objc_sync_enter(self) + if unprocessed > 0 { objc_sync_exit(self) - - for item in items { - for stype in item.registeredTypeIdentifiers { - guard let type = UTType(stype) else { continue } - - // Movies - if type.conforms(to: .movie) { - incrementUnprocessed() - item.loadFileRepresentation(forTypeIdentifier: stype, completionHandler: movieHandler) - break - } - // Images (PNG, GIF, JPEG) - else if type.conforms(to: .png) || type.conforms(to: .gif) || type.conforms(to: .jpeg) { - incrementUnprocessed() - item.loadFileRepresentation(forTypeIdentifier: stype, completionHandler: fileHandler) - break - } - // HEIC Images - else if stype == "public.heic" { - incrementUnprocessed() - item.loadFileRepresentation(forTypeIdentifier: "public.heic", completionHandler: fileHandler) - break - } - // Other Images (coerce) - else if type.conforms(to: .image) { - incrementUnprocessed() - item.loadItem(forTypeIdentifier: "public.image", options: nil, completionHandler: imageHandler) - break - } - // Contact cards - else if type.conforms(to: .vCard) { - incrementUnprocessed() - item.loadDataRepresentation(forTypeIdentifier: "public.vcard", completionHandler: contactHandler) - break - } - // Plain Text (two attempts - direct and coerced) - else if type.conforms(to: .plainText) { - incrementUnprocessed() - item.loadItem(forTypeIdentifier: "public.plain-text", options: nil, completionHandler: { (item: NSSecureCoding?, error: Error?) in - let text = item as? String ?? "" - self.sendText(text) - }) - - - incrementUnprocessed() - item.loadItem(forTypeIdentifier: "public.plain-text", options: nil, completionHandler: secureTextHandler) - break - } - // Files (PDF, generic files) - else if type.conforms(to: .pdf) || type.conforms(to: .fileURL) { - incrementUnprocessed() - item.loadFileRepresentation(forTypeIdentifier: "public.item", completionHandler: fileHandler) - break - } - // URLs - else if type.conforms(to: .url) { - incrementUnprocessed() - item.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (item: NSSecureCoding?, error: Error?) in - let url = item as? URL - self.sendText(url?.absoluteString ?? "") - }) - - break - } - } + break + } + objc_sync_exit(self) + + for item in items { + for stype in item.registeredTypeIdentifiers { + guard let type = UTType(stype) else { continue } + + // Movies + if type.conforms(to: .movie) { + incrementUnprocessed() + item.loadFileRepresentation(forTypeIdentifier: stype, completionHandler: movieHandler) + break + } + // Images (PNG, GIF, JPEG) + else if type.conforms(to: .png) || type.conforms(to: .gif) || type.conforms(to: .jpeg) { + incrementUnprocessed() + item.loadFileRepresentation(forTypeIdentifier: stype, completionHandler: fileHandler) + break + } + // HEIC Images + else if stype == "public.heic" { + incrementUnprocessed() + item.loadFileRepresentation( + forTypeIdentifier: "public.heic", completionHandler: fileHandler) + break + } + // Other Images (coerce) + else if type.conforms(to: .image) { + incrementUnprocessed() + item.loadItem( + forTypeIdentifier: "public.image", options: nil, completionHandler: imageHandler) + break + } + // Contact cards + else if type.conforms(to: .vCard) { + incrementUnprocessed() + item.loadDataRepresentation( + forTypeIdentifier: "public.vcard", completionHandler: contactHandler) + break + } + // Plain Text (two attempts - direct and coerced) + else if type.conforms(to: .plainText) { + incrementUnprocessed() + item.loadItem( + forTypeIdentifier: "public.plain-text", options: nil, + completionHandler: { (item: NSSecureCoding?, error: Error?) in + let text = item as? String ?? "" + self.sendText(text) + }) + + incrementUnprocessed() + item.loadItem( + forTypeIdentifier: "public.plain-text", options: nil, + completionHandler: secureTextHandler) + break + } + // Files (PDF, generic files) + else if type.conforms(to: .pdf) || type.conforms(to: .fileURL) { + incrementUnprocessed() + item.loadFileRepresentation( + forTypeIdentifier: "public.item", completionHandler: fileHandler) + break + } + // URLs + else if type.conforms(to: .url) { + incrementUnprocessed() + item.loadItem( + forTypeIdentifier: "public.url", options: nil, + completionHandler: { (item: NSSecureCoding?, error: Error?) in + let url = item as? URL + self.sendText(url?.absoluteString ?? "") + }) + + break + } } + } } - + incrementUnprocessed() // Clean up if we didn't find anything DispatchQueue.main.async { [weak self] in - self?.completeProcessingItemAlreadyInMainThread() + self?.completeProcessingItemAlreadyInMainThread() } -} + } } diff --git a/rnmodules/kb-common/src/MediaUtils.swift b/rnmodules/kb-common/src/MediaUtils.swift index f57b1aac0744..0ef020f9d807 100644 --- a/rnmodules/kb-common/src/MediaUtils.swift +++ b/rnmodules/kb-common/src/MediaUtils.swift @@ -1,7 +1,7 @@ -import Foundation import AVFoundation -import UIKit +import Foundation import ImageIO +import UIKit struct MediaProcessingConfig { static let imageMaxPixelSize: Int = 1200 @@ -35,12 +35,14 @@ class MediaUtils: NSObject { return [ kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceCreateThumbnailFromImageAlways: true, - kCGImageSourceThumbnailMaxPixelSize: MediaProcessingConfig.imageMaxPixelSize + kCGImageSourceThumbnailMaxPixelSize: MediaProcessingConfig.imageMaxPixelSize, ] as CFDictionary } - @objc static func processImage(fromOriginal url: URL, - completion: @escaping (Error?, URL?) -> Void) { + @objc static func processImage( + fromOriginal url: URL, + completion: @escaping (Error?, URL?) -> Void + ) { processImageAsync(fromOriginal: url) { result in switch result { case .success(let url): @@ -51,8 +53,10 @@ class MediaUtils: NSObject { } } - static func processImageAsync(fromOriginal url: URL, - completion: @escaping ProcessMediaCompletion) { + static func processImageAsync( + fromOriginal url: URL, + completion: @escaping ProcessMediaCompletion + ) { DispatchQueue.global(qos: .userInitiated).async { do { let processedURL = try processImageSync(fromOriginal: url) @@ -89,8 +93,10 @@ class MediaUtils: NSObject { return scaledURL } - @objc static func processVideo(fromOriginal url: URL, - completion: @escaping (Error?, URL?) -> Void) { + @objc static func processVideo( + fromOriginal url: URL, + completion: @escaping (Error?, URL?) -> Void + ) { processVideoAsync(fromOriginal: url) { result in switch result { case .success(let url): @@ -101,9 +107,11 @@ class MediaUtils: NSObject { } } - static func processVideoAsync(fromOriginal url: URL, - progress: ProcessMediaProgressCallback? = nil, - completion: @escaping ProcessMediaCompletion) { + static func processVideoAsync( + fromOriginal url: URL, + progress: ProcessMediaProgressCallback? = nil, + completion: @escaping ProcessMediaCompletion + ) { DispatchQueue.global(qos: .userInitiated).async { do { let processedURL = try processVideoSync(fromOriginal: url, progress: progress) @@ -118,8 +126,10 @@ class MediaUtils: NSObject { } } - private static func processVideoSync(fromOriginal url: URL, - progress: ProcessMediaProgressCallback? = nil) throws -> URL { + private static func processVideoSync( + fromOriginal url: URL, + progress: ProcessMediaProgressCallback? = nil + ) throws -> URL { guard FileManager.default.fileExists(atPath: url.path) else { throw MediaUtilsError.invalidInput("File does not exist at path: \(url.path)") } @@ -134,10 +144,11 @@ class MediaUtils: NSObject { let exportSettings = determineOptimalExportSettings(for: asset) - try exportVideoWithSettings(asset: asset, - outputURL: processedURL, - settings: exportSettings, - progress: progress) + try exportVideoWithSettings( + asset: asset, + outputURL: processedURL, + settings: exportSettings, + progress: progress) return processedURL } @@ -156,13 +167,16 @@ class MediaUtils: NSObject { generator.appliesPreferredTrackTransform = true do { - _ = try generator.copyCGImage(at: CMTime(seconds: 0, preferredTimescale: 1), actualTime: nil) + _ = try generator.copyCGImage( + at: CMTime(seconds: 0, preferredTimescale: 1), actualTime: nil) } catch { - throw MediaUtilsError.videoProcessingFailed("Failed to generate video thumbnail: \(error.localizedDescription)") + throw MediaUtilsError.videoProcessingFailed( + "Failed to generate video thumbnail: \(error.localizedDescription)") } } - private static func determineOptimalExportSettings(for asset: AVURLAsset) -> VideoExportSettings { + private static func determineOptimalExportSettings(for asset: AVURLAsset) -> VideoExportSettings + { let videoTracks = asset.tracks(withMediaType: .video) guard let firstVideoTrack = videoTracks.first else { return VideoExportSettings.default @@ -173,8 +187,9 @@ class MediaUtils: NSObject { let fileSize = getFileSize(for: asset.url) // Determine if we need to scale down - let needsScaling = pixelCount > MediaProcessingConfig.videoMaxPixels || - fileSize > MediaProcessingConfig.videoMaxFileSize + let needsScaling = + pixelCount > MediaProcessingConfig.videoMaxPixels + || fileSize > MediaProcessingConfig.videoMaxFileSize if needsScaling { return VideoExportSettings.mediumQuality @@ -183,22 +198,25 @@ class MediaUtils: NSObject { } } - private static func exportVideoWithSettings(asset: AVURLAsset, - outputURL: URL, - settings: VideoExportSettings, - progress: ProcessMediaProgressCallback?) throws { + private static func exportVideoWithSettings( + asset: AVURLAsset, + outputURL: URL, + settings: VideoExportSettings, + progress: ProcessMediaProgressCallback? + ) throws { let semaphore = DispatchSemaphore(value: 0) var exportError: Error? - guard let exportSession = AVAssetExportSession(asset: asset, presetName: settings.preset) else { + guard let exportSession = AVAssetExportSession(asset: asset, presetName: settings.preset) + else { throw MediaUtilsError.videoProcessingFailed("Failed to create export session") } exportSession.outputURL = outputURL exportSession.outputFileType = .mp4 exportSession.shouldOptimizeForNetworkUse = true - exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing() // Strips location data + exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing() // Strips location data // Set up progress monitoring if let progress = progress { @@ -223,11 +241,13 @@ class MediaUtils: NSObject { semaphore.wait() if let error = exportError { - throw MediaUtilsError.videoProcessingFailed("Export failed: \(error.localizedDescription)") + throw MediaUtilsError.videoProcessingFailed( + "Export failed: \(error.localizedDescription)") } guard exportSession.status == .completed else { - throw MediaUtilsError.videoProcessingFailed("Export session failed with status: \(exportSession.status)") + throw MediaUtilsError.videoProcessingFailed( + "Export session failed with status: \(exportSession.status)") } } @@ -240,19 +260,25 @@ class MediaUtils: NSObject { } } - private static func scaleDownCGImageSource(_ img: CGImageSource, dstURL: URL, options: CFDictionary) throws { + private static func scaleDownCGImageSource( + _ img: CGImageSource, dstURL: URL, options: CFDictionary + ) throws { guard let scaledRef = CGImageSourceCreateThumbnailAtIndex(img, 0, options) else { throw MediaUtilsError.imageProcessingFailed("Failed to create thumbnail") } - guard let scaled = UIImage(cgImage: scaledRef).jpegData(compressionQuality: MediaProcessingConfig.imageCompressionQuality) else { + guard + let scaled = UIImage(cgImage: scaledRef).jpegData( + compressionQuality: MediaProcessingConfig.imageCompressionQuality) + else { throw MediaUtilsError.imageProcessingFailed("Failed to create JPEG data") } do { try scaled.write(to: dstURL) } catch { - throw MediaUtilsError.fileOperationFailed("Failed to write scaled image: \(error.localizedDescription)") + throw MediaUtilsError.fileOperationFailed( + "Failed to write scaled image: \(error.localizedDescription)") } } @@ -270,17 +296,22 @@ class MediaUtils: NSObject { try? FileManager.default.removeItem(at: tmpDstURL) } - guard let cgDestination = CGImageDestinationCreateWithURL(tmpDstURL as CFURL, type!, count, nil) else { + guard + let cgDestination = CGImageDestinationCreateWithURL( + tmpDstURL as CFURL, type!, count, nil) + else { throw MediaUtilsError.imageProcessingFailed("Failed to create image destination") } - let removeExifProperties = [ - kCGImagePropertyExifDictionary: kCFNull, - kCGImagePropertyGPSDictionary: kCFNull - ] as CFDictionary + let removeExifProperties = + [ + kCGImagePropertyExifDictionary: kCFNull, + kCGImagePropertyGPSDictionary: kCFNull, + ] as CFDictionary for index in 0.. jsScheduler; -// sanity check the runtime isn't out of sync due to reload etc -void *currentRuntime = nil; - RCT_EXPORT_MODULE() + (BOOL)requiresMainQueueSetup { @@ -160,8 +158,8 @@ + (void)handlePastedImages:(NSArray *)images { } - (void)invalidate { + NSLog(@"Kb.invalidate"); [[NSNotificationCenter defaultCenter] removeObserver:self]; - currentRuntime = nil; _jsRuntime = nil; kbPasteImageEnabled = NO; [super invalidate]; @@ -200,7 +198,6 @@ - (void)sendToJS:(NSData *)data { NSLog(@"Failed to find jsi in sendToJS invokeAsync!!!"); return; } - int size = (int)[data length]; if (size <= 0) { NSLog(@"Invalid data size in sendToJS: %d", size); @@ -311,6 +308,7 @@ - (NSDictionary *)getConstants { } RCT_EXPORT_METHOD(engineReset) { + NSLog(@"engineReset: called (JS hot reload), resetting Go engine"); NSError *error = nil; KeybaseReset(&error); [self sendEventWithName:metaEventName body:metaEventEngineReset]; @@ -322,6 +320,7 @@ - (NSDictionary *)getConstants { RCT_EXPORT_METHOD(notifyJSReady) { __weak __typeof__(self) weakSelf = self; + NSLog(@"notifyJSReady: called from JS self=%p bridge=%p", self, self.bridge); dispatch_async(dispatch_get_main_queue(), ^{ // Setup infrastructure [[NSNotificationCenter defaultCenter] @@ -333,7 +332,7 @@ - (NSDictionary *)getConstants { // Signal to Go that JS is ready KeybaseNotifyJSReady(); - NSLog(@"Notified Go that JS is ready, starting ReadArr loop"); + NSLog(@"notifyJSReady: Notified Go that JS is ready, starting ReadArr loop"); // Start the read loop dispatch_async(self.readQueue, ^{ @@ -341,7 +340,7 @@ - (NSDictionary *)getConstants { { __typeof__(self) strongSelf = weakSelf; if (!strongSelf || !strongSelf.bridge) { - NSLog(@"Bridge dead, bailing from ReadArr loop"); + NSLog(@"ReadArr loop exit: bridge dead"); return; } } @@ -350,6 +349,9 @@ - (NSDictionary *)getConstants { NSData *data = KeybaseReadArr(&error); if (error) { NSLog(@"Error reading data: %@", error); + // Back off on error to avoid spinning at ~35K/sec and starving the main thread CPU + // during foreground re-entry (seen during hang investigation: 419K errors in 12s). + [NSThread sleepForTimeInterval:0.1]; } else if (data) { __typeof__(self) strongSelf = weakSelf; if (strongSelf) { @@ -677,3 +679,7 @@ void KbEmitPushNotification(NSDictionary *notification) { kbInitialNotification = nil; return notification; } + +void KbSetInitResult(NSString *result) { + kbInitResult = result; +} diff --git a/shared/.gitignore b/shared/.gitignore index cef9c5e90b68..114613c2f058 100644 --- a/shared/.gitignore +++ b/shared/.gitignore @@ -55,3 +55,4 @@ main.jsbundle coverage-ts temp.log +.watchman-cookie-* diff --git a/shared/.watchman-cookie-MBP-2019.local-559-1506 b/shared/.watchman-cookie-MBP-2019.local-559-1506 deleted file mode 100755 index e69de29bb2d1..000000000000 diff --git a/shared/android/app/build.gradle b/shared/android/app/build.gradle index 43cfc1c7c8ad..b20b867dd6fd 100644 --- a/shared/android/app/build.gradle +++ b/shared/android/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: "com.facebook.react" apply plugin: 'com.github.triplet.play' // KB: app version -def VERSION_NAME = "6.6.0" +def VERSION_NAME = "6.6.2" // KB: Number of commits, like ios Integer getVersionCode() { diff --git a/shared/android/app/src/main/java/io/keybase/ossifrage/MainActivity.kt b/shared/android/app/src/main/java/io/keybase/ossifrage/MainActivity.kt index 5099014b8a57..aa2146487764 100644 --- a/shared/android/app/src/main/java/io/keybase/ossifrage/MainActivity.kt +++ b/shared/android/app/src/main/java/io/keybase/ossifrage/MainActivity.kt @@ -92,18 +92,19 @@ class MainActivity : ReactActivity() { scheduleHandleIntent() - // fix for keyboard avoiding not working on 35 - if (Build.VERSION.SDK_INT >= 35) { - val rootView = findViewById(android.R.id.content) - ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets -> - val innerPadding = insets.getInsets(WindowInsetsCompat.Type.ime()) + // edgeToEdgeEnabled=true in gradle.properties causes RN to call + // WindowCompat.setDecorFitsSystemWindows(false) on all API levels, which breaks + // adjustResize. Manually apply insets so the keyboard pushes content up. + val rootView = findViewById(android.R.id.content) + ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets -> + val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) + val sysBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) rootView.setPadding( - innerPadding.left, - innerPadding.top, - innerPadding.right, - innerPadding.bottom) + sysBarInsets.left, + sysBarInsets.top, + sysBarInsets.right, + maxOf(imeInsets.bottom, sysBarInsets.bottom)) insets - } } } diff --git a/shared/android/app/src/main/java/io/keybase/ossifrage/modules/BackgroundSyncWorker.kt b/shared/android/app/src/main/java/io/keybase/ossifrage/modules/BackgroundSyncWorker.kt index f80fd27fcdf3..93632cde97a2 100644 --- a/shared/android/app/src/main/java/io/keybase/ossifrage/modules/BackgroundSyncWorker.kt +++ b/shared/android/app/src/main/java/io/keybase/ossifrage/modules/BackgroundSyncWorker.kt @@ -11,8 +11,8 @@ class BackgroundSyncWorker( params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { Log.d(TAG, "Background sync start.") - Keybase.backgroundSync() - Log.d(TAG, "Background sync complete.") + val result = Keybase.backgroundSync() + Log.d(TAG, "Background sync complete: $result") return Result.success() } diff --git a/shared/chat/inbox/filter-row.tsx b/shared/chat/inbox/filter-row.tsx index 8a5651be6445..edb820dd9ca0 100644 --- a/shared/chat/inbox/filter-row.tsx +++ b/shared/chat/inbox/filter-row.tsx @@ -75,6 +75,7 @@ const ConversationFilterInput = React.memo(function ConversationFilterInput(ownP appendNewChatBuilder() }, [appendNewChatBuilder]) Kb.useHotKey('mod+n', onHotKeys) + Kb.useHotKey('mod+k', onStartSearch) React.useEffect(() => { if (isSearching) { @@ -82,28 +83,31 @@ const ConversationFilterInput = React.memo(function ConversationFilterInput(ownP } }, [isSearching]) - const searchInput = ( + const searchInput = isSearching ? ( + ) : ( + + + + + {Kb.Styles.isMobile ? 'Search' : `Search (\u2318K)`} + + + ) return ( = (set, get) => { get().id }` ) - return + continue } set(s => { const m = s.messageMap.get(targetOrdinal) diff --git a/shared/constants/daemon/index.tsx b/shared/constants/daemon/index.tsx index 15d3f95b7f9c..08f0bc225ced 100644 --- a/shared/constants/daemon/index.tsx +++ b/shared/constants/daemon/index.tsx @@ -107,8 +107,11 @@ export const useDaemonState = Z.createZustand((set, get) => { const name = 'config.getBootstrapStatus' const {wait} = get().dispatch wait(name, version, true) + const t = Date.now() + logger.info('[Bootstrap] loadDaemonBootstrapStatus: starting') try { await get().dispatch.loadDaemonBootstrapStatus() + logger.info(`[Bootstrap] loadDaemonBootstrapStatus: done in ${Date.now() - t}ms`) storeRegistry.getState('dark-mode').dispatch.loadDarkPrefs() storeRegistry.getState('chat').dispatch.loadStaticConfig() } finally { @@ -172,7 +175,10 @@ export const useDaemonState = Z.createZustand((set, get) => { const f = async () => { const {setBootstrap} = storeRegistry.getState('current-user').dispatch const {setDefaultUsername} = storeRegistry.getState('config').dispatch + const tBootstrap = Date.now() + logger.info('[Bootstrap] configGetBootstrapStatus: starting') const s = await T.RPCGen.configGetBootstrapStatusRpcPromise() + logger.info(`[Bootstrap] configGetBootstrapStatus: done in ${Date.now() - tBootstrap}ms`) const {userReacjis, deviceName, deviceID, uid, loggedIn, username} = s setBootstrap({deviceID, deviceName, uid, username}) if (username) { @@ -206,7 +212,10 @@ export const useDaemonState = Z.createZustand((set, get) => { }, onRestartHandshakeNative: _onRestartHandshakeNative, refreshAccounts: async () => { + const tAccounts = Date.now() + logger.info('[Bootstrap] loginGetConfiguredAccounts: starting') const configuredAccounts = (await T.RPCGen.loginGetConfiguredAccountsRpcPromise()) ?? [] + logger.info(`[Bootstrap] loginGetConfiguredAccounts: done in ${Date.now() - tAccounts}ms`) // already have one? const {defaultUsername} = storeRegistry.getState('config') const {setAccounts, setDefaultUsername} = storeRegistry.getState('config').dispatch @@ -303,6 +312,8 @@ export const useDaemonState = Z.createZustand((set, get) => { s.handshakeWaiters.set(name, newCount) } }) + const remaining = get().handshakeWaiters.size + logger.info(`[Bootstrap] waiter ${increment ? '+' : '-'} ${name} v${version}, remaining: ${remaining}`) if (failedFatal) { get().dispatch.setFailed(failedReason || '') diff --git a/shared/constants/platform-specific/index.native.tsx b/shared/constants/platform-specific/index.native.tsx index 2c8f08624117..673b778aa441 100644 --- a/shared/constants/platform-specific/index.native.tsx +++ b/shared/constants/platform-specific/index.native.tsx @@ -137,6 +137,8 @@ export const showShareActionSheet = async (options: { } const loadStartupDetails = async () => { + logger.info('[Startup] loadStartupDetails: starting') + const t = Date.now() const [routeState, initialUrl, push] = await Promise.all([ neverThrowPromiseFunc(async () => { try { @@ -146,7 +148,18 @@ const loadStartupDetails = async () => { return Promise.resolve('') } }), - neverThrowPromiseFunc(async () => Linking.getInitialURL()), + neverThrowPromiseFunc(async () => { + const linkingStart = Date.now() + logger.info('[Startup] loadStartupDetails: calling Linking.getInitialURL') + const url = await Linking.getInitialURL() + const elapsed = Date.now() - linkingStart + if (url === null) { + logger.warn(`[Startup] loadStartupDetails: Linking.getInitialURL returned null in ${elapsed}ms`) + } else { + logger.info(`[Startup] loadStartupDetails: Linking.getInitialURL returned in ${elapsed}ms: ${url}`) + } + return url + }), neverThrowPromiseFunc(getStartupDetailsFromInitialPush), ] as const) @@ -192,6 +205,7 @@ const loadStartupDetails = async () => { tab = '' } + logger.info(`[Startup] loadStartupDetails: done in ${Date.now() - t}ms`) storeRegistry.getState('config').dispatch.setStartupDetails({ conversation: conversation ?? noConversationIDKey, followUser, diff --git a/shared/desktop/CHANGELOG.txt b/shared/desktop/CHANGELOG.txt index 170335b00409..b3e1f41a1943 100644 --- a/shared/desktop/CHANGELOG.txt +++ b/shared/desktop/CHANGELOG.txt @@ -1,6 +1,4 @@ -• On iOS quicky share to recent conversations -• Emoji 16 support -• iOS HEIC Avatar support -• Better sharing/push support -• Various bug fixes and performance improvements +• Improve git default branch handling +• Fix widget opening into chat search mode +• Fix copy/move menu in KBFS diff --git a/shared/fs/common/path-item-action/menu-container.tsx b/shared/fs/common/path-item-action/menu-container.tsx index 996f366ea73c..b7ceff503364 100644 --- a/shared/fs/common/path-item-action/menu-container.tsx +++ b/shared/fs/common/path-item-action/menu-container.tsx @@ -31,7 +31,7 @@ const Container = (op: OwnProps) => { const pathItemActionMenu = s.pathItemActionMenu const fileContext = s.fileContext.get(path) || FS.emptyFileContext const {cancelDownload, setPathItemActionMenuView, download, newFolderRow} = s.dispatch - const {favoriteIgnore, startRename, dismissDownload} = s.dispatch + const {favoriteIgnore, startRename, dismissDownload, setMoveOrCopySource, showMoveOrCopy} = s.dispatch const {openPathInSystemFileManagerDesktop} = s.dispatch.dynamic const sfmiEnabled = s.sfmi.driverStatus.type === T.FS.DriverStatusType.Enabled return { @@ -44,8 +44,10 @@ const Container = (op: OwnProps) => { openPathInSystemFileManagerDesktop, pathItem, pathItemActionMenu, + setMoveOrCopySource, setPathItemActionMenuView, sfmiEnabled, + showMoveOrCopy, startRename, } }) @@ -53,7 +55,7 @@ const Container = (op: OwnProps) => { const {pathItem, pathItemActionMenu, fileContext, cancelDownload} = data const {setPathItemActionMenuView, download, newFolderRow, openPathInSystemFileManagerDesktop} = data - const {sfmiEnabled, favoriteIgnore, startRename, dismissDownload} = data + const {sfmiEnabled, favoriteIgnore, startRename, dismissDownload, setMoveOrCopySource, showMoveOrCopy} = data const {downloadID, downloadIntent, view} = pathItemActionMenu const username = useCurrentUserState(s => s.username) @@ -272,6 +274,19 @@ const Container = (op: OwnProps) => { ] as const) : [] + const itemMoveOrCopy = layout.moveOrCopy + ? ([ + { + icon: 'iconfont-copy', + onClick: hideAndCancelAfter(() => { + setMoveOrCopySource(path) + showMoveOrCopy(T.FS.getPathParent(path)) + }), + title: 'Copy or move', + }, + ] as const) + : [] + const items: Kb.MenuItems = [ ...itemNewFolder, ...itemChatTeam, @@ -282,6 +297,7 @@ const Container = (op: OwnProps) => { ...itemSendToChat, ...itemSendToApp, ...itemDownload, + ...itemMoveOrCopy, ...itemIgnore, ...itemRename, ...itemArchive, diff --git a/shared/ios/Keybase/AppDelegate.swift b/shared/ios/Keybase/AppDelegate.swift index 1d6a47878c70..552e476c294e 100644 --- a/shared/ios/Keybase/AppDelegate.swift +++ b/shared/ios/Keybase/AppDelegate.swift @@ -1,12 +1,12 @@ +import AVFoundation import Expo +import ExpoModulesCore +import KBCommon +import Keybasego import React import ReactAppDependencyProvider -import KBCommon import UIKit import UserNotifications -import AVFoundation -import ExpoModulesCore -import Keybasego class KeyboardWindow: UIWindow { override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { @@ -17,13 +17,15 @@ class KeyboardWindow: UIWindow { if key.keyCode == .keyboardReturnOrEnter { if key.modifierFlags.contains(.shift) { - NotificationCenter.default.post(name: NSNotification.Name("hardwareKeyPressed"), - object: nil, - userInfo: ["pressedKey": "shift-enter"]) + NotificationCenter.default.post( + name: NSNotification.Name("hardwareKeyPressed"), + object: nil, + userInfo: ["pressedKey": "shift-enter"]) } else { - NotificationCenter.default.post(name: NSNotification.Name("hardwareKeyPressed"), - object: nil, - userInfo: ["pressedKey": "enter"]) + NotificationCenter.default.post( + name: NSNotification.Name("hardwareKeyPressed"), + object: nil, + userInfo: ["pressedKey": "enter"]) } return } @@ -33,7 +35,9 @@ class KeyboardWindow: UIWindow { } @UIApplicationMain -public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInteractionDelegate { +public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, + UIDropInteractionDelegate +{ var window: UIWindow? var reactNativeDelegate: ExpoReactNativeFactoryDelegate? @@ -43,16 +47,13 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID var fsPaths: [String: String] = [:] var shutdownTask: UIBackgroundTaskIdentifier = .invalid var iph: ItemProviderHelper? - var startupLogFileHandle: FileHandle? + private var startupLogFileHandle: FileHandle? + private let logQueue = DispatchQueue(label: "kb.startup.log", qos: .utility) public override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { - if AppDelegate.appStartTime == 0 { - AppDelegate.appStartTime = CFAbsoluteTimeGetCurrent() - } - let skipLogFile = false self.fsPaths = FsHelper().setupFs(skipLogFile, setupSharedHome: true) FsPathsHolder.shared().fsPaths = self.fsPaths @@ -62,11 +63,14 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID self.didLaunchSetupBefore() if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] { - let notificationDict = Dictionary(uniqueKeysWithValues: remoteNotification.map { (String(describing: $0.key), $0.value) }) + let notificationDict = Dictionary( + uniqueKeysWithValues: remoteNotification.map { (String(describing: $0.key), $0.value) }) KbSetInitialNotification(notificationDict) } - NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: .main) { [weak self] notification in + NotificationCenter.default.addObserver( + forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: .main + ) { [weak self] notification in NSLog("Memory warning received - deferring GC during React Native initialization") // see if this helps avoid this crash DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { @@ -85,13 +89,13 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID reactNativeFactory = factory bindReactNativeFactory(factory) -#if os(iOS) || os(tvOS) - window = KeyboardWindow(frame: UIScreen.main.bounds) - factory.startReactNative( - withModuleName: "Keybase", - in: window, - launchOptions: launchOptions) -#endif + #if os(iOS) || os(tvOS) + window = KeyboardWindow(frame: UIScreen.main.bounds) + factory.startReactNative( + withModuleName: "Keybase", + in: window, + launchOptions: launchOptions) + #endif self.writeStartupTimingLog("After RN init") self.closeStartupLogFile() @@ -112,7 +116,8 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { - return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options) + return super.application(app, open: url, options: options) + || RCTLinkingManager.application(app, open: url, options: options) } // Universal Links @@ -121,69 +126,82 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void ) -> Bool { - let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler) - return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result + let result = RCTLinkingManager.application( + application, continue: userActivity, restorationHandler: restorationHandler) + return super.application( + application, continue: userActivity, restorationHandler: restorationHandler) || result } /////// KB specific - private static var appStartTime: CFAbsoluteTime = 0 + private static let logDateFormatter: DateFormatter = { + let f = DateFormatter() + f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + f.timeZone = TimeZone(secondsFromGMT: 0) + return f + }() private func writeStartupTimingLog(_ message: String, file: String = #file, line: Int = #line) { guard let logFilePath = self.fsPaths["logFile"], !logFilePath.isEmpty else { return } - if self.startupLogFileHandle == nil { - do { + // Capture timestamp on the calling thread so it reflects when the event actually occurred. + let now = Date() + let timeInterval = now.timeIntervalSince1970 + let microseconds = Int(timeInterval.truncatingRemainder(dividingBy: 1) * 1_000_000) + let dateString = AppDelegate.logDateFormatter.string(from: now) + let timestamp = String(format: "%@.%06dZ", dateString, microseconds) + let fileName = URL(fileURLWithPath: file).lastPathComponent + let logMessage = String( + format: "%@ ▶ [DEBU keybase %@:%d] Delegate startup: %@\n", timestamp, fileName, line, message + ) + guard let logData = logMessage.data(using: .utf8) else { + return + } + + // Dispatch file I/O (including fsync) to a background queue so the calling thread + // (often the main thread) is never blocked by disk latency. + logQueue.async { [weak self] in + guard let self else { return } + if self.startupLogFileHandle == nil { if !FileManager.default.fileExists(atPath: logFilePath) { - FileManager.default.createFile(atPath: logFilePath, contents: nil, attributes: nil) + // Match the parent directory's protection class so the file remains accessible + // in the background after the device has been unlocked once. + FileManager.default.createFile( + atPath: logFilePath, contents: nil, + attributes: [.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication]) } - if let fileHandle = FileHandle(forWritingAtPath: logFilePath) { fileHandle.seekToEndOfFile() self.startupLogFileHandle = fileHandle + } else { + NSLog("Error opening startup timing log file: \(logFilePath)") + return } + } + guard let fileHandle = self.startupLogFileHandle else { return } + do { + try fileHandle.write(contentsOf: logData) + try fileHandle.synchronize() } catch { - NSLog("Error opening startup timing log file: \(error)") - return + NSLog("Error writing startup timing log: \(error)") } } - - guard let fileHandle = self.startupLogFileHandle else { - return - } - - let now = Date() - let timeInterval = now.timeIntervalSince1970 - let seconds = Int(timeInterval) - let microseconds = Int((timeInterval - Double(seconds)) * 1_000_000) - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" - dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - let dateString = dateFormatter.string(from: now) - let timestamp = String(format: "%@.%06dZ", dateString, microseconds) - - let fileName = (file as NSString).lastPathComponent - let logMessage = String(format: "%@ ▶ [DEBU keybase %@:%d] Delegate startup: %@\n", timestamp, fileName, line, message) - - guard let logData = logMessage.data(using: .utf8) else { - return - } - - do { - fileHandle.write(logData) - fileHandle.synchronizeFile() - } catch { - NSLog("Error writing startup timing log: \(error)") - } } private func closeStartupLogFile() { - if let fileHandle = self.startupLogFileHandle { - fileHandle.synchronizeFile() - fileHandle.closeFile() - self.startupLogFileHandle = nil + // Use sync so all pending async log writes are flushed before we close the handle. + logQueue.sync { + if let fileHandle = self.startupLogFileHandle { + do { + try fileHandle.synchronize() + try fileHandle.close() + } catch { + NSLog("Error closing startup timing log: \(error)") + } + self.startupLogFileHandle = nil + } } } @@ -196,11 +214,11 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID let isIPad = UIDevice.current.userInterfaceIdiom == .pad let isIOS = true -#if targetEnvironment(simulator) - let securityAccessGroupOverride = true -#else - let securityAccessGroupOverride = false -#endif + #if targetEnvironment(simulator) + let securityAccessGroupOverride = true + #else + let securityAccessGroupOverride = false + #endif self.writeStartupTimingLog("Before Go init") @@ -208,9 +226,22 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID NSLog("Starting KeybaseInit (synchronous)...") var err: NSError? let shareIntentDonator = ShareIntentDonatorImpl() - Keybasego.KeybaseInit(self.fsPaths["homedir"], self.fsPaths["sharedHome"], self.fsPaths["logFile"], "prod", securityAccessGroupOverride, nil, nil, systemVer, isIPad, nil, isIOS, shareIntentDonator, &err) - if let err { NSLog("KeybaseInit FAILED: \(err)") } - + Keybasego.KeybaseInit( + self.fsPaths["homedir"], self.fsPaths["sharedHome"], self.fsPaths["logFile"], "prod", + securityAccessGroupOverride, nil, nil, systemVer, isIPad, nil, isIOS, shareIntentDonator, &err + ) + if let err { + let initResult = "FAILED: \(err.localizedDescription) (code=\(err.code) domain=\(err.domain))" + KbSetInitResult(initResult) + NSLog( + "KeybaseInit FAILED: %@ (code=%ld domain=%@)", err.localizedDescription, err.code, + err.domain) + self.writeStartupTimingLog("KeybaseInit \(initResult)") + } else { + KbSetInitResult("succeeded") + self.writeStartupTimingLog("KeybaseInit succeeded") + } + self.writeStartupTimingLog("After Go init") } @@ -247,9 +278,10 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID self.resignImageView?.alpha = 0 self.resignImageView?.backgroundColor = rootView.backgroundColor self.resignImageView?.image = UIImage(named: "LaunchImage") - self.window?.addSubview(self.resignImageView!) + if let view = self.resignImageView { self.window?.addSubview(view) } - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum) + UIApplication.shared.setMinimumBackgroundFetchInterval( + UIApplication.backgroundFetchIntervalMinimum) } func addDrop(_ rootView: UIView) { @@ -258,15 +290,20 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID rootView.addInteraction(dropInteraction) } - public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool { + public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) + -> Bool + { return true } - public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal { + public func dropInteraction( + _ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession + ) -> UIDropProposal { return UIDropProposal(operation: .copy) } - public func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) { + public func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) + { var items: [NSItemProvider] = [] session.items.forEach { item in items.append(item.itemProvider) } @@ -279,22 +316,37 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID self.iph?.startProcessing() } - public override func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - NSLog("Background fetch started...") + public override func application( + _ application: UIApplication, + performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { + let fetchStart = CFAbsoluteTimeGetCurrent() + NSLog("Background fetch queued...") DispatchQueue.global(qos: .default).async { - Keybasego.KeybaseBackgroundSync() + NSLog("Background fetch started...") + let status = Keybasego.KeybaseBackgroundSync() + let elapsed = (CFAbsoluteTimeGetCurrent() - fetchStart) * 1000 + if status.isEmpty { + NSLog("Background fetch completed in %.0fms", elapsed) + } else { + NSLog("Background fetch completed in %.0fms: %@", elapsed, status) + } completionHandler(.newData) - NSLog("Background fetch completed...") } } - public override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + public override func application( + _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() KbSetDeviceToken(token) } - public override func application(_ application: UIApplication, didReceiveRemoteNotification notification: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + public override func application( + _ application: UIApplication, didReceiveRemoteNotification notification: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { guard let type = notification["type"] as? String else { return } if type == "chat.newmessageSilent_2" { DispatchQueue.global(qos: .default).async { @@ -311,26 +363,34 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID let pusher = PushNotifier() var err: NSError? - Keybasego.KeybaseHandleBackgroundNotification(convID, body, "", sender, membersType, displayPlaintext, messageID, pushID, badgeCount, unixTime, soundName, pusher, false, &err) + Keybasego.KeybaseHandleBackgroundNotification( + convID, body, "", sender, membersType, displayPlaintext, messageID, pushID, badgeCount, + unixTime, soundName, pusher, false, &err) if let err { NSLog("Failed to handle in engine: \(err)") } completionHandler(.newData) NSLog("Remote notification handle finished...") } } else { - var notificationDict = Dictionary(uniqueKeysWithValues: notification.map { (String(describing: $0.key), $0.value) }) + var notificationDict = Dictionary( + uniqueKeysWithValues: notification.map { (String(describing: $0.key), $0.value) }) notificationDict["userInteraction"] = false KbEmitPushNotification(notificationDict) completionHandler(.newData) } } - public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + public func userNotificationCenter( + _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { let userInfo = response.notification.request.content.userInfo - var notificationDict = Dictionary(uniqueKeysWithValues: userInfo.map { (String(describing: $0.key), $0.value) }) + var notificationDict = Dictionary( + uniqueKeysWithValues: userInfo.map { (String(describing: $0.key), $0.value) }) notificationDict["userInteraction"] = true let type = notificationDict["type"] as? String ?? "unknown" - let convID = notificationDict["convID"] as? String ?? notificationDict["c"] as? String ?? "unknown" + let convID = + notificationDict["convID"] as? String ?? notificationDict["c"] as? String ?? "unknown" // Store the notification so it can be processed when app becomes active // This ensures navigation works even if React Native isn't ready yet @@ -341,9 +401,13 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID completionHandler() } - public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + public func userNotificationCenter( + _ center: UNUserNotificationCenter, willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { let userInfo = notification.request.content.userInfo - var notificationDict = Dictionary(uniqueKeysWithValues: userInfo.map { (String(describing: $0.key), $0.value) }) + var notificationDict = Dictionary( + uniqueKeysWithValues: userInfo.map { (String(describing: $0.key), $0.value) }) notificationDict["userInteraction"] = false KbEmitPushNotification(notificationDict) completionHandler([]) @@ -363,7 +427,7 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID public override func applicationWillResignActive(_ application: UIApplication) { NSLog("applicationWillResignActive: cancelling outstanding animations...") self.resignImageView?.layer.removeAllAnimations() - self.resignImageView?.superview?.bringSubviewToFront(self.resignImageView!) + if let view = self.resignImageView { view.superview?.bringSubviewToFront(view) } NSLog("applicationWillResignActive: rendering keyz screen...") UIView.animate(withDuration: 0.3, delay: 0.1, options: .beginFromCurrentState) { self.resignImageView?.alpha = 1 @@ -417,8 +481,6 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID // This handles the case where app was backgrounded and notification was clicked // but React Native wasn't ready yet if let storedNotification = KbGetAndClearInitialNotification() { - let type = storedNotification["type"] as? String ?? "unknown" - let convID = storedNotification["convID"] as? String ?? storedNotification["c"] as? String ?? "unknown" let userInteraction = storedNotification["userInteraction"] as? Bool ?? false if userInteraction { @@ -426,11 +488,13 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID if alreadyReEmitted { KbSetInitialNotification(storedNotification) } else { - NSLog("applicationDidBecomeActive: stored notification has userInteraction=true, emitting") + NSLog( + "applicationDidBecomeActive: stored notification has userInteraction=true, emitting") KbEmitPushNotification(storedNotification) - var copy = Dictionary(uniqueKeysWithValues: storedNotification.map { (String(describing: $0.key), $0.value) }) + var copy = Dictionary( + uniqueKeysWithValues: storedNotification.map { (String(describing: $0.key), $0.value) }) copy["reEmittedInBecomeActive"] = true - KbSetInitialNotification(copy as NSDictionary as! [AnyHashable : Any]) + KbSetInitialNotification(copy) } } else { NSLog("applicationDidBecomeActive: stored notification has userInteraction=false, skipping") @@ -441,8 +505,13 @@ public class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UID } public override func applicationWillEnterForeground(_ application: UIApplication) { - NSLog("applicationWillEnterForeground: hiding keyz screen.") + NSLog("applicationWillEnterForeground: hiding keyz screen") hideCover() + NSLog("applicationWillEnterForeground: done") + } + + public func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) { + NSLog("[Startup] applicationProtectedDataDidBecomeAvailable") } } @@ -456,10 +525,11 @@ class ReactNativeDelegate: ExpoReactNativeFactoryDelegate { } override func bundleURL() -> URL? { -#if DEBUG - return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry") -#else - return Bundle.main.url(forResource: "main", withExtension: "jsbundle") -#endif + #if DEBUG + return RCTBundleURLProvider.sharedSettings().jsBundleURL( + forBundleRoot: ".expo/.virtual-metro-entry") + #else + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") + #endif } } diff --git a/shared/ios/Keybase/Fs.swift b/shared/ios/Keybase/Fs.swift index 10dc2b486a99..690768cf6f88 100644 --- a/shared/ios/Keybase/Fs.swift +++ b/shared/ios/Keybase/Fs.swift @@ -1,12 +1,15 @@ import Foundation @objc class FsHelper: NSObject { - @objc func setupFs(_ skipLogFile: Bool, setupSharedHome shouldSetupSharedHome: Bool) -> [String: String] { + @objc func setupFs(_ skipLogFile: Bool, setupSharedHome shouldSetupSharedHome: Bool) -> [String: + String] + { let setupFsStartTime = CFAbsoluteTimeGetCurrent() NSLog("setupFs: starting") var home = NSHomeDirectory() - let sharedURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.keybase") + let sharedURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: "group.keybase") var sharedHome = sharedURL?.relativePath ?? "" home = setupAppHome(home: home, sharedHome: sharedHome) @@ -16,19 +19,26 @@ import Foundation let appKeybasePath = Self.getAppKeybasePath() // Put logs in a subdir that is entirely background readable - let oldLogPath = ("~/Library/Caches/Keybase" as NSString).expandingTildeInPath - let logPath = (oldLogPath as NSString).appendingPathComponent("logs") - let serviceLogFile = skipLogFile ? "" : (logPath as NSString).appendingPathComponent("ios.log") - + let oldLogURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + .appendingPathComponent("Keybase") + let serviceLogFile = + skipLogFile + ? "" + : oldLogURL + .appendingPathComponent("logs") + .appendingPathComponent("ios.log").path + + let logDirURL = oldLogURL.appendingPathComponent("logs") if !skipLogFile { - // cleanup old log files + // cleanup old log files (they live in the logs/ subdir, not directly under oldLogURL) let fm = FileManager.default ["ios.log", "ios.log.ek"].forEach { - try? fm.removeItem(atPath: (oldLogPath as NSString).appendingPathComponent($0)) + try? fm.removeItem(at: logDirURL.appendingPathComponent($0)) } } // Create LevelDB and log directories with a slightly lower data protection // mode so we can use them in the background + let appKeybaseURL = URL(fileURLWithPath: appKeybasePath) [ "keybase.chat.leveldb", "keybase.leveldb", @@ -42,12 +52,18 @@ import Foundation "kbfs_sync_cache", "kbfs_settings", "synced_tlf_config", - "logs" ].forEach { - createBackgroundReadableDirectory(path: (appKeybasePath as NSString).appendingPathComponent($0), setAllFiles: true) + createBackgroundReadableDirectory( + path: appKeybaseURL.appendingPathComponent($0).path, setAllFiles: true) } - // Mark avatars, which are in the caches dir - createBackgroundReadableDirectory(path: (oldLogPath as NSString).appendingPathComponent("avatars"), setAllFiles: true) + // Log and avatar dirs live under the caches dir, not Application Support. + // This must run after the cleanup above so that any surviving ios.log from a + // previous session (created by Go with default FileProtectionComplete) has its + // protection downgraded to completeUntilFirstUserAuthentication before + // KeybaseInit tries to open it on a locked device. + createBackgroundReadableDirectory(path: logDirURL.path, setAllFiles: true) + createBackgroundReadableDirectory( + path: oldLogURL.appendingPathComponent("avatars").path, setAllFiles: true) let setupFsElapsed = CFAbsoluteTimeGetCurrent() - setupFsStartTime NSLog("setupFs: completed in %.3f seconds", setupFsElapsed) @@ -55,14 +71,16 @@ import Foundation return [ "home": home, "sharedHome": sharedHome, - "logFile": serviceLogFile + "logFile": serviceLogFile, ] } private func addSkipBackupAttribute(to path: String) -> Bool { - let url = Foundation.URL(fileURLWithPath: path) + var url = URL(fileURLWithPath: path) do { - try (url as NSURL).setResourceValue(true, forKey: URLResourceKey.isExcludedFromBackupKey) + var resourceValues = URLResourceValues() + resourceValues.isExcludedFromBackup = true + try url.setResourceValues(resourceValues) return true } catch { NSLog("Error excluding \(url.lastPathComponent) from backup \(error)") @@ -77,9 +95,12 @@ import Foundation // directory accessible as long as the user has unlocked the phone once. The // files are still stored on the disk encrypted (note for the chat database, // it means we are encrypting it twice), and are inaccessible otherwise. - let noProt = [FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication] + let noProt = [ + FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication + ] NSLog("creating background readable directory: path: \(path) setAllFiles: \(setAllFiles)") - _ = try? fm.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: noProt) + _ = try? fm.createDirectory( + atPath: path, withIntermediateDirectories: true, attributes: noProt) do { try fm.setAttributes(noProt, ofItemAtPath: path) } catch { @@ -93,10 +114,11 @@ import Foundation NSLog("setAllFiles is true charging forward") // Recursively set attributes on all subdirectories and files + let baseURL = URL(fileURLWithPath: path) var fileCount = 0 if let enumerator = fm.enumerator(atPath: path) { for case let file as String in enumerator { - let filePath = (path as NSString).appendingPathComponent(file) + let filePath = baseURL.appendingPathComponent(file).path do { try fm.setAttributes(noProt, ofItemAtPath: filePath) fileCount += 1 @@ -105,7 +127,9 @@ import Foundation } } let dirElapsed = CFAbsoluteTimeGetCurrent() - dirStartTime - NSLog("createBackgroundReadableDirectory completed for: \(path), processed \(fileCount) files, total: %.3f seconds", dirElapsed) + NSLog( + "createBackgroundReadableDirectory completed for: \(path), processed \(fileCount) files, total: %.3f seconds", + dirElapsed) } else { NSLog("Error creating enumerator for path: \(path)") } @@ -113,12 +137,14 @@ import Foundation private func maybeMigrateDirectory(source: String, dest: String) -> Bool { let fm = FileManager.default + let sourceURL = URL(fileURLWithPath: source) + let destURL = URL(fileURLWithPath: dest) do { - // Always do this move in case it doesn't work on previous attempts. + // Always do this move in case it doesn't work on previous attempts. let sourceContents = try fm.contentsOfDirectory(atPath: source) for file in sourceContents { - let path = (source as NSString).appendingPathComponent(file) - let destPath = (dest as NSString).appendingPathComponent(file) + let path = sourceURL.appendingPathComponent(file).path + let destPath = destURL.appendingPathComponent(file).path var isDir: ObjCBool = false if fm.fileExists(atPath: path, isDirectory: &isDir), isDir.boolValue { NSLog("skipping directory: \(file)") @@ -142,18 +168,20 @@ import Foundation } @objc static func getAppKeybasePath() -> String { - return ("~/Library/Application Support/Keybase" as NSString).expandingTildeInPath + return FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] + .appendingPathComponent("Keybase").path } @objc static func getEraseableKVPath() -> String { - return (getAppKeybasePath() as NSString).appendingPathComponent("eraseablekvstore/device-eks") + return URL(fileURLWithPath: getAppKeybasePath()) + .appendingPathComponent("eraseablekvstore/device-eks").path } private func setupAppHome(home: String, sharedHome: String) -> String { let tempUrl = FileManager.default.temporaryDirectory - // workaround a problem where iOS dyld3 loader crashes if accessing .closure files - // with complete data protection on - let dyldDir = (tempUrl.path as NSString).appendingPathComponent("com.apple.dyld") + // workaround a problem where iOS dyld3 loader crashes if accessing .closure files + // with complete data protection on + let dyldDir = tempUrl.appendingPathComponent("com.apple.dyld").path let appKeybasePath = Self.getAppKeybasePath() let appEraseableKVPath = Self.getEraseableKVPath() @@ -168,15 +196,18 @@ import Foundation private func setupSharedHome(home: String, sharedHome: String) -> String { let appKeybasePath = Self.getAppKeybasePath() let appEraseableKVPath = Self.getEraseableKVPath() - let sharedKeybasePath = (sharedHome as NSString).appendingPathComponent("Library/Application Support/Keybase") - let sharedEraseableKVPath = (sharedKeybasePath as NSString).appendingPathComponent("eraseablekvstore/device-eks") + let sharedKeybasePath = URL(fileURLWithPath: sharedHome) + .appendingPathComponent("Library/Application Support/Keybase").path + let sharedEraseableKVPath = URL(fileURLWithPath: sharedKeybasePath) + .appendingPathComponent("eraseablekvstore/device-eks").path createBackgroundReadableDirectory(path: sharedKeybasePath, setAllFiles: true) createBackgroundReadableDirectory(path: sharedEraseableKVPath, setAllFiles: true) _ = addSkipBackupAttribute(to: sharedKeybasePath) guard maybeMigrateDirectory(source: appKeybasePath, dest: sharedKeybasePath), - maybeMigrateDirectory(source: appEraseableKVPath, dest: sharedEraseableKVPath) else { + maybeMigrateDirectory(source: appEraseableKVPath, dest: sharedEraseableKVPath) + else { return home } diff --git a/shared/ios/Keybase/Info.plist b/shared/ios/Keybase/Info.plist index fb65c31b600d..162c8786e7e6 100644 --- a/shared/ios/Keybase/Info.plist +++ b/shared/ios/Keybase/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 6.6.0 + 6.6.2 CFBundleSignature ???? CFBundleURLTypes diff --git a/shared/ios/Keybase/Pusher.swift b/shared/ios/Keybase/Pusher.swift index a32c59669693..43439b2afc31 100644 --- a/shared/ios/Keybase/Pusher.swift +++ b/shared/ios/Keybase/Pusher.swift @@ -1,9 +1,12 @@ import Foundation -import UserNotifications import Keybasego +import UserNotifications class PushNotifier: NSObject, Keybasego.KeybasePushNotifierProtocol { - func localNotification(_ ident: String?, msg: String?, badgeCount: Int, soundName: String?, convID: String?, typ: String?) { + func localNotification( + _ ident: String?, msg: String?, badgeCount: Int, soundName: String?, convID: String?, + typ: String? + ) { let content = UNMutableNotificationContent() if let soundName = soundName { content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: soundName)) @@ -11,29 +14,32 @@ class PushNotifier: NSObject, Keybasego.KeybasePushNotifierProtocol { content.badge = (badgeCount >= 0) ? NSNumber(value: badgeCount) : nil content.body = msg ?? "" content.userInfo = ["convID": convID ?? "", "type": typ ?? ""] - let request = UNNotificationRequest(identifier: ident ?? UUID().uuidString, content: content, trigger: nil) + let request = UNNotificationRequest( + identifier: ident ?? UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request) { error in if let error = error { NSLog("local notification failed: %@", error.localizedDescription) } } } - + func display(_ n: KeybaseChatNotification?) { - guard let notification = n else { return } - guard let message = notification.message else { return } - + guard let notification = n, let message = notification.message else { return } + let ident = "\(notification.convID):\(message.id_)" let msg: String if notification.isPlaintext && !message.plaintext.isEmpty { let username = message.from?.keybaseUsername ?? "" let convName = notification.conversationName - msg = (username == convName || convName.isEmpty) - ? "\(username): \(message.plaintext)" - : "\(username) (\(convName)): \(message.plaintext)" + msg = + (username == convName || convName.isEmpty) + ? "\(username): \(message.plaintext)" + : "\(username) (\(convName)): \(message.plaintext)" } else { msg = message.serverMessage } - localNotification(ident, msg: msg, badgeCount: notification.badgeCount, soundName: notification.soundName, convID: notification.convID, typ: "chat.newmessage") + localNotification( + ident, msg: msg, badgeCount: notification.badgeCount, soundName: notification.soundName, + convID: notification.convID, typ: "chat.newmessage") } } diff --git a/shared/ios/Keybase/ShareIntentDonatorImpl.swift b/shared/ios/Keybase/ShareIntentDonatorImpl.swift index c5bde51cd30a..dd9f1aaa1261 100644 --- a/shared/ios/Keybase/ShareIntentDonatorImpl.swift +++ b/shared/ios/Keybase/ShareIntentDonatorImpl.swift @@ -51,7 +51,9 @@ class ShareIntentDonatorImpl: NSObject, Keybasego.KeybaseShareIntentDonatorProto NSLog("ShareIntentDonator: donateShareConversations: empty conversations array") return } - NSLog("ShareIntentDonator: donateShareConversations: donating %d conversations", conversations.count) + NSLog( + "ShareIntentDonator: donateShareConversations: donating %d conversations", conversations.count + ) donateConversations(conversations) } @@ -97,7 +99,9 @@ class ShareIntentDonatorImpl: NSObject, Keybasego.KeybaseShareIntentDonatorProto } /// Loads avatar URL(s) and composites them. Apple requires a non-nil image for share sheet suggestions. - private func loadAvatars(urls: [URL], intent: INSendMessageIntent, completion: @escaping () -> Void) { + private func loadAvatars( + urls: [URL], intent: INSendMessageIntent, completion: @escaping () -> Void + ) { DispatchQueue.global(qos: .userInitiated).async { [weak self] in let images = urls.compactMap { (try? Data(contentsOf: $0)).flatMap { UIImage(data: $0) } } if let combined = self?.compositeAvatarImages(images), let data = combined.pngData() { @@ -129,7 +133,8 @@ class ShareIntentDonatorImpl: NSObject, Keybasego.KeybaseShareIntentDonatorProto let size: CGFloat = 192 let circleSize = size * 0.65 let leftRect = CGRect(origin: .zero, size: CGSize(width: circleSize, height: circleSize)) - let rightRect = CGRect(x: size - circleSize, y: size - circleSize, width: circleSize, height: circleSize) + let rightRect = CGRect( + x: size - circleSize, y: size - circleSize, width: circleSize, height: circleSize) let fullRect = CGRect(origin: .zero, size: CGSize(width: size, height: size)) let format = UIGraphicsImageRendererFormat() format.opaque = false @@ -142,7 +147,8 @@ class ShareIntentDonatorImpl: NSObject, Keybasego.KeybaseShareIntentDonatorProto let scale = max(size / imgSize.width, size / imgSize.height) let scaledW = imgSize.width * scale let scaledH = imgSize.height * scale - let drawRect = CGRect(x: (size - scaledW) / 2, y: (size - scaledH) / 2, width: scaledW, height: scaledH) + let drawRect = CGRect( + x: (size - scaledW) / 2, y: (size - scaledH) / 2, width: scaledW, height: scaledH) self.drawImageInCircle(img, in: fullRect, drawRect: drawRect, context: cgContext) } else { self.drawImageInCircle(images[0], in: leftRect, drawRect: leftRect, context: cgContext) @@ -152,7 +158,9 @@ class ShareIntentDonatorImpl: NSObject, Keybasego.KeybaseShareIntentDonatorProto } /// Draws an image clipped to an oval. Uses aspect fill when drawRect differs from clip rect. - private func drawImageInCircle(_ image: UIImage, in clipRect: CGRect, drawRect: CGRect, context: CGContext) { + private func drawImageInCircle( + _ image: UIImage, in clipRect: CGRect, drawRect: CGRect, context: CGContext + ) { context.saveGState() UIBezierPath(ovalIn: clipRect).addClip() image.draw(in: drawRect) @@ -163,7 +171,9 @@ class ShareIntentDonatorImpl: NSObject, Keybasego.KeybaseShareIntentDonatorProto let interaction = INInteraction(intent: intent, response: nil) interaction.donate { error in if let error = error { - NSLog("ShareIntentDonator: donateIntent failed for %@: %@", intent.conversationIdentifier ?? "?", error.localizedDescription) + NSLog( + "ShareIntentDonator: donateIntent failed for %@: %@", + intent.conversationIdentifier ?? "?", error.localizedDescription) } } } diff --git a/shared/ios/KeybaseShare/Info.plist b/shared/ios/KeybaseShare/Info.plist index d7a771229ac6..6fe231622ccb 100644 --- a/shared/ios/KeybaseShare/Info.plist +++ b/shared/ios/KeybaseShare/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 6.6.0 + 6.6.2 CFBundleVersion 200 NSExtension diff --git a/shared/ios/KeybaseShare/ShareViewController.swift b/shared/ios/KeybaseShare/ShareViewController.swift index 163e8a5e7668..041e53163cec 100644 --- a/shared/ios/KeybaseShare/ShareViewController.swift +++ b/shared/ios/KeybaseShare/ShareViewController.swift @@ -8,10 +8,10 @@ import Foundation import Intents -import UIKit -import MobileCoreServices -import Keybasego import KBCommon +import Keybasego +import MobileCoreServices +import UIKit @objc(ShareViewController) public class ShareViewController: UIViewController { @@ -33,7 +33,11 @@ public class ShareViewController: UIViewController { let sel = #selector(UIApplication.open(_:options:completionHandler:)) if r.responds(to: sel) { let imp = r.method(for: sel) - typealias Func = @convention(c) (AnyObject, Selector, URL, [UIApplication.OpenExternalURLOptionsKey: Any], ((Bool) -> Void)?) -> Void + typealias Func = + @convention(c) ( + AnyObject, Selector, URL, [UIApplication.OpenExternalURLOptionsKey: Any], + ((Bool) -> Void)? + ) -> Void let f = unsafeBitCast(imp, to: Func.self) f(r, sel, url, [:], nil) return @@ -54,7 +58,9 @@ public class ShareViewController: UIViewController { } func showProgressView() { - let alertController = UIAlertController(title: "Working on it", message: "\n\nPreparing content for sharing into Keybase.", preferredStyle: .alert) + let alertController = UIAlertController( + title: "Working on it", message: "\n\nPreparing content for sharing into Keybase.", + preferredStyle: .alert) alert = alertController let spinner = UIActivityIndicatorView(style: .medium) spinner.translatesAutoresizingMaskIntoConstraints = false @@ -62,7 +68,7 @@ public class ShareViewController: UIViewController { alertController.view.addSubview(spinner) NSLayoutConstraint.activate([ spinner.centerXAnchor.constraint(equalTo: alertController.view.centerXAnchor), - spinner.centerYAnchor.constraint(equalTo: alertController.view.centerYAnchor, constant: -8) + spinner.centerYAnchor.constraint(equalTo: alertController.view.centerYAnchor, constant: -8), ]) present(alertController, animated: true, completion: nil) } @@ -76,9 +82,10 @@ public class ShareViewController: UIViewController { if let intent = extensionContext?.intent as? INSendMessageIntent { selectedConvID = intent.conversationIdentifier } - let itemArrs = extensionContext?.inputItems.compactMap { - ($0 as? NSExtensionItem)?.attachments - } ?? [] + let itemArrs = + extensionContext?.inputItems.compactMap { + ($0 as? NSExtensionItem)?.attachments + } ?? [] weak var weakSelf = self iph = ItemProviderHelper(forShare: true, withItems: itemArrs) { diff --git a/shared/ios/Podfile b/shared/ios/Podfile index 8dd9516bcbb4..73cd024161b1 100644 --- a/shared/ios/Podfile +++ b/shared/ios/Podfile @@ -32,6 +32,7 @@ target 'Keybase' do :mac_catalyst_enabled => false, # :ccache_enabled => true ) + end end diff --git a/shared/ios/Podfile.lock b/shared/ios/Podfile.lock index 6c16b2c5df90..53abe593feee 100644 --- a/shared/ios/Podfile.lock +++ b/shared/ios/Podfile.lock @@ -110,7 +110,7 @@ PODS: - UMAppLoader - fast_float (8.0.0) - FBLazyVector (0.81.5) - - fmt (11.0.2) + - fmt (12.1.0) - glog (0.3.5) - hermes-engine (0.81.5): - hermes-engine/Pre-built (= 0.81.5) @@ -167,20 +167,20 @@ PODS: - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCT-Folly/Default (= 2024.11.18.00) - RCT-Folly/Default (2024.11.18.00): - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCT-Folly/Fabric (2024.11.18.00): - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCTDeprecation (0.81.5) - RCTRequired (0.81.5) @@ -3342,7 +3342,7 @@ SPEC CHECKSUMS: EXTaskManager: 6f1a66e4c8cc6df6e24c3d90928704bc3013eae5 fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 FBLazyVector: 5beb8028d5a2e75dd9634917f23e23d3a061d2aa - fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd + fmt: 530618a01105dae0fa3a2f27c81ae11fa8f67eac glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 KBCommon: bd1f35bb07924f4cc57417b00dab6734747b6423 @@ -3351,7 +3351,7 @@ SPEC CHECKSUMS: libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 lottie-ios: a881093fab623c467d3bce374367755c272bdd59 lottie-react-native: b01c4b468aed88931afefbde03d790fc4f8b010a - RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 + RCT-Folly: b29feb752b08042c62badaef7d453f3bb5e6ae23 RCTDeprecation: 5eb1d2eeff5fb91151e8a8eef45b6c7658b6c897 RCTRequired: cebcf9442fc296c9b89ac791dfd463021d9f6f23 RCTTypeSafety: b99aa872829ee18f6e777e0ef55852521c5a6788 @@ -3434,6 +3434,6 @@ SPEC CHECKSUMS: Yoga: cc4a6600d61e4e9276e860d4d68eebb834a050ba ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: 3afa944c1a159bdd57b3aa134ffed0193619be93 +PODFILE CHECKSUM: 1230fa854271c97af86d67b7424a7496e5f328a8 COCOAPODS: 1.16.2 diff --git a/shared/logger/index.tsx b/shared/logger/index.tsx index 074a79483655..2ec57274e58e 100644 --- a/shared/logger/index.tsx +++ b/shared/logger/index.tsx @@ -109,8 +109,13 @@ class AggregateLoggerImpl { sendLogsToService = async (lines: Array) => { if (!isMobile) { - // don't want main node thread making these calls + // don't want main node thread making these calls — the node engine's + // NativeTransport forwards responses to the renderer without processing + // them locally, so RPCs sent from node never get responses. try { + if (typeof process !== 'undefined' && process.type !== 'renderer') { + return await Promise.resolve() + } const {hasEngine} = require('../engine/require') as {hasEngine: typeof HasEngineType} if (!hasEngine()) { return await Promise.resolve() diff --git a/shared/patches/react-native+0.81.5.patch b/shared/patches/react-native+0.81.5.patch index 6044f91dc4a0..edf3f186d66a 100644 --- a/shared/patches/react-native+0.81.5.patch +++ b/shared/patches/react-native+0.81.5.patch @@ -2,7 +2,7 @@ diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmana index 216bb23..ec0578d 100644 --- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -@@ -411,10 +411,11 @@ static NSLineBreakMode RCTNSLineBreakModeFromEllipsizeMode(EllipsizeMode ellipsi +@@ -411,10 +411,11 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage CGSize attachmentSize = attachment.bounds.size; CGRect glyphRect = [layoutManager boundingRectForGlyphRange:range inTextContainer:textContainer]; @@ -18,3 +18,50 @@ index 216bb23..ec0578d 100644 auto rect = facebook::react::Rect{ facebook::react::Point{frame.origin.x, frame.origin.y}, +diff --git a/node_modules/react-native/gradle/libs.versions.toml b/node_modules/react-native/gradle/libs.versions.toml +index f0902b7..cb88f66 100644 +--- a/node_modules/react-native/gradle/libs.versions.toml ++++ b/node_modules/react-native/gradle/libs.versions.toml +@@ -43,7 +43,7 @@ yoga-proguard-annotations = "1.19.0" + boost="1_83_0" + doubleconversion="1.1.6" + fastFloat="8.0.0" +-fmt="11.0.2" ++fmt="12.1.0" + folly="2024.11.18.00" + glog="0.3.5" + gflags="2.2.0" +diff --git a/node_modules/react-native/third-party-podspecs/RCT-Folly.podspec b/node_modules/react-native/third-party-podspecs/RCT-Folly.podspec +index 8852179..040c4f0 100644 +--- a/node_modules/react-native/third-party-podspecs/RCT-Folly.podspec ++++ b/node_modules/react-native/third-party-podspecs/RCT-Folly.podspec +@@ -25,7 +25,7 @@ Pod::Spec.new do |spec| + spec.dependency "DoubleConversion" + spec.dependency "glog" + spec.dependency "fast_float", "8.0.0" +- spec.dependency "fmt", "11.0.2" ++ spec.dependency "fmt", "12.1.0" + spec.compiler_flags = '-Wno-documentation -faligned-new' + spec.source_files = 'folly/String.cpp', + 'folly/Conv.cpp', +diff --git a/node_modules/react-native/third-party-podspecs/fmt.podspec b/node_modules/react-native/third-party-podspecs/fmt.podspec +index 2f38990..a40c575 100644 +--- a/node_modules/react-native/third-party-podspecs/fmt.podspec ++++ b/node_modules/react-native/third-party-podspecs/fmt.podspec +@@ -8,14 +8,14 @@ fmt_git_url = fmt_config[:git] + + Pod::Spec.new do |spec| + spec.name = "fmt" +- spec.version = "11.0.2" ++ spec.version = "12.1.0" + spec.license = { :type => "MIT" } + spec.homepage = "https://github.com/fmtlib/fmt" + spec.summary = "{fmt} is an open-source formatting library for C++. It can be used as a safe and fast alternative to (s)printf and iostreams." + spec.authors = "The fmt contributors" + spec.source = { + :git => fmt_git_url, +- :tag => "11.0.2" ++ :tag => "12.1.0" + } + spec.pod_target_xcconfig = { + "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), diff --git a/shared/router-v2/hooks.native.tsx b/shared/router-v2/hooks.native.tsx index e957781818b8..4be4928cb44a 100644 --- a/shared/router-v2/hooks.native.tsx +++ b/shared/router-v2/hooks.native.tsx @@ -7,6 +7,7 @@ import {useDeepLinksState} from '@/constants/deeplinks' import {Linking} from 'react-native' import {useColorScheme} from 'react-native' import {usePushState} from '@/constants/push' +import logger from '@/logger' type InitialStateState = 'init' | 'loading' | 'loaded' @@ -177,6 +178,7 @@ export const useInitialState = (loggedInLoaded: boolean) => { const f = async () => { await loadInitialURL() + logger.info('[Router] initialStateState loaded, rendering app') setInitialStateState('loaded') } diff --git a/shared/router-v2/router.native.tsx b/shared/router-v2/router.native.tsx index f807285c2920..670f99bfc72e 100644 --- a/shared/router-v2/router.native.tsx +++ b/shared/router-v2/router.native.tsx @@ -182,6 +182,9 @@ const RNApp = React.memo(function RNApp() { const rootKey = Hooks.useRootKey() if (initialStateState !== 'loaded' || !loggedInLoaded) { + logger.info( + `[Router] showing SimpleLoading: initialStateState=${initialStateState} loggedInLoaded=${loggedInLoaded}` + ) return ( diff --git a/skills/bumping-versions/SKILL.md b/skills/bumping-versions/SKILL.md new file mode 100644 index 000000000000..1497ded65195 --- /dev/null +++ b/skills/bumping-versions/SKILL.md @@ -0,0 +1,15 @@ +--- +name: bumping-versions +description: Use when upgrading the Keybase client version number - lists all files that must be updated together +--- + +# Bumping Versions + +Update all four files together when changing the version number: + +| File | Field | +|------|-------| +| `go/libkb/version.go` | `const Version = "X.X.X"` | +| `shared/ios/Keybase/Info.plist` | `CFBundleShortVersionString` | +| `shared/ios/KeybaseShare/Info.plist` | `CFBundleShortVersionString` | +| `shared/android/app/build.gradle` | `def VERSION_NAME = "X.X.X"` |