Skip to content

Commit

Permalink
Push Activation State Machine: add requirement for registration valid…
Browse files Browse the repository at this point in the history
…ation (#786)

* Push Activation State Machine: add requirement for registration validation

* Push LocalDevice: add requirements for clientId immutability
  • Loading branch information
paddybyers authored Feb 16, 2020
1 parent bc8b08f commit bd92372
Showing 1 changed file with 37 additions and 18 deletions.
55 changes: 37 additions & 18 deletions textile/features.textile
Original file line number Diff line number Diff line change
Expand Up @@ -953,25 +953,29 @@ h2(#push-notifications). Push notifications
* @(RSH2)@ The following should only apply to platforms that support receiving push notifications:
** @(RSH2a)@ @Push#activate@ sends a @CalledActivate@ event to "the state machine":#RSH3.
** @(RSH2b)@ @Push#deactivate@ sends a @CalledDeactivate@ event to "the state machine":#RSH3.
** @(RSH2c)@ Whenever any change arises of the push transport details for local device (eg an FCM registration token update triggered by the platform), a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3.
** @(RSH2d)@ If an attempt to obtain the push transport details for local device (eg an FCM registration token) fails, a @GettingPushDeviceDetailsFailed@ event containing the indicated error is sent to "the state machine":#RSH3.
** @(RSH2e)@ Each time the library is instanced, if the LocalDevice has push device details (eg an APNS deviceToken), it must verify the validity of those details (eg by requesting a token from the platform and comparing that with the already-known token). If as a result there are updated details, then an update to the Ably server is triggered by sending a @GotPushDeviceDetails@ event to "the state machine":#RSH3.
** @(RSH2c)@ (Moved to "@(RSH8g)@":#RSH8g ).
** @(RSH2d)@ (Moved to "@(RSH8h)@":#RSH8h ).
** @(RSH2e)@ (Moved to "@(RSH8i)@":#RSH8i ).

h3(#activation-state-machine). Activation state machine
h3(#activation-state-machine). Activation State Machine

* @(RSH3)@ In platforms that support receiving push notifications, in order to connect the device's push features with Ably's, the library must perform the process described in the following abstract state machine. While this process should be implemented in whatever way better fits the concrete platform, it should be taken into account that its lifetime is that of the _app_ that runs it, which outlives that of the @Rest@ instance or (typically) the process running the app. This typically forces some kind of on-disk storage to which the state machine's state must be persisted, so that it can be recovered later by new instances and processes running the app triggered by external events.
** @(RSH3a)@ State @NotActivated@ (the initial one).
*** @(RSH3a1)@ On event @CalledDeactivate@:
**** @(RSH3a1a)@ Makes @Push#deactivate@ return or call its callback with no error.
**** @(RSH3a1b)@ Transitions to @NotActivated@.
*** @(RSH3a2)@ On event @CalledActivate@:
**** @(RSH3a2a)@ If the local device has @deviceIdentityToken@, enqueues a @CalledActivate@ event and transitions to @WaitingForNewPushDeviceDetails@ and #RSH3a2b onwards don't apply.
**** @(RSH3a2a)@ If the local device has @deviceIdentityToken@, enqueues a @CalledActivate@ event and performs a validation of the local DeviceDetails via the following steps. "@(RSH3a2b)@":#RSH3a2b onwards then don't apply.
***** @(RSH3a2a1)@ Checks the compatibilty of the present client with the existing registration: if the @LocalDevice@ has a non-empty @clientId@, and the present identified client has a different (non-null) @clientId@, then a @SyncRegistrationFailed@ event should be fired containing an error with @code@ 61002, and skips to "@(RSH3a2a4)@":#RSH3a2a4.
***** @(RSH3a2a2)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@.
***** @(RSH3a2a3)@ Otherwise, makes an asynchronous HTTP PUT request to @/push/deviceRegistrations/:deviceId@ using the local @DeviceDetails@ with the push details as body. When the registration validation request is complete, a @RegistrationSynced@ or @SyncRegistrationFailed@ event should be fired.
***** @(RSH3a2a4)@ Transitions to @WaitingForRegistrationSync@.
**** @(RSH3a2b)@ If the local device does not have @id@ and @deviceSecret@, both are generated locally. The @id@ must be a "ulid":https://github.com/ulid/spec or similar globally-unique identifier. The @deviceSecret@ must be created using secure random data with sufficient entropy to generate a digest of at least 32 bytes (eg using sha256) and encoding that digest with base64. The local @DeviceDetails@ is updated with the resulting @deviceId@ and @deviceSecret@.
**** @(RSH3a2c)@ If the local device has the necessary push details (registration token, etc.), sends a @GotPushDeviceDetails@ event.
**** @(RSH3a2d)@ If the local device does not have the necessary push details, it initiates a request to the underlying platform (or otherwise generates them)
**** @(RSH3a2e)@ Transitions to @WaitingForPushDeviceDetails@.
*** @(RSH3a3)@ On event @GotPushDeviceDetails@:
**** @(RSH3a3a)@ Transitions to @NotActivated@. (This consumes the event; #RSH3a2 produces it again once @Push#activate@ is called.)
**** @(RSH3a3a)@ Transitions to @NotActivated@. (This consumes the event; "@(RSH3a2)@":#RSH3a2 produces it again once @Push#activate@ is called.)
** @(RSH3b)@ State @WaitingForPushDeviceDetails@:
*** @(RSH3b1)@ On event @CalledActivate@:
**** @(RSH3b1a)@ Transitions to @WaitingForPushDeviceDetails@.
Expand Down Expand Up @@ -1005,23 +1009,25 @@ h3(#activation-state-machine). Activation state machine
**** @(RSH3d2b)@ Otherwise, make an asynchronous DELETE HTTP request to "/push/deviceRegistrations":/rest-api/#delete-device-registration using the local @DeviceDetails@ 's ID. This operation requires "push device authentication":#push-device-authentication.
**** @(RSH3d2c)@ Either way, when the registration is done, a @Deregistered@ or @DeregistrationFailed@ event should be fired.
**** @(RSH3d2d)@ Transitions to @WaitingForDeregistration@.
*** @(RSH3d3)@ On event @GotPushDeviceDetails@ (note that this will only happen on platforms whose push device details, after first set, can change, e. g. GCM's registration token refresh):
*** @(RSH3d3)@ On event @GotPushDeviceDetails@ (note that this will only happen on platforms whose push device details, after first set, can change, e. g. FCM's registration token refresh):
**** @(RSH3d3a)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@ updated with the push details.
**** @(RSH3d3b)@ Otherwise, make an asynchronous PATCH HTTP request to "/push/deviceRegistrations/:deviceId":/rest-api/#update-device-registration using the local @DeviceDetails@ 's push details as body (but only the changed fields, as described in "the REST endpoint documentation":/rest-api/#update-device-registration). This operation requires "push device authentication":#push-device-authentication.
**** @(RSH3d3c)@ Either way, when the registration is done, a @RegistrationUpdated@ or @UpdatingRegistrationFailed@ event should be fired.
**** @(RSH3d3d)@ Transitions to @WaitingForRegistrationUpdate@.
** @(RSH3e)@ State @WaitingForRegistrationUpdate@:
**** @(RSH3d3c)@ Either way, when the registration is done, a @RegistrationSynced@ or @SyncRegistrationFailed@ event should be fired.
**** @(RSH3d3d)@ Transitions to @WaitingForRegistrationSync@.
** @(RSH3e)@ State @WaitingForRegistrationSync@:
*** @(RSH3e1)@ On event @CalledActivate@:
**** @(RSH3e1a)@ Makes @Push#activate@ return or call its callback with no error.
**** @(RSH3e1b)@ Transitions to @WaitingForRegistrationUpdate@.
*** @(RSH3e2)@ On event @RegistrationUpdated@:
**** @(RSH3e1b)@ Transitions to @WaitingForRegistrationSync@.
*** @(RSH3e2)@ On event @RegistrationSynced@:
**** @(RSH3e2b)@ If the machine is in state @WaitingForRegistrationSync@ as a result of a @CalledActivate@ event, make @Push#activate@ return or call its callback with no error.
**** @(RSH3e2a)@ Transitions to @WaitingForNewPushDeviceDetails@.
*** @(RSH3e3)@ On event @UpdatingRegistrationFailed@:
**** @(RSH3e3a)@ Calls the @updateFailedCallback@ provided to @Push#activate@ with the error.
**** @(RSH3e3b)@ Transitions to @AfterRegistrationUpdateFailed@.
** @(RSH3f)@ State @AfterRegistrationUpdateFailed@:
*** @(RSH3e3)@ On event @SyncRegistrationFailed@:
**** @(RSH3e3c)@ If the machine is in state @WaitingForRegistrationSync@ as a result of a @CalledActivate@ event, make @Push#activate@ return or call its callback with the error.
**** @(RSH3e3a)@ Otherwise, calls the @updateFailedCallback@ provided to @Push#activate@ with the error.
**** @(RSH3e3b)@ Transitions to @AfterRegistrationSyncFailed@.
** @(RSH3f)@ State @AfterRegistrationSyncFailed@:
*** @(RSH3f1)@ On events @CalledActivate@ or @GotPushDeviceDetails@:
**** @(RSH3f1a)@ Does the same as "RSH3d3":#RSH3d3.
**** @(RSH3f1a)@ Does the same as "RSH3a2a":#RSH3a2a.
*** @(RSH3f2)@ On events @CalledDeactivate@:
**** @(RSH3f2a)@ Does the same as "RSH3d2":#RSH3d2.
** @(RSH3g)@ State @WaitingForDeregistration@:
Expand All @@ -1033,7 +1039,7 @@ h3(#activation-state-machine). Activation state machine
**** @(RSH3g2c)@ Transitions to @NotActivated@.
*** @(RSH3g3)@ On event @DeregistrationFailed@:
**** @(RSH3g3a)@ Makes @Push#deactivate@ return or call its callback with the error.
**** @(RSH3g3b)@ Transitions to the previous state, which is either @WaitingForNewPushDeviceDetails@ or @AfterRegistrationUpdateFailed@ (so, in purity, @WaitingForDeregistration@ are two separate states, one for each previous state).
**** @(RSH3g3b)@ Transitions to the previous state, which is either @WaitingForNewPushDeviceDetails@ or @AfterRegistrationSyncFailed@ (so, in purity, @WaitingForDeregistration@ are two separate states, one for each previous state).
* @(RSH4)@ When an event is fired and a transition from the current state is not defined for such event, the event is put into a queue. Then, whenever a transition happens, an event is dequeued from the queue. If a transition from the new current state is defined for the dequeued event, such transition happens. If not, the event is put back in its place in the queue. E. g. we're @WaitingForDeregistration@, and an event @CalledActivate@ happens. This event will be put in the queue, since there's no transition defined for it. Then, an event @Deregistered@ arrives, causing a transition to @NotActivated@. Now we peek the next item on the queue: @CalledActivate@. Because @NotActivated@ transitions on @CalledActivate@, the event is consumed and the machine transitions.
* @(RSH5)@ Event handling is atomic and sequential: while an event is being handled, the next one should be handled only after the current one has caused a state transition or has been put into the pending events queue.

Expand Down Expand Up @@ -1062,6 +1068,19 @@ h3(#push-channels). Push channels
*** @(RSH7d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name.
** @(RSH7e)@ @#listSubscriptions(params)@ performs a GET request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. A @concatFilters@ param needs to be set to @true@ as well.

h3(#local-device). LocalDevice

* @(RSH8)@ In platforms that support receiving push notifications, the @device@ method on the @Rest@ or @Realtime@ interfaces returns an instance of @LocalDevice@ that represents the current state of the device in respect of it being a target for push notifications.
** @(RSH8a)@ The @LocalDevice@ is initialised when first required, either as a result of a call to @Rest#device@ or @Realtime#device@, or as a result of an operation involving the Activation State Machine. The @LocalDevice@ @id@, @clientId@, @deviceSecret@ and @deviceIdentityToken@ attributes are populated, together with any @recipient@-related attributes, to the extent that they exist, from the persisted state.
** @(RSH8b)@ The @LocalDevice@ @id@ and @deviceSecret@ attributes are generated, and persisted as part of the @LocalDevice@ state, when required by step "@(RSH3a2b)@":#RSH3a2b in the Activation State Machine. At that time, the @clientId@ attribute is also initialised, if the client is identified according to "@(RSA7)@":#RSA7.
** @(RSH8c)@ Following successful registration of a @LocalDevice@, following the procedure in "@(RSH3c2a)@":#RSH3c2a, the now known @deviceIdentityToken@ is set and persisted.
** @(RSH8d)@ If the @LocalDevice* is created by an unidentified client (see "@(RSA7)@":#RSA7 ) and therefore has no @clientId@ set, but the client subsequently becomes identified (as a result of "@(RSA7b2)@":#RSA7b2 or "@(RSA7b3)@":#RSA7b3 ), then the @LocalDevice@ @clientId@ is set and persisted.
** @(RSH8e)@ If the @LocalDevice@ @clientId@ becomes set as a result of "@(RSH8d)@":#RSH8d, and the @LocalDevice@ is already registered (ie the @deviceIdentityToken@ is set), and the ActivationStateMachine is in any state other than @NotActivated@, then a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3 once the effects of "@(RSH8d)@":#RSH8d are visible, ie. once @LocalDevice@ @clientId@ is set.
** @(RSH8f)@ If the @LocalDevice@ is created by an unidentified client (see "@(RSA7)@":#RSA7 ) and therefore has no @clientId@ set, but on receipt of a registration response (see "@(RSH3c2)@":#RSH3c2 ) the registered device has a non-empty @clientId@, then the @LocalDevice@ @clientId@ is set with that @clientId@.
** @(RSH8g)@ Whenever any change arises of the push transport details for local device (eg an FCM registration token update triggered by the platform), a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3.
** @(RSH8h)@ If an attempt to obtain the push transport details for local device (eg an FCM registration token) fails, a @GettingPushDeviceDetailsFailed@ event containing the indicated error is sent to "the state machine":#RSH3.
** @(RSH8i)@ Each time the library is instanced, if the LocalDevice has push device details (eg an APNS deviceToken), and if the platform supports it, it must verify the validity of those details (eg by requesting a token from the platform and comparing that with the already-known token). If as a result there are updated details, then an update to the Ably server is triggered by sending a @GotPushDeviceDetails@ event to "the state machine":#RSH3.

h2. Types

h3(#types). Data types
Expand Down

0 comments on commit bd92372

Please sign in to comment.