creating GCM client app, asynctask fails - google-cloud-messaging

While creating a GCM client application, asynctask is giving compilation errors.
OnCreate we are calling registerBackgrouod which will check whether gcm instance is running or not, if not create one.
But asyntask is giving error : "Asynctask cannot be resolved to a type"
private void registerBackground() {
new AsyncTask() {
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration id=" + regid;
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
// For this demo: we don't need to send it because the device
// will send upstream messages to a server that echo back the message
// using the 'from' address in the message.
// Save the regid - no need to register again.
setRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);

As already observed by the AlexBcn, and according to the documentation of AsyncTask, you would pass to the AsyncTask three types as param. Because you want to return the payload of the GCM push notification as a String, you would invoke AsyncTask<Void, Void, String>
So the correct code snippet of GCM client is:
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your app.
// For this demo: we don't need to send it because the device will send
// upstream messages to a server that echo back the message using the
// 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}.execute(null, null, null);
}

This is because of the params you pass in to Async task.
For further help:
I recently uploaded the fully functional GCM java client to my Github Account:
GCM Android Client
It has got both server and client implementation.

Related

How can a client know if it is already subscribed to a MQTT topic?

I'm subscribing to a MQTT Topic(in my case it is app unique user id).I'm using AWS IOT core services for subscription.Whenever home screen opens up and I got connected callback from awsConnectClient,I make the call for subscription. Now what is happening if app gets open up three times It subscribed to the same topic 3 time.Now whenever any message publish to that topic.It received by app 3 times.
Now what I want to do that I want to know that if this userId is already subscribed from this device I would not make a call for subscription again from same device.
One approach could be If I save in my app that I had already subscribed to this topic and do not make the call for subscription again. but I doubt if this approach could be correct for all scenarios.Could it be possible that we could drive this logic from the server end only, if any aws iot api could give me that this is already subscribed.
fun connectClick() {
Log.d(TAG, "clientId = $clientId")
try {
mqttManager.connect(clientKeyStore) { status, throwable ->
Log.d(TAG, "Status = " + status.toString())
var formattedStatus = String.format(getString(R.string.status_msg),status.toString())
if (status == AWSIotMqttClientStatusCallback.AWSIotMqttClientStatus.Connected) {
Log.i(TAG, " subscribed to - " + VoiceXPreference(this).rosterName)
unsubscribe()
subscribeClick(VoiceXPreference(this).rosterName)
}
runOnUiThread {
tv_iot_status.text = formattedStatus
if (throwable != null) {
Log.e(TAG, "Connection error.", throwable)
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Connection error.", e)
}
}
Above is my subscription code.Although I'm always unsubscribing before subscribing but this is not working for me.
Following is my initClient call which makes the connection request. I have added the if check if mqttManager is already initialised first disconnect and then make connect request. Although I have put initRequest inside onCreate() call back of app screen which calls only once when the app opens up. I have checked the logs it is being called only once.
AWSMobileClient.getInstance().initialize(this, object : Callback<UserStateDetails> {
override fun onResult(result: UserStateDetails) {
Log.i(TAG,"connect request called");
if(mqttManager != null){
mqttManager?.disconnect()
}
initIoTClient()
}
override fun onError(e: Exception) {
Log.e(TAG, "onError: ", e)
}
})
Following is my subscribe code snippet which is subscribing to unique userId
fun subscribeClick(topic: String) {
Log.d(TAG, "topic = $topic")
try {
mqttManager?.subscribeToTopic(topic, AWSIotMqttQos.QOS0,
{ topic, data ->
runOnUiThread {
try {
val message = String(data, Charsets.UTF_8)
Log.d(TAG, "Message arrived:")
Log.d(TAG, " Topic: $topic")
Log.d(TAG, " Message: $message")
val gson = Gson()
val notificationModel = gson.fromJson(message, NotificationModel::class.java)
var orderServiceMapperResponseModel = OrderServiceMapperResponseModel()
orderServiceMapperResponseModel.seatId = notificationModel.seatId
orderServiceMapperResponseModel.serviceName = notificationModel.service
orderServiceMapperResponseModel.id = notificationModel.id
orderServiceMapperResponseModel.createdDate = notificationModel.createdDate
serviceList.add(orderServiceMapperResponseModel)
if (isPictureInPictureMode) {
if (isShownNotification) {
updateNotificationCount()
} else {
updatePIPWindowContent()
}
} else {
updateAdapterDataSource()
}
} catch (e: UnsupportedEncodingException) {
Log.e(TAG, "Message encoding error.", e)
}
}
})
} catch (e: Exception) {
Log.e(TAG, "Subscription error.", e)
}
}
I'm also always making disconnect() request inside onDestroy() of my app screen
mqttManager?.disconnect()
But Still I'm getting 3 subscription messages instead of 1.
You receive 3 duplicated messages not because you subscribe 3 times but because you create 3 individual connections.
The MQTT specification clearly states that
If a Server receives a SUBSCRIBE Packet containing a Topic Filter that is identical to an existing Subscription’s Topic Filter then it MUST completely replace that existing Subscription with a new Subscription.
meaning duplicated subscriptions per connection never happen, unless the server has a broken implementation.
Your code looks like that it never send disconnect requests while a new connection is created whenever the code block is invoked.
You should keep a single MQTT session, or make sure you close the connection when the app is closed.

FCM Authorization always fails

Today i wanted to switch from GCM to FCM so i set up everything needed and wanted to implement the server side code. I used the gcm4j library and changed it so that the adress goes to https://fcm.googleapis.com/fcm/send.
So im doing the following:
FCM fcm = new FCMDefault(new FCMConfig().withKey(FCMGlobals.FCM_API_KEY));
FCMRequest request = new FCMRequest().withRegistrationId(android.getRegistration())
// .withCollapseKey(collapseKey)
.withDelayWhileIdle(true)
.withDataItem(FCMGlobals.FCM_PARAM_CODE, code)
.withDataItem(FCMGlobals.FCM_PARAM_USER_ID, "" + user.getId())
.withDataItem(FCMGlobals.FCM_PARAM_ADDITION, "" + addition);
ListenableFuture<FCMResponse> responseFuture = fcm.send(request);
Futures.addCallback(responseFuture, new FutureCallback<FCMResponse>() {
public void onFailure(Throwable t) {
log.error(t);
}
#Override
public void onSuccess(FCMResponse response) {
log.info(response.toString());
}
});
The implementation for that is:
protected FCMResponse executeRequest(FCMRequest request) throws IOException {
byte[] content = this.objectMapper.writeValueAsBytes(request);
HttpURLConnection conn = this.connectionFactory.open(this.fcmUrl);
conn.setRequestMethod("POST");
conn.addRequestProperty("Authorization", getAuthorization(request));
conn.addRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(content.length);
LoggerFactory.getLogger("FCMDefaultAbstract").info("Authorization: " + conn.getRequestProperty("Authorization"));
LoggerFactory.getLogger("FCMDefaultAbstract").info("Content-Type: " + conn.getRequestProperty("Content-Type"));
LoggerFactory.getLogger("FCMDefaultAbstract").info("send: " + new String(content));
try (OutputStream outputStream = conn.getOutputStream()) {
IOUtils.write(content, outputStream);
} catch (Exception e) {
throw new FCMNetworkException("Error sending HTTP request to FCM", e);
}
FCMResponse response;
try (InputStream inputStream = conn.getInputStream()) {
response = this.objectMapper.readValue(IOUtils.toByteArray(inputStream), FCMResponse.class);
} catch (IOException e) {
try (InputStream inputStreamError = conn.getErrorStream()) {
String str = inputStreamError != null ? IOUtils.toString(inputStreamError) : "No error details provided";
int responseCode = conn.getResponseCode();
if (responseCode < 500) {
throw new FCMNetworkException(conn.getResponseCode(), str.trim(), e);
} else {
throw new FCMNetworkException(conn.getResponseCode(), str.trim(), checkForRetryInResponse(conn), e);
}
}
}
response.setRequest(request);
response.setRetryAfter(checkForRetryInResponse(conn));
Iterator<String> iteratorId = request.getRegistrationIds().iterator();
Iterator<FCMResult> iteratorResponse = response.getResults().iterator();
while (iteratorId.hasNext() && iteratorResponse.hasNext()) {
iteratorResponse.next().setRequestedRegistrationId(iteratorId.next());
}
if (iteratorId.hasNext()) {
LOG.warn("Protocol error: Less results than requested registation IDs");
}
if (iteratorResponse.hasNext()) {
LOG.warn("Protocol error: More results than requested registation IDs");
}
return response;
}
Here the log output:
FCMDefaultAbstract Authorization: null
FCMDefaultAbstract Content-Type:application/json
FCMDefaultAbstract send: {"registration_ids":["dMpvzp*************************************2lRsSl_5lFET2"],"data":{"CODE":"201","USER_ID":"1","ADDITION":"1468083549493"},"delay_while_idle":true}
FCM FCMNetworkException: HTTP 401: No error details provided
The Authorization header is not null in fact. it is correctly set with my FCM API Key. Only the HTTPUrlConnection implementation says to return null if someone trys to access Authorization key.
As you can see i am not able to connect with FCM. The Code 401 means that authentication failed.
What could be the problem here?
Check that you are using a server type API-KEY, and not a client or browser API-KEY.
If you are using Firebase you can find the API-KEY in
Project Settings > Cloud Messaging
If you are using cloud console, or you are not sure which key you are using,
you can generate a new key through through https://console.cloud.google.com
Quoting the documentation
https://firebase.google.com/docs/cloud-messaging/concept-options#credentials
Server key: A server key that authorizes your app server for access to
Google services, including sending messages via Firebase Cloud
Messaging. [...]
Important: Do not include the server key anywhere in your client code.
Also, make sure to use only server keys to authorize your app server.
Android, iOS, and browser keys are rejected by FCM.

Netty: How to implement a telnet client handler which needs authentication

This is my first time ask question through this platform. I am sorry. I am not good in English. I will try my best to let you understand my questions.
I am totally beginner in Netty. I would like to implement a program to send commands to a telnet server and receive response message. I modified the sample telnet program to connect and get response from the serve when there is no authentication of serve.
The question is that
When the authentication processes are setup in server. (Require login name and password)
How to implement the client side program?
How can I receive the serve login request and response it?
Should I implement another handler to handle the authentication?
below shows how i send the commands to the server
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new TelnetClientInitializer(sslCtx));
// Start the connection attempt.
ChannelFuture lastWriteFuture = null;
lastWriteFuture = b.connect(HOST, PORT).sync();
Channel ch = lastWriteFuture.channel();
lastWriteFuture = ch.writeAndFlush("ls" + "\r\n", ch.newPromise());
lastWriteFuture = ch.writeAndFlush("status" + "\r\n");
lastWriteFuture = ch.writeAndFlush("ls" + "\r\n");
lastWriteFuture = ch.writeAndFlush("exit" + "\r\n");
// Wait until the connection is closed.
lastWriteFuture.channel().closeFuture().sync();
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
but what should i do before send the above commands to login into the serve?
The following picture shows what i want to do in the program
Thank you very much!!!
If we talk about TELNET as a protocol you should know that Telnet client from Netty examples does not support TELNET protocol. His name is just confusing and you can't connect to any standard telnet servers. You can read more about TELNET protocol here - THE TELNET PROTOCOL .
I see 2 ways:
write your implementation for TELNET on Netty
use another implementation for examples Apache Commons Net
Example for the first way - modified netty client, i tested him on Linux servers. He has several dirty hacks like a timer but he works.
Example for the second - Java – Writing An Automated Telnet Client:
import org.apache.commons.net.telnet.*;
import java.io.InputStream;
import java.io.PrintStream;
public class AutomatedTelnetClient {
private TelnetClient telnet = new TelnetClient();
private InputStream in;
private PrintStream out;
private String prompt = "~>";
public AutomatedTelnetClient(String server) {
try {
// Connect to the specified server
telnet.connect(server, 8023);
TerminalTypeOptionHandler ttopt = new TerminalTypeOptionHandler("VT100", false, false, true, false);
EchoOptionHandler echoopt = new EchoOptionHandler(true, false, true, false);
SuppressGAOptionHandler gaopt = new SuppressGAOptionHandler(true, true, true, true);
try {
telnet.addOptionHandler(ttopt);
telnet.addOptionHandler(echoopt);
telnet.addOptionHandler(gaopt);
} catch (InvalidTelnetOptionException e) {
System.err.println("Error registering option handlers: " + e.getMessage());
}
// Get input and output stream references
in = telnet.getInputStream();
out = new PrintStream(telnet.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
// public void su(String password) {
// try {
// write(“su”);
// readUntil(“Password: “);
// write(password);
// prompt = “#”;
// readUntil(prompt + ” “);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
public String readUntil(String pattern) {
try {
char lastChar = pattern.charAt(pattern.length() - 1);
StringBuffer sb = new StringBuffer();
boolean found = false;
char ch = (char) in.read();
while (true) {
System.out.print(ch);
sb.append(ch);
if (ch == lastChar) {
if (sb.toString().endsWith(pattern)) {
return sb.toString();
}
}
ch = (char) in.read();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void write(String value) {
try {
out.println(value);
out.flush();
System.out.println(value);
} catch (Exception e) {
e.printStackTrace();
}
}
public String sendCommand(String command) {
try {
write(command);
return readUntil(prompt + " ");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void disconnect() {
try {
telnet.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String user = "test";
String password = "test";
AutomatedTelnetClient telnet = new AutomatedTelnetClient("localhost");
// Log the user on
telnet.readUntil("login:");
telnet.write(user);
telnet.readUntil("Password:");
telnet.write(password);
// Advance to a prompt
telnet.readUntil(telnet.prompt + " ");
telnet.sendCommand("ps -ef");
telnet.sendCommand("ls");
telnet.sendCommand("w");
telnet.disconnect();
}
}
Telnet has no real concept of a password packet, a password prompt is just like any normal text output. This means that you can just send the username and password when connection as separate lines, and the telnet server will use them correctly.
ch.writeAndFlush("administrator" + "\r\n");
ch.writeAndFlush("LetMeIn4!!" + "\r\n");
If you require connecting to server that don't always require the password, then you should read the output from the server, check if it contains "username", send the username, then keep reading if it contains "password" and send the password. This is prone to breaking as servers are not required to send those strings, and legit output may also contain those. This is the downside of the telnet protocol.
I hope this my article is helpful to someone.
Netty | Implement Telnet Automated Authentication
I had to use Telnet to control the sub-equipment while developing the space ground station software. Except for the authentication, Telnet is quite similar to regular TCP server communication. So, I implemented a Handler that automatically handles Telnet authentication to communicate with the Telnet server. When connecting to the Telnet server, the following introductory message, “Username: “, “Passwrod: “ messages are displayed in sequence, and user authentication is requested. Handler automatically handles the authentication process as if a human would input account information. Below is a brief description of the implementation.
c:\> telnet 192.168.0.1 12345
Power On Self Test (POST) Passed.
Integrated Control Unit (ICU) Build xxx (Build:xxxxxx) - Feb 7 2022, 17:57:16 (Network/TCP)
Date and Time: 2022-02-16 20:01:19 (GMT)
MAC Address : [00:xx:xx:xx:C6:8F]
Username: User
Password: 1234
>
Handler
TelnetAuthenticator Handler simply works as follows.
If the message contains the string “Username: “, send the username.
If the message contains the string “Password: “, the password is sent.
If the message contains the string “>” waiting for input, delete the authentication handler from the Pipeline. After authentication, TelnetAuthenticator Handler is unnecessary.
If the account is not registered on the Telnet server or the password does not match, the string “Username: “ or “Password: “ is repeatedly received. The authentication failure error is unrecoverable, notifying the user of a failed authentication process and forcing them to disconnect.
#Slf4j
#RequiredArgsConstructor
public class TelnetAuthenticator extends SimpleChannelInboundHandler<String> {
private final ChannelSpec channelSpec;
private boolean alreadyUserTried = false;
private boolean alreadyPasswordTried = false;
#Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
// If the message contains the string “Username: “, send the username.
if (msg.contains(channelSpec.getReqUserTag())) {
if (alreadyUserTried) {
processFail(ctx);
}
ctx.channel().writeAndFlush(channelSpec.getAccount().getUser() + channelSpec.getEndLine());
alreadyUserTried = true;
return;
}
// If the message contains the string “Password: “, the password is sent.
if (msg.contains(channelSpec.getReqPasswordTag())) {
if (alreadyPasswordTried) {
processFail(ctx);
}
ctx.channel().writeAndFlush(channelSpec.getAccount().getPassword() + channelSpec.getEndLine());
alreadyPasswordTried = true;
return;
}
// If the incoming message contains an input waiting message, the Pipeline deletes the current handler.
if (msg.contains(channelSpec.getStandByTag())) {
ctx.pipeline().remove(this.getClass());
}
}
private void processFail(ChannelHandlerContext ctx) {
ctx.fireUserEventTriggered(ErrorMessage.AUTHENTICATE_FAIL);
ctx.close();
}
}
Initialize ChannelPipeline
A ChannelPipeline configuration with a TelnetAuthenticator Handler can be: First, register InboundHandlers as follows.
First, add DelimiterBasedFrameDecoder with “Username: “, “Password: “, “>” strings as delimiters. The stripDelimiter option is set to false because all delimiters must be received to recognize the authentication process.
Add StringDecoder.
Add the implemented TelnetAuthenticator Handler.
Add other necessary business logic.
Simply add StringEncoder to Outbound. You can add other Handlers as needed.
public class PipelineInitializer extends ChannelInitializer<SocketChannel> {
private ChannelSpec channelSpec;
public void init(ChannelSpec channelSpec) {
this.channelSpec = channelSpec;
}
#Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
// Inbound
.addLast(new DelimiterBasedFrameDecoder(1024, false,
channelSpec.getDelimiter().reqUserTag(),
channelSpec.getDelimiter().reqPasswordTag(),
channelSpec.getDelimiter().standByTag()))
.addLast(new StringDecoder())
.addLast(new TelnetAuthenticator(channelSpec))
.addLast(new BusinessLogic())
// Outbound
.addLast(new StringEncoder());
}
}
ChannelSpec
ChannelSpec defines specifications required for communication with Telnet server. Manage server IP, port, account information, separator, etc.
#Getter
public class ChannelSpec {
private final String serverIp = "192.168.0.1";
private final int serverPort = 12345;
private final String endLine = "\r\n";
private final String standByTag = ">";
private final String reqUserTag = "Username: ";
private final String reqPasswordTag = "Password: ";
private final Account account = new Account("User", "1234");
private final Delimiter delimiter = new Delimiter();
public class Delimiter {
public ByteBuf standByTag() {
return toByteBuf(standByTag);
}
public ByteBuf reqUserTag() {
return toByteBuf(reqUserTag);
}
public ByteBuf reqPasswordTag() {
return toByteBuf(reqPasswordTag);
}
private ByteBuf toByteBuf(String input) {
ByteBuf delimiterBuf = Unpooled.buffer();
delimiterBuf.writeCharSequence(input, StandardCharsets.UTF_8);
return delimiterBuf;
}
}
}
#RequiredArgsConstructor
#Getter
public class Account {
private final String user;
private final String password;
}

How to consume an object from azure service bus topic subscription

I got this error upon receving an object from a subscription in azure service bus.
An exception of type 'System.Runtime.Serialization.SerializationException' occurred in System.Runtime.Serialization.dll but was not handled in user code
I've tried some deserialization code but nothing works.
This is how I send a message. Please tell me how to receive it.
public void SendMessage()
{
BrokeredMessage message = new BrokeredMessage(new TestMessage() {
MsgNumber = 1, MsgContent = "testing message" }, new DataContractSerializer(typeof(TestMessage)));
// Send message to the topic
TopicClient topicClient = TopicClient.CreateFromConnectionString(cn, topicNamespace);
topicClient.Send(message);
}
public string ReceiveMessage(){
//??????
}
To receive a single message, you need to get the SubscriptionClient :
public void ReceiveMessage(string connectionString, string topicPath, string subscriptionName)
{
var subscriptionClient = SubscriptionClient.CreateFromConnectionString(connectionString, topicPath, subscriptionName);
var brokeredMessage = subscriptionClient.Receive();
var message = brokeredMessage.GetBody<TestMessage>();
}

Xamarin.Auth with Google APIs: Renew credentials?

I'm trying to use Xamarin.Auth with the Xamarin Google-APIs to login to Google and access Drive. I've managed to get nearly everything working, but the authentication tokens seem to expire after about an hour. Everything works great for awhile, but after about an hour, when I attempt access, I get an Invalid Credentials [401] error and a leak:
Google.Apis.Requests.RequestError
Invalid Credentials [401]
Errors [
Message[Invalid Credentials] Location[Authorization - header] Reason[authError] Domain[global]
]
: GoogleDriveAgent: FetchRemoteFileList() Failed! with Exception: {0}
[0:] Google.Apis.Requests.RequestError
Invalid Credentials [401]
Errors [
Message[Invalid Credentials] Location[Authorization - header] Reason[authError] Domain[global]
]
: GoogleDriveAgent: FetchRemoteFileList() Failed! with Exception: {0}
objc[37488]: Object 0x7f1530c0 of class __NSDate autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
objc[37488]: Object 0x7f151e50 of class __NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
//...more leaks.
I'd like to make sure I'm using Xamarin.Auth and the Google APIs as intended, so here is my code:
In my GoogleDriveService class, I've got an account store and a saved account:
AccountStore Store {
get {
if (m_store == null)
m_store = AccountStore.Create ();
return m_store;
}
}
Account SavedAccount {
get {
var savedAccounts = Store.FindAccountsForService ("google");
m_savedAccount = (savedAccounts as List<Account>).Count > 0 ? (savedAccounts as List<Account>) [0] : null;
return m_savedAccount;
}
}
I initialize a session and start the service:
void InitializeSession ()
{
Authenticator = new GoogleAuthenticator (ClientID, new Uri (RedirectUrl), GoogleDriveScope);
Authenticator.Completed += HandleAuthenticationCompletedEvents;
if (SavedAccount != null) {
Authenticator.Account = SavedAccount;
StartService ();
}
UpdateSignInStatus ();
}
bool StartService ()
{
try {
Service = new DriveService (Authenticator);
return true;
} catch (Exception ex) {
// Log exception
return false;
}
}
...and respond to authentication completed events:
void HandleAuthenticationCompletedEvents (object sender, AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated) { // Success
UpdateSignInStatus();
Store.Save (e.Account, "google");
Authenticator.Account = e.Account;
StartService();
LoginController.DismissViewController(true, null);
} else { // Cancelled or no success
UpdateSignInStatus();
LoginController.DismissViewController(true, null);
LoginController = null;
InitializeSession (); // Start a new session
}
}
Again, everything works fine, for awhile, but then the authentication expires. I understand that it should, but I thought the credentials saved in the AccountStore ought to still work.
In the Xamarin.Auth getting started docs, it says that calling Save again will overwrite the credentials and that "This is convenient for services that expire the credentials stored in the account object." Sounds promising...
So I tried another approach: having an IsSignedIn property that always overwrites the credentials in the getter...
public bool IsSignedIn {
get {
if (Authenticator == null) {
m_isSignedIn = false;
return m_isSignedIn;
}
if (Authenticator.Account != null) {
Store.Save (Authenticator.Account, "google"); // refresh the account store
Authenticator.Account = SavedAccount;
m_isSignedIn = StartService ();
} else {
m_isSignedIn = false;
}
return m_isSignedIn;
}
}
...and then I access IsSignedIn before any API calls (Fetching metadata, downloading, etc). It doesn't work: I'm still getting expired credentials errors shown above.
Is this a case of needing to refresh the token? What am I doing wrong?
Access tokens are supposed to expire relatively quickly. This is why after the first auth you also receive a refresh_token that you can use to get a new access token if the current one expires. Consecutive auths will not give you a refresh token necessarily, so make sure you keep the one you receive!
All you have to do after an access token becomes invalid is use the refresh_token and send an OAuthRequest to the token_url of Google's OAuth endpoint.
var postDictionary = new Dictionary<string, string>();
postDictionary.Add("refresh_token", googleAccount.Properties["refresh_token"]);
postDictionary.Add("client_id", "<<your_client_id>>");
postDictionary.Add("client_secret", "<<your_client_secret>>");
postDictionary.Add("grant_type", "refresh_token");
var refreshRequest = new OAuth2Request ("POST", new Uri (OAuthSettings.TokenURL), postDictionary, googleAccount);
refreshRequest.GetResponseAsync().ContinueWith (task => {
if (task.IsFaulted)
Console.WriteLine ("Error: " + task.Exception.InnerException.Message);
else {
string json = task.Result.GetResponseText();
Console.WriteLine (json);
try {
<<just deserialize the json response, eg. with Newtonsoft>>
}
catch (Exception exception) {
Console.WriteLine("!!!!!Exception: {0}", exception.ToString());
Logout();
}
}
});