Skip to content

Commit 8d10b22

Browse files
committed
feat: implement avatar fetching for notifications
- Added a method to fetch user avatars from the server and attach them to notifications in NotificationService. - Introduced a userAgent property in Bundle for consistent User-Agent string usage across API requests.
1 parent 81166aa commit 8d10b22

File tree

3 files changed

+80
-13
lines changed

3 files changed

+80
-13
lines changed

ios/NotificationService/NotificationService.swift

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,65 @@ class NotificationService: UNNotificationServiceExtension {
66
var bestAttemptContent: UNMutableNotificationContent?
77
var rocketchat: RocketChat?
88

9+
// MARK: - Avatar Fetching
10+
11+
func fetchAvatar(from payload: Payload, completion: @escaping (UNNotificationAttachment?) -> Void) {
12+
guard let username = payload.sender?.username else {
13+
completion(nil)
14+
return
15+
}
16+
17+
let server = payload.host.removeTrailingSlash()
18+
guard let credentials = Storage().getCredentials(server: server) else {
19+
completion(nil)
20+
return
21+
}
22+
23+
// Build authenticated avatar URL (URL encode username for special characters)
24+
guard let encodedUsername = username.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
25+
completion(nil)
26+
return
27+
}
28+
let avatarPath = "/avatar/\(encodedUsername)?format=png&size=100&rc_token=\(credentials.userToken)&rc_uid=\(credentials.userId)"
29+
guard let avatarURL = URL(string: server + avatarPath) else {
30+
completion(nil)
31+
return
32+
}
33+
34+
// Create request with 3-second timeout
35+
var request = URLRequest(url: avatarURL, timeoutInterval: 3)
36+
request.httpMethod = "GET"
37+
request.addValue(Bundle.userAgent, forHTTPHeaderField: "User-Agent")
38+
39+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
40+
guard error == nil,
41+
let data = data,
42+
let httpResponse = response as? HTTPURLResponse,
43+
httpResponse.statusCode == 200 else {
44+
completion(nil)
45+
return
46+
}
47+
48+
// Save to temp file (UNNotificationAttachment requires file URL)
49+
let tempDir = FileManager.default.temporaryDirectory
50+
let fileName = "\(username)_avatar.png"
51+
let fileURL = tempDir.appendingPathComponent(fileName)
52+
53+
do {
54+
try data.write(to: fileURL)
55+
let attachment = try UNNotificationAttachment(
56+
identifier: "avatar",
57+
url: fileURL,
58+
options: [UNNotificationAttachmentOptionsTypeHintKey: "public.png"]
59+
)
60+
completion(attachment)
61+
} catch {
62+
completion(nil)
63+
}
64+
}
65+
task.resume()
66+
}
67+
968
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
1069
self.contentHandler = contentHandler
1170
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
@@ -126,8 +185,17 @@ class NotificationService: UNNotificationServiceExtension {
126185
}
127186
}
128187

129-
if let bestAttemptContent = bestAttemptContent {
130-
contentHandler?(bestAttemptContent)
188+
// Fetch avatar and deliver notification
189+
fetchAvatar(from: payload) { [weak self] attachment in
190+
guard let self = self else { return }
191+
192+
if let attachment = attachment {
193+
self.bestAttemptContent?.attachments = [attachment]
194+
}
195+
196+
if let bestAttemptContent = self.bestAttemptContent {
197+
self.contentHandler?(bestAttemptContent)
198+
}
131199
}
132200
}
133201
}

ios/Shared/Extensions/Bundle+Extensions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,13 @@ extension Bundle {
1212

1313
return string
1414
}
15+
16+
/// Returns User-Agent string for API requests: "RC Mobile; ios {version}; v{appVersion} ({build})"
17+
static var userAgent: String {
18+
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
19+
let systemVersion = "\(osVersion.majorVersion).\(osVersion.minorVersion)"
20+
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
21+
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "unknown"
22+
return "RC Mobile; ios \(systemVersion); v\(appVersion) (\(buildNumber))"
23+
}
1524
}

ios/Shared/RocketChat/API/Request.swift

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ extension Request {
5555
request.httpMethod = method.rawValue
5656
request.httpBody = body()
5757
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
58-
request.addValue(userAgent, forHTTPHeaderField: "User-Agent")
58+
request.addValue(Bundle.userAgent, forHTTPHeaderField: "User-Agent")
5959

6060
if let userId = api.credentials?.userId {
6161
request.addValue(userId, forHTTPHeaderField: "x-user-id")
@@ -70,14 +70,4 @@ extension Request {
7070

7171
return request
7272
}
73-
74-
private var userAgent: String {
75-
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
76-
let systemVersion = "\(osVersion.majorVersion).\(osVersion.minorVersion)"
77-
78-
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
79-
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "unknown"
80-
81-
return "RC Mobile; ios \(systemVersion); v\(appVersion) (\(buildNumber))"
82-
}
8373
}

0 commit comments

Comments
 (0)