I am working on a project that retrieve Google Drive docs list using ASP.NET, but I'm getting an error:
An error occurred: Google.Apis.Requests.RequestError Daily Limit for
Unauthenticated Use Exceeded. Continued use requires signup. [403]
This is my site (getting error on this link) : http://www.netdesklive.com/
I am trying DrEdit code for this, but not working proper.
I set all the credential as per https://developers.google.com/drive/examples/dotnet but still i m getting an error
So Please, Suggest me
Code :
-> I am getting null value in state and code
public ActionResult Index(string state, string code)
{
try
{
IAuthenticator authenticator = Utils.GetCredentials(code, state);
// Store the authenticator and the authorized service in session
Session["authenticator"] = authenticator;
Session["service"] = Utils.BuildService(authenticator);
}
catch (CodeExchangeException)
{
if (Session["service"] == null || Session["authenticator"] == null)
{
Response.Redirect(Utils.GetAuthorizationUrl("", state));
}
}
catch (NoRefreshTokenException e)
{
Response.Redirect(e.AuthorizationUrl);
}
DriveState driveState = new DriveState();
if (!string.IsNullOrEmpty(state))
{
JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
driveState = jsonSerializer.Deserialize<DriveState>(state);
}
if (driveState.action == "open")
{
return OpenWith(driveState);
}
else
{
return CreateNew(driveState);
}
}
Your error message suggests that your requests are not authorized. You should authorize them using Oauth2 :
Authorizing Drive requests
Google Oauth2 documentation
Related
PROBLEM: I'm using GoogleBilling V5 to process in-app purchases. I use a Firebase firestore to store purchase tokens for verification. I am attempting to use Volley to check the store to see if a token exists. However, Volley never processes the POST method and instead returns with
E/Volley: [591] NetworkUtility.shouldRetryException: Unexpected response code 403 for https://MY_URL.cloudfunctions.net/verifyPurchases?PurchaseToken=REALLY_LONG_PURCHASE_TOKEN_STRING&purchaseTime=TIMESTAMP_LONG&orderId=ORDER_ID_STRING/
Where REALLY_LONG_PURCHASE_TOKEN_STRING, TIMESTAMP_LONG, and ORDER_ID_STRING look acceptable. That is, the token is being generated, the timestamp is appearing as a Long datatype (correct), and the OrderId is consistent with GoogleBilling.
SETUP:
Added dependencies in gradle
def billing_version = '5.0.0'
implementation "com.android.billingclient:billing:$billing_version"
def volley_version = "1.2.1"
implementation "com.android.volley:volley:$volley_version"
Verified that purchaseToken,purchaseTime,and orderId all match EXACTLY with what was updated to the the firebase functions.
Curent Firestore Rules are:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Followed this tutorial
(Selling In-App Products on Android: Implementing Google Play Billing V4): https://www.youtube.com/watch?v=KYFM2z5KPq0
Migrated to V5 per Google's migration guide: https://developer.android.com/google/play/billing/migrate-gpblv5
I instantiate the BillingClient within my singleton class as follows. The verifyInAppPurchase function is called within the PurchasesUpdatedListener.
...
billingClient = BillingClient.newBuilder(mContext)
.enablePendingPurchases()
.setListener(new PurchasesUpdatedListener() {
#Override
public void onPurchasesUpdated(#androidx.annotation.NonNull BillingResult billingResult, #Nullable #org.jetbrains.annotations.Nullable List<Purchase> list) {
//Once we receive a response from Google, we have to verify whether or not the purchase has been made
//via our Firestore database
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null){
for(Purchase purchase : list){
if(purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged()){
//Now that we've verified that there is indeed a purchase, we have to verify that the purchase token hasn't been used
// and is valid. We do this by storing all the purchase tokens in a database. New purchases won't be in the database yet.
verifyInAppPurchase(purchase);
}
}
}
}
}).build();
...
verifyInAppPurchase:
I'm almost positive the issue is in here somewhere.
Should I be using my app's SHA1 key somewhere to validate the client?
NOTE: When I just copy/paste the requestURL into a browser, I get a PERMISSION DENIED page
public void verifyInAppPurchase(Purchase purchase){
String requestURL = "https://MY_URL.cloudfunctions.net/verifyPurchases?"
+ "purchaseToken=" + purchase.getPurchaseToken() + "&"
+ "purchaseTime=" + purchase.getPurchaseTime() + "&"
+ "orderId=" + purchase.getOrderId() + "/";
Log.d("verifyInAppPurchase", "verifyInAppPurchase::RequestURL: " + requestURL); //This is as far as it gets. No other LOG messages appear (the onResponse Listener is never invoked)
StringRequest stringRequest = new StringRequest(
Request.Method.POST,
requestURL,
new Response.Listener<String>(){
#Override
public void onResponse(String response){
/* Once we made it in here, we know the user did *something* so we have to
* figure that out.
*/
try{
JSONObject purchaseInfoFromServer = new JSONObject(response);
Log.d("verifyInAppPurchase", "verifyInAppPurchase::Was the purchase valid?"); //never gets here
//Was the purchase valid?
if(purchaseInfoFromServer.getBoolean("isValid")){
//(yes!) Okay, cool. Well, we should acknowledge that (or we don't get paid)
Log.d("verifyInAppPurchase", "verifyInAppPurchase::Was the purchase valid? YES!");
AcknowledgePurchaseParams ackPurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
//So we acknowledge here and then set up a listener for when Google responds to our acknowledgement.
billingClient.acknowledgePurchase(
ackPurchaseParams,
new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(#NonNull BillingResult billingResult) {
Log.d("verifyInAppPurchase", "verifyInAppPurchase::onAcknowledgePurchaseResponse: Acknowledged!");
//Google responded! Now, we have to deliver the goods!
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
Log.d("verifyInAppPurchase", "verifyInAppPurchase::onAcknowledgePurchaseResponse: BillingResponseCode OK");
handlePurchase(purchase);
}
}
}
);
}
} catch (Exception e){
Log.d("verifyInAppPurchase", "verifyInAppPurchase::onResponse ERROR: " + e.toString());
}
}
},
new Response.ErrorListener(){
#Override
public void onErrorResponse(VolleyError error){
NetworkResponse response = error.networkResponse;
if (error instanceof ServerError && response != null) {
try {
String res = new String(response.data, HttpHeaderParser.parseCharset(response.headers, "utf-8"));
// Now you can use any deserializer to make sense of data
JSONObject obj = new JSONObject(res);
//Log.d("verifyInAppPurchase", "verifyInAppPurchase::Volley ERROR: " + obj.toString());
} catch (UnsupportedEncodingException e1) {
// Couldn't properly decode data to string
e1.printStackTrace();
//Log.d("verifyInAppPurchase", "verifyInAppPurchase::Volley ERROR: " + e1.toString());
} catch (JSONException e2) {
// returned data is not JSONObject?
e2.printStackTrace();
//Log.d("verifyInAppPurchase", "verifyInAppPurchase::Volley ERROR: " + e2.toString());
}
}
Log.d("verifyInAppPurchase", "verifyInAppPurchase::Volley ERROR: " + error.toString());
}
}
){
#Override
public String getBodyContentType(){
return "application/json";
}
};
// Volley should run on UI thread per
//https://google.github.io/volley/simple.html
Handler mainHandler = new Handler(mContext.getMainLooper());
Runnable myRunnable = () -> Volley.newRequestQueue(mContext).add(stringRequest);
mainHandler.post(myRunnable);
}
Incidentally, what changes will I have to make to ensure this is only ever accessed by my app? I'm currently thinking I'll have to change the rules to reference the 'auth' variable per (https://firebase.google.com/docs/database/security/rules-conditions), but that involves using the whole Firebase Authentication SDK which feels overkill. Is there a cleaner way of securing the function without forcing users to log in?
We use PushSharp 4.0.10 to send iOS Push Notifications:
https://github.com/Redth/PushSharp
Recently we recieved this email from Apple Developer:
"If you still send push notifications with the legacy binary protocol, it's time to update to the HTTP/2-based Apple Push Notification service (APNs) provider API. You'll be able to take advantage of great features, such as authentication with a JSON Web Token, improved error messaging, and per-notification feedback.
To give you additional time to prepare, the deadline to upgrade to the APNs provider API has been extended to March 31, 2021. We recommend upgrading as soon as possible, as APNs will no longer support the legacy binary protocol after this date."
My question is: Will PushSharp 4.0.10 still work after March 31, 2021?
There is a discussion about this but the thread was closed. But there are still some suggestions on this thread that you might want to try.
The Apple Push Notification service (APNs) will no longer support the legacy binary protocol as of November 2020
https://github.com/Redth/PushSharp/issues/923
**
EDIT - 25th March 2021
The deadline is close and #Ashita Shah asked some code snippet so I hope the following can save your time.
Add the following class dotAPNSService to your project. You can customise this structure according to your needs. Also I didn't focus the best of best coding C# standards when implementing my own push notification service. You can implement LINQ, Tasks async etc. I tested this dotAPNS library and it works perfectly fine. For Android you can still use PushSharp.
Before you implement the dotAPNSService helper class, get the following from your Apple developer account. The ApnsJwtOptions values should be:
BundleId - your app’s bundle ID. Should not include specific topics (i.e. com.myapp but not com.myapp.voip).
CertFilePath - path to the .p8 certificate you have downloaded from the Developer Center.
KeyId - The 10-character Key ID you obtained from your developer account
TeamId - The 10-character Team ID you use for developing your company’s apps. Obtain this value from your developer account.
public class dotAPNSService : IDisposable
{
public event EventHandler OnTokenExpiredHandler;
private ApnsJwtOptions options = null;
public dotAPNSService()
{
options = new ApnsJwtOptions()
{
BundleId = "com.xx.xxxx",
CertFilePath = "../../certificate.p8",
KeyId = "The_Key_Id",
TeamId = "The_Team_Id"
};
}
public void SendNotifications(String[] deviceTokens, String title, String body)
{
if (deviceTokens == null || deviceTokens.Length <= 0)
{
return;
}
if (String.IsNullOrEmpty(title))
{
return;
}
if (String.IsNullOrEmpty(body))
{
return;
}
// once you've gathered all the information needed and created an options instance, it's time to call
var apns = ApnsClient.CreateUsingJwt(new HttpClient(), options);
// start the process
foreach (String deviceToken in deviceTokens)
{
var push = new ApplePush(ApplePushType.Alert)
.AddAlert(title, body)
.AddToken(deviceToken);
Send(apns, push, deviceToken);
}
}
public void SendSilentNotifications(String[] deviceTokens)
{
try
{
if (deviceTokens == null || deviceTokens.Length <= 0)
{
return;
}
// once you've gathered all the information needed and created an options instance, it's time to call
var apns = ApnsClient.CreateUsingJwt(new HttpClient(), options);
// start the process
foreach (String deviceToken in deviceTokens)
{
var push = new ApplePush(ApplePushType.Background)
.AddContentAvailable()
.AddToken(deviceToken);
Send(apns, push, deviceToken);
}
}
finally
{
}
}
private void Send(ApnsClient apns, ApplePush push, String deviceToken)
{
try
{
var response = apns.SendAsync(push);
if (response.Result.Reason == ApnsResponseReason.Success)
{
// the notification has been sent!
}
else
{
Boolean removeToken = false;
switch (response.Result.Reason)
{
case ApnsResponseReason.BadDeviceToken:
removeToken = true;
break;
case ApnsResponseReason.TooManyRequests:
break;
}
// remove the token from database?
if (removeToken)
OnTokenExpired(new ExpiredTokenEventArgs(deviceToken));
}
}
catch (TaskCanceledException)
{
// ERROR - HTTP request timed out, you can use the deviceToken to log the error
}
catch (HttpRequestException ex)
{
// ERROR - HTTP request failed, you can use the deviceToken to log the error
}
}
protected virtual void OnTokenExpired(ExpiredTokenEventArgs args)
{
try
{
EventHandler handler = OnTokenExpiredHandler;
if (handler != null)
{
ISynchronizeInvoke target = handler.Target as ISynchronizeInvoke;
if (target != null && target.InvokeRequired)
target.Invoke(handler, new object[] { this, args });
else
handler(this, args);
}
}
catch (Exception ex)
{
}
}
}
These are the namespaces of the dotAPNSService helper class:
using System;
using System.ComponentModel;
using System.Net.Http;
using System.Threading.Tasks;
using dotAPNS;
In order to use the dotAPNSService helper on your project just pull the tokens from the database and then pass them to it. For instance, to send silent notifications:
public void SendScheduledSilentNotifications()
{
try
{
IList<User> users = _accountService.GetUsers(true);
if (users != null && users.Count > 0)
{
List<String> deviceTokens = new List<String>();
foreach (User user in users)
{
if (!String.IsNullOrEmpty(user.DeviceToken))
deviceTokens.Add(user.DeviceToken);
}
if (deviceTokens.Count > 0)
{
using (dotAPNSService service = new dotAPNSService())
{
service.OnTokenExpiredHandler += new EventHandler(OnTokenExpired);
service.SendSilentNotifications(deviceTokens.ToArray());
}
}
}
}
finally
{
}
}
To remove the expired tokens from the database you can use the following:
private void OnTokenExpired(object sender, EventArgs e)
{
if (e == null)
return;
if (e.GetType() == typeof(ExpiredTokenEventArgs))
{
var args = (ExpiredTokenEventArgs)e;
User user = _accountService.GetUserByDeviceToken(args.Token);
if (user != null)
{
user.DeviceToken = String.Empty;
Boolean success = !(_accountService.SaveUser(user) == null);
if (success)
// INFO - expired device token has been removed from database
else
// INFO - something went wrong
}
}
}
You can download the source code from here:
https://github.com/alexalok/dotAPNS
The API is now sending thousands of silent notifications at one time and there are no delays, crashes etc. Hope this code snippet helps and saves your time!
I'm using firebase anonymous authantication for my unity project.
As i always did when project is started i'm sending request to firebase for authantication,
but on my last project (which uses firebase sdk 6.16.0) my request creates new user everytime.
Here is some code about how i'm sending my request
Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
auth.SignInAnonymouslyAsync().ContinueWith((task =>
{
if (task.IsCanceled)
{
Debug.Log("task cancelled");
return;
}
if (task.IsFaulted)
{
Debug.Log("task cancelled");
return;
}
if (task.IsCompleted)
{
Firebase.Auth.FirebaseUser userr = task.Result;
firebaseUserId = userr.UserId;
Debug.Log("firebaseUserId");
Debug.Log(firebaseUserId);
//every opening returns new uniq id here.
}
}));
On firebase authantication panel i only activated anonymous login. any suggestions?
Or is there any way to downgrade unity firebase version? i've tried to import old version which i was using on my last game (sdk 6.15.2) but there is some errors on resolver.
Basically, every time you call SignInAnonymouslyAsync you'll create a new user and the last one will be basically lost (it's more or less a random hash - anonymous as it's name suggests).
I'll typically do something like:
using System;
using Firebase.Auth;
using UnityEngine;
using UnityEngine.Events;
public class Login : MonoBehaviour
{
public UnityEvent OnSignInFailed = new UnityEvent();
public UserSignedInEvent OnUserSignedIn = new UserSignedInEvent();
public async void TriggerLogin()
{
var auth = FirebaseAuth.DefaultInstance;
var user = auth.CurrentUser;
if (user == null)
{
try
{
user = await auth.SignInAnonymouslyAsync();
}
catch (Exception e)
{
Debug.LogException(e);
OnSignInFailed.Invoke();
return;
}
}
// user definitely should not be null!
if (user == null)
{
OnSignInFailed.Invoke();
Debug.LogWarning("User still null!?");
return;
}
var userName = user.UserId;
OnUserSignedIn.Invoke(userName);
Debug.Log($"Logged in as {userName}");
}
[Serializable]
public class UserSignedInEvent : UnityEvent<string>
{
}
}
Note that for this code snippet, TriggerLogin is a public method so I can chain it off of a UnityEvent in the Unity editor.
Try and Put it some kind of check to find if used is already logged in. If yes, then do a silent login, if no then use anonymous login.
Currently you are straightaway logging in user even if they logged in last time they opened the Application.
Try this link: https://github.com/firebase/quickstart-unity/issues/266#issuecomment-447981995
I have an Asp.Net MVC 5 web application which has an error handling mechanism for log error in DB and showing the errorId to the user who raised that error in a separate form which is called ErrorPage. When an error occurs in the web app I store that errorId in session and I read it from the session in ErrorPage to showing that errorId to the user who faced this error in order to be able for backup operations. The web server of this web application is processing requests with only one worker process currently, so all generated sessions are valid and accessible in the whole of the web app.
I am going to increase the number of worker processes from 1 to 4 for this web app but I have some problem with my web app. Also in IIS, I set the session state mode to In Process mode because of in the web app I used session in many cases and I can't set it to SQL Server mode because of it will increase performance overhead.
The problem is where a request goes in worker process A (for example) and a session will generate for this request in the worker process A and suppose this request encounters an error in web application, I will redirect the user to the ErrorPage and it is possible this new request (redirecting user to ErrorPage's action in ErrorController) goes in another worker process B (for example). But in the worker process B, I can't access that session which is generated for the first request because of that sessions is defined at the worker process level and they are valid only in that worker process.
So after a lot of search for this, I decided to save session info in DB instead of Ram and load it from DB when I need that info. But I have no idea about saving this info in DB with which key ID?
Imagine this scenario to find out my real problem easier:
let's have:
WorkerProcessId1 = W1;
WorkerProcessId2 = W2;
SessionId1 = S1;
SessionId2 = S2;
RequestId1 = R1;
RequestId2 = R2;
and the scenario:
R1 comes to web server
==> web server passes R1 to W1
==> W1 generates S1 for R1
==> R1 faces an error
==> for the user who sends R1 (it is possible the user has not logged in yet so I don't know the userId), I will save the error in DB using the combination of S1 and userId in a specific pattern as a unique identifier in Error table in DB
==> the user will redirect to ErrorPage with another request R2
==> web server passes R2 to W2
==> W2 generates S2 for R2
==> after the redirect is done, in the ErrorPage I need the errorId of that error which I save it to DB, for showing it to the user for backup operations
==> I don't know which error belongs to this user and which error should be load from DB????
If this is not possible to do that, is there any way to have a shared identifier across all worker processes of the web server?
Edit:
In this edit, I will explain where and how I used from the session in my ErrorHandling mechanism. At the end of the target line there is a commented phrase where it is written "Here I am using session":
namespace MyDomain.UI.Infrastructure.Attributes
{
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public CustomHandleErrorAttribute()
{
}
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
{
return;
}
if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
{
return;
}
var errorid = 0;
try
{
errorid = SaveErrorToDatabase(filterContext);
}
catch (Exception e)
{
//Console.WriteLine(e);
//throw;
}
// if the request is AJAX return JSON else view.
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
error = true,
message = "Error Message....",
errorid,
}
};
}
else
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Controller.TempData.Clear();
filterContext.Controller.TempData.Add("ErrorCode", errorid);//Here I am using session
filterContext.Result = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
}
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
private int SaveErrorToDatabase(ExceptionContext exception)
{
MyDomainDBContext dbContext = new MyDomainDBContext();
var browserType = exception.HttpContext.Request.Browser.Capabilities["type"];
var error = new Error
{
ErrorURL = exception.HttpContext.Request.Url.ToString(),
ExceptionType = exception.Exception.GetType().Name,
IsGlobalError = false,
Message = exception.Exception.Message,
StackTrace = exception.Exception.StackTrace,
ThrownTime = DateTime.Now,
UserIP = IPAddress.Parse(exception.HttpContext.Request.UserHostAddress).ToString(),
BrowserName = browserType.ToString() + "," +
GetUserPlatform(exception.HttpContext.Request)
};
AddRequestDetails(exception.Exception, exception.HttpContext.Request, error);
if (exception.Exception.InnerException != null)
{
error.Message += "\n Inner Excpetion : \n " + exception.Exception.InnerException.Message;
if (exception.Exception.InnerException.InnerException != null)
{
error.Message += "\n \t Inner Excpetion : \n " + exception.Exception.InnerException.InnerException.Message;
}
}
if (exception.HttpContext.User.Identity.IsAuthenticated)
{
error.UserID = exception.HttpContext.User.Identity.GetUserId<int>();
}
dbContext.Errors.Add(error);
dbContext.SaveChanges();
return error.ErrorID;
}
private void AddRequestDetails(Exception exception, HttpRequestBase request, Error err)
{
if (exception.GetType().Name == "HttpAntiForgeryException" && exception.Message == "The anti-forgery cookie token and form field token do not match.")
{
if (request.Form != null)
{
if (request.Cookies["__RequestVerificationToken"] != null)
{
err.RequestDetails = "Form : " + request.Form["__RequestVerificationToken"] +
" \n Cookie : " + request.Cookies["__RequestVerificationToken"].Value;
}
else
{
err.RequestDetails = "Does not have cookie for forgery";
}
}
}
}
private String GetUserPlatform(HttpRequestBase request)
{
var ua = request.UserAgent;
if (ua.Contains("Android"))
return $"Android";
if (ua.Contains("iPad"))
return $"iPad OS";
if (ua.Contains("iPhone"))
return $"iPhone OS";
if (ua.Contains("Linux") && ua.Contains("KFAPWI"))
return "Kindle Fire";
if (ua.Contains("RIM Tablet") || (ua.Contains("BB") && ua.Contains("Mobile")))
return "Black Berry";
if (ua.Contains("Windows Phone"))
return $"Windows Phone";
if (ua.Contains("Mac OS"))
return "Mac OS";
if (ua.Contains("Windows NT 5.1") || ua.Contains("Windows NT 5.2"))
return "Windows XP";
if (ua.Contains("Windows NT 6.0"))
return "Windows Vista";
if (ua.Contains("Windows NT 6.1"))
return "Windows 7";
if (ua.Contains("Windows NT 6.2"))
return "Windows 8";
if (ua.Contains("Windows NT 6.3"))
return "Windows 8.1";
if (ua.Contains("Windows NT 10"))
return "Windows 10";
//fallback to basic platform:
return request.Browser.Platform + (ua.Contains("Mobile") ? " Mobile " : "");
}
}
public class IgnoreErrorPropertiesResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (new[]{
"InputStream",
"Filter",
"Length",
"Position",
"ReadTimeout",
"WriteTimeout",
"LastActivityDate",
"LastUpdatedDate",
"Session"
}.Contains(property.PropertyName))
{
property.Ignored = true;
}
return property;
}
}
}
As you can see I filled TempData which will be stored in the session for passing errorId by ErrorCode key to ErrorPage for showing to the user.
I found a temporary solution for passing errorId to ErrorPage by creating a new class which is inherited from HandleErrorInfo with below structure, and using this errorId in ErrorPage:
public class HandleErrorInfoExtension : HandleErrorInfo
{
public HandleErrorInfoExtension(Exception exception, string controllerName, string actionName, int errorId) : base(exception, controllerName, actionName)
{
ErrorId = errorId;
}
public int ErrorId { get; set; }
}
But I don't accept my own answer because of that still I am looking for a real solution to resolve the main problem of this question which is being able to share a data (or data structure) between all worker processes of the application. You should know I used session in some other places of my application that some of these places are vital (like a payment module) so I don't find the main solution for removing the using of the session (except using DB data storing because of performance overhead) yet. So I ask the community of developers of the StackOverflow.com to help me solve this problem.
Thanks to all of you dear colleagues.
AFAIK, the Firebase Instance Token will be refreshed under the following 4 conditions:
App deletes Instance ID
App is restored on a new device
User uninstalls/reinstall the app
User clears app data
Suppose a user is using Token A as his 'FCM address'. Every time when he logs in the app, he will register the Token A to the Firestore along with this user's UUID so user-specific cloud message can be sent to him. When he logs out, the system will fire a request to firestore for removing the token A record.
Now, when the user reinstalls the app, the instance id is refreshed and a new Token B is generated. The Token A becomes useless. Unfortunately, if the user does not log out before the uninstallation, token A will stay in the firestore forever.
Any workaround or wiser way to handle this case?
Keeping your token registry up to date requires two steps:
Remove outdated tokens from your application code.
Check for outdated tokens and remove them when you send messages.
Your approach of removing a token that is no longer used, is #1.
The second step though is to remove tokens from your registry/database when you get a messaging/invalid-registration-token or messaging/registration-token-not-registered response when trying to send a message to it. The functions-samples repo contains a great example of this:
admin.messaging().sendToDevice(tokens, payload).then((response) => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
// TODO: remove the token from your registry/database
}
}
});
});
The above code uses the Firebase Admin SDK for Node.js, but the same logic could also be applied to other platforms or when sending messages through the HTTPS endpoints.
As Frank mentioned in his answer you can remove them when sending messages and getting not registered error.
Here how I delete outdated registration tokens when registering a new one using C#.
First of all using Instance ID API I get token info as following:
public async Task<FCMTokenInfo> GetTokenInfoAsync(string token)
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://iid.googleapis.com");
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", String.Format("key={0}", "your-authorization-key"));
var uri = $"/iid/info/{token}";
var httpResponse = await client.GetAsync(uri);
var responseStr = await httpResponse.Content.ReadAsStringAsync();
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
//log 400 bad request and do whatever you want
}
var result = JsonConvert.DeserializeObject<FCMTokenInfo>(responseStr);
return result;
}
catch (Exception ex)
{
//log the exception
throw;
}
}
FCMTokenInfo.cs
public class FCMTokenInfo
{
public string Application { get; set; }
public string Subtype { get; set; }
public string Scope { get; set; }
public string AuthorizedEntity { get; set; }
public string Platform { get; set; }
}
And then inside the service that saves registration tokens inside the database:
//this method gets called when a new token is sent by the javascript web app
public async Task AddTokenAsync(Guid accountId, string token)
{
try
{
//getting TokenInfo of the current token(or refreshed one for that app)
var fcmTokenInfo = await firebaseServices.GetTokenInfoAsync(token);
//adding the current token
dbContext.FcmRegisterationTokens.Add(new FcmRegisterationToken
{
Token = token,
AccountId = accountId,
AddingDate = DateTimeOffset.UtcNow,
Application = fcmTokenInfo.Application,
Subtype = fcmTokenInfo.Subtype,
AuthorizedEntity = fcmTokenInfo.AuthorizedEntity,
Scope = fcmTokenInfo.Scope,
Platform = fcmTokenInfo.Platform
});
var outdatedTokens = await dbContext.FcmRegisterationTokens
.Where(x => x.AccountId == accountId
&& x.Application == fcmTokenInfo.Application
&& x.Platform == fcmTokenInfo.Platform
).ToListAsync();
//remove them
dbContext.FcmRegisterationTokens.RemoveRange(outdatedTokens);
dbContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}