@pocketping/sdk-node 1.0.0 → 1.1.0
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 +71 -0
- package/dist/index.cjs +118 -1
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +118 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -69,7 +69,78 @@ const pp = new PocketPing({
|
|
|
69
69
|
// Protocol version settings
|
|
70
70
|
protocolVersion: '1.0',
|
|
71
71
|
minSupportedVersion: '0.1',
|
|
72
|
+
|
|
73
|
+
// IP filtering (see IP Filtering section below)
|
|
74
|
+
ipFilter: {
|
|
75
|
+
enabled: true,
|
|
76
|
+
mode: 'blocklist',
|
|
77
|
+
blocklist: ['203.0.113.0/24'],
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## IP Filtering
|
|
83
|
+
|
|
84
|
+
Block or allow specific IP addresses or CIDR ranges:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const pp = new PocketPing({
|
|
88
|
+
ipFilter: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
mode: 'blocklist', // 'allowlist' | 'blocklist' | 'both'
|
|
91
|
+
blocklist: [
|
|
92
|
+
'203.0.113.0/24', // CIDR range
|
|
93
|
+
'198.51.100.50', // Single IP
|
|
94
|
+
],
|
|
95
|
+
allowlist: [
|
|
96
|
+
'10.0.0.0/8', // Internal network
|
|
97
|
+
],
|
|
98
|
+
logBlocked: true, // Log blocked requests (default: true)
|
|
99
|
+
blockedStatusCode: 403,
|
|
100
|
+
blockedMessage: 'Forbidden',
|
|
101
|
+
},
|
|
72
102
|
});
|
|
103
|
+
|
|
104
|
+
// Or with a custom filter function
|
|
105
|
+
const pp = new PocketPing({
|
|
106
|
+
ipFilter: {
|
|
107
|
+
enabled: true,
|
|
108
|
+
mode: 'blocklist',
|
|
109
|
+
customFilter: (ip, request) => {
|
|
110
|
+
// Return true to allow, false to block, null to defer to list-based filtering
|
|
111
|
+
if (ip.startsWith('192.168.')) return true; // Always allow local
|
|
112
|
+
return null; // Use blocklist/allowlist
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Modes
|
|
119
|
+
|
|
120
|
+
| Mode | Behavior |
|
|
121
|
+
|------|----------|
|
|
122
|
+
| `blocklist` | Block IPs in blocklist, allow all others (default) |
|
|
123
|
+
| `allowlist` | Only allow IPs in allowlist, block all others |
|
|
124
|
+
| `both` | Allowlist takes precedence, then blocklist is applied |
|
|
125
|
+
|
|
126
|
+
### CIDR Support
|
|
127
|
+
|
|
128
|
+
The SDK supports CIDR notation for IP ranges:
|
|
129
|
+
- Single IP: `192.168.1.1` (treated as `/32`)
|
|
130
|
+
- Class C: `192.168.1.0/24` (256 addresses)
|
|
131
|
+
- Class B: `172.16.0.0/16` (65,536 addresses)
|
|
132
|
+
- Class A: `10.0.0.0/8` (16M addresses)
|
|
133
|
+
|
|
134
|
+
### Manual IP Check
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Check IP manually
|
|
138
|
+
const result = pp.checkIpFilter('192.168.1.50');
|
|
139
|
+
// result: { allowed: boolean, reason: string, matchedRule?: string }
|
|
140
|
+
|
|
141
|
+
// Get client IP from request headers
|
|
142
|
+
const clientIp = pp.getClientIp(request.headers);
|
|
143
|
+
// Checks: CF-Connecting-IP, X-Real-IP, X-Forwarded-For
|
|
73
144
|
```
|
|
74
145
|
|
|
75
146
|
## Architecture Options
|
package/dist/index.cjs
CHANGED
|
@@ -92,6 +92,71 @@ var MemoryStorage = class {
|
|
|
92
92
|
}
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
+
// src/utils/ip-filter.ts
|
|
96
|
+
function ipToNumber(ip) {
|
|
97
|
+
const parts = ip.split(".");
|
|
98
|
+
if (parts.length !== 4) return null;
|
|
99
|
+
let num = 0;
|
|
100
|
+
for (const part of parts) {
|
|
101
|
+
const n = parseInt(part, 10);
|
|
102
|
+
if (isNaN(n) || n < 0 || n > 255) return null;
|
|
103
|
+
num = num << 8 | n;
|
|
104
|
+
}
|
|
105
|
+
return num >>> 0;
|
|
106
|
+
}
|
|
107
|
+
function parseCidr(cidr) {
|
|
108
|
+
const [ip, bits] = cidr.split("/");
|
|
109
|
+
const base = ipToNumber(ip);
|
|
110
|
+
if (base === null) return null;
|
|
111
|
+
const prefix = bits ? parseInt(bits, 10) : 32;
|
|
112
|
+
if (isNaN(prefix) || prefix < 0 || prefix > 32) return null;
|
|
113
|
+
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
114
|
+
return { base: (base & mask) >>> 0, mask };
|
|
115
|
+
}
|
|
116
|
+
function ipMatchesCidr(ip, cidr) {
|
|
117
|
+
const ipNum = ipToNumber(ip);
|
|
118
|
+
if (ipNum === null) return false;
|
|
119
|
+
const parsed = parseCidr(cidr);
|
|
120
|
+
if (!parsed) return false;
|
|
121
|
+
return (ipNum & parsed.mask) >>> 0 === parsed.base;
|
|
122
|
+
}
|
|
123
|
+
function ipMatchesAny(ip, list) {
|
|
124
|
+
return list.some((entry) => ipMatchesCidr(ip, entry));
|
|
125
|
+
}
|
|
126
|
+
function shouldAllowIp(ip, config) {
|
|
127
|
+
const { mode = "blocklist", allowlist = [], blocklist = [] } = config;
|
|
128
|
+
switch (mode) {
|
|
129
|
+
case "allowlist":
|
|
130
|
+
if (ipMatchesAny(ip, allowlist)) {
|
|
131
|
+
return { allowed: true, reason: "allowlist" };
|
|
132
|
+
}
|
|
133
|
+
return { allowed: false, reason: "not_in_allowlist" };
|
|
134
|
+
case "blocklist":
|
|
135
|
+
if (ipMatchesAny(ip, blocklist)) {
|
|
136
|
+
return { allowed: false, reason: "blocklist" };
|
|
137
|
+
}
|
|
138
|
+
return { allowed: true, reason: "default" };
|
|
139
|
+
case "both":
|
|
140
|
+
if (ipMatchesAny(ip, allowlist)) {
|
|
141
|
+
return { allowed: true, reason: "allowlist" };
|
|
142
|
+
}
|
|
143
|
+
if (ipMatchesAny(ip, blocklist)) {
|
|
144
|
+
return { allowed: false, reason: "blocklist" };
|
|
145
|
+
}
|
|
146
|
+
return { allowed: true, reason: "default" };
|
|
147
|
+
default:
|
|
148
|
+
return { allowed: true, reason: "default" };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function checkIpFilter(ip, config, requestInfo) {
|
|
152
|
+
if (config.customFilter) {
|
|
153
|
+
const customResult = await config.customFilter(ip, requestInfo);
|
|
154
|
+
if (customResult === true) return { allowed: true, reason: "custom" };
|
|
155
|
+
if (customResult === false) return { allowed: false, reason: "custom" };
|
|
156
|
+
}
|
|
157
|
+
return shouldAllowIp(ip, config);
|
|
158
|
+
}
|
|
159
|
+
|
|
95
160
|
// src/pocketping.ts
|
|
96
161
|
function getClientIp(req) {
|
|
97
162
|
const forwarded = req.headers["x-forwarded-for"];
|
|
@@ -179,6 +244,34 @@ var PocketPing = class {
|
|
|
179
244
|
res.end();
|
|
180
245
|
return;
|
|
181
246
|
}
|
|
247
|
+
if (this.config.ipFilter?.enabled) {
|
|
248
|
+
const clientIp = getClientIp(req);
|
|
249
|
+
const filterResult = await checkIpFilter(clientIp, this.config.ipFilter, {
|
|
250
|
+
path
|
|
251
|
+
});
|
|
252
|
+
if (!filterResult.allowed) {
|
|
253
|
+
if (this.config.ipFilter.logBlocked !== false) {
|
|
254
|
+
const logEvent = {
|
|
255
|
+
type: "blocked",
|
|
256
|
+
ip: clientIp,
|
|
257
|
+
reason: filterResult.reason,
|
|
258
|
+
path,
|
|
259
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
260
|
+
};
|
|
261
|
+
if (this.config.ipFilter.logger) {
|
|
262
|
+
this.config.ipFilter.logger(logEvent);
|
|
263
|
+
} else {
|
|
264
|
+
console.log(`[PocketPing] IP blocked: ${clientIp} - reason: ${filterResult.reason}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
res.statusCode = this.config.ipFilter.blockedStatusCode ?? 403;
|
|
268
|
+
res.setHeader("Content-Type", "application/json");
|
|
269
|
+
res.end(JSON.stringify({
|
|
270
|
+
error: this.config.ipFilter.blockedMessage ?? "Forbidden"
|
|
271
|
+
}));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
182
275
|
const widgetVersion = req.headers["x-pocketping-version"];
|
|
183
276
|
const versionCheck = this.checkWidgetVersion(widgetVersion);
|
|
184
277
|
this.setVersionHeaders(res, versionCheck);
|
|
@@ -287,7 +380,31 @@ var PocketPing = class {
|
|
|
287
380
|
server,
|
|
288
381
|
path: "/pocketping/stream"
|
|
289
382
|
});
|
|
290
|
-
this.wss.on("connection", (ws, req) => {
|
|
383
|
+
this.wss.on("connection", async (ws, req) => {
|
|
384
|
+
if (this.config.ipFilter?.enabled) {
|
|
385
|
+
const clientIp = getClientIp(req);
|
|
386
|
+
const filterResult = await checkIpFilter(clientIp, this.config.ipFilter, {
|
|
387
|
+
path: "/pocketping/stream"
|
|
388
|
+
});
|
|
389
|
+
if (!filterResult.allowed) {
|
|
390
|
+
if (this.config.ipFilter.logBlocked !== false) {
|
|
391
|
+
const logEvent = {
|
|
392
|
+
type: "blocked",
|
|
393
|
+
ip: clientIp,
|
|
394
|
+
reason: filterResult.reason,
|
|
395
|
+
path: "/pocketping/stream",
|
|
396
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
397
|
+
};
|
|
398
|
+
if (this.config.ipFilter.logger) {
|
|
399
|
+
this.config.ipFilter.logger(logEvent);
|
|
400
|
+
} else {
|
|
401
|
+
console.log(`[PocketPing] WS IP blocked: ${clientIp} - reason: ${filterResult.reason}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
ws.close(4003, this.config.ipFilter.blockedMessage ?? "Forbidden");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
291
408
|
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
292
409
|
const sessionId = url.searchParams.get("sessionId");
|
|
293
410
|
if (!sessionId) {
|
package/dist/index.d.cts
CHANGED
|
@@ -56,6 +56,52 @@ interface AIProvider {
|
|
|
56
56
|
isAvailable(): Promise<boolean>;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* IP Filtering utilities for PocketPing SDK
|
|
61
|
+
* Supports CIDR notation and individual IP addresses
|
|
62
|
+
*/
|
|
63
|
+
type IpFilterMode = 'allowlist' | 'blocklist' | 'both';
|
|
64
|
+
interface IpFilterConfig {
|
|
65
|
+
/** Enable/disable IP filtering (default: false) */
|
|
66
|
+
enabled?: boolean;
|
|
67
|
+
/** Filter mode (default: 'blocklist') */
|
|
68
|
+
mode?: IpFilterMode;
|
|
69
|
+
/** IPs/CIDRs to allow (e.g., ['192.168.1.0/24', '10.0.0.1']) */
|
|
70
|
+
allowlist?: string[];
|
|
71
|
+
/** IPs/CIDRs to block (e.g., ['203.0.113.0/24', '198.51.100.50']) */
|
|
72
|
+
blocklist?: string[];
|
|
73
|
+
/** Custom filter callback for advanced logic */
|
|
74
|
+
customFilter?: IpFilterCallback;
|
|
75
|
+
/** Log blocked requests for security auditing (default: true) */
|
|
76
|
+
logBlocked?: boolean;
|
|
77
|
+
/** Custom logger function */
|
|
78
|
+
logger?: (event: IpFilterLogEvent) => void;
|
|
79
|
+
/** HTTP status code for blocked requests (default: 403) */
|
|
80
|
+
blockedStatusCode?: number;
|
|
81
|
+
/** Response message for blocked requests (default: 'Forbidden') */
|
|
82
|
+
blockedMessage?: string;
|
|
83
|
+
/** Trust proxy headers (X-Forwarded-For, etc.) (default: true) */
|
|
84
|
+
trustProxy?: boolean;
|
|
85
|
+
/** Ordered list of headers to check for client IP */
|
|
86
|
+
proxyHeaders?: string[];
|
|
87
|
+
}
|
|
88
|
+
interface IpFilterLogEvent {
|
|
89
|
+
type: 'blocked' | 'allowed';
|
|
90
|
+
ip: string;
|
|
91
|
+
reason: 'allowlist' | 'blocklist' | 'custom' | 'not_in_allowlist' | 'default';
|
|
92
|
+
path: string;
|
|
93
|
+
timestamp: Date;
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Custom IP filter callback
|
|
98
|
+
* Return true to allow, false to block, undefined to defer to list-based filtering
|
|
99
|
+
*/
|
|
100
|
+
type IpFilterCallback = (ip: string, request: {
|
|
101
|
+
path: string;
|
|
102
|
+
sessionId?: string;
|
|
103
|
+
}) => boolean | undefined | Promise<boolean | undefined>;
|
|
104
|
+
|
|
59
105
|
interface PocketPingConfig {
|
|
60
106
|
/** Storage adapter for sessions and messages */
|
|
61
107
|
storage?: Storage | 'memory';
|
|
@@ -89,6 +135,8 @@ interface PocketPingConfig {
|
|
|
89
135
|
versionWarningMessage?: string;
|
|
90
136
|
/** URL to upgrade instructions */
|
|
91
137
|
versionUpgradeUrl?: string;
|
|
138
|
+
/** IP filtering configuration (allowlist/blocklist) */
|
|
139
|
+
ipFilter?: IpFilterConfig;
|
|
92
140
|
}
|
|
93
141
|
interface AIConfig {
|
|
94
142
|
provider: AIProvider | 'openai' | 'gemini' | 'anthropic';
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,52 @@ interface AIProvider {
|
|
|
56
56
|
isAvailable(): Promise<boolean>;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* IP Filtering utilities for PocketPing SDK
|
|
61
|
+
* Supports CIDR notation and individual IP addresses
|
|
62
|
+
*/
|
|
63
|
+
type IpFilterMode = 'allowlist' | 'blocklist' | 'both';
|
|
64
|
+
interface IpFilterConfig {
|
|
65
|
+
/** Enable/disable IP filtering (default: false) */
|
|
66
|
+
enabled?: boolean;
|
|
67
|
+
/** Filter mode (default: 'blocklist') */
|
|
68
|
+
mode?: IpFilterMode;
|
|
69
|
+
/** IPs/CIDRs to allow (e.g., ['192.168.1.0/24', '10.0.0.1']) */
|
|
70
|
+
allowlist?: string[];
|
|
71
|
+
/** IPs/CIDRs to block (e.g., ['203.0.113.0/24', '198.51.100.50']) */
|
|
72
|
+
blocklist?: string[];
|
|
73
|
+
/** Custom filter callback for advanced logic */
|
|
74
|
+
customFilter?: IpFilterCallback;
|
|
75
|
+
/** Log blocked requests for security auditing (default: true) */
|
|
76
|
+
logBlocked?: boolean;
|
|
77
|
+
/** Custom logger function */
|
|
78
|
+
logger?: (event: IpFilterLogEvent) => void;
|
|
79
|
+
/** HTTP status code for blocked requests (default: 403) */
|
|
80
|
+
blockedStatusCode?: number;
|
|
81
|
+
/** Response message for blocked requests (default: 'Forbidden') */
|
|
82
|
+
blockedMessage?: string;
|
|
83
|
+
/** Trust proxy headers (X-Forwarded-For, etc.) (default: true) */
|
|
84
|
+
trustProxy?: boolean;
|
|
85
|
+
/** Ordered list of headers to check for client IP */
|
|
86
|
+
proxyHeaders?: string[];
|
|
87
|
+
}
|
|
88
|
+
interface IpFilterLogEvent {
|
|
89
|
+
type: 'blocked' | 'allowed';
|
|
90
|
+
ip: string;
|
|
91
|
+
reason: 'allowlist' | 'blocklist' | 'custom' | 'not_in_allowlist' | 'default';
|
|
92
|
+
path: string;
|
|
93
|
+
timestamp: Date;
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Custom IP filter callback
|
|
98
|
+
* Return true to allow, false to block, undefined to defer to list-based filtering
|
|
99
|
+
*/
|
|
100
|
+
type IpFilterCallback = (ip: string, request: {
|
|
101
|
+
path: string;
|
|
102
|
+
sessionId?: string;
|
|
103
|
+
}) => boolean | undefined | Promise<boolean | undefined>;
|
|
104
|
+
|
|
59
105
|
interface PocketPingConfig {
|
|
60
106
|
/** Storage adapter for sessions and messages */
|
|
61
107
|
storage?: Storage | 'memory';
|
|
@@ -89,6 +135,8 @@ interface PocketPingConfig {
|
|
|
89
135
|
versionWarningMessage?: string;
|
|
90
136
|
/** URL to upgrade instructions */
|
|
91
137
|
versionUpgradeUrl?: string;
|
|
138
|
+
/** IP filtering configuration (allowlist/blocklist) */
|
|
139
|
+
ipFilter?: IpFilterConfig;
|
|
92
140
|
}
|
|
93
141
|
interface AIConfig {
|
|
94
142
|
provider: AIProvider | 'openai' | 'gemini' | 'anthropic';
|
package/dist/index.js
CHANGED
|
@@ -65,6 +65,71 @@ var MemoryStorage = class {
|
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
// src/utils/ip-filter.ts
|
|
69
|
+
function ipToNumber(ip) {
|
|
70
|
+
const parts = ip.split(".");
|
|
71
|
+
if (parts.length !== 4) return null;
|
|
72
|
+
let num = 0;
|
|
73
|
+
for (const part of parts) {
|
|
74
|
+
const n = parseInt(part, 10);
|
|
75
|
+
if (isNaN(n) || n < 0 || n > 255) return null;
|
|
76
|
+
num = num << 8 | n;
|
|
77
|
+
}
|
|
78
|
+
return num >>> 0;
|
|
79
|
+
}
|
|
80
|
+
function parseCidr(cidr) {
|
|
81
|
+
const [ip, bits] = cidr.split("/");
|
|
82
|
+
const base = ipToNumber(ip);
|
|
83
|
+
if (base === null) return null;
|
|
84
|
+
const prefix = bits ? parseInt(bits, 10) : 32;
|
|
85
|
+
if (isNaN(prefix) || prefix < 0 || prefix > 32) return null;
|
|
86
|
+
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
87
|
+
return { base: (base & mask) >>> 0, mask };
|
|
88
|
+
}
|
|
89
|
+
function ipMatchesCidr(ip, cidr) {
|
|
90
|
+
const ipNum = ipToNumber(ip);
|
|
91
|
+
if (ipNum === null) return false;
|
|
92
|
+
const parsed = parseCidr(cidr);
|
|
93
|
+
if (!parsed) return false;
|
|
94
|
+
return (ipNum & parsed.mask) >>> 0 === parsed.base;
|
|
95
|
+
}
|
|
96
|
+
function ipMatchesAny(ip, list) {
|
|
97
|
+
return list.some((entry) => ipMatchesCidr(ip, entry));
|
|
98
|
+
}
|
|
99
|
+
function shouldAllowIp(ip, config) {
|
|
100
|
+
const { mode = "blocklist", allowlist = [], blocklist = [] } = config;
|
|
101
|
+
switch (mode) {
|
|
102
|
+
case "allowlist":
|
|
103
|
+
if (ipMatchesAny(ip, allowlist)) {
|
|
104
|
+
return { allowed: true, reason: "allowlist" };
|
|
105
|
+
}
|
|
106
|
+
return { allowed: false, reason: "not_in_allowlist" };
|
|
107
|
+
case "blocklist":
|
|
108
|
+
if (ipMatchesAny(ip, blocklist)) {
|
|
109
|
+
return { allowed: false, reason: "blocklist" };
|
|
110
|
+
}
|
|
111
|
+
return { allowed: true, reason: "default" };
|
|
112
|
+
case "both":
|
|
113
|
+
if (ipMatchesAny(ip, allowlist)) {
|
|
114
|
+
return { allowed: true, reason: "allowlist" };
|
|
115
|
+
}
|
|
116
|
+
if (ipMatchesAny(ip, blocklist)) {
|
|
117
|
+
return { allowed: false, reason: "blocklist" };
|
|
118
|
+
}
|
|
119
|
+
return { allowed: true, reason: "default" };
|
|
120
|
+
default:
|
|
121
|
+
return { allowed: true, reason: "default" };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function checkIpFilter(ip, config, requestInfo) {
|
|
125
|
+
if (config.customFilter) {
|
|
126
|
+
const customResult = await config.customFilter(ip, requestInfo);
|
|
127
|
+
if (customResult === true) return { allowed: true, reason: "custom" };
|
|
128
|
+
if (customResult === false) return { allowed: false, reason: "custom" };
|
|
129
|
+
}
|
|
130
|
+
return shouldAllowIp(ip, config);
|
|
131
|
+
}
|
|
132
|
+
|
|
68
133
|
// src/pocketping.ts
|
|
69
134
|
function getClientIp(req) {
|
|
70
135
|
const forwarded = req.headers["x-forwarded-for"];
|
|
@@ -152,6 +217,34 @@ var PocketPing = class {
|
|
|
152
217
|
res.end();
|
|
153
218
|
return;
|
|
154
219
|
}
|
|
220
|
+
if (this.config.ipFilter?.enabled) {
|
|
221
|
+
const clientIp = getClientIp(req);
|
|
222
|
+
const filterResult = await checkIpFilter(clientIp, this.config.ipFilter, {
|
|
223
|
+
path
|
|
224
|
+
});
|
|
225
|
+
if (!filterResult.allowed) {
|
|
226
|
+
if (this.config.ipFilter.logBlocked !== false) {
|
|
227
|
+
const logEvent = {
|
|
228
|
+
type: "blocked",
|
|
229
|
+
ip: clientIp,
|
|
230
|
+
reason: filterResult.reason,
|
|
231
|
+
path,
|
|
232
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
233
|
+
};
|
|
234
|
+
if (this.config.ipFilter.logger) {
|
|
235
|
+
this.config.ipFilter.logger(logEvent);
|
|
236
|
+
} else {
|
|
237
|
+
console.log(`[PocketPing] IP blocked: ${clientIp} - reason: ${filterResult.reason}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
res.statusCode = this.config.ipFilter.blockedStatusCode ?? 403;
|
|
241
|
+
res.setHeader("Content-Type", "application/json");
|
|
242
|
+
res.end(JSON.stringify({
|
|
243
|
+
error: this.config.ipFilter.blockedMessage ?? "Forbidden"
|
|
244
|
+
}));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
155
248
|
const widgetVersion = req.headers["x-pocketping-version"];
|
|
156
249
|
const versionCheck = this.checkWidgetVersion(widgetVersion);
|
|
157
250
|
this.setVersionHeaders(res, versionCheck);
|
|
@@ -260,7 +353,31 @@ var PocketPing = class {
|
|
|
260
353
|
server,
|
|
261
354
|
path: "/pocketping/stream"
|
|
262
355
|
});
|
|
263
|
-
this.wss.on("connection", (ws, req) => {
|
|
356
|
+
this.wss.on("connection", async (ws, req) => {
|
|
357
|
+
if (this.config.ipFilter?.enabled) {
|
|
358
|
+
const clientIp = getClientIp(req);
|
|
359
|
+
const filterResult = await checkIpFilter(clientIp, this.config.ipFilter, {
|
|
360
|
+
path: "/pocketping/stream"
|
|
361
|
+
});
|
|
362
|
+
if (!filterResult.allowed) {
|
|
363
|
+
if (this.config.ipFilter.logBlocked !== false) {
|
|
364
|
+
const logEvent = {
|
|
365
|
+
type: "blocked",
|
|
366
|
+
ip: clientIp,
|
|
367
|
+
reason: filterResult.reason,
|
|
368
|
+
path: "/pocketping/stream",
|
|
369
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
370
|
+
};
|
|
371
|
+
if (this.config.ipFilter.logger) {
|
|
372
|
+
this.config.ipFilter.logger(logEvent);
|
|
373
|
+
} else {
|
|
374
|
+
console.log(`[PocketPing] WS IP blocked: ${clientIp} - reason: ${filterResult.reason}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
ws.close(4003, this.config.ipFilter.blockedMessage ?? "Forbidden");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
264
381
|
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
265
382
|
const sessionId = url.searchParams.get("sessionId");
|
|
266
383
|
if (!sessionId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pocketping/sdk-node",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Node.js SDK for implementing PocketPing protocol",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -22,12 +22,16 @@
|
|
|
22
22
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
23
23
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
24
24
|
"test": "vitest run",
|
|
25
|
-
"test:watch": "vitest"
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "biome check src tests",
|
|
27
|
+
"lint:fix": "biome check --write src tests",
|
|
28
|
+
"format": "biome format --write src tests"
|
|
26
29
|
},
|
|
27
30
|
"dependencies": {
|
|
28
31
|
"ws": "^8.16.0"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
34
|
+
"@biomejs/biome": "^1.9.0",
|
|
31
35
|
"@types/ws": "^8.5.10",
|
|
32
36
|
"tsup": "^8.0.0",
|
|
33
37
|
"typescript": "^5.3.0",
|