Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
edf6b3d
WPB-20915: added Item menu entry "share"
WilhelmOks Nov 28, 2025
d25913c
WPB-20915: navigating to share link view with dummy buttons
WilhelmOks Dec 1, 2025
9aa5e67
WPB-20915: navigation to subviews password and expiration
WilhelmOks Dec 1, 2025
05e6b00
Merge branch 'develop' into feat/WPB-20915-share-public-link
WilhelmOks Dec 1, 2025
1fd2abd
WPB-20915: tint color
WilhelmOks Dec 1, 2025
ca55345
WPB-20915: files for password view
WilhelmOks Dec 1, 2025
53928c3
WPB-20915: toolbars
WilhelmOks Dec 1, 2025
29d7a54
WPB-20915: alerts
WilhelmOks Dec 1, 2025
0eb18aa
WPB-20915: description and toggle
WilhelmOks Dec 1, 2025
1be3cc0
WPB-20915: opening sheet also from AllFiles
WilhelmOks Dec 2, 2025
9d1634f
WPB-20915: -
WilhelmOks Dec 2, 2025
8f1ac8b
Merge branch 'develop' into feat/WPB-20915-share-public-link
WilhelmOks Dec 2, 2025
3209364
WPB-20915: generate-password button
WilhelmOks Dec 2, 2025
bf1787f
WPB-20915: buttons for copy password and change password
WilhelmOks Dec 3, 2025
9b88fa5
WPB-20915: password input field
WilhelmOks Dec 3, 2025
b202a41
WPB-20915: accessibility, pasteboard, refactoring
WilhelmOks Dec 3, 2025
793d656
WPB-20915: some ViewModel logic and handing back data to the previous…
WilhelmOks Dec 4, 2025
0f938af
WPB-20915: refactoring to one ViewModel for 3 Views
WilhelmOks Dec 4, 2025
09fbb85
Revert "WPB-20915: refactoring to one ViewModel for 3 Views"
WilhelmOks Dec 4, 2025
035ff40
WPB-20915: comments
WilhelmOks Dec 4, 2025
7205d5c
Merge branch 'develop' into feat/WPB-20915-share-public-link
WilhelmOks Dec 4, 2025
8c4e590
WPB-20915: added publicLinkId to FilesViewItem
WilhelmOks Dec 5, 2025
8b6e131
WPB-20915: use case for getting link data
WilhelmOks Dec 5, 2025
f8c481c
WPB-20915: comment
WilhelmOks Dec 5, 2025
1d41a1c
Merge branch 'develop' into feat/WPB-20915-share-public-link
WilhelmOks Dec 5, 2025
589fd9d
new ExpirationDatePickerView
OlivellaO Dec 9, 2025
1ece39d
add logic to show date in the past
OlivellaO Dec 12, 2025
d65d07c
code format
OlivellaO Dec 12, 2025
56bc613
update shared link view
OlivellaO Dec 15, 2025
be2ea5e
Provide correct data in WireCellsPublicLink
samwyndham Dec 16, 2025
908f109
Fix link URL
samwyndham Dec 16, 2025
1c0d014
Fix anf implement remaining rest use cases
samwyndham Dec 16, 2025
4ee9945
Hook up ShareLinkView & ExpirationDatePickerView
samwyndham Dec 16, 2025
d9c9154
delete some debug code
samwyndham Dec 16, 2025
171e566
Merge branch 'develop' into feat/WPB-20915-share-public-link
samwyndham Dec 16, 2025
e42aaa4
Fix tests
samwyndham Dec 16, 2025
5f50a8e
Include requires password in state
samwyndham Dec 16, 2025
d17c846
Add some to comments
samwyndham Dec 16, 2025
49f6ef4
add keychain use cases, related logic, fix some TODOs
jullianm Dec 18, 2025
6c1d5f3
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Dec 18, 2025
36e54aa
handle errors, loading state, clean up code, UI adjustments
jullianm Dec 18, 2025
7239387
lint and format
jullianm Dec 18, 2025
e42a87a
UI adjustments relates to link expiration
jullianm Dec 18, 2025
46336f5
add snapshot tests
jullianm Dec 18, 2025
49e5c7c
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Dec 18, 2025
ab739ec
update snapshots
jullianm Dec 18, 2025
8cf417f
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Dec 22, 2025
b2fbcaf
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Jan 5, 2026
9a12ac2
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Jan 6, 2026
5171483
add missing check
jullianm Jan 6, 2026
767f709
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Jan 6, 2026
7749b7f
lint and format
jullianm Jan 6, 2026
8783373
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Jan 6, 2026
726d907
fix PR comments
jullianm Jan 6, 2026
23aab97
lint and format
jullianm Jan 6, 2026
c947ee1
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Jan 6, 2026
90f599d
lint and format
jullianm Jan 6, 2026
27bdffc
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Jan 7, 2026
fb192a2
inject Calendar component
jullianm Jan 7, 2026
b312491
Merge branch 'develop' into feat/WPB-20915-share-public-link
jullianm Jan 7, 2026
884f17a
comment out failing snapshot test (added TODO)
jullianm Jan 7, 2026
7cd564b
Merge branch 'feat/WPB-20915-share-public-link' of ssh://github.com/w…
jullianm Jan 7, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public import WireFoundation

public actor KeychainProtocolMock: KeychainProtocol {

// MARK: - Init

public init() {}

// MARK: - addItem

public var addItemQuery_Invocations: [Set<KeychainQueryItem>] = []
Expand Down
1 change: 1 addition & 0 deletions WireMessaging/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ let package = Package(
"WireMessagingUI",
"WireMessagingDomainSupport",
.product(name: "WireDesign", package: "WireUI"),
.product(name: "WireFoundationSupport", package: "WireFoundation"),
"WireFoundation"
],
resources: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//

public import Foundation
public import UIKit

Check warning on line 20 in WireMessaging/Sources/WireMessagingAssembly/WireMessagingFactory.swift

View workflow job for this annotation

GitHub Actions / Test Results

Public import of 'SwiftUI' was not used in public declarations or inlinable code

Public import of 'SwiftUI' was not used in public declarations or inlinable code
public import SwiftUI
public import WireData
public import WireFoundation
Expand Down Expand Up @@ -200,7 +200,12 @@
getAssetUseCase: WireCellsGetAssetUseCase(
localAssetRepository: localAssetRepository,
fileCache: fileCache
)
),
getPublicLinkData: WireCellsGetPublicLinkDataUseCase(nodesAPI: nodesAPI),
createPublicLink: WireCellsCreatePublicLinkUseCase(nodesAPI: nodesAPI),
deletePublicLink: WireCellsDeletePublicLinkUseCase(nodesAPI: nodesAPI),
updatePublicLinkExpiration: WireCellsUpdatePublicLinkExpirationUseCase(nodesAPI: nodesAPI),
updatePublicLinkPassword: WireCellsUpdatePublicLinkPasswordUseCase(nodesAPI: nodesAPI)
),
isCellsStatePending: false,
localAssetRepository: localAssetRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ package struct WireCellsNodeNetworkModel: Equatable, Hashable, Sendable {
package let ownerUserId: String?
package let ownerUserName: String?
package let conversationId: String?
package let publicLinkId: String?
package let publicLinkID: String?
package let downloadURL: URL?
package let tags: [String]

Expand All @@ -60,7 +60,7 @@ package struct WireCellsNodeNetworkModel: Equatable, Hashable, Sendable {
ownerUserId: String? = nil,
ownerUserName: String?,
conversationId: String? = nil,
publicLinkId: String? = nil,
publicLinkID: String? = nil,
downloadURL: URL? = nil,
tags: [String] = []
) {
Expand All @@ -80,7 +80,7 @@ package struct WireCellsNodeNetworkModel: Equatable, Hashable, Sendable {
self.ownerUserId = ownerUserId
self.ownerUserName = ownerUserName
self.conversationId = conversationId
self.publicLinkId = publicLinkId
self.publicLinkID = publicLinkID
self.downloadURL = downloadURL
self.tags = tags
}
Expand All @@ -105,7 +105,7 @@ package extension WireCellsNodeNetworkModel {
ownerUserID: ownerUserId.flatMap { QualifiedID(string: $0) },
ownerUserName: ownerUserName,
conversationID: conversationId.flatMap(QualifiedID.init(string:)),
publicLinkID: publicLinkId.map(WireCellsPublicLinkID.init(string:)),
publicLinkID: publicLinkID.map(WireCellsPublicLinkID.init(string:)),
downloadURL: downloadURL,
tags: tags
)
Expand All @@ -130,7 +130,7 @@ package extension WireCellsNode {
ownerUserId: ownerUserID?.transportString,
ownerUserName: ownerUserName,
conversationId: conversationID?.transportString,
publicLinkId: publicLinkID?.string,
publicLinkID: publicLinkID?.string,
tags: tags
)
}
Expand Down Expand Up @@ -159,7 +159,7 @@ package extension RestNode {
ownerUserId: metadataString("usermeta-owner-uuid"),
ownerUserName: metadataString("usermeta-owner"),
conversationId: contextWorkspace?.uuid,
publicLinkId: shares?.first?.uuid,
publicLinkID: shares?.first?.uuid,
downloadURL: preSignedGET?.url.flatMap(URL.init(string:)),
tags: metadataString("usermeta-tags")?
.split(separator: ",").map { String($0) } ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
out: URL,
cellPath: String,
onProgressUpdate: @escaping @Sendable (UInt64) -> Void
) async throws {

Check warning on line 124 in WireMessaging/Sources/WireMessagingData/WireCells/NodesAPI/NodesAPI.swift

View workflow job for this annotation

GitHub Actions / Test Results

Value 'stream' was defined but never used; consider replacing with boolean test

Value 'stream' was defined but never used; consider replacing with boolean test
guard let stream = OutputStream(url: out, append: true) else {
throw NodesAPIError.failedToCreateWriteStream
}
Expand Down Expand Up @@ -169,16 +169,39 @@
try await restAPI.getEditorURL(id: id)
}

package func createPublicLink(nodeID: UUID, fileName: String) async throws -> WireCellsPublicLink {
try await restAPI.createPublicLink(uuid: nodeID, fileName: fileName)
package func createPublicLink(nodeID: UUID, label: String) async throws -> WireCellsPublicLink {
try await restAPI.createPublicLink(
uuid: nodeID,
label: label
)
}

package func getPublicLink(linkID: String) async throws -> WireCellsPublicLink {
try await restAPI.getPublicLink(linkID: linkID)
}

package func getPublicLink(linkUUID: UUID) async throws -> URL {
try await restAPI.getPublicLink(uuid: linkUUID)
package func deletePublicLink(linkID: String) async throws {
try await restAPI.deletePublicLink(linkID: linkID)
}

package func deletePublicLink(linkUUID: UUID) async throws {
try await restAPI.deletePublicLink(uuid: linkUUID)
package func updatePublicLinkExpiration(
linkID: String,
expiration: Date?
) async throws -> WireCellsPublicLink {
try await restAPI.updatePublicLinkExpiration(
linkID: linkID,
expiration: expiration
)
}

package func updatePublicLinkPassword(
linkID: String,
password: String?
) async throws -> WireCellsPublicLink {
try await restAPI.updatePublicLinkPassword(
linkID: linkID,
password: password
)
}

package func updateTags(nodeID: UUID, tags: [String]) async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum WireCellsNodesAPIError: Error {
case failedToDecodeNode
case failedToDecodeNodeVersions
case missingData(String)
case invalidParameters(String)
}

final class RestAPI: Sendable {
Expand Down Expand Up @@ -274,28 +275,18 @@ final class RestAPI: Sendable {
}
}

func getPublicLink(uuid: UUID) async throws -> URL {
func getPublicLink(linkID: String) async throws -> WireCellsPublicLink {
let response = try await NodeServiceAPI.getPublicLink(
linkUuid: uuid.transportString(),
linkUuid: linkID,
apiConfiguration: makeConfiguration()
)

guard let urlString = response.linkUrl else {
throw WireCellsNodesAPIError.missingData("Link URL not found")
}
guard let url = URL(string: urlString) else {
throw WireCellsNodesAPIError.missingData("Link URL is invalid")
}

return url
return try WireCellsPublicLink(response, serverURL: serverURLResolver())
}

func createPublicLink(uuid: UUID, fileName: String) async throws -> WireCellsPublicLink {
func createPublicLink(uuid: UUID, label: String) async throws -> WireCellsPublicLink {
let request = RestPublicLinkRequest(
link: RestShareLink(
label: fileName,
permissions: [.preview, .download]
)
link: RestShareLink(label: label, permissions: [.preview, .download])
)

let response = try await NodeServiceAPI.createPublicLink(
Expand All @@ -304,28 +295,59 @@ final class RestAPI: Sendable {
apiConfiguration: makeConfiguration()
)

guard let idString = response.uuid else {
throw WireCellsNodesAPIError.missingData("UUID is null")
}
guard let id = UUID(uuidString: idString) else {
throw WireCellsNodesAPIError.missingData("UUID is invalid")
}
return try WireCellsPublicLink(response, serverURL: serverURLResolver())
}

guard let urlString = response.linkUrl else {
throw WireCellsNodesAPIError.missingData("Link URL not found")
}
guard let url = URL(string: urlString) else {
throw WireCellsNodesAPIError.missingData("Link URL is invalid")
}
func deletePublicLink(linkID: String) async throws {
_ = try await NodeServiceAPI.deletePublicLink(
linkUuid: linkID,
apiConfiguration: makeConfiguration()
)
}

func updatePublicLinkExpiration(linkID: String, expiration: Date?) async throws -> WireCellsPublicLink {
var currentLink = try await NodeServiceAPI.getPublicLink(
linkUuid: linkID,
apiConfiguration: makeConfiguration()
)
currentLink.accessEnd = expiration.map { String(Int($0.timeIntervalSince1970)) }

let updatedLink = try await NodeServiceAPI.updatePublicLink(
linkUuid: linkID,
publicLinkRequest: RestPublicLinkRequest(
link: currentLink,
passwordEnabled: currentLink.passwordRequired
),
apiConfiguration: makeConfiguration()
)

return WireCellsPublicLink(uuid: id, url: url)
return try WireCellsPublicLink(updatedLink, serverURL: serverURLResolver())
}

func deletePublicLink(uuid: UUID) async throws {
_ = try await NodeServiceAPI.deletePublicLink(
linkUuid: uuid.transportString(),
func updatePublicLinkPassword(
linkID: String,
password: String?
) async throws -> WireCellsPublicLink {
var currentLink = try await NodeServiceAPI.getPublicLink(
linkUuid: linkID,
apiConfiguration: makeConfiguration()
)

let hasExistingPassword = currentLink.passwordRequired == true
currentLink.passwordRequired = password != nil

let updatedLink = try await NodeServiceAPI.updatePublicLink(
linkUuid: linkID,
publicLinkRequest: RestPublicLinkRequest(
createPassword: !hasExistingPassword ? password : nil,
link: currentLink,
passwordEnabled: password != nil,
updatePassword: hasExistingPassword ? password : nil,
),
apiConfiguration: makeConfiguration()
)

return try WireCellsPublicLink(updatedLink, serverURL: serverURLResolver())
}

func updateTags(uuid: UUID, tags: [String]) async throws {
Expand All @@ -349,9 +371,15 @@ final class RestAPI: Sendable {
return response.values ?? []
}

private var apiURL: URL {
get throws {
try serverURLResolver().appendingPathComponent("/v2")
}
}

private func makeConfiguration() async throws -> CellsSDKAPIConfiguration {
let config = CellsSDKAPIConfiguration()
config.basePath = try serverURLResolver().appendingPathComponent("/v2").absoluteString
config.basePath = try apiURL.absoluteString
config.customHeaders = ["Authorization": "Bearer \(try await accessTokenProvider.accessToken().token)"]
config.interceptor = LoggingIntercepter()

Expand Down Expand Up @@ -513,4 +541,22 @@ private extension RestPreSignedURL {
}
return (url: url, date: Date(timeIntervalSinceNow: expiresAtTimeInterval))
}

}

private extension WireCellsPublicLink {

init(_ value: RestShareLink, serverURL: URL) throws {
guard let linkID = value.uuid, let url = value.linkUrl else {
throw WireCellsNodesAPIError.missingData("Missing link ID or URL")
}

self.init(
linkID: linkID,
url: serverURL.appendingPathComponent(url),
requiresPassword: value.passwordRequired ?? false,
expirationDate: value.accessEnd.flatMap { TimeInterval($0) }.map { Date(timeIntervalSince1970: $0) }
Comment thread
WilhelmOks marked this conversation as resolved.
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
public import Foundation

public struct WireCellsPublicLink: Equatable, Hashable, Sendable {
public let uuid: UUID
public let linkID: String
public let url: URL
public let requiresPassword: Bool
public let expirationDate: Date?

package init(uuid: UUID, url: URL) {
self.uuid = uuid
package init(linkID: String, url: URL, requiresPassword: Bool, expirationDate: Date?) {
self.linkID = linkID
self.url = url
self.requiresPassword = requiresPassword
self.expirationDate = expirationDate
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,22 @@ package protocol NodesAPIProtocol: Sendable {

func deleteNodes(nodeIDs: [UUID], permanently: Bool) async throws -> Bool

func createPublicLink(nodeID: UUID, fileName: String) async throws -> WireCellsPublicLink
func createPublicLink(nodeID: UUID, label: String) async throws
-> WireCellsPublicLink

func getPublicLink(linkUUID: UUID) async throws -> URL
func getPublicLink(linkID: String) async throws -> WireCellsPublicLink

func deletePublicLink(linkUUID: UUID) async throws
func deletePublicLink(linkID: String) async throws

func updatePublicLinkExpiration(
linkID: String,
expiration: Date?
) async throws -> WireCellsPublicLink

func updatePublicLinkPassword(
linkID: String,
password: String?
) async throws -> WireCellsPublicLink

func updateTags(nodeID: UUID, tags: [String]) async throws

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Wire
// Copyright (C) 2026 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

public protocol WireCellsGetPublicLinkDataUseCaseProtocol: Sendable {
func invoke(linkID: String) async throws -> WireCellsPublicLink
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Wire
// Copyright (C) 2026 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import WireFoundation

extension Keychain {
static let wireDriveSharedLinkPasswordService = "wire-drive-shared-link-password"
}
Loading
Loading