53 Commits

Author SHA1 Message Date
5e570201bd wip: template
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-10-22 05:26:09 +02:00
c5ce1bfbcd feat: add meta packages
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-10-22 05:25:11 +02:00
f0b3957536 feat: make ci more robust
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-10-22 05:24:33 +02:00
8984a61ec8 refactor: move pushover into sub package, pull config into config package
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-10-22 04:46:36 +02:00
a8e03e5912 Merge pull request #4 from cis-oss/feature/readme
docs(README): Add ReadMe file
2025-09-01 00:10:54 +02:00
421821f1ee docs(README): Add ReadMe file 2025-08-31 23:51:48 +02:00
Alix von Schirp
ae34160b12 Merge pull request #3 from cis-oss/feature/release-prep 2025-08-31 15:50:22 +02:00
61ac47fd05 chore(package.json): add maintainers 2025-08-31 14:31:28 +02:00
1ea099ff98 chore(package.json): script cleanup and addition of preRelease script 2025-08-31 14:31:23 +02:00
93716ba3f8 build(devDependencies): add shx 2025-08-31 14:31:22 +02:00
98cd99e27a chore(package.json): split main files between dev and release 2025-08-31 14:31:17 +02:00
Alix von Schirp
f4f6046b78 Merge pull request #2 from cis-oss/feature/message-sending 2025-08-31 13:59:38 +02:00
d66c54d25d docs: reformat code examples to follow prettier styling 2025-08-31 00:19:29 +02:00
85663272c4 fix(send): added missing return 2025-08-30 22:27:24 +02:00
4f6015a0c2 docs(Message): added missing comma 2025-08-30 22:26:39 +02:00
5e4645097a fix: fix syntax errors introduced by copilot in previous commit
Refs: 8d711af84d
2025-08-30 22:02:16 +02:00
Alix von Schirp
9b88ae87cc fix(user-validation): remove nullish coalescing operator
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-30 21:46:40 +02:00
Alix von Schirp
8d711af84d style: add missing brackets in template
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-30 21:43:34 +02:00
143796f817 ci(pre-commit): fix hook t user pnpm instead of npm
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-08-30 20:30:46 +02:00
37fdebdcab chore(package.json): added packageManager field
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 05:24:42 +02:00
609e6dc507 build(tsconfig.json): sets strictNullChecks to true
This is needed by TypeDoc

Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 04:57:54 +02:00
6d2743e7e8 feat: exports types
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 04:54:51 +02:00
15beebf196 docs: Documented methods
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 04:54:11 +02:00
5cfbc8d441 refactor: Split out zod input and output type
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 03:51:55 +02:00
f9385c9cf0 feat: Add receipt checking and retry cancellation
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 02:52:57 +02:00
64829441e5 build(package.json): adds tsx to test functionality using ts files
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 02:48:19 +02:00
b09b0bcdae refactor: recipient handling
removes default user, changes recipient to optionally include device
Also fixes url_title replacing message title

Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 02:12:08 +02:00
2007e906a4 fix: removes module.exports
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-17 02:05:50 +02:00
65524da6dc chore(package.json): add types
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-15 16:12:42 +02:00
52b11eeffb feat: Adds user validation via api
Validates user and if specified device

Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-15 15:24:12 +02:00
6a8cf528e3 build(tests): Adds jest to run tests
Milestone: none
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-15 11:22:21 +02:00
2106734b88 feat: sending messages is now feature complete
including zod schema validation

Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-15 11:20:01 +02:00
a72a2c3358 ci(pre-commit): Pre-commit hook now should no longer block commits
Added eslint-plugin-only-warn to suppress errors

Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-15 11:16:52 +02:00
e5a3e04e0c build(tsconfig): Updates config, adds build:watch script
- Build files now include sourcemaps and declarations
- No longer compiles non-ts files
- package.json includes build:watch script

Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2025-04-15 10:55:19 +02:00
d520d52da8 feat: Adds typechecking via zod for message 2025-03-14 20:52:19 +01:00
f48bd35319 fix: log request error to console 2025-03-13 02:53:53 +01:00
ac239fd090 feat: sends message
Message sending is not yet feature complete
Missing:
- attachments
- priority 2 -> retry & expire

Format: text/markdown
Milestone: minor
2025-03-13 02:46:01 +01:00
09645c30e6 build(tsconfig): specify include globs to /src/**/*
Previous glob was `**/*`

Format: text/markdown
Milestone: none
2025-03-13 02:45:11 +01:00
4750b3baf9 revert(pre-commit)!: slapping windows specific linting back in
As it seems I did not fix the eslint not throwing, so I need to temporarily put the call to cmd back in until further investigation

Refs: 5304933
Format: text/plain
Milestone: none
BREAKING-CHANGE: calling the windows command line cmd will probably make the pre-commit hook not work on non-windows systems
2025-03-13 02:42:21 +01:00
7db7cad18f build(pre-commit): fixing eslint for windows 2025-03-13 02:30:45 +01:00
df8d0a65d3 build(devDeps): add finepack for package.json linting
Add tool for linting and verifying package.json
Tool will:
- validate existence of required keys
- organizes keys by relevance and alphabet
- validates for valid format

Tool was also added to pre-commit hook

Format: text/markdown
2025-03-13 01:33:23 +01:00
13ee710c74 fix(8f11bdc): move docs theme to dev-deps 2025-03-12 00:54:03 +01:00
418f857298 Merge remote-tracking branch 'origin/main' 2025-03-12 00:52:41 +01:00
8f11bdc43e ci(docs): Added theme to docs generator 2025-03-12 00:52:08 +01:00
Alix von Schirp
4f308f07f6 Create LICENSE 2025-03-11 02:29:11 +01:00
937358558d fix: allow docs to be commited by workflow 2025-03-10 22:26:19 +01:00
f61558cb18 fix: install 2025-03-10 22:05:44 +01:00
c55da7b33a fix: works now pls? 2025-03-10 22:04:45 +01:00
782ac97028 fix: uhhh 2025-03-10 22:01:00 +01:00
1f1762ff0c fix: cache: npm 2025-03-10 21:59:43 +01:00
e57a9cb309 fix: docs workflow - install pnpm 2025-03-10 21:55:47 +01:00
4d4706c4ba fix: make docs workflow use pnpm 2025-03-10 21:53:39 +01:00
e91ce19c03 fix: docs workflow 2025-03-10 21:49:57 +01:00
42 changed files with 6410 additions and 318 deletions

View File

@@ -1,12 +1,22 @@
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: strategy:
- name: Checkout matrix:
uses: actions/checkout@v4 node-version: [20, 22]
- name: Install Deps steps:
run: npm install -g pnpm && pnpm install - name: Checkout
- name: Typecheck & Lint uses: actions/checkout@v5
run: pnpm check - name: Install Deps
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Install
run: pnpm install
- name: Typecheck & Lint
run: SKIP_ENV_VALIDATION=true turbo check

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: get the gh-pages repo - name: get the gh-pages repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: gh-pages ref: gh-pages
@@ -19,7 +19,7 @@ jobs:
tar -cvf documentation.tar ./docs tar -cvf documentation.tar ./docs
- name: create a document artifact - name: create a document artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: documentation name: documentation
path: documentation.tar path: documentation.tar
@@ -29,31 +29,29 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout src - name: Checkout src
uses: actions/checkout@v3 uses: actions/checkout@v4
- run: mkdir -p ./docs - run: mkdir -p ./docs
- name: Download the existing documents artifact - name: Download the existing documents artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: documentation name: documentation
- run: tar -xf documentation.tar ./docs -C ./docs - run: tar -xf documentation.tar ./docs -C ./docs
- name: Build - name: Install Deps
uses: actions/setup-node@v3 run: npm install -g pnpm && pnpm install
with:
node-version: 16.x
cache: 'npm'
- run: npm ci
- run: npm run build # set up 'build' script in your package.json
- name: Build documents - name: Build lib
run: npm run docs #set up 'docs' build script in your package.json run: pnpm run build
- name: Build docs
run: pnpm run docs:generate
- name: tar the new docs - name: tar the new docs
run: tar -cvf newdocumentation.tar ./docs run: tar -cvf newdocumentation.tar ./docs
- name: create a new document artifact - name: create a new document artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: newdocumentation name: newdocumentation
path: newdocumentation.tar path: newdocumentation.tar
@@ -63,13 +61,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout the gh-pages repo - name: checkout the gh-pages repo
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: gh-pages ref: gh-pages
- run: mkdir -p ./docs - run: mkdir -p ./docs
- name: Download the new documents artifact - name: Download the new documents artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: newdocumentation name: newdocumentation
- run: tar -xf newdocumentation.tar ./docs -C ./docs - run: tar -xf newdocumentation.tar ./docs -C ./docs
@@ -78,6 +76,6 @@ jobs:
run: | run: |
git config --global user.email "cis-oss@users.noreply.github.com" git config --global user.email "cis-oss@users.noreply.github.com"
git config --global user.name "Continuous Integration" git config --global user.name "Continuous Integration"
git add . git add docs/ -f
git commit -m "CI updated the documentation" git commit -m "CI updated the documentation"
git push git push

76
.gitignore vendored
View File

@@ -1,38 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies # dependencies
/node_modules /**/node_modules
/.pnp /.pnp
.pnp.js .pnp.js
# testing # testing
/coverage /coverage
# production # production
docs/ docs/
dist/ dist/
# misc # misc
.DS_Store .DS_Store
*.pem *.pem
# debug # debug
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.pnpm-debug.log* .pnpm-debug.log*
# local env files # local env files
# do not commit any .env files to git, except for the .env.example file. # do not commit any .env files to git, except for the .env.example file.
.env .env
.env*.local .env*.local
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
# eslint # eslint
.eslintcache .eslintcache
# idea files # idea files
.idea .idea

View File

@@ -1 +1 @@
npx lint-staged pnpm exec lint-staged

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Alix von Schirp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,24 +0,0 @@
# CIS Pushover Client
A client for Pushover, a service for sending notifications. Typesafe.
Supports sending the same message to multiple users.
## Installation
⚠️ This package is **not yet published** to npm.
```bash
pnpm add @cis-oss/pushover
```
```bash
npm install @cis-oss/pushover
```
```bash
yarn add @cis-oss/pushover
```
```bash
bun add @cis-oss/pushover
```

18
config/eslint.config.mjs Normal file
View File

@@ -0,0 +1,18 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier";
import onlyWarn from "eslint-plugin-only-warn";
/** @type {import('eslint').Linter.Config[]} */
export default [
{ languageOptions: { globals: globals.node } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
eslintConfigPrettier,
{
plugins: {
onlyWarn,
},
},
];

7
config/jest.config.js Normal file
View File

@@ -0,0 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
testEnvironment: "node",
transform: {
"^.+\.tsx?$": ["ts-jest", {}],
},
};

32
config/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@repo/configs",
"version": "0.0.0",
"exports": {
"./eslint": "./eslint.config.mjs",
"./jest": "./jest.config.mjs",
"./typedoc": "./typedoc.config.mjs"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@shipgirl/typedoc-plugin-versions": "^0.3.0",
"@types/jest": "^29.5.14",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-only-warn": "^1.1.0",
"globals": "^16.4.0",
"jest": "^29.7.0",
"prettier": "3.5.3",
"ts-jest": "^29.2.6",
"typedoc": "^0.27.9",
"typedoc-github-theme": "^0.2.1",
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-extras": "^4.0.0",
"typedoc-plugin-include-example": "^2.0.2",
"typedoc-plugin-inline-sources": "^1.2.1",
"typedoc-plugin-mdn-links": "^5.0.1",
"typedoc-plugin-zod": "^1.4.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.26.0"
},
"private": true
}

View File

@@ -4,6 +4,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"target": "es2022", "target": "es2022",
"sourceMap": true,
"allowJs": true, "allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"moduleDetection": "force", "moduleDetection": "force",
@@ -11,6 +12,7 @@
/* Strictness */ /* Strictness */
"strict": true, "strict": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"checkJs": true, "checkJs": true,
@@ -20,6 +22,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"incremental": true, "incremental": true,
"outDir": "dist", "outDir": "dist",
"declaration": true,
/* Path Aliases */ /* Path Aliases */
"baseUrl": "./src", "baseUrl": "./src",
@@ -27,6 +30,6 @@
"~/*": ["./src/*"] "~/*": ["./src/*"]
} }
}, },
"include": ["**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.js"], "include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "docs", "dist"] "exclude": ["node_modules/**/*", "docs/**/*", "dist/**/*"]
} }

View File

@@ -1,14 +1,7 @@
import globals from "globals"; import repoConfig from "@repo/configs/eslint";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint"; export default [
import eslintConfigPrettier from "eslint-config-prettier"; ...repoConfig,
{ ignores: ["dist/**", "docs/**"] },
/** @type {import('eslint').Linter.Config[]} */ { files: ["**/*.{ts}"] },
export default [ ];
{ ignores: ["dist/**", "docs/**", ".*/**", "**/.*", "**/*.config.*"] },
{ files: ["**/*.{js,mjs,cjs,ts}"] },
{ languageOptions: { globals: globals.node } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
eslintConfigPrettier,
];

View File

@@ -0,0 +1,7 @@
import repoConfig from "@repo/configs/eslint";
export default [
...repoConfig,
{ ignores: ["dist/**", "docs/**"] },
{ files: ["**/*.{ts}"] },
];

View File

@@ -0,0 +1,3 @@
import repoCofig from "@repo/configs/jest";
export default [...repoCofig];

View File

@@ -0,0 +1,90 @@
{
"name": "@cis-oss/notify-push",
"description": "Send push notifications to your users. Meta package wrapping several @cis-oss packages. See README.md for more information.",
"homepage": "https://cis-oss.github.io/notify",
"version": "0.0.1",
"main": "src/index.ts",
"author": {
"email": "hi@b00tload.space",
"name": "Alix von Schirp",
"url": "https://b00tload.space"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cis-oss/pushover.git"
},
"bugs": {
"url": "https://github.com/cis-oss/pushover/issues"
},
"keywords": [
"mobile",
"notification",
"notifications",
"push",
"pushover"
],
"dependencies": {
"@cis-oss/pushover": "workspace:"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@repo/configs": "workspace:",
"@shipgirl/typedoc-plugin-versions": "^0.3.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.10",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-only-warn": "^1.1.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"shx": "^0.4.0",
"ts-jest": "^29.2.6",
"tsx": "^4.19.3",
"typedoc": "^0.27.9",
"typedoc-github-theme": "^0.2.1",
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-extras": "^4.0.0",
"typedoc-plugin-include-example": "^2.0.2",
"typedoc-plugin-inline-sources": "^1.2.1",
"typedoc-plugin-mdn-links": "^5.0.1",
"typedoc-plugin-zod": "^1.4.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.26.0"
},
"files": [
"dist/**/*.{js,ts,map}"
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"check": "pnpm run lint:ci && pnpm run typecheck",
"clean": "shx rm -rf dist/",
"docs:generate": "typedoc",
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:ci": "eslint --max-warnings 0",
"prepublishOnly": "pnpm run clean && pnpm run build",
"test": "jest",
"typecheck": "tsc --noEmit"
},
"private": true,
"license": "MIT",
"maintainers": [
{
"name": "Alix von Schirp",
"email": "hi@b00tload.space",
"url": "https://b00tload.space"
},
{
"name": "Ole",
"email": "jateute123@gmail.com",
"url": "https://github.com/jateute"
}
],
"packageManager": "pnpm@10.6.5",
"publishConfig": {
"main": "dist/index.js",
"types": "dist/index.d.ts"
},
"type": "module"
}

View File

@@ -0,0 +1,3 @@
import { Pushover } from "@cis-oss/pushover";
export { Pushover };

View File

@@ -0,0 +1,12 @@
{
"extends": "@repo/configs/tsconfig.json",
"compilerOptions": {
/* Path Aliases */
"baseUrl": "./src",
"paths": {
"~/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules/**/*", "docs/**/*", "dist/**/*"]
}

View File

@@ -0,0 +1,7 @@
import repoConfig from "@repo/configs/eslint";
export default [
...repoConfig,
{ ignores: ["dist/**", "docs/**"] },
{ files: ["**/*.{ts}"] },
];

View File

@@ -0,0 +1,3 @@
import repoCofig from "@repo/configs/jest";
export default [...repoCofig];

90
meta/notify/package.json Normal file
View File

@@ -0,0 +1,90 @@
{
"name": "@cis-oss/notify",
"description": "Send push notifications to your users. Meta package wrapping several @cis-oss packages. See README.md for more information.",
"homepage": "https://cis-oss.github.io/notify",
"version": "0.0.1",
"main": "src/index.ts",
"author": {
"email": "hi@b00tload.space",
"name": "Alix von Schirp",
"url": "https://b00tload.space"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cis-oss/pushover.git"
},
"bugs": {
"url": "https://github.com/cis-oss/pushover/issues"
},
"keywords": [
"mobile",
"notification",
"notifications",
"push",
"pushover"
],
"dependencies": {
"@cis-oss/pushover": "workspace:"
},
"devDependencies": {
"@repo/configs": "workspace:",
"@eslint/js": "^9.22.0",
"@shipgirl/typedoc-plugin-versions": "^0.3.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.10",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-only-warn": "^1.1.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"shx": "^0.4.0",
"ts-jest": "^29.2.6",
"tsx": "^4.19.3",
"typedoc": "^0.27.9",
"typedoc-github-theme": "^0.2.1",
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-extras": "^4.0.0",
"typedoc-plugin-include-example": "^2.0.2",
"typedoc-plugin-inline-sources": "^1.2.1",
"typedoc-plugin-mdn-links": "^5.0.1",
"typedoc-plugin-zod": "^1.4.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.26.0"
},
"files": [
"dist/**/*.{js,ts,map}"
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"check": "pnpm run lint:ci && pnpm run typecheck",
"clean": "shx rm -rf dist/",
"docs:generate": "typedoc",
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:ci": "eslint --max-warnings 0",
"prepublishOnly": "pnpm run clean && pnpm run build",
"test": "jest",
"typecheck": "tsc --noEmit"
},
"private": true,
"license": "MIT",
"maintainers": [
{
"name": "Alix von Schirp",
"email": "hi@b00tload.space",
"url": "https://b00tload.space"
},
{
"name": "Ole",
"email": "jateute123@gmail.com",
"url": "https://github.com/jateute"
}
],
"packageManager": "pnpm@10.6.5",
"publishConfig": {
"main": "dist/index.js",
"types": "dist/index.d.ts"
},
"type": "module"
}

3
meta/notify/src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import { Pushover } from "@cis-oss/pushover";
export { Pushover };

12
meta/notify/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "@repo/configs/tsconfig.json",
"compilerOptions": {
/* Path Aliases */
"baseUrl": "./src",
"paths": {
"~/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules/**/*", "docs/**/*", "dist/**/*"]
}

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
/** @type {import("typedoc").TypeDocOptions &
* import("typedoc-plugin-extras").ExtrasOptions &
* import("typedoc-plugin-coverage").CoverageOptions &
* import("typedoc-plugin-mdn-links").MdnLinksOptions &
* import("typedoc-plugin-zod").ZodOptions} */
const config = {
compilerOptions: {
skipLibCheck: true,
strict: false,
},
entryPoints: ["src/index.ts"],
out: "docs",
plugin: [
"typedoc-plugin-coverage",
"typedoc-plugin-extras",
"typedoc-plugin-inline-sources",
"typedoc-plugin-mdn-links",
"typedoc-plugin-zod",
"typedoc-plugin-include-example",
"@shipgirl/typedoc-plugin-versions",
"typedoc-github-theme",
],
customFooterHtml: '<p class="tsd-generator" style="display: inline-flex; flex-direction: row; justify-content: space-around; width: 100%;"> <span> Made with ❤ by <a href="https://b00tload.space">Alix von Schirp</a> @ <a href="https://github.com/cis-oss">CISLabs OSS</a> </span> <span> <a href="https://github.com/cis-oss/pushover" target="_blank">GitHub</a> | <a href="https://github.com/cis-oss/pushover/issues" target="_blank">Issues</a> | <a href="https://github.com/cis-oss/pushover/blob/main/LICENSE" target="_blank">License</a> </span> <span>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc </a> with <a href="https://github.com/JulianWowra/typedoc-github-theme" target="_blank">typedoc-github-theme</a></span></p>',
coverageOutputType: "json",
hideGenerator: true,
};
export default config;

View File

@@ -1,66 +1,34 @@
{ {
"name": "@cis-oss/pushover", "name": "@repo/root",
"version": "0.0.1", "description": "",
"type": "module", "files": [],
"description": "A client for Pushover, a service for sending notifications. Written in TypeScript. Supports sending to multiple users.", "private": true,
"keywords": [
"pushover",
"notifications",
"mobile",
"push",
"notification"
],
"homepage": "https://github.com/cis-oss/pushover#readme",
"bugs": "https://github.com/cis-oss/pushover/issues",
"license": "MIT", "license": "MIT",
"repository": {
"type": "git",
"url": "github:cis-oss/pushover"
},
"author": { "author": {
"name": "Alix von Schirp",
"email": "hi@b00tload.space", "email": "hi@b00tload.space",
"name": "Alix von Schirp",
"url": "https://b00tload.space" "url": "https://b00tload.space"
}, },
"main": "dist/index.js",
"scripts": {
"build": "tsc && typedoc",
"lint": "eslint --cache .",
"typecheck": "tsc --noEmit",
"format:check": "prettier --check .",
"check": "pnpm lint && pnpm typecheck && pnpm format:check",
"prepare": "husky",
"docs:create": "typedoc"
},
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.22.0", "@repo/configs": "workspace:",
"@shipgirl/typedoc-plugin-versions": "^0.3.0", "@turbo/gen": "^2.5.6",
"@types/node": "^22.13.10",
"eslint": "^9.22.0", "eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1", "finepack": "^2.12.7",
"globals": "^16.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^15.4.3", "lint-staged": "^15.4.3",
"prettier": "3.5.3", "prettier": "3.5.3",
"typedoc": "^0.27.9", "turbo": "^2.5.6"
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-extras": "^4.0.0",
"typedoc-plugin-include-example": "^2.0.2",
"typedoc-plugin-inline-sources": "^1.2.1",
"typedoc-plugin-mdn-links": "^5.0.1",
"typedoc-plugin-zod": "^1.4.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.26.0"
}, },
"private": true, "scripts": {
"lint-staged": { "prepare": "husky"
"*.{js,ts,jsx,tsx}": "eslint --cache --fix . || true",
"*.{js,ts,jsx,tsx,json,css,md}": "prettier --write"
},
"dependencies": {
"zod": "^3.24.2"
}, },
"husky": { "husky": {
"shell": "bash" "shell": "bash"
} },
"lint-staged": {
"package.json": "finepack",
"*.{js,ts,jsx,tsx}": "eslint --fix .",
"*.{js,ts,jsx,tsx,json,css,md}": "prettier --write"
},
"version": "0.0.0"
} }

View File

@@ -0,0 +1,48 @@
# CIS Pushover Client
![GitHub License](https://img.shields.io/github/license/cis-oss/pushover)
![NPM Version](https://img.shields.io/npm/v/%40cis-oss%2Fpushover)
![npm bundle size (scoped)](https://img.shields.io/bundlephobia/min/cis-oss/pushover?label=bundle%20size)
A client for Pushover, a service for sending notifications. Typesafe.
Supports sending the same message to multiple users.
## Installation
```bash
pnpm add @cis-oss/pushover
```
<details>
<summary>Or using npm</summary>
```bash
npm install @cis-oss/pushover
```
</details>
<details>
<summary>Or use our meta-packages</summary>
```bash
pnpm add @cis-oss/notify
```
```bash
pnpm add @cis-oss/notify-push
```
</details>
# Documentation
Documentation can be found at [https://cis-oss.github.io/pushover](https://cis-oss.github.io/pushover).
# Contributing
Contributions are welcome! Please see [CONTRIBUTING.md](https://github.com/cis-oss/.github/blob/main/CONTRIBUTING.md) for details.
Please make sure to read our [Code of Conduct](https://github.com/cis-oss/.github/blob/main/CODE_OF_CONDUCT.md) and [Support Policy](https://github.com/cis-oss/.github/blob/main/SUPPORT.md).
Disclose security issues responsibly by following our [Security Policy](https://github.com/cis-oss/.github/blob/main/SECURITY.md).

View File

@@ -0,0 +1,7 @@
import repoConfig from "@repo/configs/eslint";
export default [
...repoConfig,
{ ignores: ["dist/**", "docs/**"] },
{ files: ["**/*.{ts}"] },
];

View File

@@ -0,0 +1,3 @@
import repoCofig from "@repo/configs/jest";
export default [...repoCofig];

View File

@@ -0,0 +1,90 @@
{
"name": "@cis-oss/pushover",
"description": "A client for Pushover, a service for sending notifications. Written in TypeScript. Supports sending to multiple users.",
"homepage": "https://cis-oss.github.io/pushover",
"version": "0.0.10",
"main": "src/index.ts",
"author": {
"email": "hi@b00tload.space",
"name": "Alix von Schirp",
"url": "https://b00tload.space"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cis-oss/pushover.git"
},
"bugs": {
"url": "https://github.com/cis-oss/pushover/issues"
},
"keywords": [
"mobile",
"notification",
"notifications",
"push",
"pushover"
],
"dependencies": {
"zod": "^3.24.2"
},
"devDependencies": {
"@repo/configs": "workspace:",
"@eslint/js": "^9.22.0",
"@shipgirl/typedoc-plugin-versions": "^0.3.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.10",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-only-warn": "^1.1.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"shx": "^0.4.0",
"ts-jest": "^29.2.6",
"tsx": "^4.19.3",
"typedoc": "^0.27.9",
"typedoc-github-theme": "^0.2.1",
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-extras": "^4.0.0",
"typedoc-plugin-include-example": "^2.0.2",
"typedoc-plugin-inline-sources": "^1.2.1",
"typedoc-plugin-mdn-links": "^5.0.1",
"typedoc-plugin-zod": "^1.4.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.26.0"
},
"files": [
"dist/**/*.{js,ts,map}"
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"check": "pnpm run lint:ci && pnpm run typecheck",
"clean": "shx rm -rf dist/",
"docs:generate": "typedoc",
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:ci": "eslint --max-warnings 0",
"prepublishOnly": "pnpm run clean && pnpm run build",
"test": "jest",
"typecheck": "tsc --noEmit"
},
"private": true,
"license": "MIT",
"maintainers": [
{
"name": "Alix von Schirp",
"email": "hi@b00tload.space",
"url": "https://b00tload.space"
},
{
"name": "Ole",
"email": "jateute123@gmail.com",
"url": "https://github.com/jateute"
}
],
"packageManager": "pnpm@10.6.5",
"publishConfig": {
"main": "dist/index.js",
"types": "dist/index.d.ts"
},
"type": "module"
}

View File

@@ -0,0 +1,718 @@
import https from "node:https";
import { URLSearchParams } from "node:url";
import { z } from "zod";
/**
* @internal
* Defines the internal Zod schema for validating Pushover message payloads.
* This ensures messages conform to the Pushover API requirements before sending.
* Includes validation rules for required fields, formats, and conditional requirements.
*/
const MessageSchema = z
.object({
/**
* The message content sent to the user. Must be at least 3 characters long.
*/
message: z.string().min(3),
/**
* An optional title for the message.
*/
title: z.string().optional(),
/**
* An optional link attached to the message.
* Can be either a simple URL string or an object containing the URL and an optional display title.
*/
link: z
.string()
.url()
.or(
z.object({
/**
* The URL of the link.
*/
url: z.string().url(),
/**
* The title displayed for the link.
*/
title: z.string().optional(),
}),
)
.optional(),
/**
* Sets the notification priority for the message.
* Defaults to 0 (normal priority).
*
* - -2: Message only, no notification sound/vibration. May increment the notification bubble.
* - -1: Silent notification (no sound/vibration).
* - 0: Default notification behavior.
* - 1: High priority, ignores user's quiet hours.
* - 2: Emergency priority, requires acknowledgement. Requires `emergencyOpts`.
*/
priority: z
.union([
z.literal(-2),
z.literal(-1),
z.literal(0),
z.literal(1),
z.literal(2),
])
.optional()
.default(0),
/**
* Emergency priority options, required when `priority` is 2.
*/
emergencyOpts: z
.object({
/**
* Specifies how often (in seconds) the Pushover servers will send the same notification to the user.
* Minimum value is 30 seconds.
*/
retry: z.number().min(30),
/**
* Specifies how long (in seconds) the notification will continue to be resent.
* Maximum value is 10800 seconds (3 hours).
*/
expire: z.number().max(10800),
/**
* An optional callback URL that Pushover servers will send a request to when the notification has been acknowledged.
*/
callback: z.string().url().optional(),
/**
* Optional tags for emergency notifications. Helps with cancelling retries.
*/
tags: z.string().array().optional(),
})
.optional(),
/**
* The name of one of the predefined Pushover sounds or a custom sound uploaded by the user to be played for the notification.
*/
sound: z.string().optional(),
/**
* An optional Unix timestamp representing the message's date and time to display to the user, rather than the time Pushover received it.
*/
timestamp: z.number().optional(),
/**
* If set to true, the message content will be treated as HTML.
* Mutually exclusive with `monospace`.
*/
html: z.boolean().optional().default(false),
/**
* If set to true, the message content will be displayed using a monospace font.
* Mutually exclusive with `html`.
*/
monospace: z.boolean().optional().default(false),
/**
* Time To Live in seconds. Specifies how long the message will be kept until disappearing.
*/
ttl: z.number().optional(),
})
/**
* Validation rule: Ensures that if the priority is set to 2 (emergency),
* the `emergencyOpts` object must be provided.
*/
.refine(
(data) => {
// If priority is 2, emergencyOpts must exist.
return !(data.priority === 2 && !data.emergencyOpts);
},
{
path: ["priority", "emergencyOpts"], // Path related to the error
message: "If priority is set to 2, emergencyOpts must be included.",
},
)
/**
* Validation rule: Ensures that `html` and `monospace` formatting options
* are mutually exclusive and cannot be enabled simultaneously.
*/
.refine(
(data) => {
// Cannot have both html and monospace set to true.
return !(data.html && data.monospace);
},
{
path: ["html", "monospace"], // Path related to the error
message: "html and monospace are mutually exclusive.",
},
);
/**
* Defines the structure for a Pushover message object used when calling the `send` method.
*
* This type represents the complete set of parameters you can provide for a
* Pushover notification. It includes the required `message` field and various
* optional fields to customize the notification's appearance, behavior, priority,
* sound, and delivery options.
*
* Refer to the official Pushover API documentation for detailed explanations of each field.
* Note the specific constraints:
* - `emergencyOpts` must be provided if `priority` is set to `2`.
* - `html` and `monospace` formatting options cannot be used together.
*
* @example
* ```typescript
* import type { PushoverMessage } from '@cis-oss/pushover';
*
* const standardMessage: PushoverMessage = {
* message: "Deployment successful!",
* title: "Server Update",
* priority: 1, // High priority
* sound: "pushover",
* link: {
* url: "https://example.com/deployment/status",
* title: "View Status"
* }
* };
*
* const emergencyMessage: PushoverMessage = {
* message: "System critical: Service down!",
* priority: 2,
* emergencyOpts: {
* retry: 60, // Retry every 60 seconds
* expire: 3600, // Expire after 1 hour
* tags: ["critical", "infra"]
* },
* };
* ```
*/
export type PushoverMessage = z.input<typeof MessageSchema>;
type PushoverMessageParsed = z.output<typeof MessageSchema>;
/**
* Base interface for all Pushover API responses
*/
interface PushoverResponse {
/** Indicates the status of the request. `1` for success, `0` for failure. */
status: 0 | 1;
/** A unique identifier for the API request, generated by Pushover. */
request: string;
/** An array of error messages if the request failed (`status` is `0`). */
errors?: string[];
}
/**
* Represents the response received after successfully sending a Pushover message.
*/
export interface PushoverMessageResponse extends PushoverResponse {
/**
* A receipt ID, returned only for messages sent with emergency priority (`priority: 2`).
* This ID can be used to check the acknowledgement status or cancel retries.
*/
receipt?: string;
}
/**
* Represents the response received after validating a user or user/device combination.
*/
export interface PushoverValidationResponse extends PushoverResponse {
/** A list of the user's registered device names, returned on successful validation. */
devices?: string[];
/** A list of the user's Pushover license types (e.g., 'Android', 'iOS', 'Desktop'). */
licenses?: string[];
}
/**
* Represents the response received when checking the status of an emergency message receipt.
*/
export interface PushoverReceiptResponse extends PushoverResponse {
/** `true` if the emergency notification has been acknowledged by the user, `false` otherwise. */
acknowledged: boolean;
/** A Unix timestamp indicating when the notification was acknowledged. `0` if not acknowledged. */
acknowledged_at: number;
/** The user key of the user who first acknowledged the notification. Empty if not acknowledged. */
acknowledged_by: string;
/** The name of the device that first acknowledged the notification. Empty if not acknowledged. */
acknowledged_by_device: string;
/** A Unix timestamp indicating the last time the notification was delivered (due to retries). `0` if not delivered. */
last_delivered_at: number;
/** `true` if the notification has expired without acknowledgement, `false` otherwise. */
expired: boolean;
/** A Unix timestamp indicating when the notification expired. `0` if not expired. */
expired_at: number;
/** `true` if the optional callback URL was successfully contacted, `false` otherwise. */
called_back: boolean;
/** A Unix timestamp indicating when the callback URL was contacted. `0` if not called back. */
called_back_at: number;
}
/**
* Represents the response received when cancelling emergency message retries by tag.
*/
export interface PushoverTagCancellationResponse extends PushoverResponse {
/** The number of emergency message retries that were successfully cancelled for the given tag. */
canceled: number;
}
/**
* Defines the options for the `send` method, primarily specifying the recipients.
*/
export interface SendOptions {
/** An array of `PushoverRecipient` objects, each specifying a user/group and optional devices. */
recipients: PushoverRecipient[];
/** If true, enables verbose logging to the console during the send operation. Defaults to false. */
verbose?: boolean;
}
/**
* Defines the options for the `validate` method.
*/
export interface ValidateOptions {
/** The Pushover user key to validate. */
user: string;
/** An optional device name to validate along with the user key. */
deviceName?: string;
/** If true, enables verbose logging to the console during the send operation. Defaults to false. */
verbose?: boolean;
}
/**
* Represents a single Pushover recipient, which can be a user or a group.
*/
export interface PushoverRecipient {
/** The Pushover user key or group key. */
id: string;
/** An optional array of specific device names belonging to the user to send the notification to. If omitted, sends to all user's devices. */
devices?: string[];
}
/**
* Main class for interacting with the Pushover API (v1).
* Provides methods for sending notifications, validating users/devices,
* and managing emergency priority messages.
*
* @param token - Your Pushover application's API token.
*
* @example
* ```typescript
* import { Pushover } from "@cis-oss/pushover";
*
* // Initialize the client
* const pushover = new Pushover("YOUR_APP_API_TOKEN");
*
* // Define recipients
* const recipients = [
* { id: "USER_KEY_1" },
* { id: "USER_KEY_2", devices: ["DEVICE_1", "DEVICE_2"] },
* { id: "GROUP_KEY_1" },
* ];
*
* // Send a basic message
* const responses = pushover.send(
* {
* message: "Hello from the library!",
* title: "Test Message",
* },
* { recipients },
* );
*
* responses
* .then((responses) => {
* console.log("Messages sent:", responses);
* })
* .catch((error) => {
* console.error("Failed to send messages:", error);
* });
* ```
*/
export class Pushover {
private token: string;
private apiUrl = "https://api.pushover.net/1/";
/**
* Creates an instance of the Pushover client.
* @param token - Your Pushover application's API token. Found on your Pushover dashboard.
*/
constructor(token: string) {
this.token = token;
}
/**
* Sends a Pushover notification to one or more recipients.
*
* @param message - A `PushoverMessage` object containing the notification details.
* @param options - A `SendOptions` object specifying the recipients and optional settings.
* @returns A Promise resolving to an array of `PushoverMessageResponse` objects, one for each recipient.
* Rejects if message validation fails or if there's a fundamental issue sending to all recipients.
* Individual recipient failures are indicated within their respective response objects (`status: 0`).
*
* @example
* ```typescript
* // Send a message to a specific user and device
* const userRecipient: PushoverRecipient = { id: "user-key", devices: ["phone"] };
* await pushover.send(
* { message: "Targeted message" },
* { recipients: [userRecipient] },
* );
*
* // Send an emergency priority message and handle the receipt
* const responses = pushover.send(
* {
* message: "Emergency alert!",
* priority: 2,
* emergencyOpts: { retry: 30, expire: 3600 },
* },
* { recipients: [userRecipient] },
* );
*
* responses
* .then((responses) => {
* console.log(
* `Emergency message sent. Receipts: ${responses.map((response) => response.receipt).join(", ")}`,
* );
* // Store the receipt to check status or cancel later
* })
* .catch((error) => {
* console.error("Failed to send emergency message:", error);
* });
* ```
*/
public async send(
message: PushoverMessage,
options: SendOptions,
): Promise<PushoverMessageResponse[]> {
return new Promise((resolve, reject) => {
if (options.recipients.length === 0) {
reject(new Error("No recipients specified."));
return;
}
const {
success,
error,
data: parsedMessage,
} = MessageSchema.safeParse(message);
if (!success) {
reject(new Error(`Message validation failed: ${error}`));
return;
}
if (options.verbose) {
console.log("Verbose mode enabled. Logging message and options:");
console.log(parsedMessage);
console.log(options);
console.log("----------------------");
console.log("Sending message...");
}
const promises = options.recipients.map((recipient) =>
this.sendToSingleRecipient(
parsedMessage,
recipient,
options.verbose ?? false,
),
);
resolve(Promise.all(promises));
});
}
/**
* @internal
* Sends the validated message payload to a single recipient.
*
* @param message - The validated PushoverMessage object.
* @param recipient - The PushoverRecipient object.
* @param verbose - Optional flag for logging.
* @returns A Promise resolving to the PushoverMessageResponse.
*/
private async sendToSingleRecipient(
message: PushoverMessageParsed,
recipient: PushoverRecipient,
verbose?: boolean,
): Promise<PushoverMessageResponse> {
const params = new URLSearchParams();
// Add token and user
params.append("token", this.token);
params.append("user", recipient.id);
params.append("device", recipient.devices?.join(",") ?? "");
// Add message properties
params.append("message", message.message);
if (message.title) params.append("title", message.title);
params.append("priority", "" + message.priority);
if (message.priority === 2 && message.emergencyOpts) {
params.append("retry", String(message.emergencyOpts.retry));
params.append("expire", String(message.emergencyOpts.expire));
if (message.emergencyOpts.callback)
params.append("callback", message.emergencyOpts.callback);
if (message.emergencyOpts.tags)
params.append("tags", message.emergencyOpts.tags.join());
}
if (message.link) {
if (typeof message.link === "string") {
params.append("url", message.link);
} else {
params.append("url", message.link.url);
if (message.link.title) params.append("url_title", message.link.title);
}
}
if (message.html) params.append("html", "1");
if (message.monospace) params.append("monospace", "1");
if (message.sound) params.append("sound", message.sound);
if (message.timestamp)
params.append("timestamp", String(message.timestamp));
if (message.ttl) params.append("ttl", String(message.ttl));
return this.makeRequest<PushoverMessageResponse>(
"messages.json",
"POST",
params,
verbose ?? false,
);
}
/**
* @internal
* Makes an HTTPS request to the Pushover API.
*
* @param endpoint - The API endpoint path (e.g., "messages.json").
* @param method - The HTTP method ("POST" or "GET").
* @param params - URLSearchParams for POST body or query string.
* @param verbose - Optional flag for logging request/response details.
* @returns A Promise resolving to the parsed JSON response.
*/
private makeRequest<T extends PushoverResponse>(
endpoint: string,
method: "POST" | "GET",
params: URLSearchParams,
verbose?: boolean,
): Promise<T> {
return new Promise((resolve, reject) => {
const url = this.apiUrl + endpoint;
let requestBody: string | null = null;
let requestUrl = url;
const options: https.RequestOptions = {
method: method,
headers: {},
};
if (method === "POST") {
requestBody = params.toString();
options.headers!["Content-Type"] = "application/x-www-form-urlencoded";
options.headers!["Content-Length"] = Buffer.byteLength(requestBody);
} else {
// Append params to URL for GET requests
const queryString = params.toString();
if (queryString) {
requestUrl += "?" + queryString;
}
}
if (verbose) {
console.log(`Making ${method} request to ${requestUrl}`);
if (requestBody) {
console.log("Request Body:", requestBody);
}
}
const req = https.request(requestUrl, options, (res) => {
let data = "";
res.setEncoding("utf8"); // Ensure correct encoding
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
if (verbose) {
console.log("Received response status:", res.statusCode);
console.log("Received response headers:", res.headers);
console.log(
`Received response body (length: ${data.length}): ${data}`,
);
}
try {
// Handle potential empty responses or non-JSON responses gracefully
if (!data) {
// Reject promise on empty response
reject(
new Error(
`Request failed with status ${res.statusCode} and empty response.`,
),
);
return;
}
const response = JSON.parse(data) as T;
// Basic check for expected structure
if (
typeof response.status === "undefined" ||
typeof response.request === "undefined"
) {
reject(new Error(`Invalid response structure received: ${data}`));
return;
}
resolve(response);
} catch (error) {
if (verbose) console.error("Failed to parse JSON response:", error);
reject(
new Error(`Failed to parse API response. Raw data: ${data}`),
);
}
});
});
req.on("error", (error) => {
if (verbose)
console.error(`HTTPS request error to ${endpoint}:`, error);
reject(new Error(`API request failed: ${error.message}`));
});
if (method === "POST" && requestBody) {
req.write(requestBody);
}
req.end();
});
}
/**
* Validates a Pushover user key and optionally a specific device name associated with that user.
* Useful for verifying recipient details before sending messages.
*
* @param options - A `ValidateOptions` object containing the `user` key and optional `deviceName`.
* @returns A Promise resolving to a `PushoverValidationResponse` object.
* Check the `status` field (1 for valid, 0 for invalid) and `errors` for details on failure.
* On success, `devices` and `licenses` may be populated.
*
* @example
* ```typescript
* // Validate a user key
* const validation = await pushover.validate({ user: "user-key" });
* if (validation.status === 1) {
* console.log(
* "User is valid. Devices:",
* validation.devices,
* ", Licenses:",
* validation.licenses,
* );
* } else {
* console.error("Validation failed:", validation.errors);
* }
*
* // Validate a user and device
* const deviceValidation = await pushover.validate({
* user: "user-key",
* deviceName: "phone",
* });
* console.log("Device validation status:", deviceValidation.status);
* ```
*/
validate(options: ValidateOptions): Promise<PushoverValidationResponse> {
const params = new URLSearchParams();
params.append("token", this.token);
params.append("user", options.user);
if (options.deviceName) params.append("device", options.deviceName);
return this.makeRequest<PushoverValidationResponse>(
"users/validate.json",
"POST",
params,
options.verbose ?? false,
);
}
/**
* Checks the status of an emergency priority message using its receipt ID.
* Allows querying whether the message has been acknowledged, expired, or if the callback was triggered.
*
* @param receipt - The receipt ID obtained from the `PushoverMessageResponse` when sending an emergency message.
* @param verbose - Optional flag for logging.
* @returns A Promise resolving to a `PushoverReceiptResponse` object containing the status details.
*
* @example
* ```typescript
* const receiptId = "RECEIPT_ID_FROM_SEND_RESPONSE";
* const status = await pushover.checkReceipt(receiptId);
* if (status.status === 1) {
* console.log(
* `Acknowledged: ${status.acknowledged} by ${status.acknowledged_by}`,
* );
* console.log(`Expired: ${status.expired}`);
* } else {
* console.error("Failed to check receipt:", status.errors);
* }
* ```
*/
checkReceipt(
receipt: string,
verbose?: boolean,
): Promise<PushoverReceiptResponse> {
const params = new URLSearchParams();
return this.makeRequest<PushoverReceiptResponse>(
`receipts/${receipt}.json?token=${this.token}`,
"GET",
params,
verbose ?? false,
);
}
/**
* Cancels the retries for an emergency priority message that has not yet been acknowledged.
*
* @param receipt - The receipt ID of the emergency message whose retries should be cancelled.
* @param verbose - Optional flag for logging.
* @returns A Promise resolving to a basic `PushoverResponse`. Check `status` for success (1) or failure (0).
*
* @example
* ```typescript
* const receiptId = "RECEIPT_ID_TO_CANCEL";
* const cancelResponse = await pushover.cancelRetries(receiptId);
* if (cancelResponse.status === 1) {
* console.log("Successfully cancelled retries for receipt:", receiptId);
* } else {
* console.error("Failed to cancel retries:", cancelResponse.errors);
* }
* ```
*/
cancelRetries(receipt: string, verbose?: boolean): Promise<PushoverResponse> {
const params = new URLSearchParams();
params.append("token", this.token);
return this.makeRequest<PushoverResponse>(
`receipts/${receipt}/cancel.json`,
"POST",
params,
verbose ?? false,
);
}
/**
* Cancels the retries for all emergency priority messages associated with a specific tag
* that have not yet been acknowledged.
*
* @param tag - The tag associated with the emergency messages (set in `emergencyOpts.tags` during send).
* @param verbose - Optional flag for logging.
* @returns A Promise resolving to a `PushoverTagCancellationResponse` indicating the number of messages cancelled.
*
* @example
* ```typescript
* const tagName = "critical-db-alert";
* const cancelByTagResponse = await pushover.cancelRetriesByTag(tagName);
* if (cancelByTagResponse.status === 1) {
* console.log(
* `Successfully cancelled ${cancelByTagResponse.canceled} messages with tag: ${tagName}`,
* );
* } else {
* console.error("Failed to cancel by tag:", cancelByTagResponse.errors);
* }
* ```
*/
cancelRetriesByTag(
tag: string,
verbose?: boolean,
): Promise<PushoverTagCancellationResponse> {
const params = new URLSearchParams();
params.append("token", this.token);
return this.makeRequest<PushoverTagCancellationResponse>(
`receipts/cancel_by_tag/${tag}.json`,
"POST",
params,
verbose ?? false,
);
}
}

View File

@@ -0,0 +1,26 @@
import { Pushover } from "./Pushover";
import type {
PushoverRecipient,
PushoverMessage,
PushoverMessageResponse,
PushoverValidationResponse,
PushoverReceiptResponse,
PushoverTagCancellationResponse,
ValidateOptions,
SendOptions,
} from "./Pushover";
export default Pushover;
export { Pushover };
export type {
PushoverRecipient,
PushoverMessage,
PushoverMessageResponse,
PushoverValidationResponse,
PushoverReceiptResponse,
PushoverTagCancellationResponse,
ValidateOptions,
SendOptions,
};

View File

@@ -0,0 +1,11 @@
import Pushover from "../src";
test("Pushover is exported", () => {
expect(Pushover).toBeDefined();
expect(Pushover).toBeInstanceOf(Object);
});
test("Pushover has expected functions", () => {
expect(Pushover).toHaveProperty("prototype.constructor");
expect(Pushover).toHaveProperty("prototype.send");
});

View File

@@ -0,0 +1,12 @@
{
"extends": "@repo/configs/tsconfig.json",
"compilerOptions": {
/* Path Aliases */
"baseUrl": "./src",
"paths": {
"~/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules/**/*", "docs/**/*", "dist/**/*"]
}

4909
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

7
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,7 @@
packages:
- meta/*
- packages/*
- config
catalogMode: prefer

View File

@@ -1,111 +0,0 @@
import { z } from "zod";
const messageSchema = z.object({
/**
* The message to send.
*/
message: z.string(),
/**
* The title of the message.
*/
title: z.string().optional(),
/**
* The URL to open when the notification is clicked.
*/
url: z
.object({
/**
* The URL itself.
*/
url: z.string(),
/**
* The title of the URL to be displayed.
*/
title: z.string().optional(),
})
.optional(),
priority: z
.object({
level: z.number().default(0),
retry: z.number().optional(),
expire: z.number().optional(),
callback: z.string().optional(),
})
.optional(),
sound: z.string().optional(),
timestamp: z.date().optional(),
html: z.boolean().default(false),
attachment: z
.object({
data: z.string().or(z.instanceof(File)),
type: z.string(),
})
.optional(),
users: z.array(
z.object({
name: z.string(),
token: z.string(),
devices: z.array(z.string()),
}),
),
appToken: z.string(),
});
export type PushoverMessage = z.infer<typeof messageSchema>;
export type Pushover = {
// #################################
// # Helpers #
// #################################
/**
* Get all available sounds.
*/
getSounds: () => { sounds: string[] };
/**
* Get all available devices for a user.
*/
getDevices: (userToken: string) => { devices: string[] };
// #################################
// # Sending #
// #################################
/**
* Send the notification.
*/
send: () => void;
// #################################
// # Options #
// #################################
withMessage: (message: string) => Pushover;
withTitle: (title: string) => Pushover;
withUrl: (url: { url: string; title: string }) => Pushover;
withPriority: (priority: {
level: number;
retry?: number;
expire?: number;
callback?: string;
}) => Pushover;
withSound: (sound: string) => Pushover;
withTimestamp: (timestamp: number) => Pushover;
withHtml: (enable: boolean) => Pushover;
withAttachment: (attachment: File) => Pushover;
withBase64Attachment: (attachment: {
data: string;
type: string;
}) => Pushover;
addUsers: (users: User[]) => Pushover;
};
export type User = {
name: string;
token: string;
devices: string[];
};
console.log(
"this is indented obnoxiously far and has no semi at the end. (testing previous commits",
);

View File

View File

@@ -0,0 +1,7 @@
import repoConfig from "@repo/configs/eslint";
export default [
...repoConfig,
{ ignores: ["dist/**", "docs/**"] },
{ files: ["**/*.{ts}"] },
];

View File

@@ -0,0 +1,3 @@
import repoCofig from "@repo/configs/jest";
export default [...repoCofig];

View File

@@ -0,0 +1,87 @@
{
"name": "{{name}}",
"description": "{{description}}",
"homepage": "https://cis-oss.github.io/notify",
"version": "0.0.10",
"main": "src/index.ts",
"author": {
"email": "hi@b00tload.space",
"name": "Alix von Schirp",
"url": "https://b00tload.space"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cis-oss/pushover.git"
},
"bugs": {
"url": "https://github.com/cis-oss/pushover/issues"
},
"keywords": [
"notification",
"notify"
],
"dependencies": {
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@repo/configs": "workspace:",
"@shipgirl/typedoc-plugin-versions": "^0.3.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.10",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-only-warn": "^1.1.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"shx": "^0.4.0",
"ts-jest": "^29.2.6",
"tsx": "^4.19.3",
"typedoc": "^0.27.9",
"typedoc-github-theme": "^0.2.1",
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-extras": "^4.0.0",
"typedoc-plugin-include-example": "^2.0.2",
"typedoc-plugin-inline-sources": "^1.2.1",
"typedoc-plugin-mdn-links": "^5.0.1",
"typedoc-plugin-zod": "^1.4.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.26.0"
},
"files": [
"dist/**/*.{js,ts,map}"
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"check": "pnpm run lint:ci && pnpm run typecheck",
"clean": "shx rm -rf dist/",
"docs:generate": "typedoc",
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:ci": "eslint --max-warnings 0",
"prepublishOnly": "pnpm run clean && pnpm run build",
"test": "jest",
"typecheck": "tsc --noEmit"
},
"private": true,
"license": "MIT",
"maintainers": [
{
"name": "Alix von Schirp",
"email": "hi@b00tload.space",
"url": "https://b00tload.space"
},
{
"name": "Ole",
"email": "jateute123@gmail.com",
"url": "https://github.com/jateute"
}
],
"packageManager": "pnpm@10.6.5",
"publishConfig": {
"main": "dist/index.js",
"types": "dist/index.d.ts"
},
"type": "module"
}

View File

View File

@@ -0,0 +1,12 @@
{
"extends": "@repo/configs/tsconfig.json",
"compilerOptions": {
/* Path Aliases */
"baseUrl": "./src",
"paths": {
"~/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules/**/*", "docs/**/*", "dist/**/*"]
}

View File

@@ -0,0 +1,46 @@
import type { PlopTypes } from "@turbo/gen";
export default function generator(plop: PlopTypes.NodePlopAPI): void {
// create a generator
plop.setGenerator("package", {
description: "Generator description",
// gather information from the user
prompts: [
{
type: "input",
name: "name",
message: "Package name",
},
{
type: "input",
name: "description",
message: "Package description",
},
{
type: "input",
name: "author.name",
message: "Author name",
},
{
type: "input",
name: "author.email",
message: "Author email",
},
{
type: "input",
name: "author.url",
message: "Author URL",
},
],
// perform actions based on the prompts
actions: [
{
type: "addMany",
destination: "packages/{{name}}",
base: "templates/package",
templateFiles: "templates/package/**/*",
abortOnFail: true,
},
],
});
}

View File

@@ -1,30 +0,0 @@
// @ts-nocheck
/** @type {import("typedoc").TypeDocOptions &
* import("typedoc-plugin-extras").ExtrasOptions &
* import("typedoc-plugin-coverage").CoverageOptions &
* import("typedoc-plugin-mdn-links").MdnLinksOptions &
* import("typedoc-plugin-zod").ZodOptions} */
const config = {
compilerOptions: {
skipLibCheck: true,
strict: false,
},
entryPoints: ["src/index.ts"],
out: "docs",
plugin: [
"typedoc-plugin-coverage",
"typedoc-plugin-extras",
"typedoc-plugin-inline-sources",
"typedoc-plugin-mdn-links",
"typedoc-plugin-zod",
"typedoc-plugin-include-example",
"@shipgirl/typedoc-plugin-versions",
],
footerTypedocVersion: true,
footerLastModified: true,
coverageLabel: "Docs Coverage",
coverageOutputType: "all",
coverageSvgWidth: 130,
};
export default config;