AWS AppSync multiple subscriptions in same view controller not working - iOS Swift - ios

Just FYI I posted this question originally in the AWS AppSync forum (in case in the future AWS answers it).
I have been trying to make a simple Posts app like the one in the docs but I have found no documentation or guides that handle multiple subscriptions in one view controller.
Three mutations: onCreatePost, onUpdatePost, onDeletePost
(and of course three subscriptions to those mutations)
In Xcode, I have three functions called during viewDidLoad(): subscribeToNewPosts(), subscribeToUpdatedPosts(), subscribeToDeletedPosts()
Each subscription function works and creates a subscription with the correct functionality and updates the table view accordingly if used alone. But, if called one after the other, only the last subscription will actually receive data and update the table view. I put a breakpoint to check out topicSubscribersDictionary in AppSyncMQTTClient.swift after subscribing to all three mutations
func startNewSubscription(subscriptionInfo: AWSSubscriptionInfo) {
var topicQueue = [String]()
let mqttClient = MQTTClient<AnyObject, AnyObject>()
mqttClient.clientDelegate = self
for topic in subscriptionInfo.topics {
if topicSubscribersDictionary[topic] != nil {
// if the client wants subscriptions and is allowed we add it to list of subscribe
topicQueue.append(topic)
}
}
mqttClients.append(mqttClient)
mqttClientsWithTopics[mqttClient] = topicQueue
mqttClient.connect(withClientId: subscriptionInfo.clientId, toHost: subscriptionInfo.url, statusCallback: nil)
}
and all three subscriptions are in fact in the dictionary...
Do I need multiple instances of appSyncClient, one for each subscription? Is it a problem with the schema design?
schema.graphql
schema.json
mutations.graphql
queries.graphql
subscriptions.graphql
Example use case: simple chat app. New conversation started = OnCreatePostSubscription; new incoming message in that conversation = OnUpdatePostSubscription

Are you using API Key for authorization in AppSync? If you are using API Key only one subscription is supported by the SDK at this point. Could you switch to IAM (Cognito Identity) or Cognito UserPools based auth and see if multiple subscriptions work for you?

I managed to have several subscriptions working with API Key by replacing the call startSubscriptions to startNewSubscription inside AWSAppSyncSubscriptionWatcher
if let subscriptionInfo = subscriptionResult.subscrptionInfo {
self.subscriptionTopic = subscriptionResult.newTopics
self.client?.addWatcher(watcher: self, topics: subscriptionResult.newTopics!, identifier: self.uniqueIdentifier)
//self.client?.startSubscriptions(subscriptionInfo: subscriptionInfo)
subscriptionInfo.forEach { self.client?.startNewSubscription(subscriptionInfo: $0) }
}
Couldn't find any side effect with this approach yet, apart from requiring to fork the iOS SKD

Related

When i terminate app MUC group chat members are getting removed

When i terminate app MUC group chat members are getting removed, i have to join them again while coming back to app from bookmarks? We do not want to rejoin again and again. Can someone please suggest way how to avoid rejoining.
In Android smack there is provision for auto-rejoin.
Even from Openfire back end we have managed code to do not remove.
So Android is working fine, iOS is removing users.
Please do suggest.
Instead of rejoining the room every time, do set the presence of the group when the user relaunches the app.
Set presence with below code function iterate through all your groups name and set presence:
for group in chatListModel ?? []{
if(group.opponent_type == "2"){
print("Group Name: \(group.opponent_uuid ?? "")")
XMPPGlobal.sharedIntanceXMPP.xmppController.updatePresence(roomJID: XMPPJID(string: "\(group.opponent_uuid ?? "")#\(groupServerName)"))
}
}
Define below function in your XMPPController class:
func updatePresence(roomJID : XMPPJID?) {
let presence = XMPPPresence(type: "presence")
presence.addAttribute(withName: "from", stringValue: self.xmppStream.myJID?.user ?? "")
presence.addAttribute(withName: "to", stringValue: "\(roomJID?.full ?? "")/\(self.xmppStream.myJID?.user ?? "")")
let element = DDXMLElement.init(name: "x", xmlns: XMPPMUCNamespace)
presence.addChild(element)
self.xmppStream.send(presence)
}
Hope it will works for you.
In spite of the fact that there is the answer.
I would like to elaborate a little bit :)
In case of MUC rooms: there is an affiliation, i.e. long-lasting role (admin, owner, etc) and "subscription" role (visitor, member etc).
What you are asking is per se described in XMPP MUC. 7.1 Order of Events
You send your presence to join the room, and receive presence from other other participants, you can also get some cached messages if your XMPP backend was configured properly.
For instance for ejabberd (process-one): mod_muc: history_size settings defines in-memory cache.
You might don't want to get set of room events, as described in 7.1 above.
There might be some server's extensions. In case of ejabberd there is MUCPubSub you send your subscription (not presence) and get the following messages, plus you can get list of members or track its changes, and etc:
<subscribe xmlns='urn:xmpp:mucsub:0'
nick='mynick'
password='roompassword'>
<event node='urn:xmpp:mucsub:nodes:messages' />

How to use two stripe keys in app delegate in swift ios

I want to add two stripe keys to my App delegate field in Swift iOS. I have two stripe accounts and based on conditions I want to do credit/debit card payments. Thanks in advance.
For what I have done is instead of declaring the string keys in App delegate I am declaring the keys in the class itself where I am creating the token for payments like below. I have two classes in each class I am using each key.
#IBAction func submitCard(_ sender: AnyObject) {
let cardParams = paymentTextField.cardParams
Stripe.setDefaultPublishableKey("pk_test_1234567899875gh")// example key
// It takes the card details and creates the token here.
STPAPIClient.shared().createToken(withCard: cardParams) { token, error in
guard token != nil else {
print("Error creating token: %#", error!.localizedDescription);
return
}
print(token)
}
}
Is this write? or do I need to change the code?
Note: This is not about the environment, but two different stripe accounts.
You can simply create multiple STPAPIClients using different keys and then use them as needed, instead of using the shared instance.
https://stripe.dev/stripe-ios/docs/Classes/STPAPIClient.html#/c:objc(cs)STPAPIClient(im)initWithPublishableKey:
let clientA = STPAPIClient(publishableKey: "pk_123")
let clientB = STPAPIClient(publishableKey: "pk_456")
if usingClientA {
clientA.createToken(withCard: cardParams) { ... }
} else if usingClientB {
clientB.createToken(withCard: cardParams) { ... }
}
Also as a general point, you might want to have the public key get returned from an endpoint on your backend server rather than hardcoding into the app, as that makes it easier if you need to change the Stripe account used without going through app review.
Ideally you should use one Stripe account only. And the way you set your publishableKey in your AppDelegate depends on your current environment (i.e. development / production), like so:
let key = isProduction ? "pk_live_TYooMQauvdEDq54NiTphI7jx" : "pk_test_TYooMQauvdEDq54NiTphI7jx"
Stripe.setDefaultPublishableKey(key)
Should you need to set a new key based on "conditions" as what you've mentioned, later on, just set it with the function setDefaultPublishableKey.
You can use this condition in AppDelegate:
#if DEBUG
Stripe.setDefaultPublishableKey(testKey)
#else
Stripe.setDefaultPublishableKey(liveKey)
#endif
Result: if you'll use a debug environment, then Stripe will get the test key. In the case of archive and deploy to TestFlight, for example, Stripe will get live keys.

Cannot set PubNub auth key Swift iOS

I have a webapp and the iOS app built in Swift. The thing is I don't know Swift and I'm trying to modify the iOS app in order to add the authorization key to the PubNub client before subscribing/publishing to channels.
Link to PubNub docs
PRE:
Access Manager is enabled
my_auth_key is hardcoded and already enabled form the server for the corresponding channel I want to subscribe.
Here is the code
What's the correct way to set the auth key?
Thanks in advance
Polak, mentioned docs code snippet refer to previously created instance to get it's pre-configured PNConfiguration instance and change required field. This practice can be used in case if you need change something at run-time.
If you have data for authKey at the moment of client initialization, you can set it:
var pubnubClient: PubNub = {
let configuration = PNConfiguration(publishKey: UCConstants.PubNub.publishKey, subscribeKey: UCConstants.PubNub.subscribeKey)
configuration.authKey = "my_auth_key"
return PubNub.clientWithConfiguration(configuration)
}()
Also, I've tried exact your code and don't have any issues with setting of authKey, because I can see it with subscribe request.
If you still will have troubles with PAM and auth key usage, please contact us on support#pubnub.com
Looks like you can:
let configuration = PNConfiguration(
authKey: "super_secret",
publishKey: UCConstants.PubNub.publishKey,
subscribeKey: UCConstants.PubNub.subscribeKey
)
Based on the Obj-C code: https://github.com/pubnub/objective-c/blob/8c1f0876b5b34176f33681d22844e8d763019635/PubNub/Data/PNConfiguration.m#L174-L181

iOS: How to detect if a user is subscribed to an auto-renewable subscription

Hopefully the title is self-explanatory. I'm trying to do something like this:
checkIfUserIsSubscribedToProduct(productID, transactionID: "some-unique-transaction-string", completion: { error, status in
if error == nil {
if status == .Subscribed {
// do something fun
}
}
}
does anything like the hypothetical code I've provided exist? I feel like I'm taking crazy pills
Edit
In similar questions I keep seeing a generic answer of "oh you gotta validate the receipt" but no explanation on how, or even what a receipt is. Could someone provide me with how to "validate the receipt"? I tried this tutorial but didn't seem to work.
Edit - For Bounty
Please address the following situation: A user subscribes to my auto-renewable subscription and gets more digital content because of it - cool, implemented. But how do I check whether that subscription is still valid (i.e. they did not cancel their subscription) each time they open the app? What is the simplest solution to check this? Is there something like the hypothetical code I provided in my question? Please walk me through this and provide any further details on the subject that may be helpful.
I know everyone was very concerned about me and how I was doing on this - fear not, solved my problem. Main problem was that I tried Apple's example code from the documentation, but it wasn't working so I gave up on it. Then I came back to it and implemented it with Alamofire and it works great. Here's the code solution:
Swift 3:
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = NSData(contentsOf: receiptURL!)
let requestContents: [String: Any] = [
"receipt-data": receipt!.base64EncodedString(options: []),
"password": "your iTunes Connect shared secret"
]
let appleServer = receiptURL?.lastPathComponent == "sandboxReceipt" ? "sandbox" : "buy"
let stringURL = "https://\(appleServer).itunes.apple.com/verifyReceipt"
print("Loading user receipt: \(stringURL)...")
Alamofire.request(stringURL, method: .post, parameters: requestContents, encoding: JSONEncoding.default)
.responseJSON { response in
if let value = response.result.value as? NSDictionary {
print(value)
} else {
print("Receiving receipt from App Store failed: \(response.result)")
}
}
As some comments pointed out there's a couple flaws with these answers.
Calling /verifyReceipt from the client isn't secure.
Comparing expiration dates against the device clock can be spoofed by changing the time (always a fun hack to try after cancelling a free trial :) )
There are some other tutorials of how to set up a server to handle the receipt verification, but this is only part of the problem. Making a network request to unpack and validate a receipt on every app launch can lead to issues, so there should be some caching too to keep things running smoothly.
The RevenueCat SDK provides a good out-of-the box solution for this.
A couple reasons why I like this approach:
Validates receipt server side (without requiring me to set up a server)
Checks for an "active" subscription with a server timestamp so can't be spoofed by changing the device clock
Caches the result so it's super fast and works offline
There's some more details in this question: https://stackoverflow.com/a/55404121/3166209
What it works down to is a simple function that you can call as often as needed and will return synchronously in most cases (since it's cached).
subscriptionStatus { (subscribed) in
if subscribed {
// Show that great pro content
}
}
What are you trying to achieve in particular? Do you want to check for a specific Apple ID?
I highly doubt that this is possible through the SDK. Referring to Is it possible to get the user's apple ID through the SDK? you can see that you can't even ask for the ID directly but rather services attached to it.
What would work is caching all transactions on your own server and search its database locally but that would require the app to ask for the user's Apple ID so the app could update the subscription state whenever it launches as it can check for IAP of the ID associated with the device.
However, the user could just type whatever he wanted - and it's unlikely to get this through Apple's app review process.
I am using MKSoreKit https://github.com/MugunthKumar/MKStoreKit for auto-renew subscriptions.but it is in objective c you can check the library code for solution.I am using it in my code and it is working fine.
using below method you can easily check subscription status..
if([MKStoreManager isProductPurchased:productIdentifier]) {
//unlock it
}
It gets the apple id from device and I think that is user specific

Struggling to Understand CKSubscriptions in CloudKit

I've been reading about and trying to use the CKSubscription feature for weeks, but I can't find info about some general questions. I've read Apple docs, online tutorials, books I bought, and questions here on SO, but I still don't understand the fundamentals I think. Any help is very much appreciated.
Here are questions I cannot find answers to:
1). What is the purpose of the subscriptionID? The convenience init does not include it, so why is it needed in the designated init? If you use it, is it the same for all users of the app?
2). I saw someone mention here that you can unregister a subscriptionID. Why or how would you do this?
3). Can subscriptions be setup in both public or the user's private database?
4). If I have a query based subscription that is the same for all users, will there only ever be 1 subscription listed in the database?
For instance, I'm having trouble getting notifications to work with my
specific use case. It's not a problem in my setup, as I can get a True predicate to work and the notification comes. So I must not understand the fundamentals of how subscriptions work still.
I'm trying to setup a subscription that fires whenever a new record is created when a different user makes a comment on a post. This new record will then contain a reference to the user who created the post. The only subscription I see in the database for both users is - Notifications.user (equals reference). So, I'm assuming I'll only ever see this one subscription.(?) But how does the server keep track of every user's recordID or know when to send it to a specific device?
The problem is I can't get the notification to work. I manually add a record in the dashboard, and I put the other user's recordID as the CKReference. While I'm adding the record, I have the app running in the background on a device under the user's account whom I added as the CKReference in the field. I'd expect the query to trigger and send a push notification since someone commented on this user's post.
Here's my code to setup the subscription:
func subscribe(userID: CKRecordID) {
let options = CKSubscriptionOptions.FiresOnRecordCreation
let userRef = CKReference(recordID: userID, action: .DeleteSelf)
let predicate = NSPredicate(format: "userRef == %#", userRef)
let predicateTwo = NSPredicate(format: "read = %#", "")
let compoundPred = NSCompoundPredicate(type: .AndPredicateType, subpredicates: [predicate, predicateTwo])
let subscription = CKSubscription(recordType: "Notifications", predicate: compoundPred, subscriptionID: subscriptionID,
options: options)
subscription.notificationInfo = CKNotificationInfo()
subscription.notificationInfo.desiredKeys = ["userPost"]
subscription.notificationInfo.alertBody = "Someone replied to your Pod post"
publicDB.saveSubscription(subscription, completionHandler: {
subscription, error in
if (error != nil) {
println("error subscribing: \(error)")
} else {
println("subscribed!")
}
})
}
Let me try and answer:
SubscriptionId allows you to identify later a subscription for example to delete it using the CKDatabase method deleteSubscriptionWithID
For how see answer 1. As to why, well maybe you do not want to get notifications on this subscription any more. This depends on what you are trying to achieve with your app.
Yes
Yes, if you register only one subscription it should work for all users of your app
Regarding your issues, please note that a user recordIDs are special, so you may have issues specifically related to that due to the privacy issues around them. I would suggest to try a simple case that does not involve users and see if subscriptions are working for you. Then think again about how you use user record IDs

Resources