Skip to content
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
5 changes: 4 additions & 1 deletion Source/ARTLocalDevice.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

NSString *const ARTAPNSDeviceDefaultTokenType = @"default";
NSString *const ARTAPNSDeviceLocationTokenType = @"location";
NSString *const ARTAPNSDevicePushToStartTokenType = @"pushToStart";

NSString* ARTAPNSDeviceTokenKeyOfType(NSString *tokenType) {
return [ARTAPNSDeviceTokenKey stringByAppendingFormat:@"-%@", tokenType ?: ARTAPNSDeviceDefaultTokenType];
Expand Down Expand Up @@ -106,7 +107,8 @@ + (instancetype)deviceWithStorage:(id<ARTDeviceStorage>)storage logger:(nullable

NSArray *supportedTokenTypes = @[
ARTAPNSDeviceDefaultTokenType,
ARTAPNSDeviceLocationTokenType
ARTAPNSDeviceLocationTokenType,
ARTAPNSDevicePushToStartTokenType
];

for (NSString *tokenType in supportedTokenTypes) {
Expand Down Expand Up @@ -139,6 +141,7 @@ - (void)resetDetails {
_identityTokenDetails = nil;
[self setAPNSDeviceToken:nil tokenType:ARTAPNSDeviceDefaultTokenType];
[self setAPNSDeviceToken:nil tokenType:ARTAPNSDeviceLocationTokenType];
[self setAPNSDeviceToken:nil tokenType:ARTAPNSDevicePushToStartTokenType];
}];
}

Expand Down
8 changes: 8 additions & 0 deletions Source/ARTPush.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ + (void)didFailToRegisterForLocationNotificationsWithError:(NSError *)error real
return [ARTPushInternal didFailToRegisterForLocationNotificationsWithError:error realtime:realtime];
}

- (void)registerPushToStartToken:(NSData *)token {
[_internal registerPushToStartToken:token];
}

- (void)activate {
[_internal activate];
}
Expand Down Expand Up @@ -253,6 +257,10 @@ + (void)didFailToRegisterForLocationNotificationsWithError:(NSError *)error rest
}];
}

- (void)registerPushToStartToken:(NSData *)token {
[_rest setAndPersistAPNSDeviceTokenData:token tokenType:ARTAPNSDevicePushToStartTokenType];
}

- (void)activate {
[self getActivationMachine:^(ARTPushActivationStateMachine *_Nullable stateMachine) {
[stateMachine sendEvent:[ARTPushActivationEventCalledActivate new]];
Expand Down
4 changes: 4 additions & 0 deletions Source/ARTWrapperSDKProxyPush.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ - (instancetype)initWithPush:(ARTPush *)push proxyOptions:(ARTWrapperSDKProxyOpt

#if TARGET_OS_IOS

- (void)registerPushToStartToken:(nonnull NSData *)token {
[self.underlyingPush registerPushToStartToken:token];
}

- (void)activate {
[self.underlyingPush activate];
}
Expand Down
1 change: 1 addition & 0 deletions Source/PrivateHeaders/Ably/ARTLocalDevice+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extern NSString *const ARTClientIdKey;

extern NSString *const ARTAPNSDeviceDefaultTokenType;
extern NSString *const ARTAPNSDeviceLocationTokenType;
extern NSString *const ARTAPNSDevicePushToStartTokenType;

NSString* ARTAPNSDeviceTokenKeyOfType(NSString * _Nullable tokenType);

Expand Down
2 changes: 2 additions & 0 deletions Source/PrivateHeaders/Ably/ARTPush+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ NS_ASSUME_NONNULL_BEGIN

+ (void)didFailToRegisterForLocationNotificationsWithError:(NSError *)error realtime:(ARTRealtime *)realtime;

- (void)registerPushToStartToken:(NSData *)token;

- (void)activate;

- (void)deactivate;
Expand Down
10 changes: 10 additions & 0 deletions Source/include/Ably/ARTPush.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ NS_ASSUME_NONNULL_BEGIN
/// Call this method if you got an error calling `CLLocationManager.startMonitoringLocationPushes(completion:)`.
+ (void)didFailToRegisterForLocationNotificationsWithError:(NSError *)error realtime:(ARTRealtime *)realtime;

// Live Activity push-to-start token

/**
* Registers a Live Activity push-to-start token (obtained from `Activity.pushToStartTokenUpdates`) with Ably, adding it to the existing device registration. Use this when the device has already been activated (i.e. `-activate` has completed). The token is stored in `ARTLocalDevice`'s push recipient and synced to Ably.
Comment thread
ttypic marked this conversation as resolved.
* You should implement `-[ARTPushRegistererDelegate didUpdateAblyPush:]` to handle success or failure of this operation.
*
* @param token The push-to-start token data, as delivered by `Activity.pushToStartTokenUpdates`.
*/
- (void)registerPushToStartToken:(NSData *)token NS_SWIFT_NAME(registerPushToStartToken(_:));

/**
* Activates the device for push notifications with APNS, obtaining a unique identifier from it. Subsequently registers the device with Ably and stores the `ARTLocalDevice.identityTokenDetails` in local storage.
* You should implement `-[ARTPushRegistererDelegate didActivateAblyPush:]` to handle success or failure of this operation.
Expand Down
32 changes: 32 additions & 0 deletions Test/AblyTests/Tests/PushActivationStateMachineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,38 @@ class PushActivationStateMachineTests: XCTestCase {
try reusableTestsWrapper__Activation_state_machine__State_WaitingForNewPushDeviceDetails__on_Event_CalledDeactivate__reusableTestsRsh3d2(testCase: .should_fire_DeregistrationFailed_event)
}

// A push-to-start token added to an already-activated device (as done by
// -[ARTPush registerPushToStartToken:]) is synced to Ably via a PATCH whose
// recipient carries the token under the "pushToStart" apnsDeviceTokens key.
func test__100__Activation_state_machine__State_WaitingForNewPushDeviceDetails__on_Event_GotPushDeviceDetails__with_push_to_start_token__PATCHes_recipient() throws {
beforeEach__Activation_state_machine__State_WaitingForNewPushDeviceDetails()

let expectedToken = "70757368-746f-2d73-7461-7274"
stateMachine.rest.device.setAndPersistAPNSDeviceToken(expectedToken, tokenType: ARTAPNSDevicePushToStartTokenType)
defer { stateMachine.rest.device.setAndPersistAPNSDeviceToken(nil, tokenType: ARTAPNSDevicePushToStartTokenType) }

waitUntil(timeout: testTimeout) { done in
stateMachine.transitions = { event, _, _ in
if event is ARTPushActivationEventRegistrationSynced {
stateMachine.transitions = nil
done()
}
}
stateMachine.send(ARTPushActivationEventGotPushDeviceDetails())
}

let request = try XCTUnwrap(httpExecutor.requests.first { req in
req.httpMethod == "PATCH" && req.url?.path.hasPrefix("/push/deviceRegistrations/") == true
}, "Should have a PATCH \"/push/deviceRegistrations/{id}\" request")
let rawBody = try XCTUnwrap(request.httpBody, "Request should have a body")
let decodedBody = try XCTUnwrap(try stateMachine.rest.defaultEncoder.decode(rawBody), "Decode request body failed")
let body = try XCTUnwrap(decodedBody as? NSDictionary, "Request body is invalid")
let push = try XCTUnwrap(body.value(forKey: "push") as? [String: Any], "Body should have a push")
let recipient = try XCTUnwrap(push["recipient"] as? [String: Any], "push should have a recipient")
let apnsDeviceTokens = try XCTUnwrap(recipient["apnsDeviceTokens"] as? [String: String], "recipient should have apnsDeviceTokens")
XCTAssertEqual(apnsDeviceTokens["pushToStart"], expectedToken)
}

enum TestCase_ReusableTestsTestStateWaitingForRegistrationSyncThrough {
case on_Event_CalledActivate
case on_Event_RegistrationSynced
Expand Down
26 changes: 26 additions & 0 deletions Test/AblyTests/Tests/PushTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class PushTests: XCTestCase {
static let tokenString = tokenData.map { String(format: "%02x", $0) }.joined()
}

enum TestPushToStartToken {
static let tokenBase64 = "cHVzaC10by1zdGFydCB0b2tlbg=="
static let tokenData = Data(base64Encoded: tokenBase64, options: [])!
static let tokenString = tokenData.map { String(format: "%02x", $0) }.joined()
}

// XCTest invokes this method before executing the first test in the test suite. We use it to ensure that the global variables are initialized at the same moment, and in the same order, as they would have been when we used the Quick testing framework.
override class var defaultTestSuite: XCTestSuite {
_ = rest
Expand Down Expand Up @@ -223,6 +229,26 @@ class PushTests: XCTestCase {
})
}

// Live Activity push-to-start token

// registerPushToStartToken stores the token under the push-to-start token type and
// sends a GotPushDeviceDetails event to the state machine (which, on an already-activated
// device, syncs the recipient to Ably via a PATCH).
func test__100__push_to_start__registerPushToStartToken_stores_token_and_sends_GotPushDeviceDetails() {
defer { rest.push.internal.activationMachine.onEvent = nil }
waitUntil(timeout: testTimeout) { done in
rest.push.internal.activationMachine.onEvent = { event, _ in
if event is ARTPushActivationEventGotPushDeviceDetails {
done()
}
}
rest.push.registerPushToStartToken(TestPushToStartToken.tokenData)
}
let expectedTokenKey = "ARTAPNSDeviceToken-pushToStart" // ARTAPNSDeviceTokenKeyOfType("pushToStart")
expect(storage.keysWritten.keys).to(contain([expectedTokenKey]))
XCTAssertEqual(storage.keysWritten.at(expectedTokenKey)?.value as? String, TestPushToStartToken.tokenString)
}

// RSH8
func test__008__LocalDevice__has_a_device_method_that_returns_a_LocalDevice() {
let _: ARTLocalDevice = ARTRest(key: "fake:key").device
Expand Down
Loading