FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Tags · GetStream/stream-chat-react-native · GitHub

Tags: GetStream/stream-chat-react-native

Tags

v9.8.2

Toggle v9.8.2's commit message
chore(release): 9.8.2 [skip ci]

v9.8.2-beta.1

Toggle v9.8.2-beta.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix: disable LayoutAnimation in KeyboardCompatibleView (#3789)

## 🎯 Goal

It would appear that in `react-native-reanimated` version `4.6.0` and
onwards, the swallowing of `LayoutAnimation.configureNext` in Reanimated
has been fixed. However, this now causes our keyboard animations to
struggle with the Reanimated animation due to the fact that they are
both trying to animate the layout.

I somehow missed this in the RN 0.87 upgrade, but it should be fixed
here.

To address it, we disable the `LayoutAnimation` in our
`KeyboardCompatibleView` because:

1. It never worked pre `4.6.0` of `reanimated`
2. Where it does work, it's interfering with the actual layout animation

So this change basically makes sure we have the old behaviour again.

## 🛠 Implementation details

<!-- Provide a description of the implementation -->

## 🎨 UI Changes

<!-- Add relevant screenshots -->

<details>
<summary>iOS</summary>


<table>
    <thead>
        <tr>
            <td>Before</td>
            <td>After</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <!--<img src="" /> -->
            </td>
            <td>
                <!--<img src="" /> -->
            </td>
        </tr>
    </tbody>
</table>
</details>


<details>
<summary>Android</summary>

<table>
    <thead>
        <tr>
            <td>Before</td>
            <td>After</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <!--<img src="" /> -->
            </td>
            <td>
                <!--<img src="" /> -->
            </td>
        </tr>
    </tbody>
</table>
</details>

## 🧪 Testing

<!-- Explain how this change can be tested (or why it can't be tested)
-->

## ☑️ Checklist

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [ ] PR targets the `develop` branch
- [ ] Documentation is updated
- [ ] New code is tested in main example apps, including all possible
scenarios
  - [ ] SampleApp iOS and Android
  - [ ] Expo iOS and Android

v9.8.1

Toggle v9.8.1's commit message
chore(release): 9.8.1 [skip ci]

sampleapp@v4.15.1

Toggle sampleapp@v4.15.1's commit message
chore(release): 4.15.1 [skip ci]

v9.8.1-beta.2

Toggle v9.8.1-beta.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix: timeout on ios uploads on http 1.1 connection (#3787)

## 🎯 Goal

This PR fixes iOS attachment uploads hanging for 60s and then failing
with `-1001` (timed out) after the server has already accepted the file.

Affects apps using `useNativeMultipartUpload` specifically.

The `CFNetwork` trace of a failing upload looks something like this:

```
resuming, timeouts(60.0, …)
received response, status 201        ← server took the file
…60 s of nothing…
finished with error [-1001]
summary for task failure { response_status=201, request_bytes=2810041,
                           response_duration_ms=0, protocol="http/1.1" }
```

So the upload succeeds and the client throws it away a minute later. The
user sees a failed attachment (and the message with the attachment is
not sent consequently).

## 🛠 Implementation details

The root cause is unfortunately not as simple. The multipart body was
pretty much a hand rolled `NSInputStream` subclass. `CFNetwork` drives
an `HTTP/1.1` request body through the `CFReadStream` interface, which a
plain `InputStream` subclass cannot participate in, so it can never
report end-of-stream. `CFNetwork` stops calling `read` the moment
`Content-Length` is satisfied, so the subclass never returned 0, never
reached `.atEnd` and so `CFNetwork` never learned the body had ended.
The transaction stayed open until the timeout (which is 60 seconds
later).

This isn't something that would fire all the time, however. It happens
whenever the connection ends up on `HTTP/1.1`. That's decided on a much
lower level (during the TLS handshake), so it's server and network
determined rather than something the app controls, so if an endpoint
that doesn't advertise `h2`, a TLS intercepting proxy or a local
debugging proxy such as `Charles` or `Proxyman` will all put us there.
Over `HTTP/2` and `HTTP/3` the request body is framed and terminated by
`Content-Length`, so the missing end-of-stream signal never mattered and
the bug stayed dormant. Hence, why this has gone unnoticed for so long.
Naturally, this is an edge case altogether but I've decided to rewrite
chunks of the body stream class. There have always existed some certain
parts of it that bothered me and we attempt to address them here as
well.

`makeStream()` now hands `URLSession` the read end of a
`CFStreamCreateBoundPair`, so a real `CFReadStream` that reports every
event and a new `StreamMultipartBodyProducer` feeds the write end from
the same element list. Closing the write end is what tells `CFNetwork`
the body is complete.

The producer is driven by GCD (`CFWriteStreamSetClient` +
`CFWriteStreamSetDispatchQueue`) rather than a run loop, so it owns no
thread and can't outlive its work.

Because a bound pair has no error channel, body production failures are
recorded in a `StreamMultipartBodyErrorBox` and preferred over the
transport error in `didCompleteWithError`. The box is **per attempt** so
then `URLSession` can request a fresh body stream on retry and an
abandoned attempt's failure must not fail a later one that succeeds.

Aside from a bit of code complexity, this also costs about `128 KiB` of
extra memory per request (which is nothing). In turn we handle a lot of
odd edge cases, like:

- no longer relying on kind of undocumented behaviour
- failures actually get reported every time now
- backpressure is now being handled instead of just falsely quitting the
upload
- retry scoping is now strictly 

And naturally, `HTTP/1.1` no longer fails uploads.

## 🎨 UI Changes

## 🧪 Testing
## ☑️ Checklist

- [x] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [x] PR targets the `develop` branch
- [ ] Documentation is updated
- [x] New code is tested in main example apps, including all possible
scenarios
  - [x] SampleApp iOS and Android
  - [x] Expo iOS and Android

v9.8.0

Toggle v9.8.0's commit message
chore(release): 9.8.0 [skip ci]

v9.8.0-beta.1

Toggle v9.8.0-beta.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat: offline db encryption (#3780)

## 🎯 Goal

Let integrators encrypt the offline database at rest. The offline cache
stores channels, messages, members, drafts and reminders, and right now
we write all of it as plaintext `SQLite`.

It's opt-in. Apps that don't pass the new prop behave exactly as they do
today.

One thing to know up front, since it shapes the rest of the PR:
`op-sqlite` accepts an `encryptionKey` on a build without `SQLCipher`
and then ignores it. You get a plaintext database and no error at any
layer. So part of this change is detecting that and refusing to open the
database, instead of passing the key along and assuming it was used.

Accompanying docs PR:
GetStream/docs-content#1521

## 🛠 Implementation details

### API

`Chat` takes one new prop:

```tsx
<Chat client={client} enableOfflineSupport getEncryptionKey={getEncryptionKey}>
```

`getEncryptionKey?: () => Promise<string | undefined>` runs once per
database open, so once per launch and again after a sign-out. Its result
is passed to `SQLCipher` through `op-sqlite`. `SqliteClientError` and
`SqliteClientErrorCode` are exported too.

### We throw instead of recovering

When the database can't be opened with the encryption that was asked
for, `Chat` throws a `SqliteClientError` from render and the
integrator's error boundary handles it. We don't fall back to plaintext,
we don't switch offline support off, and we don't delete anything.

The reason is that all of those recoveries have a security consequence
and there's no default that's right for everyone. Falling back to
plaintext defeats the point of the feature and nothing tells you it
happened. Dropping the cache decides a compliance question for the
integrator. Deleting the file throws away offline actions that are still
queued. We also can't tell "the Keystore isn't unlocked yet, try again
shortly" from "something is wrong here, sign this device out". So we
detect the failure and classify it, and the app decides what to do about
it.

### Scenarios

| Scenario | What it means | What the SDK does | Recommended recovery |
| ------------------------------------------------------- |
------------------------------------------------------------ |
---------------------------------------------- |
------------------------------------------------------------------ |
| No `getEncryptionKey` passed | Encryption not requested | Opens
plaintext, same as today | n/a |
| Key supplied, fresh install | Nothing on disk yet | Creates the
database encrypted with that key | n/a |
| Key supplied, plaintext database already on disk | Integrator is
turning encryption on for an existing install | Throws
`OFFLINE_DB_UNREADABLE` | Delete the database, remount `Chat` |
| Key differs from the one the database was written with | Key rotated,
or read from the wrong place | Throws `OFFLINE_DB_UNREADABLE` | Delete
the database, remount `Chat` |
| `getEncryptionKey` removed, encrypted database on disk | Integrator is
turning encryption off again | Throws `OFFLINE_DB_UNREADABLE` | Delete
the database, remount `Chat` |
| `getEncryptionKey` throws | Key isn't available yet, e.g. Keystore
still locked | Throws `ENCRYPTION_KEY_UNAVAILABLE` | Remount to retry,
e.g. on next app foreground |
| `getEncryptionKey` resolves `undefined` | Same as above | Throws
`ENCRYPTION_KEY_UNAVAILABLE` | Remount to retry, e.g. on next app
foreground |
| Key supplied, native build has no `SQLCipher` | The key would be
ignored and the database left plaintext | Throws
`SQLCIPHER_BUILD_MISSING`, doesn't open | Not fixable at runtime,
remount with `enableOfflineSupport={false}` |
| Database file corrupted | Nothing to do with encryption | Throws
`OFFLINE_DB_UNREADABLE` | Delete the database, remount `Chat` |

Two of those rows need a closer look in review.

`OFFLINE_DB_UNREADABLE` is not gated on `getEncryptionKey` being set,
and that's on purpose, because of the "turning encryption off again"
row. If we only threw it when a key was supplied, an integrator removing
the prop would get a blank screen instead of an error they can recover
from. I hit that on device.

The last row is why the boundary is useful even for apps that never use
encryption. A corrupted database gives you the same code, so anything
using `enableOfflineSupport` can end up there.

### Where the error comes from

`AbstractOfflineDB.init` in the LLC catches whatever `initializeDB`
throws and doesn't re-throw it, so a caller can't find out why
initialisation failed. I left that alone, because changing it would tie
this PR to an LLC release. `OfflineDB` stores the reason on the instance
on the way out instead, and the new hook reads it back once `init` has
settled. No LLC changes needed for this.

- `SqliteClient` resolves the key, opens through `SQLCipher` and maps
failures onto the codes above. It also gets `preflightEncryption()`,
which runs before `setOfflineDBApi`. Without that ordering the client
attaches a database that's already dead, and the unguarded `await
this.offlineDb.upsertChannels(...)` inside `queryChannels` rejects. You
end up on a loading screen that never resolves.
- `useInitializeOfflineDb()` is new and does preflight, attach, init and
raise, with the init options behind an `options` param. It's pulled out
of `Chat`, which loses 72 lines.
- `OfflineDB` records `initializationError` and re-throws, so `init`
still marks the database uninitialised.

## 🎨 UI Changes

## 🧪 Testing

<!-- Explain how this change can be tested (or why it can't be tested)
-->

## ☑️ Checklist

- [x] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [x] PR targets the `develop` branch
- [x] Documentation is updated
- [ ] New code is tested in main example apps, including all possible
scenarios
  - [ ] SampleApp iOS and Android
  - [ ] Expo iOS and Android

v8.14.0

Toggle v8.14.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat: offline db encryption (#3781)

## 🎯 Goal

This is a port of [this
feature](#3780)
to V8.

## 🛠 Implementation details

<!-- Provide a description of the implementation -->

## 🎨 UI Changes

<!-- Add relevant screenshots -->

<details>
<summary>iOS</summary>


<table>
    <thead>
        <tr>
            <td>Before</td>
            <td>After</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <!--<img src="" /> -->
            </td>
            <td>
                <!--<img src="" /> -->
            </td>
        </tr>
    </tbody>
</table>
</details>


<details>
<summary>Android</summary>

<table>
    <thead>
        <tr>
            <td>Before</td>
            <td>After</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <!--<img src="" /> -->
            </td>
            <td>
                <!--<img src="" /> -->
            </td>
        </tr>
    </tbody>
</table>
</details>

## 🧪 Testing

<!-- Explain how this change can be tested (or why it can't be tested)
-->

## ☑️ Checklist

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [ ] PR targets the `develop` branch
- [ ] Documentation is updated
- [ ] New code is tested in main example apps, including all possible
scenarios
  - [ ] SampleApp iOS and Android
  - [ ] Expo iOS and Android

sampleapp@v4.15.0

Toggle sampleapp@v4.15.0's commit message
chore(release): 4.15.0 [skip ci]

v9.7.6

Toggle v9.7.6's commit message
chore(release): 9.7.6 [skip ci]


Back | FazBrowse Home | New Git URL