@jx_code/rsimzo-client
v0.0.68
Published
A typed JavaScript/TypeScript client for the [RS-IMZO](https://rs-imzo.uz) digital signature service. Opens a secure popup for authentication, signing, and certificate retrieval — all communication happens via `postMessage` with strict origin validation.
Readme
@jx_code/rsimzo-client
A typed JavaScript/TypeScript client for the RS-IMZO digital signature service. Opens a secure popup for authentication, signing, and certificate retrieval — all communication happens via postMessage with strict origin validation.
Installation
# npm
npm install @jx_code/rsimzo-client
# pnpm
pnpm add @jx_code/rsimzo-client
# yarn
yarn add @jx_code/rsimzo-clientTesting
A staging environment is available at https://stage.rs-imzo.uz where you can create and test EDS (electronic digital signature) keys without affecting production data. Use this server during development by setting targetOrigin:
const client = new RsimzoClient({
publicKey: 'your-public-key',
targetOrigin: 'https://stage.rs-imzo.uz',
})Quick start
import { RsimzoClient } from '@jx_code/rsimzo-client'
const client = new RsimzoClient({ publicKey: 'your-public-key' })
// Fetch certificates
const { data, error } = await client.fetchCertificates()
if (error) {
console.error(error.errorMessage)
} else {
console.log('Certificates:', data)
}Constructor
new RsimzoClient(options: RsOptions)RsOptions
| Property | Type | Required | Default | Description |
|----------|------|----------|---------|-------------|
| publicKey | string | Yes | — | Your RS-IMZO public key |
| locale | Locale | No | 'uz' | UI language of the popup |
| targetOrigin | string | No | 'https://rs-imzo.uz' | Origin of the RS-IMZO service |
| storage | RsStorageInput | No | undefined | Persist certificates after fetchCertificates(). See Certificate storage |
const client = new RsimzoClient({
publicKey: 'your-public-key',
locale: 'ru',
storage: true,
})Methods
fetchCertificates(options?)
Opens a popup to retrieve the user's available certificates. If storage is configured, persists the result automatically.
client.fetchCertificates(options?: RsCertificatesOptions): Promise<RsPostMessageResult<RsSignatureInfo[] | null>>const { data, error } = await client.fetchCertificates()
if (error) {
console.error(`[${error.errorCode}] ${error.errorMessage}`)
} else {
console.log('Certificates:', data)
}getCertificates()
Reads persisted certificates from the configured storage synchronously. Returns an empty array if storage is not configured, nothing is stored, or the stored data is corrupt.
client.getCertificates(): RsSignatureInfo[]const certs = client.getCertificates()
// always an array, never nullremoveCertificate(serial)
Removes a single certificate from storage by serial number.
client.removeCertificate(serial: string): voidremoveAllCertificates()
Clears all persisted certificates from storage.
client.removeAllCertificates(): voidsign(serialNumber?, content?, options?)
Opens a popup to sign content or authenticate with a certificate. The mode option switches between the two operations.
client.sign(
serialNumber?: string,
content?: string,
options?: RsSignOptions
): Promise<RsPostMessageResult<string | null>>| Parameter | Type | Description |
|-----------|------|-------------|
| serialNumber | string | Certificate serial number from RsSignatureInfo.serial. Omit to let the popup handle selection |
| content | string | The data to sign (Base64 encoded). Omit for auth mode |
| options.locale | Locale | Override locale for this call |
| options.mode | 'sign' \| 'auth' | 'sign' (default) for signing, 'auth' for authentication |
| options.filter | RsCertificateFilter | Pre-filter which certificates are shown in the popup |
// Sign a document
const { data, error } = await client.sign(cert.serial, btoa('content to sign'))
if (error) {
console.error(`[${error.errorCode}] ${error.errorMessage}`)
} else {
console.log('Signature:', data)
}
// Authenticate
const { data, error } = await client.sign(cert.serial, undefined, { mode: 'auth' })
// Sign without pre-selecting a certificate (popup handles selection)
const { data, error } = await client.sign(undefined, btoa('content'))Certificate filters
fetchCertificates() and sign() both accept a filter option to restrict which certificates are shown in the popup.
RsCertificateFilter
| Property | Type | Description |
|----------|------|-------------|
| serial | string[] | Show only certificates with these serial numbers |
| pin | string[] | Show only certificates matching these PINs |
| tin | string[] | Show only certificates matching these TINs |
| isLegalEntity | boolean | Filter by legal entity status |
| isIndividual | boolean | Filter by individual status |
// Fetch only legal entity certificates
await client.fetchCertificates({
filter: { isLegalEntity: true }
})
// Sign with a specific set of allowed serials
await client.sign(undefined, btoa(content), {
filter: { serial: ['ABC123', 'DEF456'] }
})
// Filter by TIN
await client.fetchCertificates({
filter: { tin: ['123456789'] }
})Certificate storage
Pass storage in the constructor to automatically persist certificates after each successful fetchCertificates() call.
Shorthand
// localStorage, key: 'rsimzo_certificates'
const client = new RsimzoClient({ publicKey: '...', storage: true })
// disabled (default)
const client = new RsimzoClient({ publicKey: '...' })Full config (RsStorage)
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| type | StorageType | 'localStorage' | Where to persist: 'localStorage', 'sessionStorage', or 'cookies' |
| key | string | 'rsimzo_certificates' | Storage key / cookie name |
| cookie | RsCookieOptions | {} | Cookie-specific options. Only used when type is 'cookies' |
// sessionStorage
const client = new RsimzoClient({
publicKey: '...',
storage: { type: 'sessionStorage', key: 'my_certs' },
})
// Cookie with expiry
const client = new RsimzoClient({
publicKey: '...',
storage: {
type: 'cookies',
key: 'my_certs',
cookie: {
expires: 7, // days; omit for session cookie
path: '/',
secure: true,
sameSite: 'Lax',
},
},
})RsCookieOptions
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| expires | number \| Date | session | Days until expiry (number) or an absolute Date |
| path | string | '/' | Cookie path |
| domain | string | — | Cookie domain |
| secure | boolean | false | Adds the Secure flag |
| sameSite | 'Strict' \| 'Lax' \| 'None' | — | SameSite policy |
Result type
All async methods return RsPostMessageResult<T> — a discriminated union:
type RsPostMessageResult<T> =
| { data: T; error: null }
| { data: null; error: RsPostMessageError }Because it is discriminated, TypeScript narrows the type after checking error:
const result = await client.fetchCertificates()
if (result.error) {
// result.data is null, result.error is RsPostMessageError
console.error(result.error.errorMessage)
} else {
// result.data is RsSignatureInfo[], result.error is null
console.log(result.data)
}RsPostMessageError
| Property | Type | Description |
|----------|------|-------------|
| errorCode | number | Numeric error code (see table below) |
| errorMessage | string | Human-readable description |
| rawError | unknown | Original error from the service, if any |
Error codes
| Code | Description |
|------|-------------|
| 13 | Incorrect password |
| 10001 | User closed the popup |
| 10002 | Failed to fetch token |
| 10003 | Operation completed with no result |
| 10004 | Certificate not found |
| 10005 | Failed to validate client origin |
| 10006 | Invalid parameters received by popup |
| 10007 | Failed to fetch timestamp |
| 10008 | Operation timed out (60 s) |
| 10009 | Popup blocked by the browser |
| 10010 | Another operation is already in progress |
Types reference
RsSignatureInfo
| Property | Type | Description |
|----------|------|-------------|
| uid | string | Unique identifier |
| serial | string | Certificate serial number — pass to sign() |
| label | string | Display label |
| fullName | string | Certificate holder full name |
| createdAt | number | Issue timestamp (Unix seconds) |
| expireAt | number | Expiry timestamp (Unix seconds) |
| country | string | Country code |
| orgName | string? | Organisation name (legal entities) |
| pin | string? | Personal identification number |
| tin | string? | Tax identification number |
| isLegalEntity | boolean | Whether the certificate belongs to a legal entity |
| isExpired | boolean | Whether the certificate is past its expiry date |
Locale
type Locale = 'uz' | 'ru' | 'en'StorageType
type StorageType = 'localStorage' | 'sessionStorage' | 'cookies'Concurrency
RsimzoClient allows only one popup operation at a time. Calling sign() or fetchCertificates() while another is already in progress immediately returns an error with code 10010.
Vue 3 example
import { ref, onMounted } from 'vue'
import { RsimzoClient } from '@jx_code/rsimzo-client'
import type { RsSignatureInfo } from '@jx_code/rsimzo-client'
const client = new RsimzoClient({
publicKey: import.meta.env.VITE_RS_IMZO_PUBLIC_KEY,
targetOrigin: import.meta.env.VITE_RS_IMZO_TARGET_ORIGIN,
storage: true,
})
const certificates = ref<RsSignatureInfo[]>([])
const loading = ref(false)
onMounted(() => {
certificates.value = client.getCertificates()
})
async function loadCertificates() {
loading.value = true
const result = await client.fetchCertificates()
loading.value = false
if (!result.error) {
certificates.value = result.data ?? []
}
}
async function signDocument(serial: string, content: string) {
const result = await client.sign(serial, btoa(content))
if (!result.error) {
console.log('Signature:', result.data)
}
}Browser requirements
The client uses window.open() to open a popup and window.postMessage for secure cross-origin communication. Ensure your deployment does not block popups — inform users to allow popups for your domain if the browser blocks them (error code 10009).
