diff --git a/libs/gundeck-types/src/Gundeck/Types/Push/V2.hs b/libs/gundeck-types/src/Gundeck/Types/Push/V2.hs index aedfc7f016..0dc12e508e 100644 --- a/libs/gundeck-types/src/Gundeck/Types/Push/V2.hs +++ b/libs/gundeck-types/src/Gundeck/Types/Push/V2.hs @@ -178,14 +178,17 @@ newtype ApsLocKey = ApsLocKey {fromLocKey :: Text} data ApsPreference = ApsStdPreference + | ApsVoIPPreference deriving (Eq, Show, Generic) deriving (Arbitrary) via GenericUniform ApsPreference instance ToJSON ApsPreference where + toJSON ApsVoIPPreference = "voip" toJSON ApsStdPreference = "std" instance FromJSON ApsPreference where parseJSON = withText "ApsPreference" $ \case + "voip" -> pure ApsVoIPPreference "std" -> pure ApsStdPreference x -> fail $ "Invalid preference: " ++ show x diff --git a/libs/wire-api/src/Wire/API/Push/V2/Token.hs b/libs/wire-api/src/Wire/API/Push/V2/Token.hs index 79f282b4d0..0cf7b292af 100644 --- a/libs/wire-api/src/Wire/API/Push/V2/Token.hs +++ b/libs/wire-api/src/Wire/API/Push/V2/Token.hs @@ -115,6 +115,8 @@ data Transport = GCM | APNS | APNSSandbox + | APNSVoIP + | APNSVoIPSandbox deriving stock (Eq, Ord, Show, Bounded, Enum, Generic) deriving (Arbitrary) via (GenericUniform Transport) deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema Transport) @@ -125,7 +127,9 @@ instance ToSchema Transport where mconcat [ element "GCM" GCM, element "APNS" APNS, - element "APNS_SANDBOX" APNSSandbox + element "APNS_SANDBOX" APNSSandbox, + element "APNS_VOIP" APNSVoIP, + element "APNS_VOIP_SANDBOX" APNSVoIPSandbox ] instance FromByteString Transport where @@ -134,6 +138,8 @@ instance FromByteString Transport where "GCM" -> pure GCM "APNS" -> pure APNS "APNS_SANDBOX" -> pure APNSSandbox + "APNS_VOIP" -> pure APNSVoIP + "APNS_VOIP_SANDBOX" -> pure APNSVoIPSandbox x -> fail $ "Invalid push transport: " <> show x newtype Token = Token diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs index 52cde0922b..a6003b36d8 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs @@ -791,6 +791,12 @@ tests = ), ( Test.Wire.API.Golden.Generated.Push_2eToken_2eTransport_user.testObject_Push_2eToken_2eTransport_user_3, "testObject_Push_2eToken_2eTransport_user_3.json" + ), + ( Test.Wire.API.Golden.Generated.Push_2eToken_2eTransport_user.testObject_Push_2eToken_2eTransport_user_4, + "testObject_Push_2eToken_2eTransport_user_4.json" + ), + ( Test.Wire.API.Golden.Generated.Push_2eToken_2eTransport_user.testObject_Push_2eToken_2eTransport_user_5, + "testObject_Push_2eToken_2eTransport_user_5.json" ) ], testGroup "Golden: Token_user" $ diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Push_2eToken_2eTransport_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Push_2eToken_2eTransport_user.hs index fc7c1ed7f1..96739ba620 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Push_2eToken_2eTransport_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Push_2eToken_2eTransport_user.hs @@ -17,7 +17,7 @@ module Test.Wire.API.Golden.Generated.Push_2eToken_2eTransport_user where -import Wire.API.Push.Token (Transport (APNS, APNSSandbox, GCM)) +import Wire.API.Push.Token (Transport (APNS, APNSSandbox, APNSVoIP, APNSVoIPSandbox, GCM)) import Wire.API.Push.Token qualified as Push.Token (Transport) testObject_Push_2eToken_2eTransport_user_1 :: Push.Token.Transport @@ -28,3 +28,9 @@ testObject_Push_2eToken_2eTransport_user_2 = APNS testObject_Push_2eToken_2eTransport_user_3 :: Push.Token.Transport testObject_Push_2eToken_2eTransport_user_3 = APNSSandbox + +testObject_Push_2eToken_2eTransport_user_4 :: Push.Token.Transport +testObject_Push_2eToken_2eTransport_user_4 = APNSVoIP + +testObject_Push_2eToken_2eTransport_user_5 :: Push.Token.Transport +testObject_Push_2eToken_2eTransport_user_5 = APNSVoIPSandbox diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs index d0e892bf70..ed40d1157b 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs @@ -42,6 +42,7 @@ import Test.Wire.API.Golden.Manual.QualifiedUserClientPrekeyMap import Test.Wire.API.Golden.Manual.SearchResultContact import Test.Wire.API.Golden.Manual.SubConversation import Test.Wire.API.Golden.Manual.TeamSize +import Test.Wire.API.Golden.Manual.Token import Test.Wire.API.Golden.Manual.UserClientPrekeyMap import Test.Wire.API.Golden.Manual.UserIdList import Test.Wire.API.Golden.Runner @@ -143,6 +144,9 @@ tests = testGroup "GroupId" $ testObjects [(testObject_GroupId_1, "testObject_GroupId_1.json")], + testGroup "PushToken" $ + testObjects + [(testObject_Token_1, "testObject_Token_1.json")], testGroup "TeamSize" $ testObjects [ (testObject_TeamSize_1, "testObject_TeamSize_1.json"), diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/Token.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/Token.hs new file mode 100644 index 0000000000..2fa8207ddc --- /dev/null +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/Token.hs @@ -0,0 +1,29 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero 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 Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module Test.Wire.API.Golden.Manual.Token where + +import Data.Id +import Wire.API.Push.V2.Token + +testObject_Token_1 :: PushToken +testObject_Token_1 = + pushToken + APNSVoIPSandbox + (AppName {appNameText = "j{\110746\SOH_\1084873M"}) + (Token {tokenText = "K"}) + (ClientId {clientToWord64 = 6}) diff --git a/libs/wire-api/test/golden/testObject_Push_2eToken_2eTransport_user_4.json b/libs/wire-api/test/golden/testObject_Push_2eToken_2eTransport_user_4.json new file mode 100644 index 0000000000..d177fe0e9d --- /dev/null +++ b/libs/wire-api/test/golden/testObject_Push_2eToken_2eTransport_user_4.json @@ -0,0 +1 @@ +"APNS_VOIP" diff --git a/libs/wire-api/test/golden/testObject_Push_2eToken_2eTransport_user_5.json b/libs/wire-api/test/golden/testObject_Push_2eToken_2eTransport_user_5.json new file mode 100644 index 0000000000..fd689b4ac1 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_Push_2eToken_2eTransport_user_5.json @@ -0,0 +1 @@ +"APNS_VOIP_SANDBOX" diff --git a/libs/wire-api/test/golden/testObject_Token_1.json b/libs/wire-api/test/golden/testObject_Token_1.json new file mode 100644 index 0000000000..36f8ff69bd --- /dev/null +++ b/libs/wire-api/test/golden/testObject_Token_1.json @@ -0,0 +1,6 @@ +{ + "app": "j{𛂚\u0001_􈷉M", + "client": "6", + "token": "K", + "transport": "APNS_VOIP_SANDBOX" +} diff --git a/libs/wire-api/test/unit/Test/Wire/API/MLS.hs b/libs/wire-api/test/unit/Test/Wire/API/MLS.hs index b1f51f3b25..d0ff8a27a3 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/MLS.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/MLS.hs @@ -308,7 +308,7 @@ spawn cp minput = do in snd <$> concurrently writeInput readOutput case (mout, ex) of (Just out, ExitSuccess) -> pure out - _ -> assertFailure $ "Failed spawning process\n" <> show mout <> "\n" <> show ex + _ -> assertFailure "Failed spawning process" cli :: String -> FilePath -> [String] -> CreateProcess cli store tmp args = diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal index 91dc8a3af6..b191dbfbde 100644 --- a/libs/wire-api/wire-api.cabal +++ b/libs/wire-api/wire-api.cabal @@ -585,6 +585,7 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Manual.SearchResultContact Test.Wire.API.Golden.Manual.SubConversation Test.Wire.API.Golden.Manual.TeamSize + Test.Wire.API.Golden.Manual.Token Test.Wire.API.Golden.Manual.UserClientPrekeyMap Test.Wire.API.Golden.Manual.UserIdList Test.Wire.API.Golden.Protobuf diff --git a/services/brig/docs/swagger-v3.json b/services/brig/docs/swagger-v3.json index b844341e75..e252a73971 100644 --- a/services/brig/docs/swagger-v3.json +++ b/services/brig/docs/swagger-v3.json @@ -14,7 +14,9 @@ "enum": [ "GCM", "APNS", - "APNS_SANDBOX" + "APNS_SANDBOX", + "APNS_VOIP", + "APNS_VOIP_SANDBOX" ], "type": "string" }, diff --git a/services/brig/docs/swagger-v4.json b/services/brig/docs/swagger-v4.json index 7ff1394f34..937aafdefc 100644 --- a/services/brig/docs/swagger-v4.json +++ b/services/brig/docs/swagger-v4.json @@ -4828,7 +4828,9 @@ "enum": [ "GCM", "APNS", - "APNS_SANDBOX" + "APNS_SANDBOX", + "APNS_VOIP", + "APNS_VOIP_SANDBOX" ], "type": "string" }, diff --git a/services/gundeck/src/Gundeck/Aws.hs b/services/gundeck/src/Gundeck/Aws.hs index b1636f33b7..ea5fe96886 100644 --- a/services/gundeck/src/Gundeck/Aws.hs +++ b/services/gundeck/src/Gundeck/Aws.hs @@ -369,12 +369,17 @@ newtype Attributes = Attributes -- Note [VoIP TTLs] -- ~~~~~~~~~~~~~~~~ --- For GCM, APNS and APNS_SANDBOX, SNS treats the TTL "0" +-- The TTL message attributes for APNS_VOIP and APNS_VOIP_SANDBOX are not +-- documented but appear to work. The reason might be that TTLs were +-- introduced before support for VoIP notifications. There is a catch, +-- however. For GCM, APNS and APNS_SANDBOX, SNS treats the TTL "0" -- specially, i.e. it forwards it to the provider where it has a special --- meaning. Which means if the TTL is lower than the "dwell time" in SNS, --- the notification is never sent to the provider. So we must specify a --- reasonably large TTL for transient VoIP notifications, so that they are --- not discarded already by SNS. +-- meaning. That does not appear to be the case for APNS_VOIP and +-- APNS_VOIP_SANDBOX, for which the TTL is interpreted normally, which means +-- if the TTL is lower than the "dwell time" in SNS, the notification is +-- never sent to the provider. So we must specify a reasonably large TTL +-- for transient VoIP notifications, so that they are not discarded +-- already by SNS. -- -- cf. http://docs.aws.amazon.com/sns/latest/dg/sns-ttl.html @@ -390,9 +395,13 @@ timeToLive t s = Attributes (Endo (ttlAttr s)) ttlNow GCM = "0" ttlNow APNS = "0" ttlNow APNSSandbox = "0" + ttlNow APNSVoIP = "15" -- See note [VoIP TTLs] + ttlNow APNSVoIPSandbox = "15" -- See note [VoIP TTLs] ttlKey GCM = "AWS.SNS.MOBILE.GCM.TTL" ttlKey APNS = "AWS.SNS.MOBILE.APNS.TTL" ttlKey APNSSandbox = "AWS.SNS.MOBILE.APNS_SANDBOX.TTL" + ttlKey APNSVoIP = "AWS.SNS.MOBILE.APNS_VOIP.TTL" + ttlKey APNSVoIPSandbox = "AWS.SNS.MOBILE.APNS_VOIP_SANDBOX.TTL" publish :: EndpointArn -> LT.Text -> Attributes -> Amazon (Either PublishError ()) publish arn txt attrs = do diff --git a/services/gundeck/src/Gundeck/Aws/Arn.hs b/services/gundeck/src/Gundeck/Aws/Arn.hs index 7a559314f6..c0be6380d7 100644 --- a/services/gundeck/src/Gundeck/Aws/Arn.hs +++ b/services/gundeck/src/Gundeck/Aws/Arn.hs @@ -134,6 +134,8 @@ arnTransportText :: Transport -> Text arnTransportText GCM = "GCM" arnTransportText APNS = "APNS" arnTransportText APNSSandbox = "APNS_SANDBOX" +arnTransportText APNSVoIP = "APNS_VOIP" +arnTransportText APNSVoIPSandbox = "APNS_VOIP_SANDBOX" -- Parsers -------------------------------------------------------------------- @@ -157,5 +159,7 @@ endpointTopicParser = do transportParser :: Parser Transport transportParser = string "GCM" $> GCM + <|> string "APNS_VOIP_SANDBOX" $> APNSVoIPSandbox + <|> string "APNS_VOIP" $> APNSVoIP <|> string "APNS_SANDBOX" $> APNSSandbox <|> string "APNS" $> APNS diff --git a/services/gundeck/src/Gundeck/Instances.hs b/services/gundeck/src/Gundeck/Instances.hs index ab70ccc440..83ab2a692b 100644 --- a/services/gundeck/src/Gundeck/Instances.hs +++ b/services/gundeck/src/Gundeck/Instances.hs @@ -40,11 +40,15 @@ instance Cql Transport where toCql GCM = CqlInt 0 toCql APNS = CqlInt 1 toCql APNSSandbox = CqlInt 2 + toCql APNSVoIP = CqlInt 3 + toCql APNSVoIPSandbox = CqlInt 4 fromCql (CqlInt i) = case i of 0 -> pure GCM 1 -> pure APNS 2 -> pure APNSSandbox + 3 -> pure APNSVoIP + 4 -> pure APNSVoIPSandbox n -> Left $ "unexpected transport: " ++ show n fromCql _ = Left "transport: int expected" diff --git a/services/gundeck/src/Gundeck/Push.hs b/services/gundeck/src/Gundeck/Push.hs index bc015b809a..02c6984873 100644 --- a/services/gundeck/src/Gundeck/Push.hs +++ b/services/gundeck/src/Gundeck/Push.hs @@ -374,16 +374,31 @@ nativeTargets psh rcps' alreadySent = null (psh ^. pushConnections) || a ^. addrConn `elem` psh ^. pushConnections -- Apply transport preference in case of alternative transports for the - -- same client. If no explicit preference is given, the default preference depends on the priority. + -- same client (currently only APNS vs APNS VoIP). If no explicit + -- preference is given, the default preference depends on the priority. preference as = let pref = psh ^. pushNativeAps >>= view apsPreference in filter (pick (fromMaybe defPreference pref)) as where pick pr a = case a ^. addrTransport of GCM -> True - APNS -> pr == ApsStdPreference - APNSSandbox -> pr == ApsStdPreference - defPreference = ApsStdPreference + APNS -> pr == ApsStdPreference || notAny a APNSVoIP + APNSSandbox -> pr == ApsStdPreference || notAny a APNSVoIPSandbox + APNSVoIP -> pr == ApsVoIPPreference || notAny a APNS + APNSVoIPSandbox -> pr == ApsVoIPPreference || notAny a APNSSandbox + notAny a t = + not + ( any + ( \a' -> + addrEqualClient a a' + && a ^. addrApp == a' ^. addrApp + && a' ^. addrTransport == t + ) + as + ) + defPreference = case psh ^. pushNativePriority of + LowPriority -> ApsStdPreference + HighPriority -> ApsVoIPPreference check :: Either SomeException [a] -> m [a] check (Left e) = mntgtLogErr e >> pure [] check (Right r) = pure r diff --git a/services/gundeck/src/Gundeck/Push/Native/Serialise.hs b/services/gundeck/src/Gundeck/Push/Native/Serialise.hs index 07f783c36d..bf9e0e491c 100644 --- a/services/gundeck/src/Gundeck/Push/Native/Serialise.hs +++ b/services/gundeck/src/Gundeck/Push/Native/Serialise.hs @@ -54,6 +54,8 @@ renderText t prio x = case t of GCM -> trim "GCM" (jsonString gcmJson) APNS -> trim "APNS" (jsonString stdApnsJson) APNSSandbox -> trim "APNS_SANDBOX" (jsonString stdApnsJson) + APNSVoIP -> trim "APNS_VOIP" (jsonString voipApnsJson) + APNSVoIPSandbox -> trim "APNS_VOIP_SANDBOX" (jsonString voipApnsJson) where gcmJson = object @@ -65,6 +67,11 @@ renderText t prio x = case t of [ "aps" .= apsDict, "data" .= x ] + voipApnsJson = + object + [ "aps" .= object [], + "data" .= x + ] -- https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications -- Must contain `mutable-content: 1` and include an alert dictionary with title, subtitle, or body information. -- Since we have no useful data here, we send a default payload that gets overridden by the client @@ -87,6 +94,8 @@ maxPayloadSize :: Transport -> Int64 maxPayloadSize GCM = 4096 maxPayloadSize APNS = 4096 maxPayloadSize APNSSandbox = 4096 +maxPayloadSize APNSVoIP = 5120 +maxPayloadSize APNSVoIPSandbox = 5120 gcmPriority :: Priority -> Text gcmPriority LowPriority = "normal" diff --git a/services/gundeck/test/integration/API.hs b/services/gundeck/test/integration/API.hs index 4ef4bd327b..b0c3a63186 100644 --- a/services/gundeck/test/integration/API.hs +++ b/services/gundeck/test/integration/API.hs @@ -838,8 +838,9 @@ testSharePushToken = do gcmTok <- Token . T.decodeUtf8 . toByteString' <$> randomId apsTok <- Token . T.decodeUtf8 . B16.encode <$> randomBytes 32 let tok1 = pushToken GCM "test" gcmTok - let tok2 = pushToken APNS "com.wire.int.ent" apsTok - forM_ [tok1, tok2] $ \tk -> do + let tok2 = pushToken APNSVoIP "com.wire.dev.ent" apsTok + let tok3 = pushToken APNS "com.wire.int.ent" apsTok + forM_ [tok1, tok2, tok3] $ \tk -> do u1 <- randomUser u2 <- randomUser c1 <- randomClientId diff --git a/services/gundeck/test/unit/Native.hs b/services/gundeck/test/unit/Native.hs index 500ec668ff..2e525f7cf1 100644 --- a/services/gundeck/test/unit/Native.hs +++ b/services/gundeck/test/unit/Native.hs @@ -73,6 +73,8 @@ instance FromJSON SnsNotification where [("GCM", String n)] -> parseGcm n [("APNS", String n)] -> parseApns APNS n [("APNS_SANDBOX", String n)] -> parseApns APNSSandbox n + [("APNS_VOIP", String n)] -> parseApns APNSVoIP n + [("APNS_VOIP_SANDBOX", String n)] -> parseApns APNSVoIPSandbox n _ -> mempty where parseApns t n =