1 module gcm.gcm;
2 
3 import vibe.core.log;
4 import vibe.http.client;
5 import vibe.stream.operations;
6 
7 import vibe.data.json;
8 
9 ///see http://developer.android.com/google/gcm/server.html#params
10 struct GCMRequest
11 {
12 	///A string array with the list of devices (registration IDs) receiving the message
13 	string[] registration_ids;
14 
15 	///A string that maps a single user to multiple registration IDs associated with that user.
16 	string notification_key;
17 
18 	///An arbitrary string (such as "Updates Available") that is used to collapse a group of like messages when the device is offline, so that only the last message gets sent to the client
19 	string collapse_key;
20 
21 	///A JSON object whose fields represents the key-value pairs of the message's payload data
22 	Json data;
23 
24 	///If included, indicates that the message should not be sent immediately if the device is idle
25 	bool delay_while_idle;
26 
27 	///How long (in seconds) the message should be kept on GCM storage if the device is offline
28 	int time_to_live = -1;
29 
30 	///A string containing the package name of your application
31 	string restricted_package_name;
32 
33 	///If included, allows developers to test their request without actually sending a message
34 	bool dry_run;
35 
36 	Json toJson()
37 	{
38 		Json result = Json.emptyObject;
39 
40 		result["registration_ids"] = serializeToJson(registration_ids);
41 
42 		if(data.type != Json.undefined)
43 			result["data"] = data;
44 
45 		if(dry_run)
46 			result["dry_run"] = true;
47 
48 		if(delay_while_idle)
49 			result["delay_while_idle"] = true;
50 
51 		if(time_to_live > -1)
52 			result["time_to_live"] = time_to_live;
53 
54 		if(collapse_key.length > 0)
55 			result["collapse_key"] = collapse_key;
56 		if(restricted_package_name.length > 0)
57 			result["restricted_package_name"] = restricted_package_name;
58 		if(notification_key.length > 0)
59 			result["notification_key"] = notification_key;
60 
61 		return result;
62 	}
63 }
64 
65 /// see http://developer.android.com/google/gcm/http.html
66 struct GCMResponse
67 {
68 	/// see http://developer.android.com/google/gcm/http.html#response
69 	int statusCode;
70 
71 	///Unique ID (number) identifying the multicast message.
72 	long multicast_id;
73 
74 	///Number of messages that were processed without an error.
75 	long success;
76 
77 	///Number of messages that could not be processed.
78 	long failure;
79 
80 	///Number of results that contain a canonical registration ID. See Advanced Topics for more discussion of this topic.
81 	long canonical_ids;
82 
83 	///
84 	Json results;
85 
86 	/// parsed errors from result, helps matching up errors to regIds
87 	GCMResponseError[] errors;
88 }
89 
90 /// matches error responses to the correct regId
91 struct GCMResponseError
92 {
93 	/// corresponding registration id triggering the error
94 	string regId;
95 
96 	/// see http://developer.android.com/google/gcm/http.html#error_codes
97 	string type;
98 }
99 
100 ///
101 class GCM
102 {
103 	private string m_apikey;
104 
105 public:
106 
107 	///
108 	this(string _key)
109 	{
110 		m_apikey = _key;
111 	}
112 
113 	///
114 	bool request(GCMRequest _req, ref GCMResponse _res)
115 	{
116 		_res.statusCode = 0;
117 
118 		try requestHTTP("https://android.googleapis.com/gcm/send",
119 					(scope req) {
120 						req.method = HTTPMethod.POST;
121 
122 						req.headers["Authorization"] = "key=" ~ m_apikey;
123 						req.headers["Content-Type"] = "application/json";
124 
125 						//logInfo("body: %s",_req.toJson().toString);
126 
127 						req.writeJsonBody(_req.toJson());
128 					},
129 					(scope HTTPClientResponse res) {
130 
131 						_res.statusCode = res.statusCode;
132 
133 						if(res.statusCode == 200)
134 							parseSuccessResult(res.readJson(), _req, _res);
135 						else
136 							logInfo("response: %s", cast(string)res.bodyReader.readAll());
137 					}
138 		);
139 		catch(Exception e)
140 		{
141 			logError("[gmc] request failed: %s",e);
142 		}
143 
144 		return _res.statusCode == 200;
145 	}
146 
147 private:
148 
149 	static void parseSuccessResult(Json _body, in GCMRequest _req, ref GCMResponse _res)
150 	{
151 		_res.multicast_id = _body["multicast_id"].get!long;
152 
153 		_res.canonical_ids = _body["canonical_ids"].get!long;
154 
155 		_res.failure = _body["failure"].get!long;
156 		_res.success = _body["success"].get!long;
157 
158 		_res.results = _body["results"];
159 
160 		_res.errors.length = cast(uint)_res.failure;
161 		_res.errors.length = 0;
162 
163 		if(_res.failure > 0)
164 		{
165 			int idx=0;
166 			foreach(resultEntry; _body["results"])
167 			{
168 				if("error" in resultEntry)
169 				{
170 					GCMResponseError err;
171 
172 					err.regId = _req.registration_ids[idx];
173 					err.type = resultEntry["error"].get!string;
174 
175 					_res.errors ~= err;
176 				}
177 
178 				idx++;
179 			}
180 		}
181 	}
182 }