electron-wns 0.0.0 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -179
- package/bin/win32-x64-128/electron-wns.node +0 -0
- package/binding.gyp +34 -0
- package/index.js +11 -0
- package/package.json +29 -40
- package/src/electron_wns.cpp +256 -0
- package/dist/channel.d.ts +0 -11
- package/dist/channel.js +0 -69
- package/dist/index.d.ts +0 -25
- package/dist/index.js +0 -28
- package/dist/powershell.d.ts +0 -36
- package/dist/powershell.js +0 -87
- package/dist/types.d.ts +0 -38
- package/dist/types.js +0 -5
- package/dist/wns-client.d.ts +0 -63
- package/dist/wns-client.js +0 -167
- package/scripts/get-channel.ps1 +0 -73
- package/scripts/wns-listener.ps1 +0 -136
package/README.md
CHANGED
|
@@ -1,179 +1,99 @@
|
|
|
1
|
-
# electron-wns
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Later, when the app is about to quit:
|
|
101
|
-
client.stopListening();
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Refresh an expired channel
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
// Channel URIs have a limited lifetime (check `channel.expiresAt` for the exact expiry).
|
|
108
|
-
// Call refreshChannel() on startup or when the channel is nearing its expiry.
|
|
109
|
-
const newChannel = await client.refreshChannel();
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Custom PowerShell path
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
const client = new WNSClient({ powershellPath: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' });
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## API
|
|
121
|
-
|
|
122
|
-
### `new WNSClient(options?)`
|
|
123
|
-
|
|
124
|
-
| Option | Type | Default | Description |
|
|
125
|
-
|---|---|---|---|
|
|
126
|
-
| `powershellPath` | `string` | `powershell.exe` (Win) / `pwsh` | Path to the PowerShell executable. |
|
|
127
|
-
|
|
128
|
-
### Methods
|
|
129
|
-
|
|
130
|
-
| Method | Returns | Description |
|
|
131
|
-
|---|---|---|
|
|
132
|
-
| `getChannel()` | `Promise<WNSChannel>` | Obtain (and cache) the WNS channel for this app. |
|
|
133
|
-
| `refreshChannel()` | `Promise<WNSChannel>` | Force a fresh channel request and emit `channelUpdated`. |
|
|
134
|
-
| `startListening()` | `void` | Spawn the background WNS listener. |
|
|
135
|
-
| `stopListening()` | `void` | Kill the background listener. |
|
|
136
|
-
| `isListening()` | `boolean` | Whether the listener is currently running. |
|
|
137
|
-
|
|
138
|
-
### Events
|
|
139
|
-
|
|
140
|
-
| Event | Payload | Description |
|
|
141
|
-
|---|---|---|
|
|
142
|
-
| `channelUpdated` | `WNSChannel` | Emitted when a new channel URI is obtained. |
|
|
143
|
-
| `notification` | `WNSNotification` | Emitted for each incoming push notification. |
|
|
144
|
-
| `error` | `Error` | Non-fatal listener error. |
|
|
145
|
-
|
|
146
|
-
### Types
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
interface WNSChannel {
|
|
150
|
-
uri: string; // The channel URI to give to your backend
|
|
151
|
-
expiresAt: string; // ISO 8601 expiry timestamp – check this field for the exact expiry
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
interface WNSNotification {
|
|
155
|
-
notificationType: 'toast' | 'tile' | 'badge' | 'raw';
|
|
156
|
-
payload: string; // XML for toast/tile/badge; arbitrary string for raw
|
|
157
|
-
timestamp: string; // ISO 8601
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## How it works internally
|
|
164
|
-
|
|
165
|
-
`electron-wns` ships two PowerShell helper scripts (`scripts/`):
|
|
166
|
-
|
|
167
|
-
| Script | Purpose |
|
|
168
|
-
|---|---|
|
|
169
|
-
| `get-channel.ps1` | Calls `PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync()` (WinRT) and writes the channel URI as JSON to stdout. |
|
|
170
|
-
| `wns-listener.ps1` | Obtains the channel, subscribes to `PushNotificationChannel.PushNotificationReceived`, and continuously writes incoming notification events as JSON to stdout. |
|
|
171
|
-
|
|
172
|
-
The TypeScript library spawns these scripts as child processes and communicates via newline-delimited JSON on stdout, keeping the library dependency-free at runtime.
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## License
|
|
177
|
-
|
|
178
|
-
MIT
|
|
179
|
-
|
|
1
|
+
# electron-wns
|
|
2
|
+
|
|
3
|
+
Node native addon that allows you to receive push messages from Windows Push Notifications Services (WNS), in Electron (and node).
|
|
4
|
+
|
|
5
|
+
- Currently in an initial state of development, but so far I have been able to successfully
|
|
6
|
+
use this library to obtain a channel URI (with an included token), that could be used to send WNS
|
|
7
|
+
push messages to.
|
|
8
|
+
|
|
9
|
+
- Tested with Electron 32.2.4, but probably will work with most modern versions of electron (and node)
|
|
10
|
+
- Tested by launching in a packaged, and code signed context via a .MSIX installer (see https://www.electronforge.io/config/makers/msix)
|
|
11
|
+
|
|
12
|
+
- Library is actively being improved!
|
|
13
|
+
|
|
14
|
+
## USAGE
|
|
15
|
+
|
|
16
|
+
1. Clone this repo
|
|
17
|
+
2. npm install
|
|
18
|
+
3. npm run build (change the Electron version in package.json to match the version of electron your working with)
|
|
19
|
+
4. Copy the built electron_wns.node into your electron project.
|
|
20
|
+
5. Ensure that it is packaged up with the app. For electron packager / forge, you may need to adjust packager config:
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
- Place the .node file in assets/win32/wns/electron_wns.node within your repo.
|
|
24
|
+
- Adjust packager config to ensure that folder is packaged up with the application files:
|
|
25
|
+
```
|
|
26
|
+
packagerConfig: {
|
|
27
|
+
extraResource: [
|
|
28
|
+
`./assets/${process.platform}`
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
- To reference the .node file by its absolute path I recommend a helper function
|
|
33
|
+
that works both when the app is packaged or not:
|
|
34
|
+
|
|
35
|
+
src/Assets.ts
|
|
36
|
+
```
|
|
37
|
+
import { app } from 'electron';
|
|
38
|
+
import path from 'path';
|
|
39
|
+
|
|
40
|
+
// Helper functions for accessing assets/ folder when packaged with running electron app.
|
|
41
|
+
class Assets {
|
|
42
|
+
static getURL() {
|
|
43
|
+
if (app.isPackaged) {
|
|
44
|
+
return process.resourcesPath;
|
|
45
|
+
} else {
|
|
46
|
+
return path.normalize(`${__dirname}../../../assets`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default Assets;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- require() the .node file to use it in the javascripts of your main process like so:
|
|
55
|
+
```
|
|
56
|
+
const assetsURL = Assets.getURL();
|
|
57
|
+
const addonPath = path.join(assetsURL, 'win32', 'wns', 'electron_wns.node');
|
|
58
|
+
|
|
59
|
+
const runtimeRequire = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : require;
|
|
60
|
+
|
|
61
|
+
// Note: its a good idea to try/catch the following require() statement which will throw a MODULE_NOT_FOUND
|
|
62
|
+
// if the absolute path the .node file is wrong, or is compiled/built againts the wrong electron/node ABI.
|
|
63
|
+
electronWNS = runtimeRequire(addonPath);
|
|
64
|
+
|
|
65
|
+
const channel = await electronWNS.getChannel();
|
|
66
|
+
console.log('WNS channel URI:', channel.uri); // Your backend can use this to send to this device
|
|
67
|
+
|
|
68
|
+
electronWNS.startForegroundNotifications((notification) => {
|
|
69
|
+
console.log('Foreground WNS notification:', notification);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
electronWNS.stopForegroundNotifications();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Exposed API of electron_wns.node:
|
|
76
|
+
|
|
77
|
+
- `getChannel(): Promise<{ uri: string; expirationTicks: number }>`
|
|
78
|
+
- `startForegroundNotifications(callback: (notification) => void): void`
|
|
79
|
+
- `stopForegroundNotifications(): void`
|
|
80
|
+
|
|
81
|
+
## Structure of Received Notifications
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
{
|
|
85
|
+
type: 'raw' | 'toast' | 'tile' | 'badge' | 'unknown',
|
|
86
|
+
content: string,
|
|
87
|
+
headers: Record<string, string> // populated for raw notifications
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## TO COME:
|
|
92
|
+
- Typescript types
|
|
93
|
+
- Improving the way the library is integrated into your app - so that you can npm install, and access via a javascript wrapper
|
|
94
|
+
- electron rebuild support that automatically would detect your version of electron (electron rebuild)
|
|
95
|
+
- Typescript types/etc.
|
|
96
|
+
|
|
97
|
+
## An Alternative Library:
|
|
98
|
+
- There is the NodeRT project: https://github.com/NodeRT/NodeRT
|
|
99
|
+
- But that is currently very outdated and does not compile against more modern versions of node/electron
|
|
Binary file
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "electron_wns",
|
|
5
|
+
"sources": [
|
|
6
|
+
"src/electron_wns.cpp"
|
|
7
|
+
],
|
|
8
|
+
"include_dirs": [
|
|
9
|
+
"<!@(node -p \"require('node-addon-api').include\")"
|
|
10
|
+
],
|
|
11
|
+
"defines": [
|
|
12
|
+
"NAPI_CPP_EXCEPTIONS"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": [
|
|
15
|
+
"<!(node -p \"require('node-addon-api').gyp\")"
|
|
16
|
+
],
|
|
17
|
+
"conditions": [
|
|
18
|
+
["OS=='win'", {
|
|
19
|
+
"libraries": [
|
|
20
|
+
"runtimeobject.lib",
|
|
21
|
+
"windowsapp.lib"
|
|
22
|
+
],
|
|
23
|
+
"msvs_settings": {
|
|
24
|
+
"VCCLCompilerTool": {
|
|
25
|
+
"AdditionalOptions": [
|
|
26
|
+
"/EHsc"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}]
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const addonPath = path.join(__dirname, 'build', 'Release', 'electron_wns.node');
|
|
4
|
+
const binding = require(addonPath);
|
|
5
|
+
|
|
6
|
+
console.log('==> index.js binding.getChannel: ', typeof binding.getChannel);
|
|
7
|
+
module.exports = {
|
|
8
|
+
getChannel: binding.getChannel,
|
|
9
|
+
startForegroundNotifications: binding.startForegroundNotifications,
|
|
10
|
+
stopForegroundNotifications: binding.stopForegroundNotifications,
|
|
11
|
+
};
|
package/package.json
CHANGED
|
@@ -1,40 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "electron-wns",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"url": "https://github.com/iotum/electron-wns/issues"
|
|
31
|
-
},
|
|
32
|
-
"homepage": "https://github.com/iotum/electron-wns#readme",
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@types/jest": "^29.5.14",
|
|
35
|
-
"@types/node": "^20.19.37",
|
|
36
|
-
"jest": "^29.7.0",
|
|
37
|
-
"ts-jest": "^29.4.6",
|
|
38
|
-
"typescript": "^5.9.3"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "electron-wns",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "Native Node addon for WNS channel creation and foreground notification listening",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"install": "npm run build:electron32",
|
|
8
|
+
"build": "npm run build:electron32",
|
|
9
|
+
"build:electron32": "electron-rebuild -f -v 32.2.4 -w electron-wns --module-dir .",
|
|
10
|
+
"build:node": "node-gyp rebuild",
|
|
11
|
+
"clean": "node-gyp clean"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"electron",
|
|
15
|
+
"wns",
|
|
16
|
+
"windows",
|
|
17
|
+
"native-addon"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"gypfile": true,
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"bindings": "^1.5.0",
|
|
23
|
+
"node-addon-api": "^8.1.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@electron/rebuild": "^3.7.1",
|
|
27
|
+
"node-gyp": "^11.1.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
|
|
3
|
+
#include <mutex>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <unordered_map>
|
|
6
|
+
|
|
7
|
+
#include <Windows.h>
|
|
8
|
+
|
|
9
|
+
#include <winrt/Windows.Data.Xml.Dom.h>
|
|
10
|
+
#include <winrt/Windows.Foundation.h>
|
|
11
|
+
#include <winrt/Windows.Foundation.Collections.h>
|
|
12
|
+
#include <winrt/Windows.Networking.PushNotifications.h>
|
|
13
|
+
#include <winrt/Windows.UI.Notifications.h>
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
using winrt::Windows::Networking::PushNotifications::PushNotificationChannel;
|
|
17
|
+
using winrt::Windows::Networking::PushNotifications::PushNotificationChannelManager;
|
|
18
|
+
using winrt::Windows::Networking::PushNotifications::PushNotificationReceivedEventArgs;
|
|
19
|
+
using winrt::Windows::Networking::PushNotifications::PushNotificationType;
|
|
20
|
+
|
|
21
|
+
std::mutex g_mutex;
|
|
22
|
+
std::once_flag g_apartmentInitFlag;
|
|
23
|
+
PushNotificationChannel g_channel{nullptr};
|
|
24
|
+
winrt::event_token g_notificationToken{};
|
|
25
|
+
bool g_isListening = false;
|
|
26
|
+
Napi::ThreadSafeFunction g_notificationTsfn;
|
|
27
|
+
|
|
28
|
+
std::string ToUtf8(std::wstring const& input);
|
|
29
|
+
std::string ToUtf8(winrt::hstring const& input);
|
|
30
|
+
PushNotificationChannel GetOrCreateChannel();
|
|
31
|
+
|
|
32
|
+
class GetChannelWorker : public Napi::AsyncWorker {
|
|
33
|
+
public:
|
|
34
|
+
GetChannelWorker(const Napi::Env& env, Napi::Promise::Deferred deferred)
|
|
35
|
+
: Napi::AsyncWorker(env), deferred_(deferred) {}
|
|
36
|
+
|
|
37
|
+
~GetChannelWorker() override = default;
|
|
38
|
+
|
|
39
|
+
void Execute() override {
|
|
40
|
+
try {
|
|
41
|
+
auto channel = GetOrCreateChannel();
|
|
42
|
+
uri_ = ToUtf8(channel.Uri());
|
|
43
|
+
|
|
44
|
+
const auto expiration = channel.ExpirationTime();
|
|
45
|
+
expirationTicks_ = expiration.time_since_epoch().count();
|
|
46
|
+
} catch (const winrt::hresult_error& error) {
|
|
47
|
+
SetError(ToUtf8(error.message()));
|
|
48
|
+
} catch (const std::exception& error) {
|
|
49
|
+
SetError(error.what());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
void OnOK() override {
|
|
54
|
+
Napi::Object result = Napi::Object::New(Env());
|
|
55
|
+
result.Set("uri", uri_);
|
|
56
|
+
result.Set("expirationTicks", Napi::Number::New(Env(), static_cast<double>(expirationTicks_)));
|
|
57
|
+
deferred_.Resolve(result);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void OnError(const Napi::Error& error) override { deferred_.Reject(error.Value()); }
|
|
61
|
+
|
|
62
|
+
private:
|
|
63
|
+
Napi::Promise::Deferred deferred_;
|
|
64
|
+
std::string uri_;
|
|
65
|
+
int64_t expirationTicks_ = 0;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
void EnsureApartmentInitialized() {
|
|
69
|
+
std::call_once(g_apartmentInitFlag, []() {
|
|
70
|
+
try {
|
|
71
|
+
winrt::init_apartment(winrt::apartment_type::multi_threaded);
|
|
72
|
+
} catch (const winrt::hresult_error& error) {
|
|
73
|
+
if (error.code() != RPC_E_CHANGED_MODE) {
|
|
74
|
+
throw;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
std::string ToUtf8(std::wstring const& input) {
|
|
81
|
+
if (input.empty()) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
int required = WideCharToMultiByte(CP_UTF8, 0, input.c_str(), static_cast<int>(input.size()), nullptr, 0, nullptr, nullptr);
|
|
86
|
+
std::string output(static_cast<size_t>(required), '\0');
|
|
87
|
+
WideCharToMultiByte(CP_UTF8, 0, input.c_str(), static_cast<int>(input.size()), output.data(), required, nullptr, nullptr);
|
|
88
|
+
return output;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
std::string ToUtf8(winrt::hstring const& input) {
|
|
92
|
+
return ToUtf8(std::wstring(input));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
PushNotificationChannel GetOrCreateChannel() {
|
|
96
|
+
EnsureApartmentInitialized();
|
|
97
|
+
|
|
98
|
+
std::lock_guard<std::mutex> lock(g_mutex);
|
|
99
|
+
if (g_channel) {
|
|
100
|
+
return g_channel;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
auto asyncOp = PushNotificationChannelManager::CreatePushNotificationChannelForApplicationAsync();
|
|
104
|
+
g_channel = asyncOp.get();
|
|
105
|
+
return g_channel;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
std::string NotificationTypeToString(PushNotificationType type) {
|
|
109
|
+
switch (type) {
|
|
110
|
+
case PushNotificationType::Toast:
|
|
111
|
+
return "toast";
|
|
112
|
+
case PushNotificationType::Tile:
|
|
113
|
+
return "tile";
|
|
114
|
+
case PushNotificationType::Badge:
|
|
115
|
+
return "badge";
|
|
116
|
+
case PushNotificationType::Raw:
|
|
117
|
+
return "raw";
|
|
118
|
+
default:
|
|
119
|
+
return "unknown";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Napi::Value GetChannelWrapped(const Napi::CallbackInfo& info) {
|
|
124
|
+
Napi::Env env = info.Env();
|
|
125
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
126
|
+
|
|
127
|
+
auto* worker = new GetChannelWorker(env, deferred);
|
|
128
|
+
worker->Queue();
|
|
129
|
+
|
|
130
|
+
return deferred.Promise();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Napi::Value StartForegroundNotificationsWrapped(const Napi::CallbackInfo& info) {
|
|
134
|
+
Napi::Env env = info.Env();
|
|
135
|
+
if (info.Length() < 1 || !info[0].IsFunction()) {
|
|
136
|
+
throw Napi::TypeError::New(env, "startForegroundNotifications requires a callback function");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Napi::Function callback = info[0].As<Napi::Function>();
|
|
140
|
+
|
|
141
|
+
EnsureApartmentInitialized();
|
|
142
|
+
auto channel = GetOrCreateChannel();
|
|
143
|
+
|
|
144
|
+
std::lock_guard<std::mutex> lock(g_mutex);
|
|
145
|
+
|
|
146
|
+
if (g_isListening) {
|
|
147
|
+
if (g_notificationTsfn) {
|
|
148
|
+
g_notificationTsfn.Release();
|
|
149
|
+
}
|
|
150
|
+
channel.PushNotificationReceived(g_notificationToken);
|
|
151
|
+
g_isListening = false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
g_notificationTsfn = Napi::ThreadSafeFunction::New(
|
|
155
|
+
env,
|
|
156
|
+
callback,
|
|
157
|
+
"wns-foreground-notification-callback",
|
|
158
|
+
0,
|
|
159
|
+
1);
|
|
160
|
+
|
|
161
|
+
g_notificationToken = channel.PushNotificationReceived([](PushNotificationChannel const&, PushNotificationReceivedEventArgs const& args) {
|
|
162
|
+
std::lock_guard<std::mutex> callbackLock(g_mutex);
|
|
163
|
+
if (!g_notificationTsfn) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
struct NotificationPayload {
|
|
168
|
+
std::string type;
|
|
169
|
+
std::string content;
|
|
170
|
+
std::unordered_map<std::string, std::string> headers;
|
|
171
|
+
} payload;
|
|
172
|
+
|
|
173
|
+
payload.type = NotificationTypeToString(args.NotificationType());
|
|
174
|
+
|
|
175
|
+
if (args.NotificationType() == PushNotificationType::Raw) {
|
|
176
|
+
auto raw = args.RawNotification();
|
|
177
|
+
payload.content = ToUtf8(raw.Content());
|
|
178
|
+
|
|
179
|
+
auto headerMap = raw.Headers();
|
|
180
|
+
for (const auto& pair : headerMap) {
|
|
181
|
+
payload.headers[ToUtf8(pair.Key())] = ToUtf8(pair.Value());
|
|
182
|
+
}
|
|
183
|
+
} else if (args.ToastNotification()) {
|
|
184
|
+
auto xmlDocument = args.ToastNotification().Content();
|
|
185
|
+
payload.content = ToUtf8(xmlDocument.GetXml());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
auto status = g_notificationTsfn.BlockingCall(
|
|
189
|
+
new NotificationPayload(std::move(payload)),
|
|
190
|
+
[](Napi::Env callbackEnv, Napi::Function jsCallback, NotificationPayload* data) {
|
|
191
|
+
Napi::Object notification = Napi::Object::New(callbackEnv);
|
|
192
|
+
notification.Set("type", data->type);
|
|
193
|
+
notification.Set("content", data->content);
|
|
194
|
+
|
|
195
|
+
Napi::Object headers = Napi::Object::New(callbackEnv);
|
|
196
|
+
for (const auto& [key, value] : data->headers) {
|
|
197
|
+
headers.Set(key, value);
|
|
198
|
+
}
|
|
199
|
+
notification.Set("headers", headers);
|
|
200
|
+
|
|
201
|
+
jsCallback.Call({notification});
|
|
202
|
+
delete data;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (status != napi_ok) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
g_isListening = true;
|
|
211
|
+
return env.Undefined();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Napi::Value StopForegroundNotificationsWrapped(const Napi::CallbackInfo& info) {
|
|
215
|
+
Napi::Env env = info.Env();
|
|
216
|
+
EnsureApartmentInitialized();
|
|
217
|
+
|
|
218
|
+
std::lock_guard<std::mutex> lock(g_mutex);
|
|
219
|
+
|
|
220
|
+
if (!g_isListening) {
|
|
221
|
+
return env.Undefined();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (g_channel) {
|
|
225
|
+
g_channel.PushNotificationReceived(g_notificationToken);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (g_notificationTsfn) {
|
|
229
|
+
g_notificationTsfn.Release();
|
|
230
|
+
g_notificationTsfn = {};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
g_isListening = false;
|
|
234
|
+
return env.Undefined();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
Napi::Object Initialize(Napi::Env env, Napi::Object exports) {
|
|
238
|
+
try {
|
|
239
|
+
EnsureApartmentInitialized();
|
|
240
|
+
} catch (const winrt::hresult_error& error) {
|
|
241
|
+
Napi::Error::New(env, ToUtf8(error.message())).ThrowAsJavaScriptException();
|
|
242
|
+
return exports;
|
|
243
|
+
} catch (const std::exception& error) {
|
|
244
|
+
Napi::Error::New(env, error.what()).ThrowAsJavaScriptException();
|
|
245
|
+
return exports;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
exports.Set("getChannel", Napi::Function::New(env, GetChannelWrapped));
|
|
249
|
+
exports.Set("startForegroundNotifications", Napi::Function::New(env, StartForegroundNotificationsWrapped));
|
|
250
|
+
exports.Set("stopForegroundNotifications", Napi::Function::New(env, StopForegroundNotificationsWrapped));
|
|
251
|
+
|
|
252
|
+
return exports;
|
|
253
|
+
}
|
|
254
|
+
} // namespace
|
|
255
|
+
|
|
256
|
+
NODE_API_MODULE(electron_wns, Initialize)
|
package/dist/channel.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WNS channel management – obtaining and caching the push notification channel URI.
|
|
3
|
-
*/
|
|
4
|
-
import { WNSChannel } from './types';
|
|
5
|
-
/**
|
|
6
|
-
* Request a push notification channel URI from WNS by running the
|
|
7
|
-
* `get-channel.ps1` PowerShell helper.
|
|
8
|
-
*
|
|
9
|
-
* @throws {Error} If PowerShell fails or the channel URI cannot be obtained.
|
|
10
|
-
*/
|
|
11
|
-
export declare function requestChannel(powershellPath: string): Promise<WNSChannel>;
|