@anephenix/sarus
Advanced tools
Comparing version
@@ -141,2 +141,65 @@ // File Dependencies | ||
}); | ||
it("should queue and deliver ArrayBuffer messages", async () => { | ||
const server: WS = new WS(url); | ||
const sarus: Sarus = new Sarus({ url }); | ||
await server.connected; | ||
const buffer = new Uint8Array([1, 2, 3, 4]).buffer; | ||
sarus.send(buffer); | ||
const received = await server.nextMessage; | ||
// WebSocket mock returns ArrayBuffer for binary | ||
expect(received instanceof ArrayBuffer).toBe(true); | ||
expect(new Uint8Array(received as ArrayBuffer)).toEqual( | ||
new Uint8Array([1, 2, 3, 4]), | ||
); | ||
await server.close(); | ||
}); | ||
it("should queue and deliver Uint8Array messages", async () => { | ||
const server: WS = new WS(url); | ||
const sarus: Sarus = new Sarus({ url }); | ||
await server.connected; | ||
const arr = new Uint8Array([5, 6, 7, 8]); | ||
sarus.send(arr); | ||
const received = await server.nextMessage; | ||
// Always expect ArrayBuffer for binary | ||
// Just check that it can be wrapped and matches | ||
expect(new Uint8Array(received as ArrayBuffer)).toEqual(arr); | ||
await server.close(); | ||
}); | ||
it("should persist and restore ArrayBuffer messages in localStorage", async () => { | ||
localStorage.clear(); | ||
const sarus: Sarus = new Sarus({ url, storageType: "local" }); | ||
const buffer = new Uint8Array([9, 10, 11]).buffer; | ||
sarus.send(buffer); | ||
sarus.disconnect(); | ||
// Simulate page reload | ||
const sarus2: Sarus = new Sarus({ url, storageType: "local" }); | ||
expect(sarus2.messages.length).toBe(1); | ||
expect(new Uint8Array(sarus2.messages[0] as ArrayBuffer)).toEqual( | ||
new Uint8Array([9, 10, 11]), | ||
); | ||
localStorage.clear(); | ||
}); | ||
it("should persist and restore Uint8Array messages in sessionStorage", async () => { | ||
sessionStorage.clear(); | ||
const sarus: Sarus = new Sarus({ url, storageType: "session" }); | ||
const arr = new Uint8Array([12, 13, 14]); | ||
sarus.send(arr); | ||
sarus.disconnect(); | ||
// Simulate page reload | ||
const sarus2: Sarus = new Sarus({ url, storageType: "session" }); | ||
expect(sarus2.messages.length).toBe(1); | ||
expect(new Uint8Array(sarus2.messages[0] as ArrayBuffer)).toEqual(arr); | ||
sessionStorage.clear(); | ||
}); | ||
// Ensure mock server is closed between tests to avoid port conflicts | ||
afterEach(() => { | ||
WS.clean(); | ||
localStorage.clear(); | ||
sessionStorage.clear(); | ||
}); | ||
}); |
# CHANGELOG | ||
### 0.6.16 - Tuesday 20th May, 2025 | ||
- Merge pull request #547 from anephenix/feature/#546-binary-support-in-message-queue | ||
- Linting fixes and moved test code to right location | ||
- Added support for storing binary messages in the message queue | ||
- Merge pull request #545 from anephenix/dependabot/npm_and_yarn/types/node-22.15.19 | ||
- Merge pull request #544 from anephenix/dependabot/npm_and_yarn/ts-jest-29.3.4 | ||
- Bump @types/node from 22.15.18 to 22.15.19 | ||
- Bump ts-jest from 29.3.3 to 29.3.4 | ||
- Merge pull request #543 from anephenix/dependabot/npm_and_yarn/ts-jest-29.3.3 | ||
- Bump ts-jest from 29.3.2 to 29.3.3 | ||
- Removed an unnecessary dependency | ||
- Merge pull request #542 from anephenix/dependabot/npm_and_yarn/types/node-22.15.18 | ||
- Bump @types/node from 22.15.17 to 22.15.18 | ||
- Merge pull request #541 from anephenix/dependabot/npm_and_yarn/types/node-22.15.17 | ||
- Bump @types/node from 22.15.16 to 22.15.17 | ||
- Merge pull request #540 from anephenix/dependabot/npm_and_yarn/types/node-22.15.16 | ||
- Bump @types/node from 22.15.14 to 22.15.16 | ||
- Merge pull request #539 from anephenix/dependabot/npm_and_yarn/babel/parser-7.27.2 | ||
- Merge pull request #538 from anephenix/dependabot/npm_and_yarn/types/node-22.15.14 | ||
- Bump @babel/parser from 7.27.1 to 7.27.2 | ||
- Bump @types/node from 22.15.11 to 22.15.14 | ||
- Merge pull request #537 from anephenix/dependabot/npm_and_yarn/types/node-22.15.11 | ||
- Bump @types/node from 22.15.3 to 22.15.11 | ||
- Merge pull request #536 from anephenix/dependabot/npm_and_yarn/babel/parser-7.27.1 | ||
- Merge pull request #535 from anephenix/dependabot/npm_and_yarn/babel/types-7.27.1 | ||
- Bump @babel/parser from 7.27.0 to 7.27.1 | ||
- Bump @babel/types from 7.27.0 to 7.27.1 | ||
- Merge pull request #534 from anephenix/dependabot/npm_and_yarn/types/node-22.15.3 | ||
- Bump @types/node from 22.15.2 to 22.15.3 | ||
- Merge pull request #533 from anephenix/dependabot/npm_and_yarn/types/node-22.15.2 | ||
- Bump @types/node from 22.15.1 to 22.15.2 | ||
- Merge pull request #532 from anephenix/dependabot/npm_and_yarn/types/node-22.15.1 | ||
- Bump @types/node from 22.14.1 to 22.15.1 | ||
- Merge pull request #531 from anephenix/dependabot/npm_and_yarn/jsdom-26.1.0 | ||
- Merge pull request #530 from anephenix/dependabot/npm_and_yarn/types/node-22.14.1 | ||
- Merge pull request #529 from anephenix/dependabot/npm_and_yarn/ts-jest-29.3.2 | ||
- Bump jsdom from 26.0.0 to 26.1.0 | ||
- Bump @types/node from 22.14.0 to 22.14.1 | ||
- Bump ts-jest from 29.3.1 to 29.3.2 | ||
### 0.6.15 - Tuesday 8th April, 2025 | ||
@@ -4,0 +45,0 @@ |
@@ -401,2 +401,10 @@ "use strict"; | ||
var self = this; | ||
// If the message is a base64-wrapped object (from legacy or manual insert), decode it | ||
if (data && | ||
typeof data === "object" && | ||
data.__sarus_type === "binary" && | ||
typeof data.data === "string") { | ||
// Reuse the deserializer for a single message | ||
data = require("./lib/dataTransformer").deserialize(JSON.stringify(data)); | ||
} | ||
self.ws.send(data); | ||
@@ -403,0 +411,0 @@ self.removeMessage(); |
@@ -1,6 +0,1 @@ | ||
/** | ||
* Serializes the data for storing in sessionStorage/localStorage | ||
* @param {*} data - the data that we want to serialize | ||
* @returns {string} - the serialized data | ||
*/ | ||
export declare const serialize: (data: unknown) => string; | ||
@@ -7,0 +2,0 @@ /** |
@@ -9,4 +9,51 @@ "use strict"; | ||
*/ | ||
var serialize = function (data) { return JSON.stringify(data); }; | ||
// Helper: ArrayBuffer/Uint8Array to base64 | ||
function bufferToBase64(buffer) { | ||
var binary = ""; | ||
var bytes = new Uint8Array(buffer); | ||
var len = bytes.byteLength; | ||
for (var i = 0; i < len; i++) { | ||
binary += String.fromCharCode(bytes[i]); | ||
} | ||
return btoa(binary); | ||
} | ||
// Helper: base64 to ArrayBuffer | ||
function base64ToBuffer(base64) { | ||
var binary = atob(base64); | ||
var len = binary.length; | ||
var bytes = new Uint8Array(len); | ||
for (var i = 0; i < len; i++) { | ||
bytes[i] = binary.charCodeAt(i); | ||
} | ||
return bytes.buffer; | ||
} | ||
// Helper: Blob to base64 (async, but we use ArrayBuffer for storage) | ||
var serialize = function (data) { | ||
// If it's an array, serialize each element | ||
if (Array.isArray(data)) { | ||
return JSON.stringify(data.map(serializeSingle)); | ||
} | ||
return JSON.stringify(serializeSingle(data)); | ||
}; | ||
exports.serialize = serialize; | ||
function serializeSingle(data) { | ||
if (data instanceof ArrayBuffer) { | ||
return { | ||
__sarus_type: "binary", | ||
format: "arraybuffer", | ||
data: bufferToBase64(data), | ||
}; | ||
} | ||
if (data instanceof Uint8Array) { | ||
return { | ||
__sarus_type: "binary", | ||
format: "uint8array", | ||
data: bufferToBase64(data.buffer), | ||
}; | ||
} | ||
if (typeof Blob !== "undefined" && data instanceof Blob) { | ||
throw new Error("Blob serialization is not supported synchronously. Convert Blob to ArrayBuffer or Uint8Array before sending."); | ||
} | ||
return data; | ||
} | ||
/** | ||
@@ -20,4 +67,19 @@ * Deserializes the data stored in sessionStorage/localStorage | ||
return null; | ||
return JSON.parse(data); | ||
var parsed = JSON.parse(data); | ||
if (Array.isArray(parsed)) { | ||
return parsed.map(deserializeSingle); | ||
} | ||
return deserializeSingle(parsed); | ||
}; | ||
exports.deserialize = deserialize; | ||
function deserializeSingle(parsed) { | ||
if (parsed && parsed.__sarus_type === "binary") { | ||
if (parsed.format === "arraybuffer") { | ||
return base64ToBuffer(parsed.data); | ||
} | ||
if (parsed.format === "uint8array") { | ||
return new Uint8Array(base64ToBuffer(parsed.data)); | ||
} | ||
} | ||
return parsed; | ||
} |
{ | ||
"name": "@anephenix/sarus", | ||
"version": "0.6.15", | ||
"version": "0.6.16", | ||
"description": "A WebSocket JavaScript library", | ||
@@ -38,3 +38,2 @@ "main": "dist/index.js", | ||
"jsdom": "^26.0.0", | ||
"mock-socket": "^9.2.1", | ||
"prettier": "^3.0.3", | ||
@@ -41,0 +40,0 @@ "size-limit": "^11.1.6", |
@@ -495,2 +495,14 @@ // File Dependencies | ||
const self: any = this; | ||
// If the message is a base64-wrapped object (from legacy or manual insert), decode it | ||
if ( | ||
data && | ||
typeof data === "object" && | ||
(data as any).__sarus_type === "binary" && | ||
typeof (data as any).data === "string" | ||
) { | ||
// Reuse the deserializer for a single message | ||
data = (require("./lib/dataTransformer") as any).deserialize( | ||
JSON.stringify(data), | ||
); | ||
} | ||
self.ws.send(data); | ||
@@ -497,0 +509,0 @@ self.removeMessage(); |
@@ -6,4 +6,57 @@ /** | ||
*/ | ||
export const serialize = (data: unknown) => JSON.stringify(data); | ||
// Helper: ArrayBuffer/Uint8Array to base64 | ||
function bufferToBase64(buffer: ArrayBuffer): string { | ||
let binary = ""; | ||
const bytes = new Uint8Array(buffer); | ||
const len = bytes.byteLength; | ||
for (let i = 0; i < len; i++) { | ||
binary += String.fromCharCode(bytes[i]); | ||
} | ||
return btoa(binary); | ||
} | ||
// Helper: base64 to ArrayBuffer | ||
function base64ToBuffer(base64: string): ArrayBuffer { | ||
const binary = atob(base64); | ||
const len = binary.length; | ||
const bytes = new Uint8Array(len); | ||
for (let i = 0; i < len; i++) { | ||
bytes[i] = binary.charCodeAt(i); | ||
} | ||
return bytes.buffer; | ||
} | ||
// Helper: Blob to base64 (async, but we use ArrayBuffer for storage) | ||
export const serialize = (data: unknown) => { | ||
// If it's an array, serialize each element | ||
if (Array.isArray(data)) { | ||
return JSON.stringify(data.map(serializeSingle)); | ||
} | ||
return JSON.stringify(serializeSingle(data)); | ||
}; | ||
function serializeSingle(data: unknown): any { | ||
if (data instanceof ArrayBuffer) { | ||
return { | ||
__sarus_type: "binary", | ||
format: "arraybuffer", | ||
data: bufferToBase64(data), | ||
}; | ||
} | ||
if (data instanceof Uint8Array) { | ||
return { | ||
__sarus_type: "binary", | ||
format: "uint8array", | ||
data: bufferToBase64(data.buffer), | ||
}; | ||
} | ||
if (typeof Blob !== "undefined" && data instanceof Blob) { | ||
throw new Error( | ||
"Blob serialization is not supported synchronously. Convert Blob to ArrayBuffer or Uint8Array before sending.", | ||
); | ||
} | ||
return data; | ||
} | ||
/** | ||
@@ -16,3 +69,19 @@ * Deserializes the data stored in sessionStorage/localStorage | ||
if (!data) return null; | ||
return JSON.parse(data); | ||
const parsed = JSON.parse(data); | ||
if (Array.isArray(parsed)) { | ||
return parsed.map(deserializeSingle); | ||
} | ||
return deserializeSingle(parsed); | ||
}; | ||
function deserializeSingle(parsed: any): any { | ||
if (parsed && parsed.__sarus_type === "binary") { | ||
if (parsed.format === "arraybuffer") { | ||
return base64ToBuffer(parsed.data); | ||
} | ||
if (parsed.format === "uint8array") { | ||
return new Uint8Array(base64ToBuffer(parsed.data)); | ||
} | ||
} | ||
return parsed; | ||
} |
149143
6.48%20
-4.76%2435
8.9%