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 }