Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.50.0 Release #3073

Merged
merged 16 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/bootstrap/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ runs:
~/Library/Caches/Homebrew/mint*
~/Library/Caches/Homebrew/xcparse*
~/Library/Caches/Homebrew/sonar-scanner*
~/Library/Caches/Homebrew/google-cloud-sdk*
key: ${{ env.IMAGE }}-brew-${{ hashFiles('**/Brewfile.lock.json') }}
restore-keys: ${{ env.IMAGE }}-brew-
- uses: ./.github/actions/ruby-cache
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/cron-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ jobs:
runs-on: ${{ matrix.os }}
env:
GITHUB_EVENT: ${{ toJson(github.event) }}
GITHUB_PR_NUM: ${{ github.event.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
STREAM_DEMO_APP_SECRET: ${{ secrets.STREAM_DEMO_APP_SECRET }}
XCODE_VERSION: ${{ matrix.xcode }}
IOS_SIMULATOR_DEVICE: "${{ matrix.device }} (${{ matrix.ios }})" # For the Allure report
steps:
- uses: actions/[email protected]
- uses: actions/download-artifact@v3
Expand Down Expand Up @@ -166,7 +166,6 @@ jobs:
runs-on: ${{ matrix.os }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUM: ${{ github.event.number }}
XCODE_VERSION: ${{ matrix.xcode }}
steps:
- uses: actions/[email protected]
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/xcmetrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Performance Benchmarks

on:
schedule:
# Runs "At 03:00 every night"
- cron: '0 3 * * *'

pull_request:
types:
- opened
- ready_for_review

workflow_dispatch:

env:
HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI

jobs:
xcmetrics:
name: XCMetrics
runs-on: macos-14
env:
GITHUB_TOKEN: '${{ secrets.CI_BOT_GITHUB_TOKEN }}'
steps:
- name: Install Bot SSH Key
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }}

- uses: actions/[email protected]
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
with:
fetch-depth: 0 # to fetch git tags

- uses: ./.github/actions/bootstrap
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
INSTALL_GCLOUD: true

- name: Run Performance Metrics
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
run: bundle exec fastlane xcmetrics
timeout-minutes: 120
env:
GITHUB_PR_NUM: ${{ github.event.pull_request.number }}
BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }}

- uses: actions/upload-artifact@v3
if: failure()
with:
name: Test Data
path: |
derived_data/Build/Products/xcodebuild_output.log
fastlane/performance/stream-chat-swift.json
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fastlane/test_output
fastlane/allurectl
fastlane/xcresults
fastlane/recordings
fastlane/performance
StreamChatCore.framework.coverage.txt
StreamChatCoreTests.xctest.coverage.txt
vendor/bundle/
Expand All @@ -90,6 +91,11 @@ spm_cache/
.buildcache
buildcache

# gcloud
google-cloud-sdk
gcloud.tar.gz
gcloud-service-account-key.json

# Ignore Products folder
Products/

Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### πŸ”„ Changed

# [4.50.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.50.0)
_March 11, 2024_

## StreamChat
### βœ… Added
- Add new `ChatMessage.textUpdatedAt` for when the message text is edited [#3059](https://github.com/GetStream/stream-chat-swift/pull/3059)
- Expose `ClientError.errorPayload` to easily check for server error details [#3061](https://github.com/GetStream/stream-chat-swift/pull/3061)
### 🐞 Fixed
- Fix token provider retrying after calling disconnect [#3052](https://github.com/GetStream/stream-chat-swift/pull/3052)
- Fix connect user never completing when disconnecting after token provider fails [#3052](https://github.com/GetStream/stream-chat-swift/pull/3052)
- Fix current user cache not deleted on logout causing unread count issues after switching users [#3055](https://github.com/GetStream/stream-chat-swift/pull/3055)
- Fix rare crash in `startObserver()` on logout when converting DTO to model in `itemCreator` [#3053](https://github.com/GetStream/stream-chat-swift/pull/3053)
- Fix invalid token triggering token refresh in an infinite loop [#3056](https://github.com/GetStream/stream-chat-swift/pull/3056)
- Do not mark a message as failed when the server returns duplicated message error [#3061](https://github.com/GetStream/stream-chat-swift/pull/3061)

## StreamChatUI
### βœ… Added
- Add new `Components.isMessageEditedLabelEnabled` [#3059](https://github.com/GetStream/stream-chat-swift/pull/3059)
- Add "Edited" label when a message is edited [#3059](https://github.com/GetStream/stream-chat-swift/pull/3059)
- Note: For now, only when the text changes it is marked as edited.
- Add `message.edited` localization key [#3059](https://github.com/GetStream/stream-chat-swift/pull/3059)

# [4.49.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.49.0)
_February 27, 2024_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ struct DemoAppConfig {

/// The details to generate expirable tokens in the demo app.
struct TokenRefreshDetails {
// The app secret from the dashboard.
/// The app secret from the dashboard.
let appSecret: String
// The duration in seconds until the token is expired.
/// The duration in seconds until the token is expired.
let duration: TimeInterval
/// In order to test token refresh fails, we can set a value of how
/// many token refresh will succeed before it starts failing.
/// By default it is 0. Which means it will always succeed.
let numberOfSuccessfulRefreshesBeforeFailing: Int
}
}

Expand Down Expand Up @@ -564,18 +568,33 @@ class AppConfigViewController: UITableViewController {
textField.placeholder = "App Secret"
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
if let appSecret = self.demoAppConfig.tokenRefreshDetails?.appSecret {
textField.text = appSecret
}
}
alert.addTextField { textField in
textField.placeholder = "Duration (Seconds)"
textField.keyboardType = .numberPad
if let duration = self.demoAppConfig.tokenRefreshDetails?.duration {
textField.text = "\(duration)"
}
}
alert.addTextField { textField in
textField.placeholder = "Number of Refreshes Before Failing"
textField.keyboardType = .numberPad
if let numberOfRefreshes = self.demoAppConfig.tokenRefreshDetails?.numberOfSuccessfulRefreshesBeforeFailing {
textField.text = "\(numberOfRefreshes)"
}
}

alert.addAction(.init(title: "Enable", style: .default, handler: { _ in
guard let appSecret = alert.textFields?[0].text else { return }
guard let duration = alert.textFields?[1].text else { return }
guard let successfulRetries = alert.textFields?[2].text else { return }
self.demoAppConfig.tokenRefreshDetails = .init(
appSecret: appSecret,
duration: TimeInterval(duration)!
duration: TimeInterval(duration) ?? 60,
numberOfSuccessfulRefreshesBeforeFailing: Int(successfulRetries) ?? 0
)
}))

Expand Down
12 changes: 8 additions & 4 deletions DemoApp/Shared/StreamChatWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import UserNotifications
final class StreamChatWrapper {
static let shared = StreamChatWrapper()

/// How many times the token has been refreshed. This is mostly used
/// to fake token refresh fails.
var numberOfRefreshTokens = 0

// This closure is called once the SDK is ready to register for remote push notifications
var onRemotePushRegistration: (() -> Void)?

Expand Down Expand Up @@ -60,8 +64,7 @@ extension StreamChatWrapper {
userInfo: userInfo,
tokenProvider: refreshingTokenProvider(
initialToken: userCredentials.token,
appSecret: tokenRefreshDetails.appSecret,
tokenDuration: tokenRefreshDetails.duration
refreshDetails: tokenRefreshDetails
),
completion: completion
)
Expand All @@ -84,6 +87,9 @@ extension StreamChatWrapper {
// Setup Stream Chat
setUpChat()

// Reset number of refresh tokens
numberOfRefreshTokens = 0

// We connect from a background thread to make sure it works without issues/crashes.
// This is for testing purposes only. As a customer you can connect directly without dispatching to any queue.
DispatchQueue.global().async {
Expand Down Expand Up @@ -115,8 +121,6 @@ extension StreamChatWrapper {
}

client.logout(completion: completion)

self.client = nil
}
}

Expand Down
24 changes: 15 additions & 9 deletions DemoApp/Shared/Token+Development.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import StreamChat
extension StreamChatWrapper {
func refreshingTokenProvider(
initialToken: Token,
appSecret: String,
tokenDuration: TimeInterval
refreshDetails: DemoAppConfig.TokenRefreshDetails
) -> TokenProvider {
{ completion in
// Simulate API call delay
Expand All @@ -19,20 +18,27 @@ extension StreamChatWrapper {

if #available(iOS 13.0, *) {
generatedToken = _generateUserToken(
secret: appSecret,
secret: refreshDetails.appSecret,
userID: initialToken.userId,
expirationDate: Date().addingTimeInterval(tokenDuration)
expirationDate: Date().addingTimeInterval(refreshDetails.duration)
)
}

if generatedToken == nil {
print("Demo App Token Refreshing: Unable to generate token.")
} else {
print("Demo App Token Refreshing: New token generated.")
log.error("Demo App Token Refreshing: Unable to generate token. Using initialToken instead.")
}

let newToken = generatedToken ?? initialToken
completion(.success(newToken))
let numberOfSuccessfulRefreshes = refreshDetails.numberOfSuccessfulRefreshesBeforeFailing
let shouldNotFail = numberOfSuccessfulRefreshes == 0
if shouldNotFail || self.numberOfRefreshTokens <= numberOfSuccessfulRefreshes {
print("Demo App Token Refreshing: New token generated successfully.")
let newToken = generatedToken ?? initialToken
completion(.success(newToken))
} else {
print("Demo App Token Refreshing: Token refresh failed.")
completion(.failure(ClientError("Token Refresh Failed")))
}
self.numberOfRefreshTokens += 1
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ final class DemoChatChannelListRouter: ChatChannelListRouter {

var channelPresentingStyle: ChannelPresentingStyle = .push
var onLogout: (() -> Void)?
var onDisconnect: (() -> Void)?

lazy var streamModalTransitioningDelegate = StreamModalTransitioningDelegate()

Expand All @@ -36,6 +37,9 @@ final class DemoChatChannelListRouter: ChatChannelListRouter {
}),
.init(title: "Logout", style: .destructive, handler: { [weak self] _ in
self?.onLogout?()
}),
.init(title: "Disconnect", style: .destructive, handler: { [weak self] _ in
self?.onDisconnect?()
})
])
}
Expand Down
40 changes: 29 additions & 11 deletions DemoApp/StreamChat/DemoAppCoordinator+DemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ extension DemoAppCoordinator {
func showChat(for user: DemoUserType, cid: ChannelId?, animated: Bool, completion: @escaping (Error?) -> Void) {
logIn(as: user, completion: completion)

let chatVC = makeChatVC(for: user, startOn: cid) { [weak self] in
guard let self = self else { return }
self.logOut()
}
let chatVC = makeChatVC(
for: user,
startOn: cid,
onLogout: { [weak self] in
self?.logOut()
},
onDisconnect: { [weak self] in
self?.disconnect()
}
)

set(rootViewController: chatVC, animated: animated)
DemoAppConfiguration.showPerformanceTracker()
Expand Down Expand Up @@ -58,7 +64,12 @@ extension DemoAppCoordinator {
return nil
}

func makeChatVC(for user: DemoUserType, startOn cid: ChannelId?, onLogout: @escaping () -> Void) -> UIViewController {
func makeChatVC(
for user: DemoUserType,
startOn cid: ChannelId?,
onLogout: @escaping () -> Void,
onDisconnect: @escaping () -> Void
) -> UIViewController {
// Construct channel list query
let channelListQuery: ChannelListQuery
switch user {
Expand Down Expand Up @@ -90,7 +101,8 @@ extension DemoAppCoordinator {
let channelListVC = makeChannelListVC(
controller: channelListController,
selectedChannel: selectedChannel,
onLogout: onLogout
onLogout: onLogout,
onDisconnect: onDisconnect
)

let channelListNVC = UINavigationController(rootViewController: channelListVC)
Expand All @@ -109,10 +121,12 @@ extension DemoAppCoordinator {
func makeChannelListVC(
controller: ChatChannelListController,
selectedChannel: ChatChannel?,
onLogout: @escaping () -> Void
onLogout: @escaping () -> Void,
onDisconnect: @escaping () -> Void
) -> UIViewController {
let channelListVC = DemoChatChannelListVC.make(with: controller)
channelListVC.demoRouter?.onLogout = onLogout
channelListVC.demoRouter?.onDisconnect = onDisconnect
channelListVC.selectedChannel = selectedChannel
channelListVC.components.isChatChannelListStatesEnabled = true
return channelListVC
Expand Down Expand Up @@ -155,15 +169,19 @@ private extension DemoAppCoordinator {
}

func logOut() {
// logout client
chat.logOut { [weak self] in
// clean user id
UserDefaults.shared.currentUserId = nil

// show login screen
self?.showLogin(animated: true)
}
}

func disconnect() {
chat.client?.disconnect { [weak self] in
DispatchQueue.main.async {
self?.showLogin(animated: true)
}
}
}
}

extension ChatChannel {
Expand Down
Loading
Loading