I use push notifications and store device tokens like I assume everyone else does. First I transform them into a string my app:
NSString *deviceTokenString = [[[token description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]
stringByReplacingOccurrencesOfString:@" " withString:@""];
Then I PUT them to my server, where ActiveRecord stores them in a character varying(255)
column:
Device.where(:token => device_token, :username => username).first_or_create!(:model => model)
I have a validation that ensures no two tokens are the same, which I understand should always be the case:
class Device < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :token
end
However, I've started to see validation errors for token uniqueness:
ActiveRecord::RecordInvalid: Validation failed: Token has already been taken
Manual query in psql confirms that a device is trying to register with a token already in the table under a different user. Isn't this supposed to be impossible? Is something in the way I'm transforming tokens truncating them? I checked every code example I could find when the problem first occurred and everyone seems to use the method I've listed in the first code sample.
It can happen that a device tries to register with a token already in the table under a different user if someone logs out and then logs in with a different account.
I would do the following on the server for a user user
and a token string token
(assuming that only one user can be logged in on one device at a time):
Device
for token_string
.token_string
and user
.user
, update its user to user
.That way, the push notifications will be sent for the last user that logged in on the device.
Concerning your way of transforming the NSData
to a hex string on the device, you should not rely on -[NSData description]
. Better do it programmatically (typed in, not tested):
- (NSString *)hexStringForData:(NSData *)data
{
NSUInteger length = data.length;
const char *bytes = data.bytes;
NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];
for (int i = 0; i < length; i++) {
[result appendFormat:@"%02x", bytes[i] & 0xff];
}
return [result copy];
}
I'll wager a guess at this one, but take it for what it's for, a guess.
When iOS devices are restored from backups, or when they are "restored" onto new devices, say, someone upgrading from a iPhone 4 to iPhone 5, or when someone gives their iPhone to their wife or sells it on eBay, you will get duplicated/redundant/confusing device data. I've definitely seen that happen, but not specifically with APNS tokens.
Here is what the APNS docs have to say about it:
By requesting the device token and passing it to the provider every time your application launches, you help to ensure that the provider has the current token for the device. If a user restores a backup to a device or computer other than the one that the backup was created for (for example, the user migrates data to a new device or computer), he or she must launch the application at least once for it to receive notifications again. If the user restores backup data to a new device or computer, or reinstalls the operating system, the device token changes. Moreover, never cache a device token and give that to your provider; always get the token from the system whenever you need it. If your application has previously registered, calling registerForRemoteNotificationTypes: results in the operating system passing the device token to the delegate immediately without incurring additional overhead.
So, I'm not looking at your code, but it seems likely that your "duplicate" tokens have to do with some combination of not registering every time, some kind of caching, and device restoration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With