@allmightypush/push-express 1.0.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 ADDED
@@ -0,0 +1,194 @@
1
+ # @allmightypush/push-express
2
+
3
+ Express middleware for managing push notification subscriptions.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @allmightypush/push-express @allmightypush/push-core express
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import express from 'express';
15
+ import { createExpressMiddleware } from '@allmightypush/push-express';
16
+ import { SQLiteStorageAdapter } from '@allmightypush/push-storage-sqlite';
17
+
18
+ const app = express();
19
+ const storage = new SQLiteStorageAdapter({ filename: './push.db' });
20
+
21
+ app.use(express.json());
22
+ app.use('/api/push', createExpressMiddleware({ storageAdapter: storage }));
23
+
24
+ app.listen(3000, () => {
25
+ console.log('Server running on port 3000');
26
+ });
27
+ ```
28
+
29
+ ## API Endpoints
30
+
31
+ ### POST /subscriptions
32
+
33
+ Create a new push notification subscription.
34
+
35
+ **Request Body:**
36
+ ```json
37
+ {
38
+ "endpoint": "https://fcm.googleapis.com/fcm/send/...",
39
+ "keys": {
40
+ "p256dh": "user-public-key",
41
+ "auth": "user-auth-secret"
42
+ },
43
+ "userId": "optional-user-id",
44
+ "metadata": {
45
+ "deviceType": "mobile",
46
+ "appVersion": "1.0.0"
47
+ }
48
+ }
49
+ ```
50
+
51
+ **Response:** `201 Created`
52
+ ```json
53
+ {
54
+ "id": "subscription-id",
55
+ "endpoint": "https://fcm.googleapis.com/fcm/send/...",
56
+ "keys": { "p256dh": "...", "auth": "..." },
57
+ "userId": "optional-user-id",
58
+ "status": "active",
59
+ "createdAt": "2024-01-01T00:00:00.000Z",
60
+ "updatedAt": "2024-01-01T00:00:00.000Z"
61
+ }
62
+ ```
63
+
64
+ ### GET /subscriptions/:id
65
+
66
+ Retrieve a subscription by ID.
67
+
68
+ **Response:** `200 OK` or `404 Not Found`
69
+
70
+ ### GET /subscriptions
71
+
72
+ List subscriptions with optional filtering and pagination.
73
+
74
+ **Query Parameters:**
75
+ - `userId` - Filter by user ID
76
+ - `status` - Filter by status (active, expired, failed)
77
+ - `limit` - Maximum results (default: 100)
78
+ - `offset` - Pagination offset (default: 0)
79
+
80
+ **Response:** `200 OK`
81
+ ```json
82
+ {
83
+ "subscriptions": [...],
84
+ "total": 10,
85
+ "limit": 100,
86
+ "offset": 0
87
+ }
88
+ ```
89
+
90
+ ### PATCH /subscriptions/:id
91
+
92
+ Update a subscription.
93
+
94
+ **Request Body:**
95
+ ```json
96
+ {
97
+ "status": "expired",
98
+ "metadata": { "updated": true }
99
+ }
100
+ ```
101
+
102
+ **Response:** `200 OK` or `404 Not Found`
103
+
104
+ ### DELETE /subscriptions/:id
105
+
106
+ Delete a subscription.
107
+
108
+ **Response:** `204 No Content` or `404 Not Found`
109
+
110
+ ## Configuration Options
111
+
112
+ ```typescript
113
+ interface ExpressMiddlewareOptions {
114
+ // Storage adapter for managing subscriptions (required)
115
+ storageAdapter: StorageAdapter;
116
+
117
+ // Optional authentication middleware
118
+ authMiddleware?: (req, res, next) => void;
119
+
120
+ // Custom base path (default: '')
121
+ basePath?: string;
122
+
123
+ // Custom validation function
124
+ validateSubscription?: (subscription) => Promise<void> | void;
125
+ }
126
+ ```
127
+
128
+ ## Examples
129
+
130
+ ### With Authentication
131
+
132
+ ```typescript
133
+ import { createExpressMiddleware } from '@allmightypush/push-express';
134
+
135
+ const authMiddleware = (req, res, next) => {
136
+ const token = req.headers.authorization?.replace('Bearer ', '');
137
+
138
+ if (!token || !isValidToken(token)) {
139
+ return res.status(401).json({ error: 'Unauthorized' });
140
+ }
141
+
142
+ next();
143
+ };
144
+
145
+ app.use('/api/push', createExpressMiddleware({
146
+ storageAdapter: storage,
147
+ authMiddleware,
148
+ }));
149
+ ```
150
+
151
+ ### With Custom Validation
152
+
153
+ ```typescript
154
+ app.use('/api/push', createExpressMiddleware({
155
+ storageAdapter: storage,
156
+ validateSubscription: async (data) => {
157
+ // Ensure endpoint uses HTTPS
158
+ if (!data.endpoint?.startsWith('https://')) {
159
+ throw new Error('Endpoint must use HTTPS');
160
+ }
161
+
162
+ // Check against allowed domains
163
+ const url = new URL(data.endpoint);
164
+ if (!allowedDomains.includes(url.hostname)) {
165
+ throw new Error('Endpoint domain not allowed');
166
+ }
167
+ },
168
+ }));
169
+ ```
170
+
171
+ ### With Custom Base Path
172
+
173
+ ```typescript
174
+ app.use('/api/push', createExpressMiddleware({
175
+ storageAdapter: storage,
176
+ basePath: '/v1', // Routes will be /api/push/v1/subscriptions
177
+ }));
178
+ ```
179
+
180
+ ## Error Handling
181
+
182
+ The middleware returns appropriate HTTP status codes:
183
+
184
+ - `200 OK` - Successful GET/PATCH
185
+ - `201 Created` - Successful POST
186
+ - `204 No Content` - Successful DELETE
187
+ - `400 Bad Request` - Invalid input
188
+ - `401 Unauthorized` - Authentication failed (if auth middleware used)
189
+ - `404 Not Found` - Resource not found
190
+ - `500 Internal Server Error` - Server error
191
+
192
+ ## License
193
+
194
+ MIT
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createExpressMiddleware = createExpressMiddleware;
4
+ function validateSubscriptionData(data) {
5
+ if (!data.endpoint || typeof data.endpoint !== 'string') {
6
+ throw new Error('Missing or invalid endpoint');
7
+ }
8
+ if (!data.keys || typeof data.keys !== 'object') {
9
+ throw new Error('Missing or invalid keys');
10
+ }
11
+ if (!data.keys.p256dh || typeof data.keys.p256dh !== 'string') {
12
+ throw new Error('Missing or invalid keys.p256dh');
13
+ }
14
+ if (!data.keys.auth || typeof data.keys.auth !== 'string') {
15
+ throw new Error('Missing or invalid keys.auth');
16
+ }
17
+ }
18
+ function createExpressMiddleware(options) {
19
+ const { storageAdapter, authMiddleware, basePath = '', validateSubscription } = options;
20
+ const express = require('express');
21
+ const router = express.Router();
22
+ if (authMiddleware) {
23
+ router.use(authMiddleware);
24
+ }
25
+ router.post(`${basePath}/subscriptions`, async (req, res) => {
26
+ try {
27
+ validateSubscriptionData(req.body);
28
+ if (validateSubscription) {
29
+ await validateSubscription(req.body);
30
+ }
31
+ const subscription = await storageAdapter.createSubscription({
32
+ endpoint: req.body.endpoint,
33
+ keys: {
34
+ p256dh: req.body.keys.p256dh,
35
+ auth: req.body.keys.auth,
36
+ },
37
+ userId: req.body.userId,
38
+ metadata: req.body.metadata,
39
+ });
40
+ res.status(201).json(subscription);
41
+ }
42
+ catch (error) {
43
+ if (error.message.includes('Missing') || error.message.includes('invalid') || error.message.includes('must')) {
44
+ res.status(400).json({ error: error.message });
45
+ }
46
+ else {
47
+ res.status(500).json({ error: 'Failed to create subscription' });
48
+ }
49
+ }
50
+ });
51
+ router.get(`${basePath}/subscriptions/:id`, async (req, res) => {
52
+ try {
53
+ const subscription = await storageAdapter.getSubscriptionById(req.params.id);
54
+ if (!subscription) {
55
+ res.status(404).json({ error: 'Subscription not found' });
56
+ return;
57
+ }
58
+ res.json(subscription);
59
+ }
60
+ catch (error) {
61
+ res.status(500).json({ error: 'Failed to retrieve subscription' });
62
+ }
63
+ });
64
+ router.get(`${basePath}/subscriptions`, async (req, res) => {
65
+ try {
66
+ const filters = {};
67
+ if (req.query.userId) {
68
+ filters.userId = req.query.userId;
69
+ }
70
+ if (req.query.status) {
71
+ filters.status = req.query.status;
72
+ }
73
+ const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
74
+ const offset = req.query.offset ? parseInt(req.query.offset, 10) : 0;
75
+ const subscriptions = await storageAdapter.findSubscriptions(filters);
76
+ const paginatedResults = subscriptions.slice(offset, offset + limit);
77
+ res.json({
78
+ subscriptions: paginatedResults,
79
+ total: subscriptions.length,
80
+ limit,
81
+ offset,
82
+ });
83
+ }
84
+ catch (error) {
85
+ res.status(500).json({ error: 'Failed to list subscriptions' });
86
+ }
87
+ });
88
+ router.patch(`${basePath}/subscriptions/:id`, async (req, res) => {
89
+ try {
90
+ const subscription = await storageAdapter.getSubscriptionById(req.params.id);
91
+ if (!subscription) {
92
+ res.status(404).json({ error: 'Subscription not found' });
93
+ return;
94
+ }
95
+ const updates = {};
96
+ if (req.body.status) {
97
+ if (!['active', 'expired', 'failed'].includes(req.body.status)) {
98
+ res.status(400).json({ error: 'Invalid status value' });
99
+ return;
100
+ }
101
+ updates.status = req.body.status;
102
+ }
103
+ if (req.body.metadata !== undefined) {
104
+ updates.metadata = req.body.metadata;
105
+ }
106
+ const updatedSubscription = await storageAdapter.updateSubscription(req.params.id, updates);
107
+ res.json(updatedSubscription);
108
+ }
109
+ catch (error) {
110
+ res.status(500).json({ error: 'Failed to update subscription' });
111
+ }
112
+ });
113
+ router.delete(`${basePath}/subscriptions/:id`, async (req, res) => {
114
+ try {
115
+ const subscription = await storageAdapter.getSubscriptionById(req.params.id);
116
+ if (!subscription) {
117
+ res.status(404).json({ error: 'Subscription not found' });
118
+ return;
119
+ }
120
+ await storageAdapter.deleteSubscription(req.params.id);
121
+ res.status(204).send();
122
+ }
123
+ catch (error) {
124
+ res.status(500).json({ error: 'Failed to delete subscription' });
125
+ }
126
+ });
127
+ return router;
128
+ }
129
+ exports.default = createExpressMiddleware;
130
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ function validateSubscriptionData(data) {
2
+ if (!data.endpoint || typeof data.endpoint !== 'string') {
3
+ throw new Error('Missing or invalid endpoint');
4
+ }
5
+ if (!data.keys || typeof data.keys !== 'object') {
6
+ throw new Error('Missing or invalid keys');
7
+ }
8
+ if (!data.keys.p256dh || typeof data.keys.p256dh !== 'string') {
9
+ throw new Error('Missing or invalid keys.p256dh');
10
+ }
11
+ if (!data.keys.auth || typeof data.keys.auth !== 'string') {
12
+ throw new Error('Missing or invalid keys.auth');
13
+ }
14
+ }
15
+ export function createExpressMiddleware(options) {
16
+ const { storageAdapter, authMiddleware, basePath = '', validateSubscription } = options;
17
+ const express = require('express');
18
+ const router = express.Router();
19
+ if (authMiddleware) {
20
+ router.use(authMiddleware);
21
+ }
22
+ router.post(`${basePath}/subscriptions`, async (req, res) => {
23
+ try {
24
+ validateSubscriptionData(req.body);
25
+ if (validateSubscription) {
26
+ await validateSubscription(req.body);
27
+ }
28
+ const subscription = await storageAdapter.createSubscription({
29
+ endpoint: req.body.endpoint,
30
+ keys: {
31
+ p256dh: req.body.keys.p256dh,
32
+ auth: req.body.keys.auth,
33
+ },
34
+ userId: req.body.userId,
35
+ metadata: req.body.metadata,
36
+ });
37
+ res.status(201).json(subscription);
38
+ }
39
+ catch (error) {
40
+ if (error.message.includes('Missing') || error.message.includes('invalid') || error.message.includes('must')) {
41
+ res.status(400).json({ error: error.message });
42
+ }
43
+ else {
44
+ res.status(500).json({ error: 'Failed to create subscription' });
45
+ }
46
+ }
47
+ });
48
+ router.get(`${basePath}/subscriptions/:id`, async (req, res) => {
49
+ try {
50
+ const subscription = await storageAdapter.getSubscriptionById(req.params.id);
51
+ if (!subscription) {
52
+ res.status(404).json({ error: 'Subscription not found' });
53
+ return;
54
+ }
55
+ res.json(subscription);
56
+ }
57
+ catch (error) {
58
+ res.status(500).json({ error: 'Failed to retrieve subscription' });
59
+ }
60
+ });
61
+ router.get(`${basePath}/subscriptions`, async (req, res) => {
62
+ try {
63
+ const filters = {};
64
+ if (req.query.userId) {
65
+ filters.userId = req.query.userId;
66
+ }
67
+ if (req.query.status) {
68
+ filters.status = req.query.status;
69
+ }
70
+ const limit = req.query.limit ? parseInt(req.query.limit, 10) : 100;
71
+ const offset = req.query.offset ? parseInt(req.query.offset, 10) : 0;
72
+ const subscriptions = await storageAdapter.findSubscriptions(filters);
73
+ const paginatedResults = subscriptions.slice(offset, offset + limit);
74
+ res.json({
75
+ subscriptions: paginatedResults,
76
+ total: subscriptions.length,
77
+ limit,
78
+ offset,
79
+ });
80
+ }
81
+ catch (error) {
82
+ res.status(500).json({ error: 'Failed to list subscriptions' });
83
+ }
84
+ });
85
+ router.patch(`${basePath}/subscriptions/:id`, async (req, res) => {
86
+ try {
87
+ const subscription = await storageAdapter.getSubscriptionById(req.params.id);
88
+ if (!subscription) {
89
+ res.status(404).json({ error: 'Subscription not found' });
90
+ return;
91
+ }
92
+ const updates = {};
93
+ if (req.body.status) {
94
+ if (!['active', 'expired', 'failed'].includes(req.body.status)) {
95
+ res.status(400).json({ error: 'Invalid status value' });
96
+ return;
97
+ }
98
+ updates.status = req.body.status;
99
+ }
100
+ if (req.body.metadata !== undefined) {
101
+ updates.metadata = req.body.metadata;
102
+ }
103
+ const updatedSubscription = await storageAdapter.updateSubscription(req.params.id, updates);
104
+ res.json(updatedSubscription);
105
+ }
106
+ catch (error) {
107
+ res.status(500).json({ error: 'Failed to update subscription' });
108
+ }
109
+ });
110
+ router.delete(`${basePath}/subscriptions/:id`, async (req, res) => {
111
+ try {
112
+ const subscription = await storageAdapter.getSubscriptionById(req.params.id);
113
+ if (!subscription) {
114
+ res.status(404).json({ error: 'Subscription not found' });
115
+ return;
116
+ }
117
+ await storageAdapter.deleteSubscription(req.params.id);
118
+ res.status(204).send();
119
+ }
120
+ catch (error) {
121
+ res.status(500).json({ error: 'Failed to delete subscription' });
122
+ }
123
+ });
124
+ return router;
125
+ }
126
+ export default createExpressMiddleware;
127
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Request, Response, NextFunction, Router } from 'express';
2
+ import type { StorageAdapter, Subscription } from '@allmightypush/push-core';
3
+ export interface ExpressMiddlewareOptions {
4
+ storageAdapter: StorageAdapter;
5
+ authMiddleware?: (req: Request, res: Response, next: NextFunction) => void | Promise<void>;
6
+ basePath?: string;
7
+ validateSubscription?: (subscription: Partial<Subscription>) => Promise<void> | void;
8
+ }
9
+ export declare function createExpressMiddleware(options: ExpressMiddlewareOptions): Router;
10
+ export default createExpressMiddleware;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAK7E,MAAM,WAAW,wBAAwB;IAIvC,cAAc,EAAE,cAAc,CAAC;IAK/B,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAK3F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAKlB,oBAAoB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACtF;AA0CD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAqLjF;AAED,eAAe,uBAAuB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@allmightypush/push-express",
3
+ "version": "1.0.0",
4
+ "description": "Express middleware for push notification library",
5
+ "main": "dist/cjs/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./dist/cjs/index.js",
11
+ "import": "./dist/esm/index.js",
12
+ "types": "./dist/types/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "npm run build:cjs && npm run build:esm && npm run build:types",
20
+ "build:cjs": "tsc -p tsconfig.cjs.json",
21
+ "build:esm": "tsc -p tsconfig.esm.json",
22
+ "build:types": "tsc -p tsconfig.types.json",
23
+ "test": "jest --passWithNoTests",
24
+ "test:watch": "jest --watch",
25
+ "test:coverage": "jest --coverage --passWithNoTests",
26
+ "clean": "rm -rf dist"
27
+ },
28
+ "keywords": [
29
+ "push",
30
+ "notification",
31
+ "express",
32
+ "middleware"
33
+ ],
34
+ "author": "Samtes64",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/Samtes64/all-mighty-push.git",
39
+ "directory": "packages/push-express"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/Samtes64/all-mighty-push/issues"
43
+ },
44
+ "homepage": "https://github.com/Samtes64/all-mighty-push/tree/main/packages/push-express#readme",
45
+ "dependencies": {
46
+ "@allmightypush/push-core": "^1.0.0"
47
+ },
48
+ "peerDependencies": {
49
+ "express": "^4.18.0 || ^5.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/express": "^4.17.21",
53
+ "@types/supertest": "^6.0.2",
54
+ "express": "^4.18.2",
55
+ "supertest": "^6.3.3"
56
+ },
57
+ "engines": {
58
+ "node": ">=16.0.0"
59
+ }
60
+ }