@nextera.one/tps-standard 0.4.3 → 0.5.1
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/LICENSE +192 -0
- package/README.md +273 -50
- package/dist/index.d.ts +394 -4
- package/dist/index.js +859 -22
- package/dist/src/index.js +681 -0
- package/dist/test/src/index.js +963 -0
- package/dist/test/test/persian-calendar.test.js +488 -0
- package/dist/test/test/tps-uid.test.js +295 -0
- package/dist/test/tps-uid.test.js +240 -0
- package/package.json +4 -3
- package/src/index.ts +1197 -24
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TPS-UID v1 Tests
|
|
3
|
+
* Tests for the TPSUID7RB (Binary Reversible) identifier format.
|
|
4
|
+
* @version 1.0
|
|
5
|
+
*/
|
|
6
|
+
import { TPSUID7RB } from '../src/index';
|
|
7
|
+
console.log('=== TPS-UID v1 Test Suite ===\n');
|
|
8
|
+
let passed = 0;
|
|
9
|
+
let failed = 0;
|
|
10
|
+
function test(name, fn) {
|
|
11
|
+
try {
|
|
12
|
+
const result = fn();
|
|
13
|
+
if (result) {
|
|
14
|
+
console.log(`✅ ${name}`);
|
|
15
|
+
passed++;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log(`❌ ${name} - assertion failed`);
|
|
19
|
+
failed++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.log(`❌ ${name} - ${error.message}`);
|
|
24
|
+
failed++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// PART 1: Basic Encode/Decode Roundtrip
|
|
29
|
+
// ============================================================================
|
|
30
|
+
console.log('--- Part 1: Basic Encode/Decode ---\n');
|
|
31
|
+
test('Base64url encode/decode roundtrip', () => {
|
|
32
|
+
const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
|
|
33
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
34
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
35
|
+
return decoded.tps === tps;
|
|
36
|
+
});
|
|
37
|
+
test('Binary encode/decode roundtrip', () => {
|
|
38
|
+
const tps = 'tps://32,35@T:greg.m3.c1.y26.M01.d09';
|
|
39
|
+
const bytes = TPSUID7RB.encodeBinary(tps);
|
|
40
|
+
const decoded = TPSUID7RB.decodeBinary(bytes);
|
|
41
|
+
return decoded.tps === tps;
|
|
42
|
+
});
|
|
43
|
+
test('Prefix is correct', () => {
|
|
44
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
45
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
46
|
+
return encoded.startsWith('tpsuid7rb_');
|
|
47
|
+
});
|
|
48
|
+
test('Magic bytes are TPU7', () => {
|
|
49
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
50
|
+
const bytes = TPSUID7RB.encodeBinary(tps);
|
|
51
|
+
const magic = String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]);
|
|
52
|
+
return magic === 'TPU7';
|
|
53
|
+
});
|
|
54
|
+
test('Version byte is 0x01', () => {
|
|
55
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
56
|
+
const bytes = TPSUID7RB.encodeBinary(tps);
|
|
57
|
+
return bytes[4] === 0x01;
|
|
58
|
+
});
|
|
59
|
+
console.log();
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// PART 2: Compression
|
|
62
|
+
// ============================================================================
|
|
63
|
+
console.log('--- Part 2: Compression ---\n');
|
|
64
|
+
test('Compressed encode/decode roundtrip', () => {
|
|
65
|
+
const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
|
|
66
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: true });
|
|
67
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
68
|
+
return decoded.tps === tps && decoded.compressed === true;
|
|
69
|
+
});
|
|
70
|
+
test('Uncompressed flag is false', () => {
|
|
71
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
72
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: false });
|
|
73
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
74
|
+
return decoded.compressed === false;
|
|
75
|
+
});
|
|
76
|
+
test('Compression flag bit is set correctly', () => {
|
|
77
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
78
|
+
const compressed = TPSUID7RB.encodeBinary(tps, { compress: true });
|
|
79
|
+
const uncompressed = TPSUID7RB.encodeBinary(tps, { compress: false });
|
|
80
|
+
return (compressed[5] & 0x01) === 0x01 && (uncompressed[5] & 0x01) === 0x00;
|
|
81
|
+
});
|
|
82
|
+
console.log();
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// PART 3: Epoch Time
|
|
85
|
+
// ============================================================================
|
|
86
|
+
console.log('--- Part 3: Epoch Time ---\n');
|
|
87
|
+
test('Epoch time is extracted correctly', () => {
|
|
88
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
|
|
89
|
+
const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
|
|
90
|
+
const expectedEpoch = Date.UTC(2026, 0, 9, 14, 30, 25);
|
|
91
|
+
return decoded.epochMs === expectedEpoch;
|
|
92
|
+
});
|
|
93
|
+
test('Custom epochMs override works', () => {
|
|
94
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
95
|
+
const customEpoch = 1700000000000;
|
|
96
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps, { epochMs: customEpoch });
|
|
97
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
98
|
+
return decoded.epochMs === customEpoch;
|
|
99
|
+
});
|
|
100
|
+
test('Epoch parsing from TPS with time-only format', () => {
|
|
101
|
+
const tps = 'tps://32,35@T:greg.m3.c1.y26.M06.d15.h10.n00.s00';
|
|
102
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
103
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
104
|
+
const expected = Date.UTC(2026, 5, 15, 10, 0, 0);
|
|
105
|
+
return decoded.epochMs === expected;
|
|
106
|
+
});
|
|
107
|
+
console.log();
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// PART 4: Validation
|
|
110
|
+
// ============================================================================
|
|
111
|
+
console.log('--- Part 4: Validation ---\n');
|
|
112
|
+
test('Valid TPS-UID passes validation', () => {
|
|
113
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
114
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
115
|
+
return TPSUID7RB.validateBinaryB64(encoded);
|
|
116
|
+
});
|
|
117
|
+
test('Invalid prefix fails validation', () => {
|
|
118
|
+
return !TPSUID7RB.validateBinaryB64('invalid_abc123');
|
|
119
|
+
});
|
|
120
|
+
test('Empty string fails validation', () => {
|
|
121
|
+
return !TPSUID7RB.validateBinaryB64('');
|
|
122
|
+
});
|
|
123
|
+
test('Wrong prefix fails validation', () => {
|
|
124
|
+
return !TPSUID7RB.validateBinaryB64('tpsuid7r_abc123');
|
|
125
|
+
});
|
|
126
|
+
console.log();
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// PART 5: Error Handling
|
|
129
|
+
// ============================================================================
|
|
130
|
+
console.log('--- Part 5: Error Handling ---\n');
|
|
131
|
+
test('Missing prefix throws error', () => {
|
|
132
|
+
try {
|
|
133
|
+
TPSUID7RB.decodeBinaryB64('abc123');
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
return e.message.includes('missing prefix');
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
test('Bad magic throws error', () => {
|
|
141
|
+
try {
|
|
142
|
+
const badBytes = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x01, 0x00]);
|
|
143
|
+
TPSUID7RB.decodeBinary(badBytes);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
return e.message.includes('bad magic');
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
test('Too short throws error', () => {
|
|
151
|
+
try {
|
|
152
|
+
const shortBytes = new Uint8Array([0x54, 0x50, 0x55, 0x37]);
|
|
153
|
+
TPSUID7RB.decodeBinary(shortBytes);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
return e.message.includes('too short');
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
test('Unsupported version throws error', () => {
|
|
161
|
+
try {
|
|
162
|
+
const badVersion = new Uint8Array(20);
|
|
163
|
+
badVersion.set([0x54, 0x50, 0x55, 0x37, 0x99], 0); // TPU7 with bad version
|
|
164
|
+
TPSUID7RB.decodeBinary(badVersion);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
return e.message.includes('unsupported version');
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
console.log();
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// PART 6: Generate
|
|
174
|
+
// ============================================================================
|
|
175
|
+
console.log('--- Part 6: Generate ---\n');
|
|
176
|
+
test('Generate creates valid TPS-UID', () => {
|
|
177
|
+
const generated = TPSUID7RB.generate();
|
|
178
|
+
return TPSUID7RB.validateBinaryB64(generated);
|
|
179
|
+
});
|
|
180
|
+
test('Generate with location includes coordinates', () => {
|
|
181
|
+
const generated = TPSUID7RB.generate({ latitude: 32.5, longitude: 35.5 });
|
|
182
|
+
const decoded = TPSUID7RB.decodeBinaryB64(generated);
|
|
183
|
+
return decoded.tps.includes('32.5,35.5');
|
|
184
|
+
});
|
|
185
|
+
test('Generate with altitude includes altitude', () => {
|
|
186
|
+
const generated = TPSUID7RB.generate({
|
|
187
|
+
latitude: 32.5,
|
|
188
|
+
longitude: 35.5,
|
|
189
|
+
altitude: 100,
|
|
190
|
+
});
|
|
191
|
+
const decoded = TPSUID7RB.decodeBinaryB64(generated);
|
|
192
|
+
return decoded.tps.includes('100m');
|
|
193
|
+
});
|
|
194
|
+
test('Generate without location uses unknown', () => {
|
|
195
|
+
const generated = TPSUID7RB.generate();
|
|
196
|
+
const decoded = TPSUID7RB.decodeBinaryB64(generated);
|
|
197
|
+
return decoded.tps.includes('unknown@');
|
|
198
|
+
});
|
|
199
|
+
console.log();
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// PART 7: Edge Cases
|
|
202
|
+
// ============================================================================
|
|
203
|
+
console.log('--- Part 7: Edge Cases ---\n');
|
|
204
|
+
test('Long TPS string roundtrip', () => {
|
|
205
|
+
const tps = 'tps://31.9523456,35.9123456,1234.56m@T:greg.m3.c1.y26.M12.d31.h23.n59.s59;extension1;extension2;extension3';
|
|
206
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
207
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
208
|
+
return decoded.tps === tps;
|
|
209
|
+
});
|
|
210
|
+
test('Unicode in extensions survives roundtrip', () => {
|
|
211
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d01;note=تجربة';
|
|
212
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
213
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
214
|
+
return decoded.tps === tps;
|
|
215
|
+
});
|
|
216
|
+
test('Privacy location types preserved', () => {
|
|
217
|
+
const types = ['unknown', 'hidden', 'redacted'];
|
|
218
|
+
return types.every((type) => {
|
|
219
|
+
const tps = `tps://${type}@T:greg.m3.c1.y26.M01.d01`;
|
|
220
|
+
const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
|
|
221
|
+
return decoded.tps.includes(type);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
console.log();
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// PART 8: Cryptographic Sealing
|
|
227
|
+
// ============================================================================
|
|
228
|
+
console.log('--- Part 8: Cryptographic Sealing ---\n');
|
|
229
|
+
// Helper to generate keys for testing
|
|
230
|
+
const crypto = require('crypto');
|
|
231
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
|
|
232
|
+
test('Seal creates valid sealed binary', () => {
|
|
233
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
234
|
+
const sealed = TPSUID7RB.seal(tps, privateKey);
|
|
235
|
+
// Minimal length: Header(16) + Payload + SealType(1) + Sig(64)
|
|
236
|
+
// Payload 'tps://...' is ~40 chars. Total ~ 120 bytes.
|
|
237
|
+
return sealed.length > 80 && sealed[sealed.length - 65] === 0x01; // Seal Type check
|
|
238
|
+
});
|
|
239
|
+
test('Verify and decode works for sealed UID', () => {
|
|
240
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09.h12.n00';
|
|
241
|
+
const sealed = TPSUID7RB.seal(tps, privateKey);
|
|
242
|
+
const result = TPSUID7RB.verifyAndDecode(sealed, publicKey);
|
|
243
|
+
return result.tps === tps;
|
|
244
|
+
});
|
|
245
|
+
test('Tampered sealed UID fails verification', () => {
|
|
246
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
247
|
+
const sealed = TPSUID7RB.seal(tps, privateKey);
|
|
248
|
+
// Tamper with payload (change a byte in the middle)
|
|
249
|
+
sealed[20] ^= 0xFF; // Flip bits
|
|
250
|
+
try {
|
|
251
|
+
TPSUID7RB.verifyAndDecode(sealed, publicKey);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
return e.message.includes('verification failed');
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
test('Verify fails with wrong public key', () => {
|
|
259
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
260
|
+
const sealed = TPSUID7RB.seal(tps, privateKey);
|
|
261
|
+
const { publicKey: otherKey } = crypto.generateKeyPairSync('ed25519');
|
|
262
|
+
try {
|
|
263
|
+
TPSUID7RB.verifyAndDecode(sealed, otherKey);
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
return e.message.includes('verification failed'); // Or openssl error
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
test('Sealing with compression works', () => {
|
|
271
|
+
const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25'; // Longer for compression
|
|
272
|
+
const sealed = TPSUID7RB.seal(tps, privateKey, { compress: true });
|
|
273
|
+
const result = TPSUID7RB.verifyAndDecode(sealed, publicKey);
|
|
274
|
+
// Check compression flag (bit 0 and bit 1 set = 0x03)
|
|
275
|
+
// Header index 5 is flags
|
|
276
|
+
// The decode result doesn't explicitly return flags byte, but 'compressed' boolean
|
|
277
|
+
return result.tps === tps && result.compressed === true;
|
|
278
|
+
});
|
|
279
|
+
console.log();
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// Summary
|
|
282
|
+
// ============================================================================
|
|
283
|
+
console.log('=== Test Summary ===\n');
|
|
284
|
+
console.log(`Total: ${passed + failed}`);
|
|
285
|
+
console.log(`Passed: ${passed}`);
|
|
286
|
+
console.log(`Failed: ${failed}`);
|
|
287
|
+
console.log();
|
|
288
|
+
if (failed > 0) {
|
|
289
|
+
console.log('❌ Some tests failed!');
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.log('✅ All tests passed!');
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TPS-UID v1 Tests
|
|
3
|
+
* Tests for the TPSUID7RB (Binary Reversible) identifier format.
|
|
4
|
+
* @version 1.0
|
|
5
|
+
*/
|
|
6
|
+
import { TPSUID7RB } from '../src/index';
|
|
7
|
+
console.log('=== TPS-UID v1 Test Suite ===\n');
|
|
8
|
+
let passed = 0;
|
|
9
|
+
let failed = 0;
|
|
10
|
+
function test(name, fn) {
|
|
11
|
+
try {
|
|
12
|
+
const result = fn();
|
|
13
|
+
if (result) {
|
|
14
|
+
console.log(`✅ ${name}`);
|
|
15
|
+
passed++;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log(`❌ ${name} - assertion failed`);
|
|
19
|
+
failed++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.log(`❌ ${name} - ${error.message}`);
|
|
24
|
+
failed++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// PART 1: Basic Encode/Decode Roundtrip
|
|
29
|
+
// ============================================================================
|
|
30
|
+
console.log('--- Part 1: Basic Encode/Decode ---\n');
|
|
31
|
+
test('Base64url encode/decode roundtrip', () => {
|
|
32
|
+
const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
|
|
33
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
34
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
35
|
+
return decoded.tps === tps;
|
|
36
|
+
});
|
|
37
|
+
test('Binary encode/decode roundtrip', () => {
|
|
38
|
+
const tps = 'tps://32,35@T:greg.m3.c1.y26.M01.d09';
|
|
39
|
+
const bytes = TPSUID7RB.encodeBinary(tps);
|
|
40
|
+
const decoded = TPSUID7RB.decodeBinary(bytes);
|
|
41
|
+
return decoded.tps === tps;
|
|
42
|
+
});
|
|
43
|
+
test('Prefix is correct', () => {
|
|
44
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
45
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
46
|
+
return encoded.startsWith('tpsuid7rb_');
|
|
47
|
+
});
|
|
48
|
+
test('Magic bytes are TPU7', () => {
|
|
49
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
50
|
+
const bytes = TPSUID7RB.encodeBinary(tps);
|
|
51
|
+
const magic = String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]);
|
|
52
|
+
return magic === 'TPU7';
|
|
53
|
+
});
|
|
54
|
+
test('Version byte is 0x01', () => {
|
|
55
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
56
|
+
const bytes = TPSUID7RB.encodeBinary(tps);
|
|
57
|
+
return bytes[4] === 0x01;
|
|
58
|
+
});
|
|
59
|
+
console.log();
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// PART 2: Compression
|
|
62
|
+
// ============================================================================
|
|
63
|
+
console.log('--- Part 2: Compression ---\n');
|
|
64
|
+
test('Compressed encode/decode roundtrip', () => {
|
|
65
|
+
const tps = 'tps://31.95,35.91,800m@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
|
|
66
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: true });
|
|
67
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
68
|
+
return decoded.tps === tps && decoded.compressed === true;
|
|
69
|
+
});
|
|
70
|
+
test('Uncompressed flag is false', () => {
|
|
71
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
72
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps, { compress: false });
|
|
73
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
74
|
+
return decoded.compressed === false;
|
|
75
|
+
});
|
|
76
|
+
test('Compression flag bit is set correctly', () => {
|
|
77
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
78
|
+
const compressed = TPSUID7RB.encodeBinary(tps, { compress: true });
|
|
79
|
+
const uncompressed = TPSUID7RB.encodeBinary(tps, { compress: false });
|
|
80
|
+
return (compressed[5] & 0x01) === 0x01 && (uncompressed[5] & 0x01) === 0x00;
|
|
81
|
+
});
|
|
82
|
+
console.log();
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// PART 3: Epoch Time
|
|
85
|
+
// ============================================================================
|
|
86
|
+
console.log('--- Part 3: Epoch Time ---\n');
|
|
87
|
+
test('Epoch time is extracted correctly', () => {
|
|
88
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09.h14.n30.s25';
|
|
89
|
+
const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
|
|
90
|
+
const expectedEpoch = Date.UTC(2026, 0, 9, 14, 30, 25);
|
|
91
|
+
return decoded.epochMs === expectedEpoch;
|
|
92
|
+
});
|
|
93
|
+
test('Custom epochMs override works', () => {
|
|
94
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
95
|
+
const customEpoch = 1700000000000;
|
|
96
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps, { epochMs: customEpoch });
|
|
97
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
98
|
+
return decoded.epochMs === customEpoch;
|
|
99
|
+
});
|
|
100
|
+
test('Epoch parsing from TPS with time-only format', () => {
|
|
101
|
+
const tps = 'tps://32,35@T:greg.m3.c1.y26.M06.d15.h10.n00.s00';
|
|
102
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
103
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
104
|
+
const expected = Date.UTC(2026, 5, 15, 10, 0, 0);
|
|
105
|
+
return decoded.epochMs === expected;
|
|
106
|
+
});
|
|
107
|
+
console.log();
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// PART 4: Validation
|
|
110
|
+
// ============================================================================
|
|
111
|
+
console.log('--- Part 4: Validation ---\n');
|
|
112
|
+
test('Valid TPS-UID passes validation', () => {
|
|
113
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d09';
|
|
114
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
115
|
+
return TPSUID7RB.validateBinaryB64(encoded);
|
|
116
|
+
});
|
|
117
|
+
test('Invalid prefix fails validation', () => {
|
|
118
|
+
return !TPSUID7RB.validateBinaryB64('invalid_abc123');
|
|
119
|
+
});
|
|
120
|
+
test('Empty string fails validation', () => {
|
|
121
|
+
return !TPSUID7RB.validateBinaryB64('');
|
|
122
|
+
});
|
|
123
|
+
test('Wrong prefix fails validation', () => {
|
|
124
|
+
return !TPSUID7RB.validateBinaryB64('tpsuid7r_abc123');
|
|
125
|
+
});
|
|
126
|
+
console.log();
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// PART 5: Error Handling
|
|
129
|
+
// ============================================================================
|
|
130
|
+
console.log('--- Part 5: Error Handling ---\n');
|
|
131
|
+
test('Missing prefix throws error', () => {
|
|
132
|
+
try {
|
|
133
|
+
TPSUID7RB.decodeBinaryB64('abc123');
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
return e.message.includes('missing prefix');
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
test('Bad magic throws error', () => {
|
|
141
|
+
try {
|
|
142
|
+
const badBytes = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x01, 0x00]);
|
|
143
|
+
TPSUID7RB.decodeBinary(badBytes);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
return e.message.includes('bad magic');
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
test('Too short throws error', () => {
|
|
151
|
+
try {
|
|
152
|
+
const shortBytes = new Uint8Array([0x54, 0x50, 0x55, 0x37]);
|
|
153
|
+
TPSUID7RB.decodeBinary(shortBytes);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
return e.message.includes('too short');
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
test('Unsupported version throws error', () => {
|
|
161
|
+
try {
|
|
162
|
+
const badVersion = new Uint8Array(20);
|
|
163
|
+
badVersion.set([0x54, 0x50, 0x55, 0x37, 0x99], 0); // TPU7 with bad version
|
|
164
|
+
TPSUID7RB.decodeBinary(badVersion);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
return e.message.includes('unsupported version');
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
console.log();
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// PART 6: Generate
|
|
174
|
+
// ============================================================================
|
|
175
|
+
console.log('--- Part 6: Generate ---\n');
|
|
176
|
+
test('Generate creates valid TPS-UID', () => {
|
|
177
|
+
const generated = TPSUID7RB.generate();
|
|
178
|
+
return TPSUID7RB.validateBinaryB64(generated);
|
|
179
|
+
});
|
|
180
|
+
test('Generate with location includes coordinates', () => {
|
|
181
|
+
const generated = TPSUID7RB.generate({ latitude: 32.5, longitude: 35.5 });
|
|
182
|
+
const decoded = TPSUID7RB.decodeBinaryB64(generated);
|
|
183
|
+
return decoded.tps.includes('32.5,35.5');
|
|
184
|
+
});
|
|
185
|
+
test('Generate with altitude includes altitude', () => {
|
|
186
|
+
const generated = TPSUID7RB.generate({
|
|
187
|
+
latitude: 32.5,
|
|
188
|
+
longitude: 35.5,
|
|
189
|
+
altitude: 100,
|
|
190
|
+
});
|
|
191
|
+
const decoded = TPSUID7RB.decodeBinaryB64(generated);
|
|
192
|
+
return decoded.tps.includes('100m');
|
|
193
|
+
});
|
|
194
|
+
test('Generate without location uses unknown', () => {
|
|
195
|
+
const generated = TPSUID7RB.generate();
|
|
196
|
+
const decoded = TPSUID7RB.decodeBinaryB64(generated);
|
|
197
|
+
return decoded.tps.includes('unknown@');
|
|
198
|
+
});
|
|
199
|
+
console.log();
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// PART 7: Edge Cases
|
|
202
|
+
// ============================================================================
|
|
203
|
+
console.log('--- Part 7: Edge Cases ---\n');
|
|
204
|
+
test('Long TPS string roundtrip', () => {
|
|
205
|
+
const tps = 'tps://31.9523456,35.9123456,1234.56m@T:greg.m3.c1.y26.M12.d31.h23.n59.s59;extension1;extension2;extension3';
|
|
206
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
207
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
208
|
+
return decoded.tps === tps;
|
|
209
|
+
});
|
|
210
|
+
test('Unicode in extensions survives roundtrip', () => {
|
|
211
|
+
const tps = 'tps://unknown@T:greg.m3.c1.y26.M01.d01;note=تجربة';
|
|
212
|
+
const encoded = TPSUID7RB.encodeBinaryB64(tps);
|
|
213
|
+
const decoded = TPSUID7RB.decodeBinaryB64(encoded);
|
|
214
|
+
return decoded.tps === tps;
|
|
215
|
+
});
|
|
216
|
+
test('Privacy location types preserved', () => {
|
|
217
|
+
const types = ['unknown', 'hidden', 'redacted'];
|
|
218
|
+
return types.every((type) => {
|
|
219
|
+
const tps = `tps://${type}@T:greg.m3.c1.y26.M01.d01`;
|
|
220
|
+
const decoded = TPSUID7RB.decodeBinaryB64(TPSUID7RB.encodeBinaryB64(tps));
|
|
221
|
+
return decoded.tps.includes(type);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
console.log();
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// Summary
|
|
227
|
+
// ============================================================================
|
|
228
|
+
console.log('=== Test Summary ===\n');
|
|
229
|
+
console.log(`Total: ${passed + failed}`);
|
|
230
|
+
console.log(`Passed: ${passed}`);
|
|
231
|
+
console.log(`Failed: ${failed}`);
|
|
232
|
+
console.log();
|
|
233
|
+
if (failed > 0) {
|
|
234
|
+
console.log('❌ Some tests failed!');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
console.log('✅ All tests passed!');
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextera.one/tps-standard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The Universal Protocol for Space-Time Coordinates. A standard URI scheme (tps://) combining WGS84 spatial data with hierarchical, multi-calendar temporal coordinates.",
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "The Universal Protocol for Space-Time Coordinates. A standard URI scheme (tps://) combining WGS84 spatial data with hierarchical, multi-calendar temporal coordinates. Includes TPS-UID: time-first, reversible event identifiers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tps",
|
|
7
|
+
"tps-uid",
|
|
7
8
|
"time-protocol",
|
|
8
9
|
"space-time",
|
|
9
10
|
"reality-address",
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"type": "git",
|
|
24
25
|
"url": "git+https://github.com/nextera-one/tps.git"
|
|
25
26
|
},
|
|
26
|
-
"license": "
|
|
27
|
+
"license": "Apache-2.0",
|
|
27
28
|
"author": "Mohammed Ayesh",
|
|
28
29
|
"types": "dist/index.d.ts",
|
|
29
30
|
"main": "dist/index.js",
|