mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-25 09:41:02 +00:00
Cleanup + Polish push notifications
This commit is contained in:
parent
a68efaef56
commit
ed8208857a
10 changed files with 127 additions and 103 deletions
|
@ -19,6 +19,7 @@
|
||||||
9F2A5424296AB67A009B2D7C /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A5423296AB67A009B2D7C /* Env */; };
|
9F2A5424296AB67A009B2D7C /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A5423296AB67A009B2D7C /* Env */; };
|
||||||
9F2A5426296AB67E009B2D7C /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A5425296AB67E009B2D7C /* KeychainSwift */; };
|
9F2A5426296AB67E009B2D7C /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A5425296AB67E009B2D7C /* KeychainSwift */; };
|
||||||
9F2A5428296AB683009B2D7C /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A5427296AB683009B2D7C /* Models */; };
|
9F2A5428296AB683009B2D7C /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F2A5427296AB683009B2D7C /* Models */; };
|
||||||
|
9F2A542A296AF557009B2D7C /* NotificationServiceSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2A5429296AF557009B2D7C /* NotificationServiceSupport.swift */; };
|
||||||
9F2B92F6295AE04800DE16D0 /* Tabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2B92F5295AE04800DE16D0 /* Tabs.swift */; };
|
9F2B92F6295AE04800DE16D0 /* Tabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2B92F5295AE04800DE16D0 /* Tabs.swift */; };
|
||||||
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2B92F9295DA7D700DE16D0 /* AddAccountsView.swift */; };
|
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2B92F9295DA7D700DE16D0 /* AddAccountsView.swift */; };
|
||||||
9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2B92FB295DA94500DE16D0 /* InstanceInfoView.swift */; };
|
9F2B92FC295DA94500DE16D0 /* InstanceInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2B92FB295DA94500DE16D0 /* InstanceInfoView.swift */; };
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
9F2A5418296AB631009B2D7C /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
9F2A5418296AB631009B2D7C /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||||
9F2A541A296AB631009B2D7C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
9F2A541A296AB631009B2D7C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
9F2A5422296AB64B009B2D7C /* IceCubesNotifications.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesNotifications.entitlements; sourceTree = "<group>"; };
|
9F2A5422296AB64B009B2D7C /* IceCubesNotifications.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesNotifications.entitlements; sourceTree = "<group>"; };
|
||||||
|
9F2A5429296AF557009B2D7C /* NotificationServiceSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceSupport.swift; sourceTree = "<group>"; };
|
||||||
9F2B92F5295AE04800DE16D0 /* Tabs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tabs.swift; sourceTree = "<group>"; };
|
9F2B92F5295AE04800DE16D0 /* Tabs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tabs.swift; sourceTree = "<group>"; };
|
||||||
9F2B92F9295DA7D700DE16D0 /* AddAccountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountsView.swift; sourceTree = "<group>"; };
|
9F2B92F9295DA7D700DE16D0 /* AddAccountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountsView.swift; sourceTree = "<group>"; };
|
||||||
9F2B92FB295DA94500DE16D0 /* InstanceInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceInfoView.swift; sourceTree = "<group>"; };
|
9F2B92FB295DA94500DE16D0 /* InstanceInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceInfoView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -165,6 +167,7 @@
|
||||||
children = (
|
children = (
|
||||||
9F2A5422296AB64B009B2D7C /* IceCubesNotifications.entitlements */,
|
9F2A5422296AB64B009B2D7C /* IceCubesNotifications.entitlements */,
|
||||||
9F2A5418296AB631009B2D7C /* NotificationService.swift */,
|
9F2A5418296AB631009B2D7C /* NotificationService.swift */,
|
||||||
|
9F2A5429296AF557009B2D7C /* NotificationServiceSupport.swift */,
|
||||||
9F2A541A296AB631009B2D7C /* Info.plist */,
|
9F2A541A296AB631009B2D7C /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = IceCubesNotifications;
|
path = IceCubesNotifications;
|
||||||
|
@ -414,6 +417,7 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
9F2A542A296AF557009B2D7C /* NotificationServiceSupport.swift in Sources */,
|
||||||
9F2A5419296AB631009B2D7C /* NotificationService.swift in Sources */,
|
9F2A5419296AB631009B2D7C /* NotificationService.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -15,7 +15,7 @@ class AppAccountsManager: ObservableObject {
|
||||||
@Published var availableAccounts: [AppAccount]
|
@Published var availableAccounts: [AppAccount]
|
||||||
@Published var currentClient: Client
|
@Published var currentClient: Client
|
||||||
|
|
||||||
var pushAccounts: [PushNotifications.PushAccounts] {
|
var pushAccounts: [PushNotificationsService.PushAccounts] {
|
||||||
availableAccounts.filter{ $0.oauthToken != nil}
|
availableAccounts.filter{ $0.oauthToken != nil}
|
||||||
.map{ .init(server: $0.server, token: $0.oauthToken!) }
|
.map{ .init(server: $0.server, token: $0.oauthToken!) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ struct IceCubesApp: App {
|
||||||
.environmentObject(userPreferences)
|
.environmentObject(userPreferences)
|
||||||
.environmentObject(theme)
|
.environmentObject(theme)
|
||||||
.environmentObject(watcher)
|
.environmentObject(watcher)
|
||||||
.environmentObject(PushNotifications.shared)
|
.environmentObject(PushNotificationsService.shared)
|
||||||
.quickLookPreview($quickLook.url, in: quickLook.urls)
|
.quickLookPreview($quickLook.url, in: quickLook.urls)
|
||||||
}
|
}
|
||||||
.onChange(of: scenePhase, perform: { scenePhase in
|
.onChange(of: scenePhase, perform: { scenePhase in
|
||||||
|
@ -154,7 +154,7 @@ struct IceCubesApp: App {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshPushSubs() {
|
private func refreshPushSubs() {
|
||||||
PushNotifications.shared.requestPushNotifications()
|
PushNotificationsService.shared.requestPushNotifications()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,10 +166,10 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
|
|
||||||
func application(_ application: UIApplication,
|
func application(_ application: UIApplication,
|
||||||
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||||
PushNotifications.shared.pushToken = deviceToken
|
PushNotificationsService.shared.pushToken = deviceToken
|
||||||
Task {
|
Task {
|
||||||
await PushNotifications.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts)
|
await PushNotificationsService.shared.fetchSubscriptions(accounts: AppAccountsManager.shared.pushAccounts)
|
||||||
await PushNotifications.shared.updateSubscriptions(accounts: AppAccountsManager.shared.pushAccounts)
|
await PushNotificationsService.shared.updateSubscriptions(accounts: AppAccountsManager.shared.pushAccounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ struct AddAccountView: View {
|
||||||
@EnvironmentObject private var appAccountsManager: AppAccountsManager
|
@EnvironmentObject private var appAccountsManager: AppAccountsManager
|
||||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||||
@EnvironmentObject private var currentInstance: CurrentInstance
|
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||||
@EnvironmentObject private var pushNotifications: PushNotifications
|
@EnvironmentObject private var pushNotifications: PushNotificationsService
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
@State private var instanceName: String = ""
|
@State private var instanceName: String = ""
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Env
|
||||||
struct PushNotificationsView: View {
|
struct PushNotificationsView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
@EnvironmentObject private var appAccountsManager: AppAccountsManager
|
@EnvironmentObject private var appAccountsManager: AppAccountsManager
|
||||||
@EnvironmentObject private var pushNotifications: PushNotifications
|
@EnvironmentObject private var pushNotifications: PushNotificationsService
|
||||||
|
|
||||||
@State private var subscriptions: [PushSubscription] = []
|
@State private var subscriptions: [PushSubscription] = []
|
||||||
|
|
||||||
|
@ -19,25 +19,30 @@ struct PushNotificationsView: View {
|
||||||
Toggle(isOn: $pushNotifications.isPushEnabled) {
|
Toggle(isOn: $pushNotifications.isPushEnabled) {
|
||||||
Text("Push notification")
|
Text("Push notification")
|
||||||
}
|
}
|
||||||
|
} footer: {
|
||||||
|
Text("Receive push notifications on new activities")
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
if pushNotifications.isPushEnabled {
|
if pushNotifications.isPushEnabled {
|
||||||
Section {
|
Section {
|
||||||
|
Toggle(isOn: $pushNotifications.isMentionNotificationEnabled) {
|
||||||
|
Label("Mentions", systemImage: "at")
|
||||||
|
}
|
||||||
Toggle(isOn: $pushNotifications.isFollowNotificationEnabled) {
|
Toggle(isOn: $pushNotifications.isFollowNotificationEnabled) {
|
||||||
Text("Follow notification")
|
Label("Follows", systemImage: "person.badge.plus")
|
||||||
}
|
}
|
||||||
Toggle(isOn: $pushNotifications.isFavoriteNotificationEnabled) {
|
Toggle(isOn: $pushNotifications.isFavoriteNotificationEnabled) {
|
||||||
Text("Favorite notification")
|
Label("Favorites", systemImage: "star")
|
||||||
}
|
}
|
||||||
Toggle(isOn: $pushNotifications.isReblogNotificationEnabled) {
|
Toggle(isOn: $pushNotifications.isReblogNotificationEnabled) {
|
||||||
Text("Boost notification")
|
Label("Boosts", systemImage: "arrow.left.arrow.right.circle")
|
||||||
}
|
|
||||||
Toggle(isOn: $pushNotifications.isMentionNotificationEnabled) {
|
|
||||||
Text("Mention notification")
|
|
||||||
}
|
}
|
||||||
Toggle(isOn: $pushNotifications.isPollNotificationEnabled) {
|
Toggle(isOn: $pushNotifications.isPollNotificationEnabled) {
|
||||||
Text("Polls notification")
|
Label("Polls Results", systemImage: "chart.bar")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $pushNotifications.isNewPostsNotificationEnabled) {
|
||||||
|
Label("New Posts", systemImage: "bubble.right")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
@ -77,6 +82,9 @@ struct PushNotificationsView: View {
|
||||||
.onChange(of: pushNotifications.isFavoriteNotificationEnabled) { _ in
|
.onChange(of: pushNotifications.isFavoriteNotificationEnabled) { _ in
|
||||||
updateSubscriptions()
|
updateSubscriptions()
|
||||||
}
|
}
|
||||||
|
.onChange(of: pushNotifications.isNewPostsNotificationEnabled) { _ in
|
||||||
|
updateSubscriptions()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateSubscriptions() {
|
private func updateSubscriptions() {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Models
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
|
||||||
struct SettingsTabs: View {
|
struct SettingsTabs: View {
|
||||||
|
@EnvironmentObject private var pushNotifications: PushNotificationsService
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
@EnvironmentObject private var client: Client
|
@EnvironmentObject private var client: Client
|
||||||
@EnvironmentObject private var currentInstance: CurrentInstance
|
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||||
|
@ -57,6 +58,11 @@ struct SettingsTabs: View {
|
||||||
.onDelete { indexSet in
|
.onDelete { indexSet in
|
||||||
if let index = indexSet.first {
|
if let index = indexSet.first {
|
||||||
let account = appAccountsManager.availableAccounts[index]
|
let account = appAccountsManager.availableAccounts[index]
|
||||||
|
if let token = account.oauthToken {
|
||||||
|
Task {
|
||||||
|
await pushNotifications.deleteSubscriptions(accounts: [.init(server: account.server, token: token)])
|
||||||
|
}
|
||||||
|
}
|
||||||
appAccountsManager.delete(account: account)
|
appAccountsManager.delete(account: account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,10 @@ class NotificationService: UNNotificationServiceExtension {
|
||||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
self.contentHandler = contentHandler
|
self.contentHandler = contentHandler
|
||||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||||
bestAttemptContent?.title = "A new notification have been received"
|
|
||||||
|
|
||||||
if let bestAttemptContent {
|
if let bestAttemptContent {
|
||||||
let privateKey = PushNotifications.shared.notificationsPrivateKeyAsKey
|
let privateKey = PushNotificationsService.shared.notificationsPrivateKeyAsKey
|
||||||
let auth = PushNotifications.shared.notificationsAuthKeyAsKey
|
let auth = PushNotificationsService.shared.notificationsAuthKeyAsKey
|
||||||
|
|
||||||
guard let encodedPayload = bestAttemptContent.userInfo["m"] as? String,
|
guard let encodedPayload = bestAttemptContent.userInfo["m"] as? String,
|
||||||
let payload = Data(base64Encoded: encodedPayload.URLSafeBase64ToBase64()) else {
|
let payload = Data(base64Encoded: encodedPayload.URLSafeBase64ToBase64()) else {
|
||||||
|
@ -80,86 +79,4 @@ class NotificationService: UNNotificationServiceExtension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func decrypt(payload: Data, salt: Data, auth: Data, privateKey: P256.KeyAgreement.PrivateKey, publicKey: P256.KeyAgreement.PublicKey) -> Data? {
|
|
||||||
guard let sharedSecret = try? privateKey.sharedSecretFromKeyAgreement(with: publicKey) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let keyMaterial = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: auth, sharedInfo: Data("Content-Encoding: auth\0".utf8), outputByteCount: 32)
|
|
||||||
|
|
||||||
let keyInfo = info(type: "aesgcm", clientPublicKey: privateKey.publicKey.x963Representation, serverPublicKey: publicKey.x963Representation)
|
|
||||||
let key = HKDF<SHA256>.deriveKey(inputKeyMaterial: keyMaterial, salt: salt, info: keyInfo, outputByteCount: 16)
|
|
||||||
|
|
||||||
let nonceInfo = info(type: "nonce", clientPublicKey: privateKey.publicKey.x963Representation, serverPublicKey: publicKey.x963Representation)
|
|
||||||
let nonce = HKDF<SHA256>.deriveKey(inputKeyMaterial: keyMaterial, salt: salt, info: nonceInfo, outputByteCount: 12)
|
|
||||||
|
|
||||||
let nonceData = nonce.withUnsafeBytes(Array.init)
|
|
||||||
|
|
||||||
guard let sealedBox = try? AES.GCM.SealedBox(combined: nonceData + payload) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _plaintext: Data?
|
|
||||||
do {
|
|
||||||
_plaintext = try AES.GCM.open(sealedBox, using: key)
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
guard let plaintext = _plaintext else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let paddingLength = Int(plaintext[0]) * 256 + Int(plaintext[1])
|
|
||||||
guard plaintext.count >= 2 + paddingLength else {
|
|
||||||
print("1")
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
let unpadded = plaintext.suffix(from: paddingLength + 2)
|
|
||||||
|
|
||||||
return Data(unpadded)
|
|
||||||
}
|
|
||||||
|
|
||||||
static private func info(type: String, clientPublicKey: Data, serverPublicKey: Data) -> Data {
|
|
||||||
var info = Data()
|
|
||||||
|
|
||||||
info.append("Content-Encoding: ".data(using: .utf8)!)
|
|
||||||
info.append(type.data(using: .utf8)!)
|
|
||||||
info.append(0)
|
|
||||||
info.append("P-256".data(using: .utf8)!)
|
|
||||||
info.append(0)
|
|
||||||
info.append(0)
|
|
||||||
info.append(65)
|
|
||||||
info.append(clientPublicKey)
|
|
||||||
info.append(0)
|
|
||||||
info.append(65)
|
|
||||||
info.append(serverPublicKey)
|
|
||||||
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension String {
|
|
||||||
func escape() -> String {
|
|
||||||
return self
|
|
||||||
.replacingOccurrences(of: "&", with: "&")
|
|
||||||
.replacingOccurrences(of: "<", with: "<")
|
|
||||||
.replacingOccurrences(of: ">", with: ">")
|
|
||||||
.replacingOccurrences(of: """, with: "\"")
|
|
||||||
.replacingOccurrences(of: "'", with: "'")
|
|
||||||
.replacingOccurrences(of: "'", with: "’")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func URLSafeBase64ToBase64() -> String {
|
|
||||||
var base64 = replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
|
|
||||||
let countMod4 = count % 4
|
|
||||||
|
|
||||||
if countMod4 != 0 {
|
|
||||||
base64.append(String(repeating: "=", count: 4 - countMod4))
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
86
IceCubesNotifications/NotificationServiceSupport.swift
Normal file
86
IceCubesNotifications/NotificationServiceSupport.swift
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
extension NotificationService {
|
||||||
|
static func decrypt(payload: Data, salt: Data, auth: Data, privateKey: P256.KeyAgreement.PrivateKey, publicKey: P256.KeyAgreement.PublicKey) -> Data? {
|
||||||
|
guard let sharedSecret = try? privateKey.sharedSecretFromKeyAgreement(with: publicKey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyMaterial = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: auth, sharedInfo: Data("Content-Encoding: auth\0".utf8), outputByteCount: 32)
|
||||||
|
|
||||||
|
let keyInfo = info(type: "aesgcm", clientPublicKey: privateKey.publicKey.x963Representation, serverPublicKey: publicKey.x963Representation)
|
||||||
|
let key = HKDF<SHA256>.deriveKey(inputKeyMaterial: keyMaterial, salt: salt, info: keyInfo, outputByteCount: 16)
|
||||||
|
|
||||||
|
let nonceInfo = info(type: "nonce", clientPublicKey: privateKey.publicKey.x963Representation, serverPublicKey: publicKey.x963Representation)
|
||||||
|
let nonce = HKDF<SHA256>.deriveKey(inputKeyMaterial: keyMaterial, salt: salt, info: nonceInfo, outputByteCount: 12)
|
||||||
|
|
||||||
|
let nonceData = nonce.withUnsafeBytes(Array.init)
|
||||||
|
|
||||||
|
guard let sealedBox = try? AES.GCM.SealedBox(combined: nonceData + payload) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _plaintext: Data?
|
||||||
|
do {
|
||||||
|
_plaintext = try AES.GCM.open(sealedBox, using: key)
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
guard let plaintext = _plaintext else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let paddingLength = Int(plaintext[0]) * 256 + Int(plaintext[1])
|
||||||
|
guard plaintext.count >= 2 + paddingLength else {
|
||||||
|
print("1")
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
let unpadded = plaintext.suffix(from: paddingLength + 2)
|
||||||
|
|
||||||
|
return Data(unpadded)
|
||||||
|
}
|
||||||
|
|
||||||
|
static private func info(type: String, clientPublicKey: Data, serverPublicKey: Data) -> Data {
|
||||||
|
var info = Data()
|
||||||
|
|
||||||
|
info.append("Content-Encoding: ".data(using: .utf8)!)
|
||||||
|
info.append(type.data(using: .utf8)!)
|
||||||
|
info.append(0)
|
||||||
|
info.append("P-256".data(using: .utf8)!)
|
||||||
|
info.append(0)
|
||||||
|
info.append(0)
|
||||||
|
info.append(65)
|
||||||
|
info.append(clientPublicKey)
|
||||||
|
info.append(0)
|
||||||
|
info.append(65)
|
||||||
|
info.append(serverPublicKey)
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
func escape() -> String {
|
||||||
|
return self
|
||||||
|
.replacingOccurrences(of: "&", with: "&")
|
||||||
|
.replacingOccurrences(of: "<", with: "<")
|
||||||
|
.replacingOccurrences(of: ">", with: ">")
|
||||||
|
.replacingOccurrences(of: """, with: "\"")
|
||||||
|
.replacingOccurrences(of: "'", with: "'")
|
||||||
|
.replacingOccurrences(of: "'", with: "’")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func URLSafeBase64ToBase64() -> String {
|
||||||
|
var base64 = replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
|
||||||
|
let countMod4 = count % 4
|
||||||
|
|
||||||
|
if countMod4 != 0 {
|
||||||
|
base64.append(String(repeating: "=", count: 4 - countMod4))
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Models
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class PushNotifications: ObservableObject {
|
public class PushNotificationsService: ObservableObject {
|
||||||
enum Constants {
|
enum Constants {
|
||||||
static let endpoint = "https://icecubesrelay.fly.dev"
|
static let endpoint = "https://icecubesrelay.fly.dev"
|
||||||
static let keychainGroup = "346J38YKE3.com.thomasricouard.IceCubesApp"
|
static let keychainGroup = "346J38YKE3.com.thomasricouard.IceCubesApp"
|
||||||
|
@ -25,7 +25,7 @@ public class PushNotifications: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static let shared = PushNotifications()
|
public static let shared = PushNotificationsService()
|
||||||
|
|
||||||
@Published public var pushToken: Data?
|
@Published public var pushToken: Data?
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ public class PushNotifications: ObservableObject {
|
||||||
@Published public var isReblogNotificationEnabled: Bool = true
|
@Published public var isReblogNotificationEnabled: Bool = true
|
||||||
@Published public var isMentionNotificationEnabled: Bool = true
|
@Published public var isMentionNotificationEnabled: Bool = true
|
||||||
@Published public var isPollNotificationEnabled: Bool = true
|
@Published public var isPollNotificationEnabled: Bool = true
|
||||||
|
@Published public var isNewPostsNotificationEnabled: Bool = true
|
||||||
|
|
||||||
private var subscriptions: [PushSubscription] = []
|
private var subscriptions: [PushSubscription] = []
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ public class PushNotifications: ObservableObject {
|
||||||
p256dh: notificationsPrivateKeyAsKey.publicKey.x963Representation,
|
p256dh: notificationsPrivateKeyAsKey.publicKey.x963Representation,
|
||||||
auth: notificationsAuthKeyAsKey,
|
auth: notificationsAuthKeyAsKey,
|
||||||
mentions: isMentionNotificationEnabled,
|
mentions: isMentionNotificationEnabled,
|
||||||
status: true,
|
status: isNewPostsNotificationEnabled,
|
||||||
reblog: isReblogNotificationEnabled,
|
reblog: isReblogNotificationEnabled,
|
||||||
follow: isFollowNotificationEnabled,
|
follow: isFollowNotificationEnabled,
|
||||||
favourite: isFavoriteNotificationEnabled,
|
favourite: isFavoriteNotificationEnabled,
|
||||||
|
@ -119,6 +120,7 @@ public class PushNotifications: ObservableObject {
|
||||||
isReblogNotificationEnabled = sub.alerts.reblog
|
isReblogNotificationEnabled = sub.alerts.reblog
|
||||||
isMentionNotificationEnabled = sub.alerts.mention
|
isMentionNotificationEnabled = sub.alerts.mention
|
||||||
isPollNotificationEnabled = sub.alerts.poll
|
isPollNotificationEnabled = sub.alerts.poll
|
||||||
|
isNewPostsNotificationEnabled = sub.alerts.status
|
||||||
} else {
|
} else {
|
||||||
isPushEnabled = false
|
isPushEnabled = false
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ public struct PushSubscription: Identifiable, Decodable {
|
||||||
public let reblog: Bool
|
public let reblog: Bool
|
||||||
public let mention: Bool
|
public let mention: Bool
|
||||||
public let poll: Bool
|
public let poll: Bool
|
||||||
|
public let status: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
public let id: Int
|
public let id: Int
|
||||||
|
|
Loading…
Reference in a new issue