I need to send a Push notification out through Parse.com's API using Delphi.
I see there is a TParseApi but the documentation is, as usual, rather sparse on the subject.
How can I do this?
Drop a TParseProvider and a TBackendPush component onto a form or datamodule. Connect them and enter your credentials in the appropriate properties of the provider. Set the backend Message property to the message to send and call Push.
There are at least three ways of doing this:
1) A direct method would be to create your own HTTP request with custom headers and JSON
Procedure TForm1.ParseDotComPushNotification(pushMessage: string);
var
parseDotComUrl: string;
JSON: TStringStream;
webRequest: TIDHttp;
response: string;
whereJson: TJSONObject;
alertJson: TJSONObject;
mainJsonObject: TJSONObject;
begin
parseDotComUrl := 'https://api.parse.com/1/push';
// Modify the JSON as required to push to whomever you want to.
// This one is set up to push to EVERYONE.
// JSON := TStringStream.Create('{ "where": {}, ' + '"data" : {"alert":"'
// + pushMessage + '"}' + '}', TEncoding.UTF8);
mainJsonObject := TJSONObject.Create;
whereJson := TJSONObject.Create;
mainJsonObject.AddPair(TJSONPair.Create('where', whereJson));
alertJson := TJSONObject.Create;
alertJson.AddPair(TJSONPair.Create('alert', pushMessage));
mainJsonObject.AddPair(TJSONPair.Create('data', alertJson));
JSON := TStringStream.Create(mainJsonObject.ToJSON);
mainJsonObject.Free; // free all the child objects.
webRequest := TIDHttp.Create(nil);
webRequest.Request.Connection := 'Keep-Alive';
webRequest.Request.CustomHeaders.Clear;
webRequest.Request.CustomHeaders.AddValue('X-Parse-Application-Id',
'YourApplicationID');
webRequest.Request.CustomHeaders.AddValue('X-Parse-REST-API-KEY',
'YourRestApiKey');
webRequest.Request.ContentType := 'application/json';
webRequest.Request.CharSet := 'utf-8';
webRequest.Request.ContentLength := JSON.Size;
try
try
response := webRequest.Post(parseDotComUrl, JSON);
except
on E: Exception do
begin
showmessage(response);
end;
end;
finally
webRequest.Free;
JSON.Free;
end;
end;
Thus bypassing the need for TParseApi
2) Based on UweRabbe's answer, you can also do it like this in code:
procedure TForm1.parseProviderCodeButtonClick(Sender: TObject);
var
myParseProvider: TParseProvider;
myBackendPush: TBackendPush;
myStrings: Tstrings;
whereJson: TJSONObject;
alertJson: TJSONObject;
mainJsonObject: TJSONObject;
begin
mainJsonObject := TJSONObject.Create;
whereJson := TJSONObject.Create;
mainJsonObject.AddPair(TJSONPair.Create('where', whereJson));
alertJson := TJSONObject.Create;
alertJson.AddPair(TJSONPair.Create('alert', pushMessage));
mainJsonObject.AddPair(TJSONPair.Create('data', alertJson));
myParseProvider := TParseProvider.Create(nil);
myParseProvider.ApiVersion := '1';
myParseProvider.ApplicationID := 'YourApplicationID';
myParseProvider.MasterKey := 'YourMasterKey';
myParseProvider.RestApiKey := 'YourRestApiKey';
myBackendPush := TBackendPush.Create(nil);
myBackendPush.Provider := myParseProvider;
// myBackendPush.Message := 'Hello world';
myStrings := TStringList.Create;
myStrings.Clear;
// I like putting the message in when I generate the JSON for the Target
// (since it seems I have to do it anyways, my not do it all in one place).
// You could however us TBackendPush.Message as I've commented out above.
// myStrings.Add('{ "where": { }, "data" : {"alert":"goodbye world"}}');
myStrings.Add(mainJsonObject.ToJSON);
myBackendPush.Target := myStrings;
myStrings.Free;
mainJsonObject.Free; // free all the child objects.
myBackendPush.Push;
myBackendPush.Free;
myParseProvider.Free;
end;
3) And to round this out into one complete answer (again based on UweRabbe's answer)
On your form/datamodule:
Place a TParseProvider
Place a TBackendPush - this should automatically set its Provider filed to the name of the TParseProvider you created in the previous step.
Set the TBackendPush's ApplicationID, MasterKey, RestApiKey, and Message properties
Set the TBackendPush's Push method from code.
e.g.,
procedure TForm1.Button1(Sender: TObject);
begin
BackendPush1.Push;
end;
Related
I have been using Indy's TIdTCPClient component successfully for quite a while now to send push notifications to my mobile app users. Lately, due to new push notification requirements of Apple, this feature has stopped being functional for iOS users.
For the past three days, I've been trying to reconstruct this function by using the TIdHTTP component instead, following the new HTTP/2 format advised by Apple to send push notifications. Unfortunately, I've yet to come up with something operational.
Here's my failing code:
procedure TForm5.BtnPushClick(Sender: TObject);
var
vHTTP: TIdHTTP;
SslIOHandlerHTTP : TIdSSLIOHandlerSocketOpenSSL;
RequestBody: TStream;
ResponseBody, vCertFileName: string;
vJsonToSend : TStringStream;
begin
try
vCertFileName := 'apns-dist.pem';
vIdHTTP := TIdHTTP.Create(nil);
SslIOHandlerHTTP := TIdSSLIOHandlerSocketOpenSSL.Create(vHTTP);
SslIOHandlerHTTP.OnVerifyPeer := SslIOHandlerHTTP1VerifyPeer;
SslIOHandlerHTTP.OnGetPassword := SslIOHandlerHTTP1GetPassword;
SslIOHandlerHTTP.SSLOptions.CertFile := vCertFileName;
SslIOHandlerHTTP.SSLOptions.KeyFile := vCertFileName;
SslIOHandlerHTTP.SSLOptions.Method := sslvTLSv1_2;
SslIOHandlerHTTP.SSLOptions.Mode := sslmBoth;
SslIOHandlerHTTP.SSLOptions.SSLVersions := [sslvTLSv1_2];
SslIOHandlerHTTP.SSLOptions.VerifyMode := [sslvrfPeer];
SslIOHandlerHTTP.SSLOptions.VerifyDepth := 2;
//--
vHTTP.Request.CustomHeaders.AddValue(':method', 'POST');
vHTTP.Request.CustomHeaders.AddValue(':scheme', 'https');
vHTTP.Request.CustomHeaders.AddValue(':path', '/3/device/111890052839d3ed4f596c94712329e20812464afe30ebd28487c4e65986c6f0');
vHTTP.Request.CustomHeaders.AddValue('host', 'api.push.apple.com');
vHTTP.Request.CustomHeaders.AddValue('apns-id', 'eabeae54-14a8-11e5-b60b-1697f925ec7b');
vHTTP.Request.CustomHeaders.AddValue('apns-push-type', 'alert');
vHTTP.Request.CustomHeaders.AddValue('apns-expiration', '0');
vHTTP.Request.CustomHeaders.AddValue('apns-priority', '10');
vHTTP.Request.CustomHeaders.AddValue('apns-topic', 'com.TS.JACK');
//--
vHTTP.IOHandler := SslIOHandlerHTTP;
vHTTP.Request.Accept := 'application/json';
vHTTP.Request.ContentType := 'application/json';
vHTTP.HTTPOptions := [hoForceEncodeParams];
vHTTP.ProtocolVersion := pv1_1;
SslIOHandlerHTTP.PassThrough := true;
vJsonToSend := TStringStream.Create('{ "aps" : { "alert" : "Hello" } }', TEncoding.UTF8);
ResponseBody := vHTTP.Post('https://api.push.apple.com', vJsonToSend);
except
on E: Exception do
begin
Memo1.Lines.Add(IntToStr(vHTTP.ResponseCode)+':' + E.Message);
end;
end;
A bit of an odd one here, when setting "UseTLS" to utUseExplicitTLS and then connecting to a mail server on its Implicit TLS port, the first attempt allows the connection and sends the email, subsequent attempts on that port correctly fail.
Just wondering if anyone has any ideas on how to avoid the false positive on the initial connect and send.
The check is to handle that non-standard ports may be getting used for a user's mail server. Pretty much all examples I've seen assume that the correct information will always be provided.
Below is the code portion that handles it (excluding error logging):
function SendTestEmail(EmailAddress: String): Boolean;
var
EmailMessage: TidMessage;
begin
IdSMTPEmail.AuthType := satDefault
IdSMTPEmail.Username := ...;
IdSMTPEmail.Password := ...;
IdSMTPEmail.Port := 465;
IdSMTPEmail.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdSMTPEmail);
IdSMTPEmail.UseTLS := utUseExplicitTLS;
TIdSSLIOHandlerSocketOpenSSL(IdSMTPEmail.IOHandler).SSLOptions.Method := sslvTLSv1_2;
try
// Connect
IdSMTPEmail.Connect('smtp.gmail.com');
try
// Create
EmailMessage := TidMessage.Create(nil);
try
// Set values
EmailMessage.Body.Add('Test Email');
EmailMessage.Subject := 'Test Email';
// Set sender details
EmailMessage.From.Address := 'test#test.com';
EmailMessage.From.Name := 'SSL Test';
// Set recipient
EmailMessage.Recipients.Add.Address := EmailAddress;
try
// Send message
IdSMTPEmail.Send(EmailMessage);
except
// Exception
on E: EIdSMTPReplyError do
begin
// Result
Result := False;
end;
end;
finally
// Free email
EmailMessage.Free;
end;
finally
// Disconnect
IdSMTPEmail.Disconnect;
end;
except
// Exception
on E: Exception do
begin
IdSMTPEmail.Disconnect;
// Result
Result := False;
end;
end;
end;
Using the below code, correctly failed on all attempts of trying to email to port 465.
I don't even pretend to understand why this works but my original didn't.
function SendTestEmail(EmailAddress: String): Boolean;
var
EmailMessage: TidMessage;
IdSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
IdSMTPEmail.AuthType := satDefault;
IdSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create;
IdSSLHandler.SSLOptions.Method := sslvTLSv1_2;
IdSMTPEmail.IOHandler := IdSSLHandler;
IdSMTPEmail.UseTLS := utUseExplicitTLS;
IdSMTPEmail.Username := ...;
IdSMTPEmail.Password := ...;
IdSMTPEmail.Port := 465;
try
// Connect
IdSMTPEmail.Connect('smtp.gmail.com');
try
// Create
EmailMessage := TidMessage.Create(nil);
try
// Set values
EmailMessage.Body.Add('Test Email');
EmailMessage.Subject := 'Test Email';
// Set sender details
EmailMessage.From.Address := 'test#test.com';
EmailMessage.From.Name := 'SSL Test';
// Set recipient
EmailMessage.Recipients.Add.Address := EmailAddress;
try
// Send message
IdSMTPEmail.Send(EmailMessage);
except
// Exception
on E: EIdSMTPReplyError do
begin
// Result
Result := False;
end;
end;
finally
// Free email
EmailMessage.Free;
end;
finally
// Disconnect
IdSMTPEmail.Disconnect;
end;
except
// Exception
on E: Exception do
begin
IdSMTPEmail.Disconnect;
// Result
Result := False;
end;
end;
end;
All,
I am working on a new datasnap project based on the example project located in C:\Users\Public\Documents\Embarcadero\Studio\18.0\Samples\Object Pascal\DataSnap\FireDAC_DBX.
I am trying to transfer a large stream (1,606,408 bytes) from datasnap server to client. I am running into what appears to be a common issue and that is that the entire stream does not make it to the client.
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): TStringStream;
begin
Result := TStringStream.Create;
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(Result, TFDStorageFormat.sfBinary);
Result.Position := 0;
// Result.SaveToFile('output.adb');
except
raise;
end;
end;
Here is my client code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
LStringStream := TStringStream.Create(FDStoredProcBPInfo.ParamByName('ReturnValue').asBlob);
//LStringStream.Clear;
//LStringStream.LoadFromFile('Output.adb');
try
if LStringStream <> nil then
begin
LStringStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(LStringStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
LStringStream.Free;
end;
end;
You will see the stream save and load code; that is how I determined that the server was getting the entire result set into the stream, and that the client could handle the entire result set and display it properly.
So smaller streams transfer just fine, but this big one, when examined in the ide debugger, does not start with the 65,66,68,83 characters and the load fails with the error, '[FireDAC][Stan]-710. Invalid binary storage format'.
I know from extended Googling that there are work-arounds for this, but I do not understand how to apply the workarounds to my case, with the use of Tfdstoredproc and TfdSchemaAdaptor components. I'm trying to stay with this coding scheme.
How do I adapt this code to correctly receive large streams?
Update 1:
Ok, I tried strings and Base64 encoding. It didn't work.
Client Code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
TempStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
try
TempStream := TStringStream.Create;
TIdDecoderMIME.DecodeStream(FDStoredProcBPInfo.ParamByName('ReturnValue').asString,TempStream);
if TempStream <> nil then
begin
TempStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(TempStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
TempStream.Free;
end;
end;
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): String;
var
TempStream : TMemoryStream;
OutputStr : String;
begin
Result := '';
TempStream := TMemoryStream.Create;
try
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(TempStream, TFDStorageFormat.sfBinary);
TempStream.Position := 0;
OutputStr := IdEncoderMIMEBPInfo.EncodeStream(TempStream);
Result := OutputStr
except
raise;
end;
finally
TempStream.Free;
end;
end;
The result is the same.
Please help to understand about routing and web-url of web-service below.
type
TAirportService = class(TInterfacedObject, IAirportService)
public
procedure GetAirportDefinition(const AirPortID: integer; out Definition: TDTOAirportDefinition);
end;
procedure TAirportService.GetAirportDefinition(const AirPortID: integer;
out Definition: TDTOAirportDefinition);
begin
// create an object from static data
// (real application may use database and complex code to retrieve the values)
with Definition.Airport.Add do begin
Location := 'LAX';
Terminal := TRawUTF8DynArrayFrom(['terminalA', 'terminalB', 'terminalC']);
Gate := TRawUTF8DynArrayFrom(['gate1', 'gate2', 'gate3', 'gate4', 'gate5']);
BHS := 'Siemens';
DCS := 'Altiea';
end;
with Definition.Airline.Add do begin
CX := TRawUTF8DynArrayFrom(['B777', 'B737', 'A380', 'A320']);
QR := TRawUTF8DynArrayFrom(['A319', 'A380', 'B787']);
ET := '380';
SQ := 'A320';
end;
Definition.GroundHandler := TRawUTF8DynArrayFrom(['Swissport','SATS','Wings','TollData']);
end;
procedure StartWebService();
var
aModel: TSQLModel;
aDB: TSQLRestServer;
aServer: TSQLHttpServer;
begin
// set the logs level to only important events (reduce .log size)
TSQLLog.Family.Level := LOG_STACKTRACE+[sllInfo,sllServer];
// initialize the ORM data model
aModel := TSQLModel.Create([]);
try
// create a fast in-memory ORM server
aDB := TSQLRestServerFullMemory.Create(aModel,'test.json',false,false);
try
// register our TAirportServer implementation
// aDB.ServiceRegister(TServiceCalculator,[TypeInfo(ICalculatorXML)],sicShared);
aDB.ServiceRegister(TAirportService,[TypeInfo(IAirportService)],sicShared);
// launch the HTTP server
aServer := TSQLHttpServer.Create('8092', [aDB], '+', useHttpApiRegisteringURI);
try
aServer.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
writeln('Background server is running'#10);
write('Press [Enter] to close the server.');
ConsoleWaitForEnterKey;
finally
aServer.Free;
end;
finally
aDB.Free;
end;
finally
aModel.Free;
end;
end;
I try to call follow web-urls:
http://localhost:8092/root/AirportService/GetAirportDefinition?AirPortID=1
http://localhost:8092/root/AirportService.GetAirportDefinition?AirPortID=1
http://localhost:8092/root/AirportService/GetAirportDefinition
http://localhost:8092/AirportService/GetAirportDefinition?AirPortID=1
http://localhost:8092/AirportService.GetAirportDefinition?AirPortID=1
http://localhost:8092/AirportService/GetAirportDefinition
but every time I get:
{
"errorCode":400,
"errorText":"Bad Request"
}
or Bad request
Where am I wrong?
A was wrong, really urls below works as needed:
http://localhost:8092/root/AirportService/GetAirportDefinition?AirPortID=1
http://localhost:8092/root/AirportService.GetAirportDefinition?AirPortID=1
I have a working application that can access Outlook via COM and send, save or show emails I create inside this app.
What I want is all the settings of the account in Outlook getting applied on my mail too, so this means which mail-type (text, html or rich), custom fonts, signatures, and so on.
here a SSCCE (the the rest of the code is just some logging, and the form only contains the most neccessary controls):
...
strict private
FOutlook: _Application;
...
procedure TMainForm.ShowMailDlg(aModal: Boolean);
var
mail: _MailItem;
begin
Connect();
mail := FOutlook.CreateItem(olMailItem) as _MailItem;
mail.Recipients.Add(Trim(EdTo.Text));
mail.CC := Trim(EdCc.Text);
mail.Subject := Trim(EdSubject.Text);
mail.Body := EmailText.Lines.Text;
mail.SendUsingAccount := GetAccountForEmailAddress(Trim(EdFrom.Text));
//mail.Attachments.Add('Path1', olByValue, 1, 'Caption1');
//mail.Attachments.Add('Path2', olByValue, 2, 'Caption2');
mail.Display(aModal);
end;
procedure TMainForm.Connect;
begin
FOutlook := CreateOleObject('Outlook.Application') as _Application;
end;
function TMainForm.GetAccountForEmailAddress(const aSmtp: string): _Account;
var
accounts: _Accounts;
account: _Account;
i: Integer;
begin
accounts := FOutlook.Session.Accounts;
for i := 1 to accounts.Count do begin
account := accounts.Item(i);
if LowerCase(account.SmtpAddress) = LowerCase(aSmtp) then begin
Result := account;
Exit;
end;
end;
raise Exception.Create('No Account with SMTP address ' + aSmtp + ' found!');
end;
How can I get the MailItem to use all formatting-options from the chosen account?
I still haven't found a real solution, but here is a workaround.
The trick is to use the CreateItemFromTemplate-method, where your template contains all the settings. Oviously the user is required to create a template for this purpose, but it's a one-time-action which shoulnd't be too hard.
procedure TMainForm.DoMailAction(aAction: TMailAction);
var
mail: _MailItem;
folder: OleVariant;
begin
Connect();
folder := FOutlook.Session.GetDefaultFolder(olFolderDrafts);
mail := FOutlook.CreateItemFromTemplate('C:\\Users\\fkoch\\default.oft', folder) as _MailItem;
...
Additionally, the selected folder "Drafts" causes the signature getting attached to the mailbody, as long as the mailItem is manually send by the user in the mail-dialog (mail.display(False)). This doesn't happen when directly processed via mail.send() or mail.save().
I've found the solution now. I'v set the body the wrong way, thats why it didn't work.
procedure CreateMail(aMailInfo.TMailInfo)
var
...
insp: _Inspector;
editor: OleVariant;
begin
FMailItem := FOutlook.CreateItem(olMailItem) as _MailItem;
...
insp := FMailItem.GetInspector;
if (insp.EditorType = olEditorWord) then begin
editor := insp.WordEditor;
editor.Characters.item(1).InsertBefore(mailText);
end else begin
if FMailItem.BodyFormat = olFormatHTML then begin
regex := TRegEx.Create(cReplaceNewline);
FMailItem.HTMLBody := regex.Replace(mailText, '<br />');
end else
FMailItem.Body := mailText;
end;
...
end;