Skip to content

Commit 9436756

Browse files
committed
wip: working on uwp support
1 parent c454ad0 commit 9436756

4 files changed

Lines changed: 374 additions & 1 deletion

File tree

plugin.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

3-
<plugin xmlns="http://www.apache.org/cordova/ns/plugins/1.0" id="parse-push-plugin" version="0.9.9">
3+
<plugin xmlns="http://www.apache.org/cordova/ns/plugins/1.0" id="parse-push-plugin" version="1.0.0">
44
<name>ParsePushPlugin</name>
55
<description>Parse.Push plugin for phonegap/cordova/ionic framework</description>
66
<keywords>cordova,phonegap,ionic framework, parse server, parse push, push notification</keywords>
@@ -89,4 +89,11 @@
8989
<framework src="src/ios/Frameworks/Parse.framework" custom="true" />
9090
<framework src="src/ios/Frameworks/Bolts.framework" custom="true" />
9191
</platform>
92+
93+
<platform name="windows">
94+
<js-module src="src/windows/jsTimezoneDetect.min.js" name="jsTimezoneDetect" />
95+
<js-module src="src/windows/ParsePushPluginProxy.js" name="ParsePushPluginProxy">
96+
<merges target="" />
97+
</js-module>
98+
</platform>
9299
</plugin>
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
var jstz = require('parse-push-plugin.jsTimezoneDetect');
2+
var pushNotifications = Windows.Networking.PushNotifications;
3+
var localSettings = Windows.Storage.ApplicationData.current.localSettings.values;
4+
5+
cordova.commandProxy.add("ParsePushPlugin", {
6+
registerCallback: function (successCb, errorCb) {
7+
var pnCallback = successCb;
8+
9+
//localSettings.remove(ParseUtil._installationKey);
10+
11+
ParseUtil.initializeViaConfigXML();
12+
13+
WNSUtil.registerChannelAsync(pnCallback).then(function (channel) {
14+
ParseUtil.saveInstallationAsync(channel);
15+
}, function (err) {
16+
console.error("Unable to register WNS channel. Error: " + JSON.stringify(err));
17+
errorCb(err);
18+
});
19+
},
20+
21+
getInstallationId: function (successCb, errorCb) {
22+
var installation = ParseUtil.getCurrentInstallation();
23+
24+
if (installation.objectId) {
25+
successCb(installation.installationId);
26+
} else {
27+
var msg = "You don't have a stored installation";
28+
console.error(msg);
29+
errorCb(msg);
30+
}
31+
},
32+
33+
getInstallationObjectId: function(){
34+
var installation = ParseUtil.getCurrentInstallation();
35+
36+
if (installation.objectId) {
37+
successCb(installation.objectId);
38+
} else {
39+
var msg = "You don't have a stored installation";
40+
console.error(msg);
41+
errorCb(msg);
42+
}
43+
},
44+
45+
getSubscriptions: function(successCb, errorCb){
46+
successCb(ParseUtil.getCurrentInstallation().channels || []);
47+
},
48+
subscribe: function (successCb, errorCb, argArray) {
49+
var channel = argArray[0];
50+
51+
ParseUtil.subscribeAsync(channel).then(function () {
52+
successCb()
53+
}, errorCb);
54+
},
55+
unsubscribe: function(successCb, errorCb, argArray){
56+
var channel = argArray[0];
57+
58+
ParseUtil.unsubscribeAsync(channel).then(function () {
59+
successCb()
60+
}, errorCb);
61+
},
62+
initialize: function(successCb, errorCb){
63+
//noop
64+
}
65+
});
66+
67+
68+
var ParseUtil = {
69+
_installationKey: "ParsePushPluginInstallation",
70+
_parseVersion: "1.7.0.0", //we're actually using the REST api, so this is just a filler
71+
_serverUrl: null,
72+
_xhrHeaders: {},
73+
74+
_hexOctet: function () {
75+
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
76+
},
77+
_generateId: function () {
78+
return this._hexOctet() + this._hexOctet() + '-'
79+
+ this._hexOctet() + '-'
80+
+ this._hexOctet() + '-'
81+
+ this._hexOctet() + '-'
82+
+ this._hexOctet() + this._hexOctet() + this._hexOctet();
83+
},
84+
_formUrl: function(tail){
85+
return [this._serverUrl, tail].join('');
86+
},
87+
88+
initializeViaConfigXML: function(){
89+
this._xhrHeaders = {
90+
"Content-Type": "application/json",
91+
"X-Parse-Application-Id": CordovaUtil.getPreference("ParseAppId")
92+
};
93+
94+
this._serverUrl = CordovaUtil.getPreference("ParseServerUrl");
95+
if (this._serverUrl === "PARSE_DOT_COM") {
96+
//legacy mode
97+
this._serverUrl = "https://api.parse.com/1/";
98+
this._xhrHeaders["X-Parse-Windows-Key"] = CordovaUtil.getPreference("ParseWindowsKey");
99+
}
100+
101+
if (!this._serverUrl.endsWith('/')) {
102+
this._serverUrl += '/';
103+
}
104+
},
105+
106+
getCurrentInstallation: function(){
107+
var stringCache = localSettings[ParseUtil._installationKey];
108+
if (stringCache) {
109+
return JSON.parse(stringCache);
110+
} else {
111+
//return a new unsaved installation object
112+
var appMeta = CordovaUtil.getAppMeta();
113+
var deviceMeta = {
114+
deviceType: 'winrt',
115+
deviceUris: null, //initialized deviceUris to null, but must be set appropriately before/during save
116+
timeZone: LocaleUtil.getIANATimeZone(),
117+
localeIdentifier: LocaleUtil.getLocalId(),
118+
};
119+
var installationMeta = {
120+
parseVersion: ParseUtil._parseVersion,
121+
installationId: ParseUtil._generateId()
122+
};
123+
124+
return Object.assign({}, appMeta, deviceMeta, installationMeta);
125+
}
126+
},
127+
128+
_updateInstallationAsync: function (installationData) {
129+
console.log("db-update installation objectId: " + installationData.objectId);
130+
return WinJS.xhr({
131+
type: "PUT",
132+
url: ParseUtil._formUrl('installations/' + installationData.objectId),
133+
headers: ParseUtil._xhrHeaders,
134+
data: JSON.stringify(installationData)
135+
});
136+
},
137+
_createInstallationAsync: function (installationData) {
138+
console.log("db-create new installation: " + installationData.installationId);
139+
return WinJS.xhr({
140+
type: "POST",
141+
url: ParseUtil._formUrl('installations'),
142+
headers: ParseUtil._xhrHeaders,
143+
data: JSON.stringify(installationData)
144+
});
145+
},
146+
_saveLocalInstallation: function (installationData) {
147+
//
148+
// save to WinJS's local storage mechanism
149+
localSettings[this._installationKey] = JSON.stringify(installationData);
150+
},
151+
saveInstallationAsync: function (wnsChannel) {
152+
var installationData = this.getCurrentInstallation();
153+
154+
if (wnsChannel) {
155+
var storedUri = (installationData.deviceUris || {})._Default;
156+
157+
if (installationData.objectId && storedUri === wnsChannel.uri) {
158+
//
159+
// channel has not changed, no need to save to database
160+
return WinJS.Promise.as(installationData);
161+
} else {
162+
installationData.deviceUris = { _Default: wnsChannel.uri };
163+
var promise = (installationData.objectId) ? ParseUtil._updateInstallationAsync(installationData) : ParseUtil._createInstallationAsync(installationData);
164+
165+
//
166+
// store newly saved object to local settings cache
167+
return promise.then(function (xhr) {
168+
var response = JSON.parse(xhr.response);
169+
if (response.objectId) {
170+
installationData.objectId = response.objectId;
171+
}
172+
173+
ParseUtil._saveLocalInstallation(installationData);
174+
return installationData;
175+
});
176+
}
177+
} else {
178+
throw "Missing parameter 'wnsChannel'";
179+
}
180+
},
181+
subscribeAsync: function (channel) {
182+
var installationData = ParseUtil.getCurrentInstallation();
183+
184+
if (!installationData || !installationData.objectId) {
185+
var msg = "You have not saved an installation. Can't subscribe.";
186+
console.error(msg);
187+
return WinJS.Promise.wrapError(msg);
188+
}
189+
190+
if (!installationData.channels)
191+
installationData.channels = [];
192+
193+
if (installationData.channels.indexOf(channel) > -1) {
194+
console.log("Noop. Already subscribed to channel: " + channel);
195+
return WinJS.Promise.as(true);
196+
} else {
197+
installationData.channels.push(channel);
198+
}
199+
200+
return ParseUtil._updateChannelsAsync(installationData);
201+
},
202+
unsubscribeAsync: function (channel) {
203+
var installationData = ParseUtil.getCurrentInstallation();
204+
205+
if (!installationData || !installationData.objectId) {
206+
var msg = "You have not saved an installation. Can't unsubscribe.";
207+
console.error(msg);
208+
return WinJS.Promise.wrapError(msg);
209+
}
210+
211+
if (!installationData.channels) {
212+
var msg = "You have not subscribed to any channel. Can't unsubscribe";
213+
console.warn(msg);
214+
return WinJS.Promise.wrapError(msg);
215+
}
216+
217+
var index = installationData.channels.indexOf(channel);
218+
if (index >= 0) {
219+
installationData.channels.splice(index, 1);
220+
} else {
221+
var msg = "You are not subscribed to: '" + channel + "'. Can't unsubscribe";
222+
console.warn(msg);
223+
return WinJS.Promise.wrapError(msg);
224+
}
225+
226+
return ParseUtil._updateChannelsAsync(installationData);
227+
},
228+
_updateChannelsAsync: function (installationData) {
229+
return WinJS.xhr({
230+
type: "PUT",
231+
url: ParseUtil._formUrl('installations/' + installationData.objectId),
232+
headers: ParseUtil._xhrHeaders,
233+
data: JSON.stringify({ channels: installationData.channels })
234+
}).then(function (xhr) {
235+
ParseUtil._saveLocalInstallation(installationData);
236+
return true;
237+
});
238+
}
239+
};
240+
241+
242+
var WNSUtil = {
243+
registerChannelAsync: function (pnCallback) {
244+
//
245+
// Note: WNS channels can expire (30 days) and a call to createPushNotificationChannelForApplicationAsync() may return
246+
// a new channel so call this function each time the app starts. Let MS take care of returning same channel
247+
// or new one.
248+
//
249+
// Readings:
250+
// https://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-windows-push-notification-services--wns--overview
251+
// https://msdn.microsoft.com/en-us/library/windows/apps/hh465412.aspx
252+
//
253+
254+
var onPushReceive = function(e) {
255+
debugger;
256+
console.log("***** ONPUSH RECEIVE", e);
257+
WNSUtil._onPushReceive(e, pnCallback);
258+
}
259+
260+
return pushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync().then(
261+
function (wnsChannel) {
262+
wnsChannel.addEventListener("pushnotificationreceived", onPushReceive);
263+
wnsChannel.onpushnotificationreceived = onPushReceive;
264+
265+
console.log('channel', wnsChannel);
266+
267+
WNSUtil.channel = wnsChannel; //save channel so we can unregister events
268+
269+
270+
//var context = cordova.require('cordova/platform').activationContext;
271+
//console.log('CONTEXt', context);
272+
273+
return wnsChannel;
274+
}
275+
);
276+
},
277+
278+
_onPushReceive: function (e, pnCallback) {
279+
var pnPayload;
280+
281+
switch (e.notificationType) {
282+
case pushNotifications.PushNotificationType.toast:
283+
console.log('toast', e);
284+
pnPayload = e.toastNotification.content.getXml();
285+
break;
286+
287+
case pushNotifications.PushNotificationType.tile:
288+
console.log('tile', e);
289+
pnPayload = e.tileNotification.content.getXml();
290+
break;
291+
292+
case pushNotifications.PushNotificationType.badge:
293+
console.log('badge', e);
294+
pnPayload = e.badgeNotification.content.getXml();
295+
break;
296+
297+
case pushNotifications.PushNotificationType.raw:
298+
console.log('raw', e);
299+
pnPayload = e.rawNotification.content;
300+
break;
301+
}
302+
303+
console.log('payload', pnPayload);
304+
e.cancel = true;
305+
306+
pnCallback(pnPayload, "RECEIVE");
307+
}
308+
};
309+
310+
var CordovaUtil = {
311+
_configXML: null,
312+
getConfigXML: function () {
313+
if (!this._configXML) {
314+
//
315+
// load configXML synchronously.
316+
// making this synchronous simplifies the rest of the code because the rest of
317+
// the code depends on these config parameters
318+
//
319+
var req = new XMLHttpRequest();
320+
req.open("GET", "../config.xml", false); //false -> synchronous
321+
req.send();
322+
this._configXML =req.responseXML.querySelector("widget");
323+
}
324+
return this._configXML;
325+
},
326+
getAppMeta: function(){
327+
var configXML = this.getConfigXML();
328+
return {
329+
appName: configXML.querySelector("name").textContent,
330+
appIdentifier: configXML.getAttribute("id"),
331+
appVersion: configXML.getAttribute("version"),
332+
}
333+
},
334+
getPreference: function(name){
335+
var configXML = this.getConfigXML();
336+
var selector = "preference[name='" + name + "']";
337+
338+
return configXML.querySelector(selector).getAttribute("value");
339+
},
340+
getPreferences: function (prefNames) {
341+
//
342+
// Input: array of preference names
343+
// Output: Object of preferences with preference names as keys
344+
//
345+
var configXML = this.getConfigXML();
346+
347+
var prefs = {};
348+
prefNames.forEach(function (name) {
349+
var selector = "preference[name='" + name + "']";
350+
prefs[name] = configXML.querySelector(selector).getAttribute("value");
351+
});
352+
return prefs;
353+
}
354+
}
355+
356+
var LocaleUtil = {
357+
getLocalId: function(){
358+
return Intl.DateTimeFormat().resolvedOptions().locale || navigator.browserLanguage || navigator.language;
359+
},
360+
getIANATimeZone: function(){
361+
return jstz.determine().name();
362+
}
363+
}

0 commit comments

Comments
 (0)