diff --git a/4N.html b/4N.html
new file mode 100644
index 0000000..d0b3351
--- /dev/null
+++ b/4N.html
@@ -0,0 +1,145 @@
+
+
+
+ Qlogic Device User
+
+
+
+
+
+
+
+
+
+
+
Boarding Pass Data:
+
+
+
+
+
+
+
+
+
+
Bag Tag Data:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index d924c9d..5945534 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,10 @@
# signal-code-samples
+## Instructions
+See **4N.html** for sample code that demonstrates how to include the required libraries and use the client wrapper provided in **qlogic-signal.js**.
+
+* JavaScript includes:
+ * JQuery
+ * JQuery SignalR (for Framework API)
+ * Microsoft's SignalR (for Core API)
+ * qlogic-signal.js (wraps Framework and Core clients)
diff --git a/internal_notes.md b/internal_notes.md
new file mode 100644
index 0000000..f1b4505
--- /dev/null
+++ b/internal_notes.md
@@ -0,0 +1,20 @@
+# Internal Notes
+
+## Created hard links to share files between projects
+```
+PS C:\Users\brian\source\repos\qlogic\signal-code-samples> New-Item -Itemtype HardLink -Path qlogic-signal.js -Target C:\Users\brian\source\repos\qlogic\signal-lib-dotnetframework\samples\DeviceUserWebApp\Scripts\qlogic-signal.js
+
+ Directory: C:\Users\brian\source\repos\qlogic\signal-code-samples
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+-a--- 3/30/2026 8:29 PM 5244 qlogic-signal.js
+
+PS C:\Users\brian\source\repos\qlogic\signal-code-samples> New-Item -Itemtype HardLink -Path 4N.html -Target C:\Users\brian\source\repos\qlogic\signal-lib-dotnetframework\samples\DeviceUserWebApp\4N.html
+
+ Directory: C:\Users\brian\source\repos\qlogic\signal-code-samples
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+-a--- 3/30/2026 8:31 PM 11172 4N.html
+```
\ No newline at end of file
diff --git a/qlogic-signal.js b/qlogic-signal.js
new file mode 100644
index 0000000..66002e5
--- /dev/null
+++ b/qlogic-signal.js
@@ -0,0 +1,159 @@
+"use strict";
+
+// Convert querystring name/value pairs into dictionary.
+const QUERY_STRING = (function (a) {
+ if (a == '') return {};
+ const b = {};
+ for (let i = 0; i < a.length; ++i) {
+ const p = a[i].split('=', 2);
+ if (p.length == 1)
+ b[p[0]] = '';
+ else
+ b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, ' '));
+ }
+ return b;
+})(window.location.search.substr(1).split('&'));
+function onReaderDataReceived(deviceType, data) {
+ console.log(data + '\n' + hexView(data));
+}
+
+// Name of group that the client will join on connection
+const QSS_GROUP_NAME = 'DeviceUsers';
+
+const SIGNALR_CONNECTION_STATE_LABEL = {
+ 0: 'Connecting',
+ 1: 'Connected',
+ 2: 'Reconnecting',
+ 4: 'Disconnected'
+};
+
+// A unified wrapper to handle both Framework and Core SignalR APIs.
+const QSS_API_WRAPPER = {
+ connection: null,
+ type: null, // 'framework' or 'core'
+ deviceHubProxy: null, // only populated in 'framework'
+
+ // Map a server-side call
+ invokeServerMethod: function (methodName, ...args) {
+ if (this.type === 'framework') {
+
+ // Framework API call
+ return this.deviceHubProxy.invoke(methodName, ...args);
+ } else {
+
+ // Core API call
+ return this.connection.invoke(methodName, ...args);
+ }
+ },
+
+ // Map a client-side listener
+ onClientMethod: function (methodName, callback) {
+ if (this.type === 'framework') {
+
+ // Framework client-side listener
+ this.deviceHubProxy.on(methodName, callback);
+ } else {
+
+ // Core client-side listener
+ this.connection.on(methodName, callback);
+ }
+ }
+};
+
+function isNullEmptyOrWhitespace(value) {
+ if (value == null) {
+ return true;
+ }
+ if (typeof value === 'string') {
+ return value.trim().length === 0;
+ }
+ return false;
+}
+
+async function startSignalR(callback) {
+ if (!('qssUrl' in QUERY_STRING) || isNullEmptyOrWhitespace(QUERY_STRING['qssUrl'])) {
+ console.warn('Missing "qssUrl" in query string');
+ return;
+ }
+ console.log('Attempting SignalR ASP.NET Framework connection...');
+
+ //
+ // ASP.NET Framework
+ //
+
+ QSS_API_WRAPPER.connection = $.hubConnection(QUERY_STRING['qssUrl'], { useDefaultPath: false });
+ QSS_API_WRAPPER.deviceHubProxy = QSS_API_WRAPPER.connection.createHubProxy('deviceHub');
+
+ // Redirect Framework events to event handlers provided in callback class
+ QSS_API_WRAPPER.connection.starting(function () {
+ callback.framework_starting();
+ });
+ QSS_API_WRAPPER.connection.received(function (data) {
+ callback.framework_received(data);
+ });
+ QSS_API_WRAPPER.connection.connectionSlow(function () {
+ callback.framework_connectionSlow();
+ });
+ QSS_API_WRAPPER.connection.reconnecting(function () {
+ callback.framework_reconnecting();
+ });
+ QSS_API_WRAPPER.connection.reconnected(function () {
+ callback.framework_reconnected();
+ });
+ QSS_API_WRAPPER.connection.stateChanged(function (change) {
+ callback.framework_stateChanged(change);
+ });
+ QSS_API_WRAPPER.connection.disconnected(function () {
+ callback.framework_disconnected();
+ });
+
+ try {
+ await new Promise((resolve, reject) => {
+ QSS_API_WRAPPER.connection.start()
+ .done(() => {
+ console.log("Connected to ASP.NET Framework SignalR!");
+
+ QSS_API_WRAPPER.deviceHubProxy.invoke('JoinGroup', QSS_GROUP_NAME)
+ .done(function () {
+ console.log('Joined group: ' + QSS_GROUP_NAME);
+ })
+ .fail(function (error) {
+ console.error('Invocation of JoinGroup failed. Error:', error);
+ });
+
+ resolve();
+ })
+ .fail((err) => {
+ reject(err);
+ });
+ });
+ QSS_API_WRAPPER.type = 'framework';
+
+ } catch (frameworkErr) {
+ console.warn("Framework connection failed. Trying ASP.NET Core...", frameworkErr);
+
+ //
+ // ASP.NET Core
+ //
+
+ QSS_API_WRAPPER.connection = new signalR.HubConnectionBuilder()
+ .withUrl(QUERY_STRING['qssUrl'])
+ .withAutomaticReconnect() // Required to perform automatic reconnects
+ .build();
+
+ // Redirect Core events to event handlers provided in callback class
+ QSS_API_WRAPPER.connection.onreconnecting(function (error) {
+ console.log('onreconnecting');
+ callback.core_onreconnecting(error);
+ });
+ QSS_API_WRAPPER.connection.onreconnected(function (connectionId) {
+ console.log('onreconnected');
+ callback.core_onreconnected(connectionId);
+ });
+ QSS_API_WRAPPER.connection.onclose(function (error) {
+ console.log('onclose');
+ callback.core_onclose(error);
+ });
+
+ try {
+
\ No newline at end of file