Free Pascal and sending push-notifications via FCM lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-07 17:54 Hi All, I recall having read somewhere that someone is working on using Free Pascal to send push-notifications to a Google Firebase server. I wrote a freepascal linux service that handled this for many years, but now Google is switching to a new protocol (v1). Their doc doesn't help me, I'm stuck. Is someone working on this? tia, John ---------------------------------------------------------------------- Thaddy · 2024-05-07 18:48 Usually this is picked up by regenerating the google api package, but I am not sure. It is pretty much automated and there are instructions on how to do that yourself. ( If I am wrong the term is Michael Automata ) ---------------------------------------------------------------------- rvk · 2024-05-07 19:53 What's the problem you are experiencing when switching to https://fcm.googleapis.com/v1/projects/xxx ? I see I still have the legacy http in my small php script so I guess I have to switch soon  https://firebase.google.com/docs/cloud-messaging/migrate-v1 ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-08 10:55 Yes, the legacy protocol worked great for years, and still does. The new V1 protocol gives me headaches.... I try (in a GUI): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  if radiobutton1.Checked then ato:=edit1.Text; // iOS FCMDevicetokenif radiobutton2.Checked then ato:=edit2.Text; // Android FCMDevicetoken    TheMessage:='{"message": {'+                              '"token":"'+ato+'",'+                              '"notification":{'+                                               '"title":"Notification title",'+                                             '"body":"Notification body" '+                                           '}}}';  projectid   := 'ellip.........6407'; access_token  := '5663a6ca0c115f787.........72b76af6c2dd5'; topost  := 'https://fcm.googleapis.com/v1/projects/'+projectid+'/messages:send HTTP/1.1';  memo2.Lines.Add(topost);  IdHTTP := TIdHTTP.Create(nil);  try    IdHTTP.ProtocolVersion     := TidHttpProtocolversion.pv1_1;    idHttp.HTTPOptions         := [hoKeepOrigProtocol,hoForceEncodeParams];    IdHTTP.Request.ContentType := 'application/json';    IdHTTP.Request.Charset     := 'UTF-8';    idhttp.HandleRedirects     := True;    IdHTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer '+access_token;     try     JsonToSend := TStringStream.Create(TheMessage, TEncoding.UTF8);     response   := IdHTTP.Post(topost, jsontosend);     Memo2.Lines.Add('resp:'+response);     except     on E: EIdHTTPProtocolException do      begin       Memo2.Lines.add(IntToStr(E.ErrorCode) + ' ' + E.Message);      end;     on E:Exception do     begin      Memo2.Lines.add('Error: '#13 + E.ClassName + #13 + E.Message);     end;    end;   finally    IdHTTP.Free;  end;   jsonToSend.Free; But this gives me an error: 400 HTTP/1.0 400 Bad Request ---------------------------------------------------------------------- rvk · 2024-05-08 11:30 Quote from: JohnvdWaeter on May 08, 2024, 12:55:38 pmCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  topost  := 'https://fcm.googleapis.com/v1/projects/'+projectid+'/messages:send HTTP/1.1'; I haven't tried yet but above line is definitely wrong. The HTTP/1.1 shouldn't be there. It isn't part of the URL !! Try it without. If it doesn't work, I'll create a working example here (but not with Indy but with fphttp). ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-08 11:44 If I remove HTTP/1.1 I get: 401 HTTP/1.1 401 Unauthorized ---------------------------------------------------------------------- rvk · 2024-05-08 12:23 Quote from: JohnvdWaeter on May 08, 2024, 01:44:03 pmIf I remove HTTP/1.1 I get: 401 HTTP/1.1 401 Unauthorized QuoteCode: [Select]access_token  := '5663a6ca0c115f787.........72b76af6c2dd5';How did you get that access_token? Shouldn't this be in the form of ya29.... ?? The new API uses short lived access tokens (which you should retrieve before sending a message). Normally a library would do this for you but if you are calling the API bare boned, you'll need to do this yourself. ---------------------------------------------------------------------- rvk · 2024-05-08 12:35 BTW. This should also be clear from the body that's returned...  Quote{   "error": {     "code": 401,     "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole- project.",     "status": "UNAUTHENTICATED"   } } O, and BTW. I got the example for TFCMClient in FPC working. .\fpc\packages\fcl-web\examples\fcm\cli\sendmsg.lpi It will retrieve an access_token and send a message (when given a correct service.json). sendmsg --service-account=xxx.json --title=test --body=body --recipient="xxx" --log=test.txt ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-08 13:46 I never saw a token starting with ya29... So many keys, certificates, tokens... But... if you have it working... Does this path .\fpc\packages\fcl-web\examples\fcm\cli\sendmsg.lpi point to github repository? ---------------------------------------------------------------------- rvk · 2024-05-08 13:54 Quote from: JohnvdWaeter on May 08, 2024, 03:46:09 pmBut... if you have it working... Does this path .\fpc\packages\fcl-web\examples\fcm\cli\sendmsg.lpi point to github repository? It should be in your FPC (or Lazarus) installation. But it could be that it's only in trunk for now (I see it was probably added one month ago). https://gitlab.com/freepascal.org/fpc/source/-/tree/main/packages/fcl- web/examples/fcm Google didn't make it easier  You would need to download the service json (which is the easiest part). For the communication you would first need to retrieve an access token (which would be valid for one hour). This is done by constructing a JWT and encoding it and passing it to https://oauth2.googleapis.com/token The you get an access_token which you can use with the code you already got. This is from the log from the example project (anonymized). Quote----------------------------------------------------------------- --------------- Request : POST https://oauth2.googleapis.com/token Headers: Content-Type:application/x-www-form-urlencoded Body: grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt- bearer&assertion=eyAid12345ABCD Trying multiple protocols. ---------------------------------------------------------------------- ---------- Response : 200 : OK Headers: Content-Type: application/json; charset=UTF-8 Vary: X-Origin Vary: Referer Date: Wed, 08 May 2024 13:04:43 GMT Server: scaffolding on HTTPServer2 Cache-Control: private X-XSS-Protection: 0 X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 Accept-Ranges: none Vary: Origin,Accept-Encoding Connection: close Transfer-Encoding: chunked Body: {"access_token":"ya29.c.c0AY_VpZhwxHJSG12345abcd_very_long_token","exp ires_in":3599,"token_type":"Bearer"} ---------------------------------------------------------------------- ---------- Request : POST https://fcm.googleapis.com/v1/projects/xxxx- xxxx-12345/messages:send Headers: Accept:application/json Content-Type:application/json Authorization:Bearer ya29.c.c0AY_VpZhwxHJSG12345abcd_very_long_token Body: { "message" : { "notification" : { "title" : "test", "body" : "body" }, "android" : { "notification" : {} }, "webpush" : {}, "apns" : {}, "fcm_options" : {}, "token" : "ejEw2lxxxxx" } } Trying multiple protocols. ---------------------------------------------------------------------- ---------- Response : 200 : OK Headers: Content-Type: application/json; charset=UTF-8 Vary: X-Origin Vary: Referer Date: Wed, 08 May 2024 13:04:44 GMT Server: scaffolding on HTTPServer2 Cache-Control: private X-XSS-Protection: 0 X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 Accept-Ranges: none Vary: Origin,Accept-Encoding Connection: close Transfer-Encoding: chunked Body: {   "name": "projects/xx-xx-1234/messages/0:1234%31abc" } ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-08 14:48 Ok, thanks for the link. I have this sendmsg project, but it needs units fpfcmclient and fpfcmtypes. Where to find them? Sorry, I'm a little (?) ignorant when it comes to using gitlab contributions... ---------------------------------------------------------------------- rvk · 2024-05-08 14:51 Quote from: JohnvdWaeter on May 08, 2024, 04:48:52 pmOk, thanks for the link. I have this sendmsg project, but it needs units fpfcmclient and fpfcmtypes. Where to find them? Sorry, I'm a little (?) ignorant when it comes to using gitlab contributions... https://gitlab.com/freepascal.org/fpc/source/-/tree/main/packages/fcl- web/src/fcm I'm not sure if you can use those without changes in the rest of FPC. ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-08 15:56 Not immediately, there's a chain of dependencies.... ---------------------------------------------------------------------- rvk · 2024-05-08 17:59 I hacked my send.php really fast to see if it could work and, besides the link and color properties I have, it worked fine. Code: PHP  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function enc($data) {  $base64 = base64_encode($data);  $base64Url = strtr($base64, '+/', '-_');  return rtrim($base64Url, '=');} function create_jwt($payload) {  $base64UrlHeader = enc(json_encode(["typ" => "JWT", "alg" => "RS256"]));  $base64UrlPayload = enc(json_encode($payload));  $fp=fopen("jwtRS256.key", "r");  $privKey=fread($fp,8192);  fclose($fp);  $private=openssl_get_privatekey($privKey);  openssl_sign("$base64UrlHeader.$base64UrlPayload", $signature, $private, "sha256WithRSAEncryption");  openssl_free_key($private);  $base64UrlSignature = enc($signature);  return "$base64UrlHeader.$base64UrlPayload.$base64UrlSignature";} $payload = [  "iss" => "firebase-adminsdk-123@xxx-123.iam.gserviceaccount.com",  "aud" => "https://oauth2.googleapis.com/token",  "exp" => time() + 3600, // Token expiration time (1 hour)  "iat" => time(),  "scope" => "https://www.googleapis.com/auth/firebase.messaging"]; $jwt = create_j wt($payload);$data="grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant- type%3Ajwt-bearer&assertion=".$jwt; $headers = array(  'Content-Type: application/x-www-form-urlencoded'); $ch = curl_init();curl_setopt( $ch,CURLOPT_URL, 'https://oauth2.googleapis.com/token' );curl_setopt( $ch,CURLOPT_POST, true );curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );curl_setopt( $ch,CURLOPT_POSTFIELDS, $data );$result = curl_exec($ch );curl_close( $ch );// echo $result; $arr=json_decode($result, true);$access_token=$arr['access_token']; I got a valid access_token with which I could send a message. I didn't do this in FPC yet because I needed it in PHP for my server. But create a JWT is just calling openssl with the correct private key (you should have in your downloaded service.json). ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-08 20:29 Nice job! But I'll await the FPC. I guess I'm too old to learn ans understand all this. ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-09 08:30 After resolving all the dependencies it works! Thanks! Well, for now in Windows Just one little question: where should I insert "sound":"true" in the message? I tried the defaultsound in the fpfcmtypes, but it had no effect. ---------------------------------------------------------------------- rvk · 2024-05-09 09:03 For Apple, the sound object needs to be part of an aps object (in payload). For Android it can be in notification. See an example here https://stackoverflow.com/a/48690940/1037511 ---------------------------------------------------------------------- JohnvdWaeter · 2024-05-11 11:08 Ok, got that (sound for apple) working too.... Last (I think) problem is UTF8. If I insert --body="bóçdy", The client receives "b dy" (without the quotes). In fpfcmclient, line 225, I added utf8encode() , Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Request.SetContentFromString(utf8encode(AJSON));   //<<-- added utf8encode and then the text appeared correctly in the push notification. But I'm not sure if this is the right way or right place to do it.... Edit: it was not. The cause was title and body not surviving the passing via a commandline. Of course not I ended up base64 encoding the title and the body and decoding it back in the sendmsg. All works great now, thanks RVK! ---------------------------------------------------------------------- JohnvdWaeter · 2024-10-21 09:00 Hi RVK, Still one question... where should I insert {"badge":"1"} in the JSON? I tried several places, but no effect... ---------------------------------------------------------------------- rvk · 2024-10-21 10:01 Quote from: JohnvdWaeter on October 21, 2024, 11:00:35 amHi RVK, Still one question... where should I insert {"badge":"1"} in the JSON? I tried several places, but no effect... Where did you find the documentation for this? If you are using https://fcm.googleapis.com/v1/ shouldn't it be notification_count then? https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messag es (It also needs to be supported by the receiving program.) Edit: BTW. It's really important how you construct your message. If you are going to use the notification section, you need to add 'android' etc for the special fields. For example.. this should work for if you add the notification_count to 'android' subsection notification (it does for me): Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {    "message": {        "notification": {            "title": "hello",          "body": "mememe"        },        "android": {            "notification": {                "click_action": "OPEN_ACTIVITY_1",              "tag": "foo",                "notification_count": 13          }        },        "token": "xx",    }} ---------------------------------------------------------------------- JohnvdWaeter · 2024-10-21 12:24 Yes, I have it working okay on Android. It's the apns/aps-part of the message: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {   "aps" : {      "badge" : 9,      "sound" : "bingbong.aiff"   },   "messageID" : "ABCDEFGHIJ"} How to get the "badge":"1" there? ---------------------------------------------------------------------- rvk · 2024-10-21 12:39 Quote from: JohnvdWaeter on October 21, 2024, 02:24:56 pmIt's the apns/aps-part of the message: [] How to get the "badge":"1" there? I'm not familiar with the apns/aps-part (Apple). Did you wrap the apns in a payload key? Like is mentioned here: https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messag es#ApnsConfig ---------------------------------------------------------------------- JohnvdWaeter · 2024-10-21 14:01 I managed to get a badge number 1 with adding 1 line of code in unit fpfcmtypes: In procedure AddPayLoad, there is a line  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  soundobj.Add('sound','default'); I added one line below it: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  soundobj.Add('sound','default'); soundobj.Add('badge',1);   Now I have my badge number on iOS ---------------------------------------------------------------------- rvk · 2024-10-21 14:06 Quote from: JohnvdWaeter on October 21, 2024, 04:01:56 pmI managed to get a badge number 1 with adding 1 line of code in unit fpfcmtypes: In procedure AddPayLoad, there is a line  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  soundobj.Add('sound','default'); I added one line below it: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  soundobj.Add('sound','default'); soundobj.Add('badge',1);   Now I have my badge number on iOS Ah, you are using fpfcmtypes (and the Google suite) in FPC. I've never used that. But in fpfcptypes.pp I also see this (in TAppleConfig.AddPayLoad): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i begin  if (AlertObject.Count>0) then    Obj.Add('alert',AlertObject.Clone)  else    JSONAdd(Obj,'alert',Alert);  JSONAdd(Obj,'badge',Badge);  JSONAdd(Obj,'sound',Sound);  JSONAdd(Obj,'thread-id',ThreadID); Isn't "badge" already there then? Shouldn't you just assign 1 to component.Badge to make it add the badge tag? Or do you have older source where this line isn't present? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   TAppleConfig = class(THeadersPlatformConfig)  private    FAlert: string;    FAlertObject: TJSONObject;//...  Published    Property Alert : string Read FAlert Write falert;    property Badge : Integer Read FBadge write fbadge;    Property ThreadID : string read Fthreadid Write Fthreadid; I guess you are working with an older version of FPC. ---------------------------------------------------------------------- JohnvdWaeter · 2024-10-21 14:22 QuoteI guess you are working with an older version of FPC.Yes, I guess so..  3.2.0 on linux. I've had so many headaches updating.... But yes, the line   Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i JSONAdd(Obj,'badge',Badge);is there, and I tried replacing it with JSONAdd(Obj,'badge',1); but then it didn't appear in the aps-key. Now it appears where it should: Code: Javascript  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i ... "apns":{      "payload":{        "aps":{          "sound":"default",          "badge":1,        }      }}... ---------------------------------------------------------------------- rvk · 2024-10-21 14:40 Quote from: JohnvdWaeter on October 21, 2024, 04:22:37 pmI've had so many headaches updating.... But yes, the line   Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i JSONAdd(Obj,'badge',Badge);is there, and I tried replacing it with JSONAdd(Obj,'badge',1); but then it didn't appear in the aps-key. But is that line is there, the 'badge' should be added if you use it correctly. So assigning 1 to TAppleConfig.Badge should do it. So... for the TNotificationMessage that should be Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i NotificationMessage.AppleConfig.Badge := 1; But you haven't shown any code how you are using fpfcmtypes. Adding badge hardcoded as 1 is just a 'hack' ---------------------------------------------------------------------- JohnvdWaeter · 2024-10-21 14:48 QuoteAdding badge hardcoded as 1 is just a 'hack'  Yes   Whenever I have the time, I will study decent coding... for now I have to focus on getting things done.... Thanks again for your patience!