Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
39ee8bf
feat: add-recognize-proxy
ryanbas21 Apr 23, 2026
b60f46c
feat: implement recognize client and error handling in SDK
eugeniobet-ping Jun 5, 2026
e9817c8
feat: refactor recognize SDK error handling and improve type definitions
eugeniobet-ping Jun 11, 2026
0d673aa
feat: update error handling in recognize SDK to throw descriptive err…
eugeniobet-ping Jun 12, 2026
b66ab2b
feat: update RecognizeWcCompleteDetail to use interface instead of ty…
eugeniobet-ping Jun 12, 2026
7bd16ba
feat: enhance recognize tests and update vitest configuration for jsd…
eugeniobet-ping Jun 12, 2026
43da855
feat: update createRecognizeError to use Error type for cause and imp…
eugeniobet-ping Jun 12, 2026
01690ab
feat: optimize error and event handling
dariosechi-ping Jun 12, 2026
79e5c7e
fix: export RecognizeError from recognize.types so index.ts can re-ex…
eugeniobet-ping Jun 15, 2026
af2a930
chore: replace empty interfaces with type aliases and reorder Recogni…
eugeniobet-ping Jun 15, 2026
1ce8a57
feat(recognize): rename Wc types, refactor error model, update bundle…
dariosechi-ping Jun 18, 2026
6f3cc8d
chore: update ESLint config to ignore recognize-sdk directory
eugeniobet-ping Jun 26, 2026
ca4d39b
feat: updated to use sdk 3.0
eugeniobet-ping Jul 6, 2026
821537f
feat: update Keyless SDK bundle and WASM binaries to v3.0
eugeniobet-ping Jul 6, 2026
f56144a
chore(recognize): remove stale WebSocket error codes dropped in v3.0
eugeniobet-ping Jul 6, 2026
f1b5aa8
feat(recognize): update Keyless SDK bundle and WASM binaries to v3.0.1
eugeniobet-ping Jul 7, 2026
0d316df
feat(recognize): update Keyless SDK type definitions to v3.0.1
eugeniobet-ping Jul 7, 2026
3929c3d
feat(recognize): handle recoverable-error, recognition-failure, and n…
eugeniobet-ping Jul 8, 2026
84922f6
feat(recognize): update Keyless SDK bundle, add new error codes and w…
dariosechi-ping Jul 8, 2026
f510de4
test(recognize): distinguish KeylessRecoverableErrorEvent from ErrorE…
dariosechi-ping Jul 8, 2026
9c87943
feat(recognize): add recognize package
eugeniobet-ping Jul 14, 2026
3f074dc
fix (recognize): removed internal SDK license file
eugeniobet-ping Jul 22, 2026
4e3ea50
test(recognize): rename recognize.spec.ts to recognize.test.ts
eugeniobet-ping Jul 23, 2026
d6ca76a
refactor(recognize): convert RecognizeError class and RecognizeErrorC…
eugeniobet-ping Jul 23, 2026
ea83f91
refactor(recognize): separate runtime and type exports into index.ts …
eugeniobet-ping Jul 23, 2026
3954b2e
refactor(recognize): replace dispatch(type, detail) with typed event …
eugeniobet-ping Jul 23, 2026
792caa3
refactor(recognize): type CAMERA_ONLY_DISABLE_STEPS as KeylessCompone…
eugeniobet-ping Jul 23, 2026
80263df
refactor(recognize): remove RecognizeWebComponent from public types
eugeniobet-ping Jul 24, 2026
940b95c
refactor(recognize): extract setAttributes to standalone function
eugeniobet-ping Jul 24, 2026
4a89559
fix(recognize): call aborter.abort() on dispose to remove DOM event l…
eugeniobet-ping Jul 24, 2026
437bdb5
chore(recognize): add MIT LICENSE file
eugeniobet-ping Jul 24, 2026
661bc5a
fix(recognize): add missing ping copyright headers, small refactor to…
dariosechi-ping Jul 24, 2026
fb4ae5f
refactor(recognize): type theme, localization, and logger config with…
eugeniobet-ping Jul 27, 2026
8d85363
fix(recognize): remove unsafe cast in setAttributes and tighten confi…
eugeniobet-ping Jul 27, 2026
d121969
fix(recognize): update tests to match plain-object RecognizeError shape
eugeniobet-ping Jul 27, 2026
136f3d3
refactor(recognize): extract inline event handlers into named functions
eugeniobet-ping Jul 27, 2026
fc57210
fix(recognize): read err.error.* nested shape in e2e app error callback
eugeniobet-ping Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions e2e/recognize-app/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import baseConfig from '../../eslint.config.mjs';

export default [
{
ignores: [
'node_modules',
'*.md',
'LICENSE',
'.swcrc',
'.babelrc',
'.env*',
'.bin',
'dist',
'.eslintignore',
'*.html',
'*.svg',
'*.css',
'public',
'*.json',
'*.d.ts',
],
},
...baseConfig,
];
18 changes: 18 additions & 0 deletions e2e/recognize-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@forgerock/recognize-app",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "pnpm nx nxBuild",
"lint": "pnpm nx nxLint",
"preview": "pnpm nx nxPreview",
"serve": "pnpm nx nxServe"
},
"dependencies": {
"@forgerock/journey-client": "workspace:*",
"@forgerock/recognize": "workspace:*"
},
"nx": {
"tags": ["scope:e2e"]
}
}
14 changes: 14 additions & 0 deletions e2e/recognize-app/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Recognize E2E Test Index | Ping Identity JavaScript SDK</title>
</head>
<body>
<div id="app">
<h2>Recognize E2E Test Index | Ping Identity JavaScript SDK</h2>
</div>
<script type="module" src="index.ts"></script>
</body>
</html>
37 changes: 37 additions & 0 deletions e2e/recognize-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { recognize } from '@forgerock/recognize';
import './styles.css';

const client = recognize({
authorizationToken: 'USER_AUTHORIZATION_FROM_CUSTOMER',
customer: 'CUSTOMER_NAME',
key: 'IMAGE_ENCRYPTION_PUBLIC_KEY',
keyID: 'IMAGE_ENCRYPTION_KEY_ID',
transactionData: 'DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED',
wsURL: 'KEYLESS_AUTHENTICATION_SERVICE_URL',
});

client.subscribe({
next: (event) => {
console.log('[recognize]', event.type, event.detail);
},
error: (err) => {
console.error('[recognize] error', {
code: err.code,
message: err.message,
name: err.name,
cause: err.cause,
});
Comment thread
SteinGabriel marked this conversation as resolved.
},
complete: (detail) => {
console.log('[recognize] complete', detail);
},
});

const appEl = document.getElementById('app');
if (appEl) {
client
.init({ mode: 'mount', container: appEl, type: 'auth', username: 'USERNAME' })
.then((err) => {
if (err) console.error('[recognize] init error', err);
});
Comment on lines +29 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the rejected init() promise path.

Only the fulfilled path is handled. If init() rejects, this can become an unhandled rejection and destabilize/flaky-fail E2E runs.

Proposed fix
 if (appEl) {
   client
     .init({ mode: 'mount', container: appEl, type: 'auth', username: 'USERNAME' })
     .then((err) => {
       if (err) console.error('[recognize] init error', err);
-    });
+    })
+    .catch((err) => {
+      console.error('[recognize] init rejected', err);
+    });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
client
.init({ mode: 'mount', container: appEl, type: 'auth', username: 'USERNAME' })
.then((err) => {
if (err) console.error('[recognize] init error', err);
});
client
.init({ mode: 'mount', container: appEl, type: 'auth', username: 'USERNAME' })
.then((err) => {
if (err) console.error('[recognize] init error', err);
})
.catch((err) => {
console.error('[recognize] init rejected', err);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/recognize-app/src/index.ts` around lines 32 - 36, The client.init()
promise chain in the mount initialization section only handles the fulfilled
path with .then(), leaving promise rejections unhandled which can cause flaky
E2E test failures. Add a .catch() handler after the .then() block to properly
handle any rejections that occur during the init() call, ensuring errors from
rejected promises are logged and don't destabilize the test execution.

}
11 changes: 11 additions & 0 deletions e2e/recognize-app/src/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
body {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
margin: 0;
padding: 2rem;
}

#app {
max-width: 960px;
margin: 0 auto;
}
29 changes: 29 additions & 0 deletions e2e/recognize-app/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": ["node"],
"rootDir": "src",
"module": "esnext",
"moduleResolution": "bundler",
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"eslint.config.js",
"eslint.config.cjs",
"eslint.config.mjs"
],
"include": ["src/**/*.ts"],
"references": [
{
"path": "../../packages/recognize/tsconfig.lib.json"
},
{
"path": "../../packages/journey-client/tsconfig.lib.json"
}
]
}
10 changes: 10 additions & 0 deletions e2e/recognize-app/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}
33 changes: 33 additions & 0 deletions e2e/recognize-app/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { defineConfig } from 'vite';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));

export default defineConfig(() => ({
root: __dirname + '/src',
cacheDir: '../../node_modules/.vite/e2e/recognize-app',
publicDir: __dirname + '/public',
server: {
port: 8443,
host: 'localhost',
},
preview: {
port: 8443,
host: 'localhost',
},
plugins: [],
build: {
outDir: __dirname + '/dist',
emptyOutDir: true,
reportCompressedSize: true,
rollupOptions: {
input: {
main: resolve(__dirname + '/src', 'index.html'),
},
output: {
entryFileNames: '[name]/main.js',
},
},
},
}));
11 changes: 11 additions & 0 deletions packages/recognize/README.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably update this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we officially publish this, we will need to add a summary, instructions, example code, and how to find more documentation and such to this README.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# recognize

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build recognize` to build the library.

## Running unit tests

Run `nx test recognize` to execute the unit tests via [Vitest](https://vitest.dev/).
26 changes: 26 additions & 0 deletions packages/recognize/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import baseConfig from '../../eslint.config.mjs';

export default [
...baseConfig,
{
files: ['**/*.json'],
rules: {
'@nx/dependency-checks': [
'error',
{
ignoredFiles: [
'{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}',
'{projectRoot}/vite.config.{js,ts,mjs,mts}',
'{projectRoot}/src/lib/recognize-sdk/**/*',
],
},
],
},
languageOptions: {
parser: await import('jsonc-eslint-parser'),
},
},
{
ignores: ['**/out-tsc'],
},
];
45 changes: 45 additions & 0 deletions packages/recognize/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@forgerock/recognize",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/src/index.js",
"default": "./dist/src/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/src/index.js",
"module": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"dependencies": {
"tslib": "^2.3.0"
},
"devDependencies": {
"@aracna/web-components": "^1.1.18"
},
"nx": {
"tags": ["scope:package"],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "packages/recognize/dist",
"main": "packages/recognize/src/index.ts",
"tsConfig": "packages/recognize/tsconfig.lib.json",
"generatePackageJson": false,
"assets": [
{
"input": "packages/recognize/src/lib/recognize-sdk",
"glob": "**/*",
"output": "./src/lib/recognize-sdk"
}
]
}
}
}
}
}
18 changes: 18 additions & 0 deletions packages/recognize/src/index.ts

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We like to use the convention of separating runtime code and type definitions. We try to only export the runtime code in the root index.ts file, and then export all types needed for the dev through types/.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export { recognize, RecognizeError } from './lib/recognize.js';
export { RecognizeErrorCode } from './lib/defs/recognize-error-code.js';
export type {
RecognizeSessionType,
RecognizeWebComponent,
RecognizeWebComponentClient,
RecognizeWebComponentCompleteData,
RecognizeWebComponentConfiguration,
RecognizeWebComponentEvent,
RecognizeWebComponentFrameResultsEventDetail,
RecognizeWebComponentInitOptions,
RecognizeWebComponentObserver,
RecognizeWebComponentStepChangeEventDetail,
RecognizeWebComponentUnsubscribe,
RecognizeWebComponentVideoFrameQualityEventDetail,
RecognizeWebComponentWebSocketCloseEventDetail,
RecognizeWebComponentWebSocketOpenEventDetail,
} from './lib/recognize.types.js';
13 changes: 13 additions & 0 deletions packages/recognize/src/lib/classes/recognize-error.ts

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We like to treat errors within the SDK as plain objects, rather than extensions of the Error class. Could we convert this to a regular object?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { RecognizeErrorCode } from '../defs/recognize-error-code.js';

/** @public */
export class RecognizeError extends Error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we enforce functional patterns here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

code: RecognizeErrorCode;

constructor(code: RecognizeErrorCode, options?: ErrorOptions) {
super(RecognizeErrorCode[code], options);

this.code = code;
this.name = 'RecognizeError';
}
}
11 changes: 11 additions & 0 deletions packages/recognize/src/lib/defs/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @internal */
export const CAMERA_ONLY_DISABLE_STEPS: string[] = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the benefit - if it's helpful or not, but we can make this an as const array and rather than "string[]" we can have a more type safe array of these literals.

If it's possible for any string then this can be ignored, but it seems the steps here are finite.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok (I'll retrieve it from KeylessStep)

'bootstrap',
'camera-instructions',
'done',
'error',
'microphone-permission',
'server-computation',
'stm-choice',
'stm-qrcode',
];
54 changes: 54 additions & 0 deletions packages/recognize/src/lib/defs/recognize-error-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
*
* Copyright © 2026 Ping Identity Corporation. All right reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*
*/

/** @public */
export enum RecognizeErrorCode {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically we try to avoid enums because they create runtime and type level code that compiles into an iife and is not treeshaekable. They also can be considered a bad typescript practice because there are gotcha's that are not obvious to all developers.

The alternative approach would be to use a typed as const object and export the type level and object literal expression.

i.e.

// produces a runtime object and a strictly typed object
const RecognizeErrorCode = {
  SDK_ERROR: 1000
} as const

// if you want a union of allowed keys
export type RecognizeErrorCodes = keyof typeof RecognizeErrorCode

// if you want a union of allowed values
export type RecognizeErrorCodes = typeof RecognizeErrorCodes[keyof typeof RecognizeErrorCodes]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try to avoid enums in our code. I'm guessing this enum is preferred to avoid writing a structure for runtime and a duplicate structure for types, which both need to be identical. What we do is use a combination of as const, typeof keyof techniques along with unions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we enforce functional patterns here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

SDK_ERROR = 1000,
SDK_NOT_CONFIGURED = 1001,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are defining error codes here, but a lot of them seem to be unused. Are they being handled by wasm pre-compiled code?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this enum is the shared errors, it's not meant to be used within the proxy module. the end consumer of this will be actual integrator. the code is mapped from the native websdk error code to this one for usage in the callbacks.

SDK_INVALID_CONFIGURATION = 1002,
SDK_LOGGING_CONFIGURATION_FAILED = 1003,
SDK_STORAGE_FAILED = 1004,
SDK_USER_CANCELLED = 1005,
SDK_TIMEOUT = 1006,
SDK_NO_NETWORK_CONNECTION = 1007,
SDK_DYNAMIC_LINKING_PAYLOAD_MALFORMED = 1008,
SDK_ARTIFACT_RETRIEVE_FAILED = 1009,
SDK_INVALID_CLIENT_STATE = 1010,
SDK_WEB_SOCKET_ERROR = 1011,
SDK_OUTDATED_APP = 1012,
SDK_WEB_ASSEMBLY_IMPORT_FAILED = 1013,
SDK_WEB_ASSEMBLY_IMPORT_NOT_FULFILLED = 1014,
SDK_WEB_ASSEMBLY_ERROR = 1015,
SDK_INVALID_CUSTOMER_PROPERTIES = 1016,
CAMERA_ERROR = 2000,
CAMERA_NOT_FOUND = 2001,
CAMERA_PERMISSION_DENIED = 2002,
CAMERA_NOT_SUPPORTED = 2003,
CORE_ERROR = 3000,
CORE_NOT_ENOUGH_API_KEY_SEATS = 3001,
CORE_USER_ALREADY_ENROLLED = 3002,
CORE_USER_NOT_ENROLLED = 3003,
CORE_FACE_NOT_MATCHING = 3004,
CORE_NOT_ENOUGH_CIRCUITS = 3005,
CORE_SECRET_NOT_FOUND = 3006,
CORE_USER_LOCKED_OUT = 3007,
CORE_CUSTOMER_NOT_FOUND = 3008,
BIOM_ERROR = 4000,
BIOM_REJECTED = 4001,
BIOM_GENUINE_PRESENCE_NOT_ESTABLISHED = 4002,
BIOM_LIVENESS_ENVIRONMENT_AWARE_NOT_SUPPORTED = 4003,
BIOM_DEVICE_ENVIRONMENT_AWARE_NOT_SUPPORTED = 4004,
SERVER_ERROR = 5000,
SERVER_ENCRYPTION_FAILED = 5001,
SERVER_RECOGNITION_FAILED = 5002,
SERVER_AUTHORIZATION_FAILED = 5003,
SERVER_TIMEOUT = 5004,
SECURITY_ERROR = 6000,
SECURITY_DEVICE_NOT_GENUINE = 6001,
}
Loading
Loading