JWT mode previously failed closed for visitors with no session: _jwtEnsureFreshAccessToken called onFail('no_refresh_token') whenever there was no access token AND no refresh token, so an anonymous visitor's request never left the browser — public/catalog pages could not run on JWT mode at all (the gap that kept storefronts pinned to HMAC).
Now a true anonymous visitor (no access token, no refresh token) proceeds via onReady(): the request fires with x-kyte-appid only — no Authorization header. The server decides whether to serve it (kyte-php v4.11.0's AppContextStrategy + the per-app Application.allow_public opt-in; requireAuth=true controllers still 403). A present-but-unrefreshable session still fails closed and destroys the session exactly as before.
Fail-safe against older servers: on kyte-php ≤ 4.10.x (no anonymous path) the appid-only request is rejected server-side — the same net failure as the old client-side block — so this client can ship ahead of server rollouts. Logged-in JWT flows and HMAC mode are entirely unchanged.
_jwtStoreTokens hardcoded the kyte_jwt_refresh cookie TTL to 30 days (60 * 24 * 30 minutes), regardless of what the server's refresh-token TTL actually allowed. Server defaulted to 7 days in 4.4.x; even there, the cookie outlived the server by 23 days. With kyte-php 4.5.0 dropping the server-side default to 4 hours, the mismatch became a real problem — closing the browser left a "zombie" refresh cookie that the server had long since stopped honoring.
Fix: derive the cookie TTL from response.refresh_expires_at (unix seconds, set by the server in /jwt/login and /jwt/refresh responses). Cookie now expires precisely when the server's refresh token expires. If the server omits refresh_expires_at (older deployments / unexpected response shape), fall through to a session cookie (browser-session lifetime) rather than the old 30-day hardcode — safer-by-default, the user gets logged out when they close the browser.
Regression coverage in tests/kyte.test.js:
- Cookie TTL matches
refresh_expires_at - Missing
refresh_expires_at→ session cookie - Asserts the 30-day literal (
43200minutes) never reappears + sanity bound < 1 week
No API surface change for the consuming app. Recommended pairing with kyte-php 4.5.0 (KYTE_JWT_FAMILY_MAX_LIFETIME introduces a 12-hour absolute session cap; the cookie-TTL fix here ensures the client-side cookie expires in lockstep).
2.1.0 (2026-06-11)
- anonymous fall-through for JWT-mode public access (KYTE-#229) (620d0fe)
- anonymous fall-through for JWT-mode public access (KYTE-#229) (7258a5b)
- jwt: _sessionDestroyJwt must invoke completion callback on all paths (e9213a6)
- jwt: _sessionDestroyJwt must invoke completion callback on all paths (ebb5225)
- jwt: derive refresh cookie TTL from server's refresh_expires_at (aeb0f78)
- jwt: derive refresh cookie TTL from server's refresh_expires_at (0b9363f)
_sessionDestroyJwt's success and no-refresh-token early-return paths both forgot to invoke the user-supplied callback. The HMAC equivalent (sessionDestroy proper) does invoke it on both success and error. addLogoutHandler puts location.href = '/' in that callback, so in JWT mode the redirect never fired — clicking logout cleared tokens locally and revoked them server-side, but the user stayed on the post-logout page, looking like they were still logged in.
Fix: invoke the completion callback on all three exit paths of _sessionDestroyJwt — success, error, and the !refreshToken early return. Matches the HMAC sessionDestroy contract.
Regression coverage in tests/kyte.test.js: three new tests cover each path, mocking $.ajax and asserting the callback fires + tokens are cleared.
Customer-visible impact: Shipyard 2.0.0 users on JWT mode (default) couldn't actually log out from the UI prior to 2.0.1.
KyteJS now supports both the existing HMAC sign/rotate auth and the
new JWT-bearer auth introduced by kyte-php Phase 3. The two coexist
on the same install — clients pick per-instance via the new
authMode constructor option.
-
authMode: 'hmac'(default, backward compatible) — preserves v1.x behavior exactly. Every request goes throughsign()and carries the legacyx-kyte-signature/x-kyte-identityheaders. No changes required for existing apps. -
authMode: 'jwt'(new) —sessionCreate()posts to/jwt/login, stores an access JWT plus a rotating refresh token. All subsequentget/post/put/deletecalls sendAuthorization: Bearer <jwt>instead of HMAC headers. The access token is auto-refreshed via/jwt/refreshwhenever it's withinjwtRefreshSkewSeconds(default 30s) of expiring, so callers never see a 401 due to expiry under normal conditions.
// HMAC (unchanged):
const k = new Kyte(url, accessKey, identifier, accountNum, appId);
// JWT (new):
const k = new Kyte(url, null, null, null, appId, { authMode: 'jwt' });
k.init();
k.sessionCreate({ email, password }, onSuccess, onError);
// from here all CRUD calls are JWT-authenticated transparentlyThe HMAC positional args are accepted as null in JWT mode — they
have no effect, but the signature stays compatible with v1.x
callers that already pass them.
options.authMode—'hmac'(default) or'jwt'.options.jwtRefreshSkewSeconds— refresh access token this many seconds before itsexp. Default 30. Tighter values reduce wasted refresh round-trips at the cost of more 401s under clock drift.
-
The HMAC code path is completely untouched. JWT mode is implemented as parallel
_sendDataJwt,_sessionCreateJwt,_sessionDestroyJwt,_jwtEnsureFreshAccessToken,_jwtStoreTokens,_jwtClearTokensmethods invoked only whenauthMode === 'jwt'. Existing apps get bit-identical v1.x behavior. -
Concurrent requests after access-token expiry share a single in-flight refresh (
_jwtRefreshInFlightpromise). Without this, a burst of three parallel requests on an expired token would burn three refresh tokens — the second and third would race-fail with reuse-detection on the server (which would then revoke the legitimate session). -
checkSession()in JWT mode reports active while a refresh token is held — access-token expiry is transparent and handled inline. -
sessionDestroy()in JWT mode posts to/jwt/logoutto revoke server-side, then clears local cookies. Local state is cleared even on transient logout failure. -
JWT state lives in three cookies:
kyte_jwt_access(sized to access TTL),kyte_jwt_refresh(sized to 30 days; server is source of truth),kyte_jwt_expires(unix epoch of access exp).
- No action required for existing v1.x apps —
authModedefaults to'hmac'. - New apps that want to use JWT pass
{ authMode: 'jwt' }and callsessionCreate({ email, password }, ...)exactly like before. - Shipyard's page generator will set this flag for new apps in a follow-up release.
-
CRITICAL FIX: Fixed KyteTable/KyteForm integration bug
- After KyteTable v1.3.0 rewrite, forms that updated/created records would throw errors
- Update handler used old DataTables API:
obj.selectedRow.data(response.data[0]).draw() - Create handler used old DataTables API:
obj.KyteTable.table.row.add(item).draw() - Both now correctly call
obj.KyteTable.draw()to refresh the table
-
MAJOR: KyteForm modernization with new features (100% backward compatible)
- All new features are opt-in with safe defaults
- Existing forms continue to work without any changes
-
New Constructor Properties:
showLoadingOverlay(default: true) - Show/hide loading spinner during submitloadingText- Custom text for loading spinnershowSuccessToast(default: false) - Show Bootstrap 5 toast on successful savesuccessMessage- Custom success toast messageautoCloseModal(default: true) - Auto-close modal after submitautoCloseDelay(default: 0) - Delay before auto-close (ms)resetOnSuccess(default: true) - Reset form after successful submitvalidateOnBlur(default: false) - Real-time field validation on blurshowInlineErrors(default: false) - Show error messages below invalid fieldsscrollToFirstError(default: false) - Auto-scroll to first invalid fieldtrackDirtyState(default: false) - Track unsaved changesconfirmDirtyClose(default: false) - Warn before closing with unsaved changesdisableSubmitOnProcess(default: true) - Disable submit button during processingsubmitButtonLoadingText- Custom text for submit button while processingfocusFirstField(default: true) - Auto-focus first field when modal opensdebug(default: false) - Log form events to console
-
New Event Hooks:
events.beforeInit- Called before form initializationevents.afterInit- Called after form initializationevents.beforeOpen- Called before modal opens (return false to cancel)events.afterOpen- Called after modal opens with dataevents.beforeClose- Called before modal closes (return false to cancel)events.afterClose- Called after modal closesevents.beforeSubmit- Called before form submits (return false to cancel)events.afterSubmit- Called after successful submitevents.beforeValidate- Called before validationevents.afterValidate- Called after validation with resultsevents.onFieldChange- Called when field value changesevents.onError- Called when an error occursevents.onDirtyChange- Called when dirty state changes
-
New Public Methods:
getData()- Get all form data as objectgetFieldValue(fieldName)- Get single field valuesetFieldValue(fieldName, value, triggerChange)- Set single field valuesetData(data)- Set multiple field values at onceclearForm()- Clear all form fields (alias for resetForm)isEditMode()- Check if form is in edit modeisDirty()- Check if form has unsaved changesmarkClean()- Mark form as cleanmarkDirty()- Mark form as dirtysubmit()- Programmatically submit formrefreshSelects()- Refresh AJAX select fieldsloadRecord(idx)- Load record for editingvalidateForm()- Validate form and return resultssetFieldError(fieldName, message)- Set field as invalidclearFieldError(fieldName)- Clear field errorclearValidation()- Clear all validation statesresetForm()- Reset form to initial stategetFormElement()- Get jQuery form elementgetModalElement()- Get jQuery modal elementaddHiddenField(name, value)- Add hidden field dynamicallyremoveHiddenField(name)- Remove hidden fieldsetDisabled(disabled)- Enable/disable entire formsetFieldVisible(fieldName, visible)- Show/hide specific field
-
Enhanced Methods:
showModal(idx)- Now accepts optional idx parameter and emits eventshideModal(force)- Now supports dirty check and force parameterappendErrorMessage(message, dismissable)- Now supports dismissable alerts and better error formatting
-
Internal Improvements:
- Added private helper methods for cleaner code
- Better error message formatting
- Consistent loading state management
- Improved submit button handling during processing
- MAJOR: Complete KyteTable rewrite - no longer depends on DataTables library
- Modern, self-contained table implementation with automatic style injection
- Fully backwards compatible with existing KyteTable API
- Improved performance with optimized rendering and data handling
- Modern UI design with clean, professional styling
- Smart action dropdown positioning with viewport-aware logic
- Automatically positions dropdown to stay within viewport
- Never hidden behind table footer or header
- Supports long menus with internal scrolling (max-height: 400px)
- Uses
position: fixedfor proper layering and visibility
- Enhanced features:
- Skeleton loading states with smooth animations
- Real-time search with debouncing
- Intelligent pagination with ellipsis for large page counts
- Sortable columns with visual indicators
- Responsive design for mobile/tablet
- Customizable page sizes
- Row hover effects and click handling
- Reduces external dependencies (no DataTables CSS required)
- Only requires jQuery and Font Awesome 5.x
- Approximately 400 lines of injected CSS for complete styling
- Works identically in new projects without additional stylesheets
- Update Bootstrap 4 classes to Bootstrap 5 for full compatibility
- Fix modal close button: Changed from
class="close"with×toclass="btn-close"(fixes white square issue) - Update font weight:
font-weight-bold→fw-bold - Update text alignment:
text-right→text-end - Update button sizing:
btn-small→btn-sm(corrects invalid class name)
- Fix modal close button: Changed from
- Fix issue with field name and missing quotation
- Store select value as a data attribute
- Fix issue with select
- Just assert library version
- Simply select update by triggering change
- Assert versions
- Make sure the selected option is also visually selected
- Fix issue with undeclared variable
- Allow for custom column names to be used as values for select options
- Fix issue with undeclared variable
- Fix issue where select doesn't have the correct value picked
- Fix issue with class names containing illegal characters
- Fix issue with select data not populating
- Fix bug where field name still contained
[]. - Add ability to define a custom field name if the input name doens't match db column names.
- Fix bug that prevented itemized data from an external table from loading if the external table feature is disabled or not defined in the backend.
- Enhance table row click handler to support nested property access
- Added a helper function that retrieves nested properties using dot-separated paths (e.g., "person.id")
- This update ensures robust access to deeply nested values in data objects without breaking existing functionality
- Fix bug where externalData wasn't defined
- Fix bug where custom headers were not sent for deletion from KyteTable
- Fix bug where custom headers were not sent when loading form data for updates.
- Add support for custom headers for KyteForm
- Fixes bug where
idvalue was being added as an attribute and not a value ofid
- Add
idandclassto nav and sidenav items - URL encode field name and value in URL paths for API request
- Add logic to render nav items with different styles
- Fix issue where logout button in sidenav would navigate to 404 and logout
- Add logic to render side nav item if item is a logout button
- Clean up unused variable
- Add class variable to configure session controller name
- Bypass login redirect if 403 response comes from session controller
- Update to use classes for logout handler
- Add ability to make side nav label centered and icon block
- Clean up session validation and destroy code
- Add flag to
isSessionfor disabling periodic session checks - Add flag to
isSessionfor disabling redirect behavior - Add param to
isSessionfor customizing interval for session timer - Remove JavaScript alerts for ajax errors and replace with console.error
- Improve error messages for better clarity
- Add session handling in ajax call if response is 403
- Updated the session expiration message for clarity and better user experience.
- Fixed an issue where the session expiration message would incorrectly trigger every 30 minutes. Adjusted the session monitoring logic to prevent false positives and ensure timely notifications only upon actual session expiration.
- Introduced the Kyte Web Component class, enhancing UI flexibility and interactivity. This feature allows dynamic binding of data to templates with support for custom mutator functions. It significantly streamlines the process of rendering data-driven components, such as product cards, by automatically replacing placeholders in HTML templates with actual data from JSON objects.
- Display login redirect for 403 as it conflicts with callback
- Dismiss any loaders that may be open when session expires prior to redirect
- Fix issue with api not being defined for redirect
- Fix page redirect for expired session
- Add page redirect for 403
- Refactor: Convert class methods to arrow functions for consistent 'this' context
- Update session timeout to display message and include redir in url param
- Add dismiss flag to alert to toggle dismissable alerts.
- Update to bs 5.x
- Add selected row to callback param
- Add support for maxlength for text input and text area
- Allow for custom validation function to be called before submiting form
- Expose form object to click/change callback
- Wrap DT search with form with autocomplete off to prevent chrome from autofilling
- Check if callback is a function before calling it
- Add autocomplete off for DataTables to prevent chrome from attempting to autocomplete
- Update form element callback to include selector
- Fix bug where modal would not open for edit or be dismissed
- Fix bug where field id was not generating correctly for hidden fields
- Clean up KyteForm code base
- Add support for custom field IDs for form fields
- Add support for formatting col sizes for form fields
- Add unit tests
- Option to pass custom headers to table
- Refactor KyteTable initializer
- Update utility scripts
- fix typo with object class name
- Ability to toggle whether cookie should have domain
- Add version number constant
- rename function version() to apiVersion() for retrieving endpoint version
- Add domain to cookie so subdomains can also access cookie
- Send device information (user agent) via headers
- refrac: return null if cookie doesn't exist
- refrac: allow for non-expiring cookies to be set
- Initial release