Hi I am facing an issue when triggering the tapkey lock it scans for the lock and successfully scans it. The when I trigger unlcok againsat the PhysicalLockId I get the lock blinks red and I get message Unauthorized.
Using Token Exchange Mechanism, generated Identity provider against Oauth Client.
The lock is assigned to the user as unrestricted
iOS trigger Lock Function
private func triggerLock(physicalLockId: String) -> TKMPromise<Bool> {
guard let bluetoothAddress = self.bleLockScanner.getLock(
physicalLockId: physicalLockId)?.bluetoothAddress else {
self.showAlert(title: "Alert", message: "Lock not nearby", okTitle: R.string.localizable.commonCancel(), cancelString: R.string.localizable.commonScanAgain(), cancelHandle: { _ in
self.scanLock()
})
return TKMAsync.promiseFromResult(false)
}
let ct = TKMCancellationTokens.fromTimeout(timeoutMs: 15000)
// Use the BLE lock communicator to send a command to the lock
return self.bleLockCommunicator.executeCommandAsync(
bluetoothAddress: bluetoothAddress,
physicalLockId: physicalLockId,
commandFunc: { tlcpConnection -> TKMPromise<TKMCommandResult> in
let triggerLockCommand = TKMDefaultTriggerLockCommandBuilder()
.build()
// Pass the TLCP connection to the command execution facade
return self.commandExecutionFacade!.executeStandardCommandAsync(tlcpConnection, command: triggerLockCommand, cancellationToken: ct)
},
cancellationToken: ct)
// Process the command's result
.continueOnUi({ commandResult in
let code: TKMCommandResult.TKMCommandResultCode = commandResult?.code ??
TKMCommandResult.TKMCommandResultCode.technicalError
switch code {
case TKMCommandResult.TKMCommandResultCode.ok:
return true
default:
return false
}
})
.catchOnUi({ (_: TKMAsyncError) -> Bool in
NSLog("Trigger lock failed")
self.showAlert(title: "Alert", message: "Trigger lock failed")
return false
})
}
The error Unautorized means usually, that the current user don't have a grant to this specific lock. In other cases there would be a different error code.
As you are using your own app and so an own identity provider, you have also to create a contact for this specific users.
https://developers.tapkey.io/openapi/tapkey_management_api_v1/#/Contacts/Contacts_Put
If you are creating a contact, you have to specify the id of your identity provider as ipId, otherwise it will create a contact for a tapkey user.
As I can see in your account, you successfully created a user for your identity provider, but then created a contact for an tapkey user.
Related
I'm new to the WTelegramClient C# Library and was used to TLSharp (not working anymore)
I'm trying to understand how I get User info after update is received,
I have the example code that listen to updates and write them in console
but I can't understand how I can respond to the user that sent the message (new update)
I think I need the user id/access_hash to send message to the sender but I can't understand how
Here is how I get the new messages but it can get only username or name/id
private static void DisplayMessage(MessageBase messageBase, bool edit = false)
{
if (edit) Console.Write("(Edit): ");
switch (messageBase)
{
case Message m: Console.WriteLine($"{Peer(m.from_id) ?? m.post_author} in {Peer(m.peer_id)}> {m.message}"); break;
case MessageService ms: Console.WriteLine($"{Peer(ms.from_id)} in {Peer(ms.peer_id)} [{ms.action.GetType().Name[13..]}]"); break;
}
}
Here i can get the name or username of sender(if have) and the message itself
MessageService ('user' not channel or group) for example get me only firstname and lastname
How to get all info of sender or chat itself (i want to try mark as read the message)
I'm used to TLSharp and the new library WTelegramClient is different.
Thanks!!!
Below is a quick example on how to modify DisplayMessage to react to a message sent in private from a user, get the details about this user, verify who it is and which text was sent to us, and then send him a message back.
Notes:
For this example to work, you will need the latest version of Program_ListenUpdates.cs with static variables
DisplayMessage is now async Task, in order to use await
You can pass user to send a message because class User is implicitly converted to InputPeerUser (with the user id/access_hash).
You can do similarly for messages coming from chats, using PeerChat/PeerChannel classes and the _chats dictionary to get chat details
private static async Task DisplayMessage(MessageBase messageBase, bool edit = false)
{
if (edit) Console.Write("(Edit): ");
switch (messageBase)
{
case Message m:
Console.WriteLine($"{Peer(m.from_id) ?? m.post_author} in {Peer(m.peer_id)}> {m.message}");
if (m.flags.HasFlag(Message.Flags.out_))
break; // ignore our own outgoing messages
if (m.Peer is PeerUser pu) // got a message in a direct chat with a user
{
if (_users.TryGetValue(pu.user_id, out var user)) // get user details
{
if (user.username == "Wiz0u" && m.message == "hello")
{
await Client.SendMessageAsync(user, $"hi {user.first_name}, I'm {My.first_name}");
}
}
}
break;
case MessageService ms:
Console.WriteLine($"{Peer(ms.from_id)} in {Peer(ms.peer_id)} [{ms.action.GetType().Name[13..]}]");
break;
}
}
I'd like to alert on the lack of a heartbeat (or 0 bytes received) from any one of large number of Google IOT core devices. I can't seem to do this in Stackdriver. It instead appears to let me alert on the entire device registry which does not give me what I'm looking for (How would I know that a particular device is disconnected?)
So how does one go about doing this?
I have no idea why this question was downvoted as 'too broad'.
The truth is Google IOT doesn't have per device alerting, but instead offers only alerting on an entire device registry. If this is not true, please reply to this post. The page that clearly states this is here:
Cloud IoT Core exports usage metrics that can be monitored
programmatically or accessed via Stackdriver Monitoring. These metrics
are aggregated at the device registry level. You can use Stackdriver
to create dashboards or set up alerts.
The importance of having per device alerting is built into the promise assumed in this statement:
Operational information about the health and functioning of devices is
important to ensure that your data-gathering fabric is healthy and
performing well. Devices might be located in harsh environments or in
hard-to-access locations. Monitoring operational intelligence for your
IoT devices is key to preserving the business-relevant data stream.
So its not easy today to get an alert if one among many, globally dispersed devices, loses connectivity. One needs to build that, and depending on what one is trying to do, it would entail different solutions.
In my case I wanted to alert if the last heartbeat time or last event state publish was older than 5 minutes. For this I need to run a looping function that scans the device registry and performs this operation regularly. The usage of this API is outlined in this other SO post: Google iot core connection status
For reference, here's a Firebase function I just wrote to check a device's online status, probably needs some tweaks and further testing, but to help anybody else with something to start with:
// Example code to call this function
// const checkDeviceOnline = functions.httpsCallable('checkDeviceOnline');
// Include 'current' key for 'current' online status to force update on db with delta
// const isOnline = await checkDeviceOnline({ deviceID: 'XXXX', current: true })
export const checkDeviceOnline = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'You must be logged in to call this function!');
}
// deviceID is passed in deviceID object key
const deviceID = data.deviceID
const dbUpdate = (isOnline) => {
if (('wasOnline' in data) && data.wasOnline !== isOnline) {
db.collection("devices").doc(deviceID).update({ online: isOnline })
}
return isOnline
}
const deviceLastSeen = () => {
// We only want to use these to determine "latest seen timestamp"
const stamps = ["lastHeartbeatTime", "lastEventTime", "lastStateTime", "lastConfigAckTime", "deviceAckTime"]
return stamps.map(key => moment(data[key], "YYYY-MM-DDTHH:mm:ssZ").unix()).filter(epoch => !isNaN(epoch) && epoch > 0).sort().reverse().shift()
}
await dm.setAuth()
const iotDevice: any = await dm.getDevice(deviceID)
if (!iotDevice) {
throw new functions.https.HttpsError('failed-get-device', 'Failed to get device!');
}
console.log('iotDevice', iotDevice)
// If there is no error status and there is last heartbeat time, assume device is online
if (!iotDevice.lastErrorStatus && iotDevice.lastHeartbeatTime) {
return dbUpdate(true)
}
// Add iotDevice.config.deviceAckTime to root of object
// For some reason in all my tests, I NEVER receive anything on lastConfigAckTime, so this is my workaround
if (iotDevice.config && iotDevice.config.deviceAckTime) iotDevice.deviceAckTime = iotDevice.config.deviceAckTime
// If there is a last error status, let's make sure it's not a stale (old) one
const lastSeenEpoch = deviceLastSeen()
const errorEpoch = iotDevice.lastErrorTime ? moment(iotDevice.lastErrorTime, "YYYY-MM-DDTHH:mm:ssZ").unix() : false
console.log('lastSeen:', lastSeenEpoch, 'errorEpoch:', errorEpoch)
// Device should be online, the error timestamp is older than latest timestamp for heartbeat, state, etc
if (lastSeenEpoch && errorEpoch && (lastSeenEpoch > errorEpoch)) {
return dbUpdate(true)
}
// error status code 4 matches
// lastErrorStatus.code = 4
// lastErrorStatus.message = mqtt: SERVER: The connection was closed because MQTT keep-alive check failed.
// will also be 4 for other mqtt errors like command not sent (qos 1 not acknowledged, etc)
if (iotDevice.lastErrorStatus && iotDevice.lastErrorStatus.code && iotDevice.lastErrorStatus.code === 4) {
return dbUpdate(false)
}
return dbUpdate(false)
})
I also created a function to use with commands, to send a command to the device to check if it's online:
export const isDeviceOnline = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'You must be logged in to call this function!');
}
// deviceID is passed in deviceID object key
const deviceID = data.deviceID
await dm.setAuth()
const dbUpdate = (isOnline) => {
if (('wasOnline' in data) && data.wasOnline !== isOnline) {
console.log( 'updating db', deviceID, isOnline )
db.collection("devices").doc(deviceID).update({ online: isOnline })
} else {
console.log('NOT updating db', deviceID, isOnline)
}
return isOnline
}
try {
await dm.sendCommand(deviceID, 'alive?', 'alive')
console.log('Assuming device is online after succesful alive? command')
return dbUpdate(true)
} catch (error) {
console.log("Unable to send alive? command", error)
return dbUpdate(false)
}
})
This also uses my version of a modified DeviceManager, you can find all the example code on this gist (to make sure using latest update, and keep post on here small):
https://gist.github.com/tripflex/3eff9c425f8b0c037c40f5744e46c319
All of this code, just to check if a device is online or not ... which could be easily handled by Google emitting some kind of event or adding an easy way to handle this. COME ON GOOGLE GET IT TOGETHER!
I'm calling the Facebook Graph API to get the email, facebook ID and name of a user that logs into my app through Facebook.
I successfully get the information; I'm now trying to use dispatch groups so the function that call graph waits until the graph API call completes before returning. The graph request is asynchronous.
I can't figure out why this code is locking up.
1) Create a dispatch group
2) Enter said display group
3) Leave the group once info is retrieved or an error is found
4) Wait for the group leave before returning
It seems like my dispatch group enter is not called correctly but I can't figure out why.
class func getFBInformation()->Bool {
var fbResult = false
let fbGraphGroup = DispatchGroup()
fbGraphGroup.enter()
FBSDKGraphRequest(graphPath: "/me", parameters: ["fields": "id, name, email"]).start { (connection, result, err) in
if err != nil {
fbResult = false
print("Pre Error Signal")
fbGraphGroup.leave()
return
}
if let resultDict = result as? [String:AnyObject] {
<Do things with graph results>
print("Pre success signal")
fbResult = true
fbGraphGroup.leave()
}
}
fbGraphGroup.wait()
print("Post signal")
return fbResult
}
How could it work?
First you enter the group, next you are waiting on main thread for completion handler until it leave the group. But the completion handler is not able to execute on the main thread to be able to leave the group.
As far as I know, your completion handler is dispatched on the main queue by the API. It is not necessary to use any other kind of synchronization.
Following the answer here:
https://github.com/aws/aws-sdk-ios/issues/357
At the very bottom there is a mini guide on getting swift and cognito working.
I've made a AWSCustomIdentityProvider as such:
import Foundation
import AWSCognitoIdentityProvider
import AWSCognito
class AWSCustomIdentityProvider: NSObject, AWSIdentityProviderManager
{
private var dict = NSDictionary()
func addToken(value:NSString)
{
dict = ["graph.facebook.com":value]
}
public func logins() -> AWSTask<NSDictionary>
{
return AWSTask(result: dict)
}
}
And I have a login method from facebook:
public func loginButtonDidCompleteLogin(_ loginButton: FacebookLogin.LoginButton, result: FacebookLogin.LoginResult){
switch result {
case .failed(let error):
print("FACEBOOK LOGIN FAILED: \(error)")
case .cancelled:
print("User cancelled login.")
case .success(_, _, let accessToken):
let customIdentity = AWSCustomIdentityProvider()
let token = accessToken.authenticationToken
customIdentity.addToken(value: token as NSString)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: REGIONTYPE, identityPoolId: "XXXXXXXXXXXXXXXXXXXXXXX", identityProviderManager:customIdentity)
credentialsProvider.clearKeychain()
credentialsProvider.clearCredentials()
let serviceConfiguration = AWSServiceConfiguration(region: REDIONTYPE, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration;
credentialsProvider.getIdentityId().continue( { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error: " + (task.error?.localizedDescription)!)// gets called
}
else {
print(task.result)//identityid
}
return nil
})
}
}
However I get the error:
Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=8 "(null)" UserInfo={__type=NotAuthorizedException, message=Logins don't match. Please include at least one valid login for this identity or identity pool.}
Please let me know if you have any idea on how to resolve my issue. I've tried also following the docs and setting the logins directly "credentialsProvider.logins = {"graph.facebook.com": mytoken}
and that produces a different exception upon invoking a lambda method but DOES retrieve the identityID properly. However doing it according to the docs makes a warning that the method I'm using is deprecated.
The error I get:
UserInfo={NSLocalizedDescription=serialized object is neither a valid json Object nor NSData object: }
However that only happens sometimes. If I retry then i can potentially get the identity id but upon invoking a lambda method, I get the same error. I'm assuming it is a cognito issue.
UPDATE
If I use AWSCognitoLoginProviderKey.facebook.rawValue instead of graph.facebook.com in the first part, then it gives me the cognito ID and then I invoke the lambda method. I'll include the lambda method just incase that's the part I'm getting wrong but I'm decently sure that it's cognito that is preventing me from calling the lambda method:
import AWSLambda
import Foundation
struct AWSHelper{
let lambda = AWSLambda.default()
let APPLICATION_NAME = "MYAPPLICATION"
init(){
}
func getFunctionName(funcName: String) -> String{
return "\(funcName)_\(APPLICATION_NAME)"
}
func login(facebookID: String, cognitoID:String, callback:#escaping (Bool) -> Void){
let req = AWSLambdaInvocationRequest();
req?.invocationType = AWSLambdaInvocationType.requestResponse
req?.payload = ["cognitoID" : cognitoID, "facebookID" : facebookID]
req?.functionName = getFunctionName(funcName: "MYFUNCNAME")
lambda.invoke(req!) { (response: AWSLambdaInvocationResponse?,error: Error?) in
print(error)
let payload = response?.payload
print(payload)
callback(true)
}
}
}
Update 2
I have found out that calling a refresh method like this:
credentialsProvider.credentials().continue({ (task: AWSTask!) -> Any? in
print(task.result)
})
Causes an error like this:
AWSiOSSDK v2.4.10 [Error] AWSCredentialsProvider.m line:577 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke.352 | Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=10 "(null)" UserInfo={__type=ResourceNotFoundException, message=Identity 'us-east-1:0db18266-1baa-4c59-9110-f9041dc92ead' not found.}]
I believe the big string that looks like an identitypoolID is actually the identityID for the given user that I have, so cognito has distributed an ID but is not able to query it?
the error:
Logins don't match. Please include at least one valid login for this identity or identity pool
Can also occur because you attempt to log in as another user without logging out, so the token in the logins dictionary is compared to the identityId for a different identity (and doesn't match). In this case the SDK usually recovers by retrying, clearing and reestablishing the identityId, and then it works.
But in your case since you are constructing your own logins dictionary, the issue is more likely that you have constructed a token that does not match. You can inspect tokens using https://jwt.io. (though I admit it works for google and cognito user pools, but not on facebook tokens (I don't know why this is)),
I think doesn't match means that the identityId records a different unique user than is specified in the token.
Are you sure the token is constructed correctly?
As you mentioned... the documentation.. well .. I find the documentation is not worth looking at, So I set up my projects so I can review working code and set breakpoints.
Here is a snippet of the code from Mobile Hub Helper's Facebook AWSSignInProvider, which shows what they use to get the token (token.tokenstring).
- (AWSTask<NSString *> *)token {
FBSDKAccessToken *token = [FBSDKAccessToken currentAccessToken];
NSString *tokenString = token.tokenString;
NSDate *idTokenExpirationDate = token.expirationDate;
if (tokenString
// If the cached token expires within 10 min, tries refreshing a token.
&& [idTokenExpirationDate compare:[NSDate dateWithTimeIntervalSinceNow:AWSFacebookSignInProviderTokenRefreshBuffer]] == NSOrderedDescending) {
return [AWSTask taskWithResult:tokenString];
}
AWSTaskCompletionSource *taskCompletionSource = [AWSTaskCompletionSource taskCompletionSource];
[FBSDKLoginManager renewSystemCredentials:^(ACAccountCredentialRenewResult result, NSError *error) {
if (result == ACAccountCredentialRenewResultRenewed) {
FBSDKAccessToken *token = [FBSDKAccessToken currentAccessToken];
NSString *tokenString = token.tokenString;
taskCompletionSource.result = tokenString;
} else {
taskCompletionSource.error = error;
}
}];
return taskCompletionSource.task;
}
Also... It bears mentioning. The AWSIdentityManager, and it's associated AWSSignInProviders is a nice architecture for getting signed in with Facebook and Google. Even if you don't use the rest of Mobile Hub Helper. Why re-invent the wheel, they did a very good job on the Identity portion of aws-mobilehub-helper-ios
I have a version of that library posted on github that adds an AWSSignInProvider for Cognito User Pools as well. SignIn-awsmhh it requires some fixes in the aws-mobilehub-helper-ios to use cognito user pools they are here aws-mobilehub-helper-ios (so if you clone do a clone --recursive and you will be set up for debugging using breakpoints in the library).
Few things that made it work I think.
I made the correct move by making my own identityprovidermanager and I think the main thing that was blocking me from executing a lambda method was actually the fact that I was using AWSLambda instead of AWSLambdaInvoker. After I switched it started making errors that made sense.
Context
From Facebook Best Pratices I've understand that I should request a minimum set of permissions on the initial login page and delay the request of extended permissions for when they are really required.
For example. Say that my original login request for two extended profile properties:
<fb:login-button show-faces="true" width="200" max-rows="1"
scope="user_photos, friends_photos">
</fb:login-button>
Now, at some latter point, the user wishes to upload a photo back their profile using my app (which requires publish_actions).
As I understand it my App have to:
Check if the user have already granted that permission (say, with FB.api('/me/permissions')) to avoid triggering a login flow for no reason
Ask for the new permission by means of performing a new login:
FB.login(function(response) {
// handle the response
}, {scope: 'publish_actions'});
Perform a second permission check to see if the user have granted the new permission. If the user have granted the new permission perform the upload, else display some kind of error message explaining to the user that he should grant the new permission before being able to upload.
And my doubts are:
On the second step outlined above, should I ask only for publish_actions or should I also request already granted permissions?
{scope: 'user_photos, friends_photos, publish_actions'});
I'm using a Login on Client, API Calls from Server model:
So, at the moment that I request the new permission, my server will be holding a long-lived access token with the two initial permissions (user_photos, friends_photos). If the user grants publish_actions, am I supposed to go through the entire server side token exchange process again (using the new short lived access-token) before uploading?
GET /oauth/access_token?
grant_type=fb_exchange_token&
client_id={app-id}&
client_secret={app-secret}&
fb_exchange_token={new-short-lived-token}
Or will the new permission be promptly available for the long-lived token?
Answering my own question to anyone facing the same problem.
Yes, granted permissions are cumulative.
Should I ask only for publish_actions or should I also request already granted permissions?
I only need to ask for new permissions. Previously granted permissions remain available.
If the user grants publish_actions (with FB.login), am I supposed to go through the entire server side token exchange process again (using the new short lived access-token) before uploading? Or will the new permission be promptly available for the long-lived token?
The client side call to FB.login suffices. If the user grants publish_actions I can use the previously stored server side token to post content.
One small gotcha: When you call FB.login the user may skip the permission again (and it will return response.status === "connected"). So I had to actually double check for permissions:
Function to check permissions with callbacks:
function checkPermissions(perms, callback, failCallback) {
FB.api('/me/permissions', function (response) {
var fbPerms = response.data[0];
var haveAllPermissions = true;
if (typeof perms === 'string') {
perms = [ perms ];
}
for (var i in perms) {
if (fbPerms[perms[i]] == null) {
haveAllPermissions = false;
break;
}
}
if (haveAllPermissions) {
callback();
} else {
failCallback();
}
});
}
Assuming that photoUpload is a function that requires the publish_actions permission, here is the usage pattern that worked for me:
// First check
checkPermissions("publish_actions",
// If the required permissions have already been granted
// call the desired method
photoUpload,
// else
function () {
// Asks for permission
FB.login(function () {
// double check - the usar may have skiped the permission again
checkPermissions("publish_actions",
// if the user granted the permission, call the desired method
photoUpload,
// else cancel everything and warn the user
function () {
alert("Can't post without permissions");
});
}, {scope: "publish_actions"});
});