Publish curated chimera source from 95e6b4be8a9669686a6241fda6f22034fd8d0b5c
This commit is contained in:
1 parent
c737a37f71
commit
3ceefb175f
7 files changed
+1361
-1001
No files matched your search
@@ -4,6 +4,7 @@ ENV NODE_ENV=production CHIMERA_DATA_DIR=/data
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force
|
||||
COPY --chown=node:node src ./src
|
||||
COPY --chown=node:node PSNotifyModule ./PSNotifyModule
|
||||
COPY --chown=node:node renderer ./renderer
|
||||
COPY --chown=node:node build ./build
|
||||
COPY --chown=node:node server.js ./server.js
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# PSNotifyModule
|
||||
|
||||
Drop-in Node.js / Electron module for sending toast notifications to a jailbroken PS5 running `customPSNotify.js`.
|
||||
|
||||
No npm dependencies — uses Node's built-in `net` module only.
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
1. Copy the `PSNotifyModule/` folder into your project.
|
||||
2. Make sure `customPSNotify.js` is loaded and running on your PS5.
|
||||
3. `require` the module by path — done.
|
||||
|
||||
```js
|
||||
const { PS5Notifier, notify } = require('./PSNotifyModule');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
### `notify(host, message, [opts])` — one-shot function
|
||||
|
||||
Connects, sends, waits for ACK, disconnects. No instance needed.
|
||||
|
||||
```js
|
||||
const { notify } = require('./PSNotifyModule');
|
||||
|
||||
await notify('192.168.1.100', 'Build finished!');
|
||||
|
||||
await notify('192.168.1.100', 'Deploy complete', {
|
||||
subMessage : 'v2.1.0 is live',
|
||||
port : 6969, // default
|
||||
timeout : 5000, // ms, default
|
||||
});
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `host` | string | yes | PS5 IP address |
|
||||
| `message` | string | yes | Main toast body |
|
||||
| `opts.subMessage` | string | no | Smaller text below body |
|
||||
| `opts.port` | number | no | Port (default `6969`) |
|
||||
| `opts.timeout` | number | no | Timeout in ms (default `5000`) |
|
||||
|
||||
**Returns:** `Promise<{ ok: boolean }>` — resolves with the ACK from the PS5, rejects on connection error / timeout.
|
||||
|
||||
---
|
||||
|
||||
### `PS5Notifier` — stateful class
|
||||
|
||||
Instantiate once, reuse throughout your app. Remembers the host, port, and timeout. Keeps a history of sent notifications.
|
||||
|
||||
```js
|
||||
const { PS5Notifier } = require('./PSNotifyModule');
|
||||
|
||||
const ps5 = new PS5Notifier('192.168.1.100');
|
||||
|
||||
// send(message, [opts])
|
||||
await ps5.send('App started');
|
||||
await ps5.send('Build complete', { subMessage: 'v2.1.0 — 0 errors' });
|
||||
|
||||
// history([n]) — last n sent notifications (default 50)
|
||||
const log = ps5.history();
|
||||
// [{ ts, message, subMessage, ok }, ...]
|
||||
|
||||
// setHost(host) — update IP at runtime
|
||||
ps5.setHost('192.168.1.105');
|
||||
```
|
||||
|
||||
#### Constructor
|
||||
|
||||
```js
|
||||
new PS5Notifier(host, [defaults])
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `host` | string | yes | PS5 IP address |
|
||||
| `defaults.port` | number | no | Default port (6969) |
|
||||
| `defaults.timeout` | number | no | Default timeout ms (5000) |
|
||||
|
||||
#### Methods
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `send(message, [opts])` | `Promise<{ok}>` | Send a notification |
|
||||
| `history([n])` | `Array` | Last `n` sent entries |
|
||||
| `setHost(host)` | `void` | Change the target PS5 IP |
|
||||
|
||||
---
|
||||
|
||||
## Electron example
|
||||
|
||||
```js
|
||||
// main.js
|
||||
const { PS5Notifier } = require('./PSNotifyModule');
|
||||
|
||||
const ps5 = new PS5Notifier(
|
||||
'192.168.1.100',
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
// Call from anywhere in your app:
|
||||
ipcMain.on('build-complete', async (_, version) => {
|
||||
await ps5.send('Build complete', { subMessage: version }).catch(() => {});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
node test.js <ps5-ip>
|
||||
node test.js <ps5-ip> "Custom message" "Sub text"
|
||||
```
|
||||
|
||||
Or on Windows, double-click **`test.bat`** and enter your PS5 IP when prompted.
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `index.js` | Module — `notify()` + `PS5Notifier` class |
|
||||
| `test.js` | Standalone test runner |
|
||||
| `test.bat` | Windows batch launcher for test.js |
|
||||
| `package.json` | Module metadata |
|
||||
| `README.md` | This file |
|
||||
|
||||
---
|
||||
|
||||
## Wire protocol (for reference)
|
||||
|
||||
The module sends a UTF-8 JSON line to the PS5 over TCP:
|
||||
|
||||
```
|
||||
{"message":"Hello!","subMessage":"optional"}\n
|
||||
```
|
||||
|
||||
The PS5 responds:
|
||||
|
||||
```
|
||||
{"ok":true}\n
|
||||
```
|
||||
@@ -0,0 +1,188 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* PSNotifyModule
|
||||
* ──────────────
|
||||
* Drop this folder into any Node.js / Electron project.
|
||||
* Sends toast notifications to a PS5 running customPSNotify.js.
|
||||
*
|
||||
* QUICK START
|
||||
* ───────────
|
||||
* const { PS5Notifier } = require('./PSNotifyModule');
|
||||
*
|
||||
* const ps5 = new PS5Notifier('192.168.1.100');
|
||||
* await ps5.send('Build finished!');
|
||||
* await ps5.send('Deploy done', { subMessage: 'v2.1.0 is live' });
|
||||
*
|
||||
* ONE-SHOT (no instance needed)
|
||||
* ─────────────────────────────
|
||||
* const { notify } = require('./PSNotifyModule');
|
||||
* await notify('192.168.1.100', 'Hello PS5!');
|
||||
*/
|
||||
|
||||
const net = require('net');
|
||||
|
||||
const DEFAULT_PORT = 6969;
|
||||
const DEFAULT_TIMEOUT = 5000; // ms
|
||||
|
||||
// ─── Low-level send ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Send a single notification to a PS5.
|
||||
*
|
||||
* @param {string} host PS5 IP address or hostname
|
||||
* @param {string} message Main toast body text
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.subMessage] Smaller line below the body
|
||||
* @param {number} [opts.port] TCP port (default 6969)
|
||||
* @param {number} [opts.timeout] Timeout in ms (default 5000)
|
||||
* @returns {Promise<{ok: boolean}>} Resolves with the ACK from the PS5
|
||||
*/
|
||||
function notify(host, message, opts = {}) {
|
||||
const port = opts.port ?? DEFAULT_PORT;
|
||||
const timeout = opts.timeout ?? DEFAULT_TIMEOUT;
|
||||
const subMessage = opts.subMessage ?? '';
|
||||
|
||||
if (!host) return Promise.reject(new TypeError('host is required'));
|
||||
if (!message) return Promise.reject(new TypeError('message is required'));
|
||||
|
||||
// Encode the JSON packet as explicit UTF-8 bytes so that emoji and other
|
||||
// multi-byte Unicode characters survive the trip to the PS5 intact.
|
||||
const packetBuf = Buffer.from(JSON.stringify({ message, subMessage }) + '\n', 'utf8');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const sock = net.createConnection({ host, port });
|
||||
let raw = '';
|
||||
let done = false;
|
||||
|
||||
// Hard timeout — fires if the server never responds.
|
||||
const timer = setTimeout(() => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
sock.destroy();
|
||||
reject(new Error(`Timed out connecting to ${host}:${port} after ${timeout}ms`));
|
||||
}, timeout);
|
||||
|
||||
const finish = (err) => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
clearTimeout(timer);
|
||||
sock.destroy();
|
||||
if (err) return reject(err);
|
||||
try { resolve(JSON.parse(raw.trim())); }
|
||||
catch { resolve({ ok: true }); }
|
||||
};
|
||||
|
||||
sock.once('connect', () => {
|
||||
sock.write(packetBuf, (err) => {
|
||||
if (err) finish(err);
|
||||
// Don't close — wait for the {"ok":true} response from the PS5 server.
|
||||
});
|
||||
});
|
||||
|
||||
sock.on('data', (chunk) => {
|
||||
raw += chunk.toString();
|
||||
// The PS5 server responds with {"ok":true} then keeps the socket open.
|
||||
// As soon as we have a complete JSON response, resolve and close.
|
||||
if (raw.includes('}')) finish(null);
|
||||
});
|
||||
|
||||
// If the server does close its side, honour it.
|
||||
// If the server closes its side gracefully, honour it.
|
||||
sock.once('end', () => finish(null));
|
||||
sock.once('error', (err) => {
|
||||
// ECONNRESET means the server RST'd after receiving our packet — the
|
||||
// notification was delivered. A genuine connection failure (wrong IP /
|
||||
// port not open) produces ECONNREFUSED or a timeout, never a reset.
|
||||
// Use setImmediate so any buffered 'data' events get one more tick to
|
||||
// fire and populate `raw` before we resolve.
|
||||
if (err.code === 'ECONNRESET') {
|
||||
setImmediate(() => finish(null));
|
||||
} else {
|
||||
finish(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─── PS5Notifier class ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Stateful client that remembers the PS5 host and default options.
|
||||
* Designed to be instantiated once per app and reused.
|
||||
*
|
||||
* @example
|
||||
* const ps5 = new PS5Notifier('192.168.1.100');
|
||||
* await ps5.send('Hello!');
|
||||
* await ps5.send('Status', { subMessage: 'All good' });
|
||||
*/
|
||||
class PS5Notifier {
|
||||
/**
|
||||
* @param {string} host PS5 IP address
|
||||
* @param {object} [defaults]
|
||||
* @param {number} [defaults.port] Default port (6969)
|
||||
* @param {number} [defaults.timeout] Default timeout ms (5000)
|
||||
*/
|
||||
constructor(host, defaults = {}) {
|
||||
if (!host) throw new TypeError('PS5Notifier: host is required');
|
||||
this.host = host;
|
||||
this.port = defaults.port ?? DEFAULT_PORT;
|
||||
this.timeout = defaults.timeout ?? DEFAULT_TIMEOUT;
|
||||
this._history = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a notification to the PS5.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.subMessage]
|
||||
* @param {number} [opts.port] Override port for this call
|
||||
* @param {number} [opts.timeout] Override timeout for this call
|
||||
* @returns {Promise<{ok: boolean}>}
|
||||
*/
|
||||
async send(message, opts = {}) {
|
||||
const result = await notify(this.host, message, {
|
||||
subMessage: opts.subMessage ?? '',
|
||||
port: opts.port ?? this.port,
|
||||
timeout: opts.timeout ?? this.timeout,
|
||||
});
|
||||
|
||||
this._history.push({
|
||||
ts: new Date().toISOString(),
|
||||
message,
|
||||
subMessage: opts.subMessage ?? '',
|
||||
ok: result.ok ?? true,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last N notifications sent (default 50).
|
||||
* @param {number} [n]
|
||||
*/
|
||||
history(n = 50) {
|
||||
return this._history.slice(-n);
|
||||
}
|
||||
|
||||
/** Update the PS5 host at runtime (e.g. if IP changes). */
|
||||
setHost(host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `PS5Notifier(${this.host}:${this.port})`;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Exports ─────────────────────────────────────────────────────────────────
|
||||
|
||||
module.exports = {
|
||||
/** One-shot function — no instance needed */
|
||||
notify,
|
||||
/** Stateful class — instantiate once and reuse */
|
||||
PS5Notifier,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TIMEOUT,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "ps-notify-module",
|
||||
"version": "1.0.0",
|
||||
"description": "Send toast notifications to a jailbroken PS5 from Node.js or Electron",
|
||||
"main": "index.js",
|
||||
"keywords": ["ps5", "notify", "toast", "electron", "jailbreak"],
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
# Public source export
|
||||
|
||||
This source snapshot was generated from the private canonical repository at revision `a8ae5b1db07fb49926c94585e2718e5130330974`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
|
||||
This source snapshot was generated from the private canonical repository at revision `95e6b4be8a9669686a6241fda6f22034fd8d0b5c`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
|
||||
+1014
-999
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
# Third-party notices
|
||||
|
||||
The Apache-2.0 license applies to original Chimera source. Dependencies retain their own license and copyright notices; versions are pinned in `package-lock.json`.
|
||||
The Apache-2.0 license applies to original Chimera source. The bundled `PSNotifyModule` retains its existing MIT package license declaration. Dependencies retain their own license and copyright notices; versions are pinned in `package-lock.json`.
|
||||
|
||||
Historical bundled tools and payloads whose provenance or redistribution rights were not established are excluded from this public source and public releases, including ELF/BIN console payloads, native EXE/DLL/PDB helpers, JAR archives, firmware and package assets. No license grant is made for those components. Obtain optional prerequisites separately from their authorized publishers and comply with their terms.
|
||||
|
||||
|
||||
Reference in new issue
Block a user