I need to build a provider server in Delphi to send push notification messages to my iPhone app via APNS.
I have read that this is possible to do through Indy components. It is also required to install an SSL certificate (.p12) provided by apple.
I'm looking for some pointers to get started with this in Delphi.
What would be a good library to use, and does anyone know of any example code to do something like this?
Here are samples for Ruby & PHP, C# and JAVA
OK I managed this as follows:
Add an indy TidTCPClient and TIdSSLIOHandlerSocket on your form and link them. Set the SSL options in the TIdSSLIOHandlerSocket, set CertFile and KeyFile to the appropriate .pem files. Set method to sslvSSLv23 and mode to sslmClient.
In the IOHandler's OnGetPassword event set your key's password.
Useful URLs:
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html
http://www.raywenderlich.com/3443/apple-push-notification-services-tutorial-part-12
On the coding front:
N.b. HexData is the ID sent from the IPhone App
function SendNotification(HexData: String; Count: Integer): Boolean;
var
p_DataSize,
I: Integer;
p_payllen: Byte;
p_json : String;
p_sndmsg : String;
begin
// Delphi 6 so needed to create JSON by hand<br>
p_json := '{"aps":{';
if (Count > 0) then
begin
p_json := p_json + '"alert":"You Have ' + IntToStr(Count);
if (count = 1) then
p_json := p_json + ' Reminder'
else<br>
p_json := p_json + ' Reminders';
p_json := p_json + '","sound": "default",';
end;
p_json := p_json + '"badge":' + inttostr(Count) + '}}';
p_payllen := length(p_json);
// Hard code the first part of message as it never changes
p_sndmsg := chr(0) + chr(0) + chr(32);
// Convert hex string to binary data
p_DataSize := Length(HexData) div 2;
for I := 0 to p_DataSize-1 do
p_sndmsg := p_sndmsg + char(Byte(StrToInt('$' + Copy(HexData, (I*2)+1,
2))));
//Now need to add length of json string and string itself
p_sndmsg := p_sndmsg + chr(0) + Char(p_payllen) + p_json;
try
// According to Apple can't connect/disconnect for each message so leave open for later
if (not PushClient.Connected) then
PushClient.Connect;
PushClient.IOHandler.Send(p_sndmsg[1], length(p_sndmsg));
except
on e : exception do
Log_Error(e.message);
end;
end;
you can try porting java/php or ruby code as Rogier said. mean while have a look at his http://www.pushwoosh.com/ some thing similar to http://urbanairship.com/ .
Related
In my Delphi 10 application i use tidIMAP4 to retrieve eMails with this (a portion of the) code
with IMAP4 do begin
case kind of
1 : MsgBox := 'INBOX';
10 : MsgBox := '[Gmail]/Sent Mail';//for GMail
2 : MsgBox := '[Gmail]/Bin/Deleted Items';//for GMail
3 : MsgBox := '[Gmail]/Spam';//for GMail
end;
SelectMailBox(MsgBox);
SetLength(SearchInfo, 1);
SearchInfo[0].Date := date-1;
SearchInfo[0].SearchKey := skSince;
if SearchMailBox(SearchInfo)
and (High(MailBox.SearchResult) > -1) then
try
msgs := High(MailBox.SearchResult)+1;
.....
I can find the mailboxes of the server with this code
var st : TstringList := TstringList.Create;
IMAP4.ListMailBoxes(st);
showmessage(st.Text);
st.Free;
so i can know the folder names that i must put in the SelectMailBox(MsgBox) function when i know the IMAP4 server a priori.
But i want a consistent way to know them for every server (yahoo, user's server etc)
Something like SelectMailBox('deleted') or similar.
Is this possible ?
All their examples use HS*** with none in RS*** and trying to change the examples to suite dont seem to be working.
My problem seems to be getting the private key loaded for signing. I'm using a PEM in a string, setting up the claims, using this
Procedure RunTest2b;
var
LToken: TJWT;
LSigner: TJWS;
LKey: TJWK;
LAlg: TJOSEAlgorithmId;
s: String;
begin
LToken := TJWT.Create;
try
LToken.Claims.Subject := 'Paolo Rossi';
LToken.Claims.Issuer := 'Delphi JOSE Library';
LToken.Claims.IssuedAt := Now;
LToken.Claims.Expiration := Now + 1;
// Signing algorithm
LAlg := TJOSEAlgorithmId.RS256;
LSigner := TJWS.Create(LToken);
LKey := TJWK.Create(gPrivateKey);
try
// With this option you can have keys < algorithm length
LSigner.SkipKeyValidation := True;
LSigner.Sign(LKey, LAlg);
s := 'Header: ' + LSigner.Header + #13#10 +
'Payload: ' + LSigner.Payload + #13#10 +
'Signature: ' + LSigner.Signature + #13#10 +
'Compact Token: ' + LSigner.CompactToken;
if s = '' then;
finally
LKey.Free;
LSigner.Free;
end;
finally
LToken.Free;
end;
end;
This fails in the sign method saying "Unable to load private key:" and a bunch of weird characters which makes it look like maybe I have a wide string when I should have an ansistring, but changing it doesn't seem to help.
I have also tried using the TBase64.Decode and TBase64.UrlDecode to transform the key before I pass it into the sign method without success.
Can anyone see where I'm making a mistake ?
I recently jump thru a few hoops to do some JWT testing using JOSE. I didn't sign anything, but did have to use the PEM to verify the JWT which was using RS. While doing so I made the mistake of concatenating the PEM string into a single line of characters without preserving the line feeds. I wonder if you made the same mistake with your keys?
i.e. bad PEM format
myPem := '-----BEGIN PUBLIC KEY-----'
+ 'A23BBjhasdfbewisudvnacwerf823rdsvcp2'
+ 'bDenDfsub893rghvsaefawerd'
+ '-----END PUBLIC KEY-----';
i.e. good PEM format
myPem := '-----BEGIN PUBLIC KEY-----'
+ #13#10 + 'A23BBjhasdfbewisudvnacwerf823rdsvcp2'
+ #13#10 + 'bDenDfsub893rghvsaefawerd'
+ #13#10 + '-----END PUBLIC KEY-----';
I have client-server system that uses DataSnap. I want to log the client application data so I use the TDSServer - OnConnect Event. In this event I can access what I want with the following code:
IP:= DSConnectEventObject.ChannelInfo.ClientInfo.IpAddress
ClientPort:= DSConnectEventObject.ChannelInfo.ClientInfo.ClientPort
Protocol:= DSConnectEventObject.ChannelInfo.ClientInfo.Protocol
AppName:= DSConnectEventObject.ChannelInfo.ClientInfo.AppName
first 3 lines are OK but AppName is empty!!!
(I run server and client on the same computer i.e. localhost)
I have been unable to find any online information about how to specify the AppName when the client connects via TCP/IP. If you look at the code
procedure TDSTCPChannel.Open;
var
ClientInfo: TDBXClientInfo;
begin
inherited;
FreeAndNil(FChannelInfo);
FChannelInfo := TDBXSocketChannelInfo.Create(IntPtr(FContext.Connection), FContext.Connection.Socket.Binding.PeerIP);
ClientInfo := FChannelInfo.ClientInfo;
ClientInfo.IpAddress := FContext.Connection.Socket.Binding.PeerIP;
ClientInfo.ClientPort := IntToStr(FContext.Connection.Socket.Binding.PeerPort);
ClientInfo.Protocol := 'tcp/ip';
FChannelInfo.ClientInfo := ClientInfo;
end;
in DataSnap.DSTCPServerTransport.Pas it is evident that the ClientInfo.AppName is not set.
However, the following work-around works with the Seattle demo DataSnap Basic Server + Client:
In the client, add a Param 'AppName' to the SqlConnection1 component's Params and
set its value to something like 'MyTestApp'. Recompile the client.
Open the server in the IDE and modify the ServerContainerForm's code as shown below.
Code:
uses
[...], DBXTransport;
procedure TForm8.DSServer1Connect(DSConnectEventObject: TDSConnectEventObject);
var
S : String; // added
Info : TDBXClientInfo; // added
begin
ActiveConnections.Insert;
if DSConnectEventObject.ChannelInfo <> nil then
begin
ActiveConnections['ID'] := DSConnectEventObject.ChannelInfo.Id;
ActiveConnections['Info'] := DSConnectEventObject.ChannelInfo.Info;
end;
ActiveConnections['UserName'] := DSConnectEventObject.ConnectProperties[TDBXPropertyNames.UserName];
ActiveConnections['ServerConnection'] := DSConnectEventObject.ConnectProperties[TDBXPropertyNames.ServerConnection];
ActiveConnections.Post;
InsertEvent('Connect');
// following added to get AppName from client
S := DSConnectEventObject.ConnectProperties['AppName'];
Info := DSConnectEventObject.ChannelInfo.ClientInfo;
Info.AppName := S;
DSConnectEventObject.ChannelInfo.ClientInfo := Info;
Caption := DSConnectEventObject.ChannelInfo.ClientInfo.AppName;
end;
As you can see, it works by picking up the value set for AppName in the client's
SqlConnection1.Params in the call to `DSConnectEventObject.ConnectProperties['AppName']'
and then display it on the Caption of the ServerContainerForm.
Obviously, you could pass any other name/value pair by adding them to the SqlConnection's Params on the client and then pick them up on the server by calling DSConnectEventObject.ConnectProperties[].
I have some code written in Lazarus/FreePascal that uses the Synapse IMAPSend library unit. I attempt to login to an IMAP server over SSL (IMAPS) but the call to Login fails.
I've tried checking for exceptions - none are thrown.
Wireshark shows nothing beyond a TCP three-way handshake to the appropriate server and port.
Here's the code
function GetImapResponse(host, port, user, pass:String): String;
var
response: String = '';
imap: TIMAPSend;
no_unseen: integer;
begin
imap := TIMAPSend.create;
try
imap.TargetHost := host; //'10.0.0.16';
imap.TargetPort := port; //'993';
imap.UserName := user; //'red';
imap.Password := pass; //'********';
imap.AutoTLS := false;
imap.FullSSL := true;
response := response + 'IMAP login to '+user+'#'+host+':'+port+' ... ';
if imap.Login then
begin
response := response + 'Logged in OK. ';
// How many unseen?
no_unseen := imap.StatusFolder('INBOX','UNSEEN');
Form1.Label2.Caption := IntToStr(no_unseen);
response := 'INBOX contains ' + IntToStr(no_unseen) + ' unseen messages. ';
end
else
begin
response := response + 'IMAP Login failed. ';
end;
except
on E: Exception do
begin
response := response + 'Exception: ' + E.Message;
showMessage(response);
end;
end;
{
finally
imap.free;
response := response + 'Finally. ';
end;
}
Result := response;
end;
Here's the String result of this function
IMAP login to red#10.0.0.16:993 ... IMAP Login failed.
Question: Is there a way to see some details of what IMAPSend thinks happened?
Update 1
As shown in SimaWB's answer plus comments by Arioch 'The
mySynaDebug := TsynaDebug.Create;
imap.Sock.OnStatus := #MySynaDebug.HookStatus;
imap.Sock.OnMonitor := #MySynaDebug.HookMonitor;
produced a projectname.slog file containing
20130722-103643.605 0011F230HR_SocketClose:
20130722-103643.609 0011F230HR_ResolvingBegin: 10.0.0.16:993
20130722-103643.620 0011F230HR_ResolvingEnd: 10.0.0.16:993
20130722-103643.623 0011F230HR_SocketCreate: IPv4
20130722-103643.628 0011F230HR_Connect: 10.0.0.16:993
20130722-103643.631 0011F230HR_Error: 10091,SSL/TLS support is not compiled!
So I am moving forward :-)
I do have libssl32.dll and libeay32.dll in my project folder but will check I have the right versions and have done the right things with the chicken entrails.
Update 2:
I was using the 64-bit version of Lazarus. The OpenSSL DLLs are 32-bit DLLs which Synapse's ssl_openssl unit loads dynamically. I installed the 32-bit version of Lazarus/FPC and now my IMAP/SSL client program compiles and works as expected.
It seems the current 64-bit Lazarus/FPC binary distribution (v1.0.10/2.6.2) cannot cross-compile to i386 (which I think might have helped).
Did you try synadbg unit of Synapse?
imap := TIMAPSend.create;
imap.Sock.OnStatus := TSynaDebug.HookStatus;
imap.Sock.OnMonitor := TSynaDebug.HookMonitor;
Then the application create a log file with extension '.slog'. May be you can find more details in the log file.
Same error in the .slog file on Ubuntu 64-bit was fixed with "sudo apt-get install libssl-dev" and including "ssl_openssl" in the uses section.
The problem is here: imap.AutoTLS := false;
imap.FullSSL := true; try to set true to false and false to true...
and port should be 587
I'm getting this error message under heavy load. Here is code abstract and message from my error log.
I tried everything I could think of. Any suggestion would be greatly appreciated.
Procedure tCacheInMemory.StreamValue(Name: String; IgnoreCase: Boolean; Var Stream: TStringStream);
Var
i: Integer;
Begin
i := 0;
Try
If Not active Then
exit;
arrayLock.BeginRead;
Try
i := Search(Name);
If i > -1 Then Begin
If fItems[i].value = Nil Then
exit;
fItems[i].value.Position := 0;
Stream.Position := 0;
Stream.CopyFrom(fItems[i].value, fItems[i].value.Size);
End;
Finally
arrayLock.EndRead;
End;
Except { ...execution jumps to here }
On E: Exception Do Begin
x.xLogError('LogErrorCacheInMemory.txt', 'StreamValue:' + E.Message + ' ItemsCount:' + IntToStr( High(fItems)) + 'Memory:' + IntToStr(x.GetMemoryInfoMemory) + endLn + 'StreamSize : ' + IntToStr(fItems[i].value.Size) + ' i=' + IntToStr(i) + 'Name: ' + Name);
Clear;
End
End;
End;
Log Entries:
3/10/2011 10:52:59 AM: StreamValue:Stream read error ItemsCount:7562 Memory:240816
StreamSize : 43 i=7506 Name: \\xxxxxxxx\WebRoot\\images\1x1.gif
3/10/2011 12:39:14 PM: StreamValue:Stream read error ItemsCount:10172 Memory:345808
StreamSize : 849 i=10108 Name: \\xxxxxxxx\WebRoot\\css\screen.add.css
3/10/2011 3:45:29 PM: StreamValue:Stream read error ItemsCount:11200 Memory:425464
StreamSize : 3743 i=11198 Name: \\xxxxxxxx\WebRoot\\JS\ArtWeb.js
P.S.
arrayLock: TMultiReadExclusiveWriteSynchronizer;
fItems: Array Of rCache;
Type
rCache = Record
Name: String;
value: TStringStream;
expired: TDateTime;
End;
And calling function:
Function tCacheInMemory.CacheCheck(cName: String; Out BlobStream: TStringStream): Boolean;
Begin
Result := False;
If Not IfUseCache Then
exit;
BlobStream.SetSize(0);
BlobStream.Size := 0;
StreamValue(trim(cName), True, BlobStream);
If BlobStream.Size > 0 Then
Result := True;
End;
`
You're not using correct locking. You're acquiring a read lock on the array of cache entries, but once you find the item you want, you modify it. First, you explicitly modify it by assigning its Position property, and then you implicitly modify it by reading from it, which modifies its Position property again. When other code attempts to read from that same cache item, you'll have interference. If the source stream's Position property changes between the time the destination stream calculates how many bytes are available and the time it actually requests to read those bytes, you'll get a stream-read error.
I have a couple pieces of advice related to this:
Don't use streams as a storage device in the first place. You're apparently holding the contents of files. You're not going to change those, so you don't need a data structure designed for making sequential changes. Instead, just store the data in simple arrays of bytes: TBytes. (Also, use of TStringStream in particular introduces confusion over whether those strings' encodings are important. A simple file cache shouldn't be concerned with string encodings at all. If you must use a stream, use a content-agnostic class like TMemoryStream.)
Don't quell an exception that you haven't actually handled. In this code, you're catching all exception types, logging some information, clearing the cache, and then proceeding as though everything is normal. But you haven't done anything to resolve the problem that triggered the exception, so everything is not normal. Since you're not really handling the exception, you need to make sure it propagates to the caller. Call raise after to call Clear. (And when you log the exception, make sure you log the exception's ClassName value as well as its message.)
It looks like something external is blocking your stream files.
You could try to use Process Monitor to see what blocks it.
Another thing you can try is to open the stream in read-deny-write mode (please show us how you open the stream).
Something like this:
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite) ;
Edit 1: Disregard the strike through part: you are using TStringStream.
I'll keep the answer just in case anyone ever gets this kind of error when using TFileStream.
Edit 2: Yuriy posted this interesting addendum, but I'm not sure it will work, as the BlobStream is not initialized, just like Robert Love suspected:
Function TCacheInMemory.CacheCheck(cName: String; Out BlobStream: TStringStream): Boolean;
Begin
Result := False;
Try
If Not IfUseCache Then
exit;
BlobStream.SetSize(0);
BlobStream.Size := 0;
StreamValue(trim(cName), True, BlobStream);
If BlobStream.Size > 0 Then
Result := True;
Except
On E: Exception Do
Begin
x.xLogError('LogErrorCacheInMemory.txt', 'CheckCacheOutStream:' + E.Message + ' ItemsCount:' + IntToStr( High(fItems)) + 'Memory:' + IntToStr(x.GetMemoryInfoMemory));
End;
End;
End;
--jeroen