@myxo-victor/chexjs 6.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/ChexJs/.htaccess +9 -0
- package/ChexJs/Chex.js +712 -0
- package/ChexJs/Chex.php +354 -0
- package/ChexJs/Chex_notify.php +68 -0
- package/ChexJs/LICENSE +22 -0
- package/ChexJs/README.md +121 -0
- package/ChexJs/api/read.txt +4 -0
- package/ChexJs/app/api/api.txt +4 -0
- package/ChexJs/app/components/main.js +4 -0
- package/ChexJs/app/index.css +12 -0
- package/ChexJs/app/index.html +28 -0
- package/ChexJs/app/logic/app.js +4 -0
- package/ChexJs/components/demo.js +45 -0
- package/ChexJs/components/main.js +609 -0
- package/ChexJs/images/logo.png +0 -0
- package/ChexJs/index.css +118 -0
- package/ChexJs/index.html +44 -0
- package/ChexJs/libs/modal.js +617 -0
- package/ChexJs/libs/orbit.js +217 -0
- package/ChexJs/libs/racket.js +374 -0
- package/ChexJs/libs/rinx.js +88 -0
- package/ChexJs/libs/scrollEcho.js +212 -0
- package/ChexJs/libs/skeleton.js +341 -0
- package/ChexJs/libs/smooth.js +647 -0
- package/ChexJs/logic/app.js +36 -0
- package/ChexJs/notification_handler.txt +1 -0
- package/ChexJs/sw.js +67 -0
- package/package.json +12 -0
package/ChexJs/Chex.php
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Chex 6.0.0 Secure Database Engine
|
|
6
|
+
*
|
|
7
|
+
* Configure your database credentials in CONFIG below.
|
|
8
|
+
* This endpoint is used by Chex.db.connect(...).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
header('Content-Type: application/json; charset=utf-8');
|
|
12
|
+
|
|
13
|
+
// ----------------------------
|
|
14
|
+
// CONFIG: EDIT THESE VALUES
|
|
15
|
+
// ----------------------------
|
|
16
|
+
$CONFIG = [
|
|
17
|
+
// Add your database host, database name, username, and password here.
|
|
18
|
+
'db_host' => '127.0.0.1',
|
|
19
|
+
'db_port' => 3306,
|
|
20
|
+
'db_name' => 'test', #Change this to your own database name
|
|
21
|
+
'db_user' => 'root', #This too
|
|
22
|
+
'db_pass' => '', #This too
|
|
23
|
+
'db_charset' => 'utf8mb4',
|
|
24
|
+
|
|
25
|
+
// Create a long random secret key and use same key in Chex.db.connect({ apiKey: '...' }).
|
|
26
|
+
// Leave empty to disable API key enforcement (NOT recommended for production).
|
|
27
|
+
'api_key' => 'CHANGE_THIS_TO_A_LONG_RANDOM_SECRET',
|
|
28
|
+
|
|
29
|
+
// Allowed frontend origins. Keep minimal in production.
|
|
30
|
+
'allowed_origins' => [
|
|
31
|
+
'http://localhost', #This too, this one is for localhost
|
|
32
|
+
'http://127.0.0.1' #and this
|
|
33
|
+
],
|
|
34
|
+
|
|
35
|
+
// Whitelist allowed tables to prevent access to unexpected DB tables.
|
|
36
|
+
'allowed_tables' => [
|
|
37
|
+
'users' #Add your database tables here
|
|
38
|
+
],
|
|
39
|
+
|
|
40
|
+
// Set true in development to include exception details.
|
|
41
|
+
'debug' => true,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function respond(int $status, array $payload): void {
|
|
45
|
+
http_response_code($status);
|
|
46
|
+
echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
47
|
+
exit;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sanitize_identifier(string $value): string {
|
|
51
|
+
if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $value)) {
|
|
52
|
+
throw new InvalidArgumentException('Invalid identifier.');
|
|
53
|
+
}
|
|
54
|
+
return $value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sanitize_identifier_list($value): array {
|
|
58
|
+
if (!is_array($value) || count($value) === 0) {
|
|
59
|
+
throw new InvalidArgumentException('Invalid column list.');
|
|
60
|
+
}
|
|
61
|
+
$out = [];
|
|
62
|
+
foreach ($value as $column) {
|
|
63
|
+
if (!is_string($column)) {
|
|
64
|
+
throw new InvalidArgumentException('Invalid column in list.');
|
|
65
|
+
}
|
|
66
|
+
$out[] = sanitize_identifier($column);
|
|
67
|
+
}
|
|
68
|
+
return array_values(array_unique($out));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensure_allowed_table(string $table, array $allowedTables): string {
|
|
72
|
+
$safeTable = sanitize_identifier($table);
|
|
73
|
+
if (!in_array($safeTable, $allowedTables, true)) {
|
|
74
|
+
throw new InvalidArgumentException('Table is not allowed.');
|
|
75
|
+
}
|
|
76
|
+
return $safeTable;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function require_json_payload(): array {
|
|
80
|
+
$raw = file_get_contents('php://input');
|
|
81
|
+
$data = json_decode((string)$raw, true);
|
|
82
|
+
if (!is_array($data)) {
|
|
83
|
+
throw new InvalidArgumentException('Invalid JSON payload.');
|
|
84
|
+
}
|
|
85
|
+
return $data;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function build_where_clause(array $where, array &$params): string {
|
|
89
|
+
if (count($where) === 0) return '';
|
|
90
|
+
$parts = [];
|
|
91
|
+
$i = 0;
|
|
92
|
+
foreach ($where as $column => $value) {
|
|
93
|
+
if (!is_string($column)) {
|
|
94
|
+
throw new InvalidArgumentException('Invalid where column.');
|
|
95
|
+
}
|
|
96
|
+
$safeColumn = sanitize_identifier($column);
|
|
97
|
+
$param = ':w' . $i++;
|
|
98
|
+
$parts[] = "`{$safeColumn}` = {$param}";
|
|
99
|
+
$params[$param] = $value;
|
|
100
|
+
}
|
|
101
|
+
return ' WHERE ' . implode(' AND ', $parts);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
106
|
+
http_response_code(204);
|
|
107
|
+
exit;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
111
|
+
respond(405, ['ok' => false, 'error' => 'Method not allowed. Use POST.']);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
|
115
|
+
if ($origin !== '' && in_array($origin, $CONFIG['allowed_origins'], true)) {
|
|
116
|
+
header('Access-Control-Allow-Origin: ' . $origin);
|
|
117
|
+
header('Vary: Origin');
|
|
118
|
+
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
|
119
|
+
header('Access-Control-Allow-Headers: Content-Type, X-Chex-Key');
|
|
120
|
+
} elseif ($origin !== '') {
|
|
121
|
+
respond(403, ['ok' => false, 'error' => 'Origin not allowed.']);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
$apiKey = trim((string)$CONFIG['api_key']);
|
|
125
|
+
if ($apiKey !== '') {
|
|
126
|
+
$requestKey = (string)($_SERVER['HTTP_X_CHEX_KEY'] ?? '');
|
|
127
|
+
if ($requestKey === '' || !hash_equals($apiKey, $requestKey)) {
|
|
128
|
+
respond(401, ['ok' => false, 'error' => 'Unauthorized request key.']);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
$payload = require_json_payload();
|
|
133
|
+
$op = isset($payload['op']) ? (string)$payload['op'] : '';
|
|
134
|
+
$table = isset($payload['table']) ? (string)$payload['table'] : '';
|
|
135
|
+
$safeTable = ensure_allowed_table($table, $CONFIG['allowed_tables']);
|
|
136
|
+
|
|
137
|
+
$dsn = sprintf(
|
|
138
|
+
'mysql:host=%s;port=%d;dbname=%s;charset=%s',
|
|
139
|
+
$CONFIG['db_host'],
|
|
140
|
+
(int)$CONFIG['db_port'],
|
|
141
|
+
$CONFIG['db_name'],
|
|
142
|
+
$CONFIG['db_charset']
|
|
143
|
+
);
|
|
144
|
+
$pdo = new PDO($dsn, $CONFIG['db_user'], $CONFIG['db_pass'], [
|
|
145
|
+
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
146
|
+
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
147
|
+
PDO::ATTR_EMULATE_PREPARES => false,
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
switch ($op) {
|
|
151
|
+
case 'read': {
|
|
152
|
+
$select = isset($payload['select']) ? sanitize_identifier_list($payload['select']) : ['*'];
|
|
153
|
+
$selectSql = $select === ['*'] ? '*' : implode(', ', array_map(fn($c) => "`{$c}`", $select));
|
|
154
|
+
|
|
155
|
+
$params = [];
|
|
156
|
+
$where = is_array($payload['where'] ?? null) ? $payload['where'] : [];
|
|
157
|
+
$whereSql = build_where_clause($where, $params);
|
|
158
|
+
|
|
159
|
+
$orderBySql = '';
|
|
160
|
+
if (isset($payload['orderBy']) && is_string($payload['orderBy']) && $payload['orderBy'] !== '') {
|
|
161
|
+
$direction = 'ASC';
|
|
162
|
+
$orderBy = trim($payload['orderBy']);
|
|
163
|
+
if (strpos($orderBy, '-') === 0) {
|
|
164
|
+
$direction = 'DESC';
|
|
165
|
+
$orderBy = substr($orderBy, 1);
|
|
166
|
+
}
|
|
167
|
+
$safeOrder = sanitize_identifier($orderBy);
|
|
168
|
+
$orderBySql = " ORDER BY `{$safeOrder}` {$direction}";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
$limitSql = '';
|
|
172
|
+
if (isset($payload['limit'])) {
|
|
173
|
+
$limit = (int)$payload['limit'];
|
|
174
|
+
if ($limit < 1 || $limit > 500) {
|
|
175
|
+
throw new InvalidArgumentException('limit must be between 1 and 500.');
|
|
176
|
+
}
|
|
177
|
+
$limitSql = " LIMIT {$limit}";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
$offsetSql = '';
|
|
181
|
+
if (isset($payload['offset'])) {
|
|
182
|
+
$offset = (int)$payload['offset'];
|
|
183
|
+
if ($offset < 0) {
|
|
184
|
+
throw new InvalidArgumentException('offset must be >= 0.');
|
|
185
|
+
}
|
|
186
|
+
$offsetSql = " OFFSET {$offset}";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
$sql = "SELECT {$selectSql} FROM `{$safeTable}`{$whereSql}{$orderBySql}{$limitSql}{$offsetSql}";
|
|
190
|
+
$stmt = $pdo->prepare($sql);
|
|
191
|
+
$stmt->execute($params);
|
|
192
|
+
$rows = $stmt->fetchAll();
|
|
193
|
+
respond(200, ['ok' => true, 'data' => $rows, 'count' => count($rows)]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
case 'create': {
|
|
197
|
+
$data = $payload['data'] ?? null;
|
|
198
|
+
if (!is_array($data) || count($data) === 0) {
|
|
199
|
+
throw new InvalidArgumentException('create requires non-empty data object.');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
$columns = [];
|
|
203
|
+
$placeholders = [];
|
|
204
|
+
$params = [];
|
|
205
|
+
$i = 0;
|
|
206
|
+
foreach ($data as $column => $value) {
|
|
207
|
+
if (!is_string($column)) {
|
|
208
|
+
throw new InvalidArgumentException('Invalid create column.');
|
|
209
|
+
}
|
|
210
|
+
$safeColumn = sanitize_identifier($column);
|
|
211
|
+
$param = ':c' . $i++;
|
|
212
|
+
$columns[] = "`{$safeColumn}`";
|
|
213
|
+
$placeholders[] = $param;
|
|
214
|
+
$params[$param] = $value;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
$sql = "INSERT INTO `{$safeTable}` (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
|
218
|
+
$stmt = $pdo->prepare($sql);
|
|
219
|
+
$stmt->execute($params);
|
|
220
|
+
respond(201, ['ok' => true, 'data' => ['id' => $pdo->lastInsertId()]]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
case 'register': {
|
|
224
|
+
$data = $payload['data'] ?? null;
|
|
225
|
+
if (!is_array($data) || count($data) === 0) {
|
|
226
|
+
throw new InvalidArgumentException('register requires non-empty data object.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Hash the password before storing it
|
|
230
|
+
if (isset($data['password'])) {
|
|
231
|
+
$data['password_hash'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
|
232
|
+
unset($data['password']);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
$columns = [];
|
|
236
|
+
$placeholders = [];
|
|
237
|
+
$params = [];
|
|
238
|
+
$i = 0;
|
|
239
|
+
foreach ($data as $column => $value) {
|
|
240
|
+
if (!is_string($column)) {
|
|
241
|
+
throw new InvalidArgumentException('Invalid register column.');
|
|
242
|
+
}
|
|
243
|
+
$safeColumn = sanitize_identifier($column);
|
|
244
|
+
$param = ':c' . $i++;
|
|
245
|
+
$columns[] = "`{$safeColumn}`";
|
|
246
|
+
$placeholders[] = $param;
|
|
247
|
+
$params[$param] = $value;
|
|
248
|
+
}
|
|
249
|
+
$sql = "INSERT INTO `{$safeTable}` (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
|
250
|
+
$stmt = $pdo->prepare($sql);
|
|
251
|
+
$stmt->execute($params);
|
|
252
|
+
respond(201, ['ok' => true, 'data' => ['id' => $pdo->lastInsertId()]]);
|
|
253
|
+
}
|
|
254
|
+
case 'update': {
|
|
255
|
+
$where = $payload['where'] ?? null;
|
|
256
|
+
$data = $payload['data'] ?? null;
|
|
257
|
+
if (!is_array($where) || count($where) === 0) {
|
|
258
|
+
throw new InvalidArgumentException('update requires a non-empty where object.');
|
|
259
|
+
}
|
|
260
|
+
if (!is_array($data) || count($data) === 0) {
|
|
261
|
+
throw new InvalidArgumentException('update requires a non-empty data object.');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
$sets = [];
|
|
265
|
+
$params = [];
|
|
266
|
+
$i = 0;
|
|
267
|
+
foreach ($data as $column => $value) {
|
|
268
|
+
if (!is_string($column)) {
|
|
269
|
+
throw new InvalidArgumentException('Invalid update column.');
|
|
270
|
+
}
|
|
271
|
+
$safeColumn = sanitize_identifier($column);
|
|
272
|
+
$param = ':u' . $i++;
|
|
273
|
+
$sets[] = "`{$safeColumn}` = {$param}";
|
|
274
|
+
$params[$param] = $value;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
$whereSql = build_where_clause($where, $params);
|
|
278
|
+
$sql = "UPDATE `{$safeTable}` SET " . implode(', ', $sets) . $whereSql;
|
|
279
|
+
$stmt = $pdo->prepare($sql);
|
|
280
|
+
$stmt->execute($params);
|
|
281
|
+
respond(200, ['ok' => true, 'data' => ['affectedRows' => $stmt->rowCount()]]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case 'delete': {
|
|
285
|
+
$where = $payload['where'] ?? null;
|
|
286
|
+
if (!is_array($where) || count($where) === 0) {
|
|
287
|
+
throw new InvalidArgumentException('delete requires a non-empty where object.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
$params = [];
|
|
291
|
+
$whereSql = build_where_clause($where, $params);
|
|
292
|
+
$sql = "DELETE FROM `{$safeTable}`{$whereSql}";
|
|
293
|
+
$stmt = $pdo->prepare($sql);
|
|
294
|
+
$stmt->execute($params);
|
|
295
|
+
respond(200, ['ok' => true, 'data' => ['affectedRows' => $stmt->rowCount()]]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
case 'exists': {
|
|
299
|
+
$where = $payload['where'] ?? null;
|
|
300
|
+
if (!is_array($where) || count($where) === 0) {
|
|
301
|
+
throw new InvalidArgumentException('exists requires a non-empty where object.');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
$params = [];
|
|
305
|
+
$whereSql = build_where_clause($where, $params);
|
|
306
|
+
$sql = "SELECT 1 FROM `{$safeTable}`{$whereSql} LIMIT 1";
|
|
307
|
+
$stmt = $pdo->prepare($sql);
|
|
308
|
+
$stmt->execute($params);
|
|
309
|
+
$exists = (bool)$stmt->fetchColumn();
|
|
310
|
+
respond(200, ['ok' => true, 'data' => ['exists' => $exists]]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
case 'login': {
|
|
314
|
+
$credentials = $payload['credentials'] ?? null;
|
|
315
|
+
$loginConfig = $payload['login'] ?? null;
|
|
316
|
+
if (!is_array($credentials) || !is_array($loginConfig)) {
|
|
317
|
+
throw new InvalidArgumentException('login requires credentials and login config.');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
$userField = sanitize_identifier((string)($loginConfig['userField'] ?? 'email'));
|
|
321
|
+
$passField = sanitize_identifier((string)($loginConfig['passField'] ?? 'password_hash'));
|
|
322
|
+
$userValue = (string)($credentials[$userField] ?? '');
|
|
323
|
+
$password = (string)($credentials['password'] ?? '');
|
|
324
|
+
|
|
325
|
+
if ($userValue === '' || $password === '') {
|
|
326
|
+
respond(400, ['ok' => false, 'error' => 'Missing login credentials.']);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
$select = isset($payload['select']) ? sanitize_identifier_list($payload['select']) : ['id', $userField];
|
|
330
|
+
if (!in_array($passField, $select, true)) {
|
|
331
|
+
$select[] = $passField;
|
|
332
|
+
}
|
|
333
|
+
$selectSql = implode(', ', array_map(fn($c) => "`{$c}`", $select));
|
|
334
|
+
|
|
335
|
+
$sql = "SELECT {$selectSql} FROM `{$safeTable}` WHERE `{$userField}` = :u LIMIT 1";
|
|
336
|
+
$stmt = $pdo->prepare($sql);
|
|
337
|
+
$stmt->execute([':u' => $userValue]);
|
|
338
|
+
$row = $stmt->fetch();
|
|
339
|
+
|
|
340
|
+
if (!$row || !isset($row[$passField]) || !password_verify($password, (string)$row[$passField])) {
|
|
341
|
+
respond(401, ['ok' => false, 'error' => 'Invalid credentials.']);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
unset($row[$passField]);
|
|
345
|
+
respond(200, ['ok' => true, 'data' => $row]);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
default:
|
|
349
|
+
respond(400, ['ok' => false, 'error' => 'Unsupported operation.']);
|
|
350
|
+
}
|
|
351
|
+
} catch (Throwable $e) {
|
|
352
|
+
$message = $CONFIG['debug'] ? $e->getMessage() : 'Request failed.';
|
|
353
|
+
respond(500, ['ok' => false, 'error' => $message]);
|
|
354
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* @author Myxo victor
|
|
4
|
+
* Chex v6.0.0 Notification Handler
|
|
5
|
+
* Receives device subscription data from Chex and stores it for server-side push notifications.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
header("Access-Control-Allow-Origin: *");
|
|
9
|
+
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
|
10
|
+
header("Access-Control-Allow-Headers: Content-Type");
|
|
11
|
+
header("Content-Type: application/json");
|
|
12
|
+
|
|
13
|
+
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
14
|
+
http_response_code(200);
|
|
15
|
+
exit;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
19
|
+
$input = file_get_contents('php://input');
|
|
20
|
+
$data = json_decode($input, true);
|
|
21
|
+
|
|
22
|
+
if (isset($data['deviceId']) && !empty($data['deviceId'])) {
|
|
23
|
+
$deviceId = htmlspecialchars($data['deviceId']);
|
|
24
|
+
$type = isset($data['type']) ? htmlspecialchars($data['type']) : 'unknown';
|
|
25
|
+
$timestamp = date("Y-m-d H:i:s");
|
|
26
|
+
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
|
27
|
+
$userAgent = $_SERVER['HTTP_USER_AGENT'];
|
|
28
|
+
|
|
29
|
+
$logEntry = "------------------------------------------------" . PHP_EOL;
|
|
30
|
+
$logEntry .= "Date: $timestamp" . PHP_EOL;
|
|
31
|
+
$logEntry .= "Device ID: $deviceId" . PHP_EOL;
|
|
32
|
+
$logEntry .= "Type: $type" . PHP_EOL;
|
|
33
|
+
$logEntry .= "IP: $ipAddress" . PHP_EOL;
|
|
34
|
+
$logEntry .= "User Agent: $userAgent" . PHP_EOL;
|
|
35
|
+
|
|
36
|
+
$filePath = 'notification_handler.txt';
|
|
37
|
+
|
|
38
|
+
if (file_put_contents($filePath, $logEntry, FILE_APPEND | LOCK_EX)) {
|
|
39
|
+
echo json_encode([
|
|
40
|
+
"status" => "success",
|
|
41
|
+
"code" => 200,
|
|
42
|
+
"message" => "Device successfully registered to the Chex 6.0 ecosystem."
|
|
43
|
+
]);
|
|
44
|
+
} else {
|
|
45
|
+
http_response_code(500);
|
|
46
|
+
echo json_encode([
|
|
47
|
+
"status" => "error",
|
|
48
|
+
"code" => 500,
|
|
49
|
+
"message" => "Failed to write to notification_handler.txt. Check folder permissions."
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
http_response_code(400);
|
|
54
|
+
echo json_encode([
|
|
55
|
+
"status" => "error",
|
|
56
|
+
"code" => 400,
|
|
57
|
+
"message" => "Invalid payload. 'deviceId' is required."
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
http_response_code(405);
|
|
62
|
+
echo json_encode([
|
|
63
|
+
"status" => "error",
|
|
64
|
+
"code" => 405,
|
|
65
|
+
"message" => "Method Not Allowed. Use POST via Chex 6.0 notification APIs."
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
?>
|
package/ChexJs/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aximon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/ChexJs/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Chex 6.0.0
|
|
2
|
+
|
|
3
|
+
Chex is a lightweight JavaScript UI engine for fast, reactive, component-driven interfaces. It runs directly in the browser with no compiler or build step.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
Include the engine, then write components with the tag helpers you need:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script src="./Chex.js"></script>
|
|
11
|
+
<script src="./components/main.js"></script>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
const { div, h1, button } = Chex;
|
|
16
|
+
const app = document.getElementById('app');
|
|
17
|
+
const count = Chex.signal(0);
|
|
18
|
+
|
|
19
|
+
const Counter = () => div({ class: 'counter' }, [
|
|
20
|
+
h1(`Count: ${count.value}`),
|
|
21
|
+
button({ onClick: () => (count.value += 1) }, 'Increment')
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
Chex.render(app, Counter);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Fast element API
|
|
28
|
+
|
|
29
|
+
Chex tag helpers accept props when you have them, and accept children directly when you do not:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const { div, h1, button } = Chex;
|
|
33
|
+
|
|
34
|
+
// With props
|
|
35
|
+
div({ class: 'hero' }, [
|
|
36
|
+
h1({}, 'Title'),
|
|
37
|
+
button({ onClick: save }, 'Save')
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
// No empty props object required
|
|
41
|
+
div([
|
|
42
|
+
h1('Title'),
|
|
43
|
+
button('Save')
|
|
44
|
+
]);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You can also use `Chex.div(...)`, `Chex.h1(...)`, and any standard HTML tag directly.
|
|
48
|
+
|
|
49
|
+
## Core features
|
|
50
|
+
|
|
51
|
+
- Fast VNode rendering and DOM patching with `Chex.render(...)`
|
|
52
|
+
- Concise tag helpers and custom-tag support
|
|
53
|
+
- Signals and effects: `Chex.signal(...)`, `Chex.effect(...)`
|
|
54
|
+
- Hash or history routing with `Chex.createRouter(...)`
|
|
55
|
+
- Fetch and cached query helpers through `Chex.api`
|
|
56
|
+
- Secure PHP database client through `Chex.db`
|
|
57
|
+
- Notifications, service-worker registration, and animation helpers
|
|
58
|
+
- Reusable UI components: Button, Input, AppBar, SideBar, BottomNav, and Card
|
|
59
|
+
|
|
60
|
+
## Backend database engine
|
|
61
|
+
|
|
62
|
+
`Chex.php` is the optional secure PHP/PDO endpoint used by `Chex.db.connect(...)`.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const server = Chex.db.connect({
|
|
66
|
+
endpoint: '/Chex.php',
|
|
67
|
+
apiKey: 'YOUR_SECURE_API_KEY',
|
|
68
|
+
table: 'users'
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await server.create({ email: 'demo@example.com' });
|
|
72
|
+
const users = await server.read({ select: ['id', 'email'], limit: 10 });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Open `Chex.php` to set database credentials, allowed origins, allowed tables, and the API key. The client sends the key in the `X-Chex-Key` header.
|
|
76
|
+
|
|
77
|
+
`Chex_notify.php` receives browser notification registrations. Pair it with `Chex.notification.ask('/Chex_notify.php')` when notifications are enabled.
|
|
78
|
+
|
|
79
|
+
## Included libraries
|
|
80
|
+
|
|
81
|
+
All libraries are standalone and dependency-free. Load them after `Chex.js` when needed.
|
|
82
|
+
|
|
83
|
+
| File | Global | Purpose |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `scrollEcho.js` | `ScrollEcho` | Scroll-triggered text and element reveals. |
|
|
86
|
+
| `racket.js` | `racket` | Responsive multi-panel image carousel. |
|
|
87
|
+
| `orbit.js` | `orbit` | Swipe-friendly banner and slider helper. |
|
|
88
|
+
| `rinx.js` | `rinx` | Horizontal and vertical scroll-card layouts. |
|
|
89
|
+
| `smooth.js` | `smooth` | Continuous ticker and infinite carousel. |
|
|
90
|
+
| `modal.js` | `modal` | Self-styling, zero-dependency accessible modal engine. |
|
|
91
|
+
| `skeleton.js` | `skeleton` | DOM-to-skeleton shimmer loader that reduces layout shift. |
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<script src="./Chex.js"></script>
|
|
97
|
+
<script src="./libs/modal.js"></script>
|
|
98
|
+
<script src="./libs/skeleton.js"></script>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Project structure
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
ChexJs/
|
|
105
|
+
Chex.js Core engine
|
|
106
|
+
Chex.php Optional PHP/PDO backend
|
|
107
|
+
Chex_notify.php Notification registration endpoint
|
|
108
|
+
components/ Page components and views
|
|
109
|
+
libs/ Standalone visual and UI helpers
|
|
110
|
+
index.html Welcome page launcher
|
|
111
|
+
index.css Welcome page styles
|
|
112
|
+
sw.js Service worker
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
Open `docs/index.html` through a local web server for the full API, component, backend, library, and example guides.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT License. Copyright (c) 2026 Aximon. Created by Myxo Victor.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Chex App</title>
|
|
7
|
+
|
|
8
|
+
<link rel="stylesheet" href="index.css">
|
|
9
|
+
<script src="../Chex.js"></script>
|
|
10
|
+
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
|
|
14
|
+
<link rel="icon" href="../icon/logo.png" type="image/png">
|
|
15
|
+
<meta name="theme-color" content="#0f766e">
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
h1, h2, h3, h4, h5, h6, p, span, li, a, button {
|
|
19
|
+
font-family: "IBM Plex Sans", sans-serif;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<div id="app"></div>
|
|
25
|
+
<script src="./components/main.js"></script>
|
|
26
|
+
<script src="./logic/app.js"></script>
|
|
27
|
+
</body>
|
|
28
|
+
</html>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* VenJS 5.0 — Demo showcase components.
|
|
2
|
+
* Loaded before components/main.js. Exposes a global DemoPage()
|
|
3
|
+
* that the router can render at the "/demo" route. */
|
|
4
|
+
|
|
5
|
+
const demoCount = venjs.signal(0);
|
|
6
|
+
const demoName = venjs.signal("Ada");
|
|
7
|
+
|
|
8
|
+
const DemoCounter = () => venjs.div({ class: "page" }, [
|
|
9
|
+
venjs.h2({ class: "page-title" }, "Signals in action"),
|
|
10
|
+
venjs.p({ class: "page-copy" }, "Count: " + demoCount.value),
|
|
11
|
+
venjs.div({ class: "nav" }, [
|
|
12
|
+
venjs.button({ class: "nav-btn", onclick: () => (demoCount.value += 1) }, "+1"),
|
|
13
|
+
venjs.button({ class: "nav-btn", onclick: () => (demoCount.value -= 1) }, "-1")
|
|
14
|
+
])
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const DemoBinding = () => venjs.div({ class: "page" }, [
|
|
18
|
+
venjs.h2({ class: "page-title" }, "Two-way input"),
|
|
19
|
+
venjs.input({
|
|
20
|
+
label: "Your name",
|
|
21
|
+
value: demoName.value,
|
|
22
|
+
oninput: (e) => (demoName.value = e.target.value)
|
|
23
|
+
}),
|
|
24
|
+
venjs.p({ class: "page-copy" }, "Hello, " + demoName.value + "!")
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const DemoCards = () => venjs.div({ class: "page" }, [
|
|
28
|
+
venjs.h2({ class: "page-title" }, "Component kit"),
|
|
29
|
+
venjs.card({}, [
|
|
30
|
+
venjs.h3({ style: { marginTop: "0" } }, "Built with VenJS"),
|
|
31
|
+
venjs.p({ class: "page-copy" }, "Button, Input and Card components composed together."),
|
|
32
|
+
venjs.div({ class: "nav" }, [
|
|
33
|
+
venjs.button({ class: "nav-btn active", onclick: () => {} }, "Primary"),
|
|
34
|
+
venjs.button({ variant: "outline", class: "nav-btn", onclick: () => {} }, "Outline")
|
|
35
|
+
])
|
|
36
|
+
])
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
window.DemoPage = () => venjs.div({ class: "app" }, [
|
|
40
|
+
venjs.h1({ class: "hero-title" }, "VenJS Live Demo"),
|
|
41
|
+
venjs.p({ class: "hero-copy" }, "A few examples built with signals and components."),
|
|
42
|
+
DemoCounter(),
|
|
43
|
+
DemoBinding(),
|
|
44
|
+
DemoCards()
|
|
45
|
+
]);
|