@oxyhq/core 15.0.1 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/OxyServices.errors.js +33 -1
- package/dist/cjs/OxyServices.js +2 -1
- package/dist/cjs/index.js +8 -5
- package/dist/cjs/mixins/OxyServices.assets.js +31 -8
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +5 -5
- package/dist/cjs/utils/accountUtils.js +6 -1
- package/dist/cjs/utils/displayNamePolicyRanges.generated.js +28 -3
- package/dist/cjs/utils/validationUtils.js +110 -21
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/OxyServices.errors.js +31 -0
- package/dist/esm/OxyServices.js +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/mixins/OxyServices.assets.js +32 -9
- package/dist/esm/mixins/OxyServices.deviceBoot.js +5 -5
- package/dist/esm/utils/accountUtils.js +6 -1
- package/dist/esm/utils/displayNamePolicyRanges.generated.js +27 -2
- package/dist/esm/utils/validationUtils.js +110 -21
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.d.ts +2 -2
- package/dist/types/OxyServices.errors.d.ts +33 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/mixins/OxyServices.assets.d.ts +17 -5
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +5 -5
- package/dist/types/utils/displayNamePolicyRanges.generated.d.ts +27 -2
- package/dist/types/utils/validationUtils.d.ts +104 -20
- package/package.json +2 -2
- package/src/OxyServices.errors.ts +45 -0
- package/src/OxyServices.ts +2 -2
- package/src/index.ts +3 -1
- package/src/mixins/OxyServices.assets.ts +36 -9
- package/src/mixins/OxyServices.deviceBoot.ts +5 -5
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +52 -3
- package/src/utils/__tests__/accountUtils.test.ts +22 -0
- package/src/utils/__tests__/coldBoot.test.ts +9 -4
- package/src/utils/__tests__/validationUtils.test.ts +292 -1
- package/src/utils/accountUtils.ts +9 -1
- package/src/utils/displayNamePolicyRanges.generated.ts +31 -2
- package/src/utils/validationUtils.ts +113 -20
|
@@ -49,6 +49,37 @@ export class AssetUrlResolutionError extends Error {
|
|
|
49
49
|
this.cause = cause;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Thrown when one or more chunks of `getServiceAssetMetadataByIds` could not be
|
|
54
|
+
* resolved.
|
|
55
|
+
*
|
|
56
|
+
* Exists because for this endpoint a FAILED request and an ABSENT asset produce
|
|
57
|
+
* the same observable result. The server legitimately omits unknown/deleted ids,
|
|
58
|
+
* so callers are documented to map the response by `id` and treat a missing
|
|
59
|
+
* entry as "no such asset" — which means a chunk that 429s, times out or 5xxs
|
|
60
|
+
* reads as authoritative absence unless it is raised.
|
|
61
|
+
*
|
|
62
|
+
* That was not hypothetical: a metadata backfill counted every throttled asset
|
|
63
|
+
* as needing no update and exited 0, and the MTN signed-record builder embedded
|
|
64
|
+
* media with no content hash into records that are immutable once signed. Both
|
|
65
|
+
* paths reported success and wrote nothing.
|
|
66
|
+
*
|
|
67
|
+
* `unresolvedIds` carries every id in a failed chunk — not the subset the server
|
|
68
|
+
* would have omitted anyway, which is unknowable when the request never landed.
|
|
69
|
+
* A caller that wants best-effort passes `{ partial: true }` and never sees this.
|
|
70
|
+
*/
|
|
71
|
+
export class ServiceAssetMetadataError extends Error {
|
|
72
|
+
constructor(unresolvedIds, statuses, cause) {
|
|
73
|
+
const uniqueStatuses = Array.from(new Set(statuses)).sort((a, b) => a - b);
|
|
74
|
+
const statusSuffix = uniqueStatuses.length > 0 ? ` — status ${uniqueStatuses.join(', ')}` : '';
|
|
75
|
+
super(`Could not resolve asset metadata for ${unresolvedIds.length} id(s)${statusSuffix}. Treat this as unknown, not as absent; pass { partial: true } for best-effort.`);
|
|
76
|
+
this.code = 'SERVICE_ASSET_METADATA_UNRESOLVED';
|
|
77
|
+
this.name = 'ServiceAssetMetadataError';
|
|
78
|
+
this.unresolvedIds = unresolvedIds;
|
|
79
|
+
this.statuses = uniqueStatuses;
|
|
80
|
+
this.cause = cause;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
52
83
|
export class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
|
|
53
84
|
constructor(operationName, timeoutMs) {
|
|
54
85
|
super(`Authentication timeout (${timeoutMs}ms): ${operationName} requires user authentication. Please ensure the user is logged in before calling this method.`, 'AUTH_TIMEOUT', 408);
|
package/dist/esm/OxyServices.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors.js';
|
|
1
|
+
import { AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeoutError, ServiceAssetMetadataError } from './OxyServices.errors.js';
|
|
2
2
|
// Import mixin composition helper
|
|
3
3
|
import { composeOxyServices } from './mixins/index.js';
|
|
4
4
|
/**
|
|
@@ -50,7 +50,7 @@ export class OxyServices extends OxyServicesComposed {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
// Re-export error classes for convenience
|
|
53
|
-
export { AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeoutError };
|
|
53
|
+
export { AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeoutError, ServiceAssetMetadataError };
|
|
54
54
|
/**
|
|
55
55
|
* Default Oxy Cloud URL — used when no `cloudURL` is provided to OxyServices.
|
|
56
56
|
*/
|
package/dist/esm/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import './crypto/polyfill.js';
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
// API client
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
|
-
export { OxyServices, AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.js';
|
|
24
|
+
export { OxyServices, AssetUrlResolutionError, OxyAuthenticationError, OxyAuthenticationTimeoutError, ServiceAssetMetadataError } from './OxyServices.js';
|
|
25
25
|
export { OXY_CLOUD_URL, oxyClient } from './OxyServices.js';
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
// Authentication
|
|
@@ -119,7 +119,7 @@ export { retryAsync } from './utils/asyncUtils.js';
|
|
|
119
119
|
// ---------------------------------------------------------------------------
|
|
120
120
|
// Validation
|
|
121
121
|
// ---------------------------------------------------------------------------
|
|
122
|
-
export { EMAIL_REGEX, USERNAME_REGEX, PASSWORD_REGEX, isValidEmail, isValidUsername, isValidPassword, isValidDisplayName, DISPLAY_NAME_ALLOWED_SCRIPTS, DISPLAY_NAME_DISALLOWED_SOURCE, DISPLAY_NAME_ORPHANED_MARK_SOURCE, isRequiredString, isRequiredNumber, isRequiredBoolean, isValidArray, isValidObject, isValidUUID, isValidURL, isValidDate, isValidFileSize, isValidFileType, sanitizeString, sanitizeHTML, isValidObjectId, validateAndSanitizeUserInput, } from './utils/validationUtils.js';
|
|
122
|
+
export { EMAIL_REGEX, USERNAME_REGEX, PASSWORD_REGEX, MAX_DISPLAY_NAME_LENGTH, isValidEmail, isValidUsername, isValidPassword, isValidDisplayName, DISPLAY_NAME_ALLOWED_SCRIPTS, DISPLAY_NAME_DISALLOWED_SOURCE, DISPLAY_NAME_ORPHANED_MARK_SOURCE, DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE, isRequiredString, isRequiredNumber, isRequiredBoolean, isValidArray, isValidObject, isValidUUID, isValidURL, isValidDate, isValidFileSize, isValidFileType, sanitizeString, sanitizeHTML, isValidObjectId, validateAndSanitizeUserInput, } from './utils/validationUtils.js';
|
|
123
123
|
// ---------------------------------------------------------------------------
|
|
124
124
|
// Text normalization
|
|
125
125
|
// ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isReactNative } from '@oxyhq/protocol';
|
|
2
2
|
import { logger } from '../logger/index.js';
|
|
3
|
-
import { AssetUrlResolutionError } from '../OxyServices.errors.js';
|
|
3
|
+
import { AssetUrlResolutionError, ServiceAssetMetadataError } from '../OxyServices.errors.js';
|
|
4
4
|
import { extractErrorStatus } from '../utils/errorUtils.js';
|
|
5
5
|
/**
|
|
6
6
|
* Maximum number of ids sent per `POST /assets/service/by-ids` request. Matches
|
|
@@ -300,15 +300,25 @@ export function OxyServicesAssetsMixin(Base) {
|
|
|
300
300
|
* throws because no credentials are available. A plain user-session request
|
|
301
301
|
* is rejected by the route's service-auth guard.
|
|
302
302
|
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
303
|
+
* FAILURE IS NOT ABSENCE. The server legitimately omits unknown/deleted ids,
|
|
304
|
+
* so a short result is normal — which means a chunk that FAILED (a 429, a
|
|
305
|
+
* timeout, a 5xx) is indistinguishable from "those assets don't exist" if it
|
|
306
|
+
* simply contributes nothing. This method used to swallow a failed chunk and
|
|
307
|
+
* return the rest, so every caller silently read a throttled request as "no
|
|
308
|
+
* metadata": a backfill counted the asset as needing no update, and the
|
|
309
|
+
* signed-record builder embedded a media item with no hash. Both reported
|
|
310
|
+
* success while writing nothing, and the MTN chain's records are immutable.
|
|
311
|
+
*
|
|
312
|
+
* So a failed chunk THROWS {@link ServiceAssetMetadataError} by default,
|
|
313
|
+
* carrying the ids it could not resolve. A caller that genuinely wants
|
|
314
|
+
* best-effort opts in with `{ partial: true }` and gets the old behaviour
|
|
315
|
+
* explicitly. An empty/whitespace-only input resolves immediately with `[]`
|
|
316
|
+
* and performs no network call.
|
|
307
317
|
*
|
|
308
318
|
* Not cached at the SDK layer: it's a POST keyed on a multi-id body (low hit
|
|
309
319
|
* rate), mirroring the sibling service/POST methods which never cache.
|
|
310
320
|
*/
|
|
311
|
-
async getServiceAssetMetadataByIds(ids) {
|
|
321
|
+
async getServiceAssetMetadataByIds(ids, options = {}) {
|
|
312
322
|
const uniqueIds = Array.from(new Set(ids.filter((id) => typeof id === 'string' && id.trim().length > 0)));
|
|
313
323
|
if (uniqueIds.length === 0) {
|
|
314
324
|
return [];
|
|
@@ -317,22 +327,35 @@ export function OxyServicesAssetsMixin(Base) {
|
|
|
317
327
|
for (let i = 0; i < uniqueIds.length; i += SERVICE_ASSET_METADATA_CHUNK_SIZE) {
|
|
318
328
|
chunks.push(uniqueIds.slice(i, i + SERVICE_ASSET_METADATA_CHUNK_SIZE));
|
|
319
329
|
}
|
|
320
|
-
//
|
|
330
|
+
// Chunks stay independent so one failure never cancels work already in
|
|
331
|
+
// flight; the failures are collected and re-raised together below.
|
|
332
|
+
const unresolvedIds = [];
|
|
333
|
+
const statuses = [];
|
|
334
|
+
let firstError;
|
|
321
335
|
const settled = await Promise.all(chunks.map(async (chunk) => {
|
|
322
336
|
try {
|
|
323
337
|
const entries = await this.makeServiceRequest('POST', '/assets/service/by-ids', { ids: chunk });
|
|
324
338
|
return Array.isArray(entries) ? entries : [];
|
|
325
339
|
}
|
|
326
340
|
catch (error) {
|
|
327
|
-
|
|
341
|
+
const status = extractErrorStatus(error);
|
|
342
|
+
logger.warn('getServiceAssetMetadataByIds: chunk failed', {
|
|
328
343
|
method: 'getServiceAssetMetadataByIds',
|
|
329
344
|
chunkSize: chunk.length,
|
|
330
|
-
status
|
|
345
|
+
status,
|
|
346
|
+
partial: options.partial === true,
|
|
331
347
|
error: error instanceof Error ? error.message : String(error),
|
|
332
348
|
});
|
|
349
|
+
unresolvedIds.push(...chunk);
|
|
350
|
+
if (typeof status === 'number')
|
|
351
|
+
statuses.push(status);
|
|
352
|
+
firstError ?? (firstError = error);
|
|
333
353
|
return [];
|
|
334
354
|
}
|
|
335
355
|
}));
|
|
356
|
+
if (unresolvedIds.length > 0 && options.partial !== true) {
|
|
357
|
+
throw new ServiceAssetMetadataError(unresolvedIds, statuses, firstError);
|
|
358
|
+
}
|
|
336
359
|
return settled.flat();
|
|
337
360
|
}
|
|
338
361
|
/**
|
|
@@ -151,11 +151,11 @@ export function OxyServicesDeviceBootMixin(Base) {
|
|
|
151
151
|
*
|
|
152
152
|
* That paragraph is LOAD-BEARING, not belt-and-braces: the route sits above
|
|
153
153
|
* oxy-api's router-wide origin guard (deliberately, so a native client with
|
|
154
|
-
* no `Origin` is not rejected)
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
154
|
+
* no `Origin` is not rejected). oxy-api additionally refuses callers that
|
|
155
|
+
* carry browser context signals (`Origin` or `Sec-Fetch-Site`) with
|
|
156
|
+
* `403 browser_not_allowed` — native HTTP clients send neither. Gate the
|
|
157
|
+
* caller by platform on the client as well; do not widen the 404 degrade to
|
|
158
|
+
* 403, which would also swallow a genuine origin misconfiguration.
|
|
159
159
|
*
|
|
160
160
|
* @returns the provisioned credential, or `null` when the endpoint is absent
|
|
161
161
|
* (404). The API deploy leads the SDK release, so a client on a newer SDK
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Used by the @oxyhq/services account stores (Expo/RN and RN-Web).
|
|
4
4
|
*/
|
|
5
5
|
import { translate } from '../i18n/index.js';
|
|
6
|
+
import { getNormalizedUserHandle } from './userHandle.js';
|
|
6
7
|
/**
|
|
7
8
|
* Truncate a long public key for display, e.g. `0x12345678…`.
|
|
8
9
|
* Falls back to the raw key if it's too short to truncate.
|
|
@@ -96,7 +97,11 @@ export const buildAccountsArray = (accounts, order) => {
|
|
|
96
97
|
* @param getFileDownloadUrl - Function to generate avatar download URL from file ID
|
|
97
98
|
*/
|
|
98
99
|
export const createQuickAccount = (sessionId, userData, existingAccount, getFileDownloadUrl) => {
|
|
99
|
-
const
|
|
100
|
+
const nameObj = userData.name && typeof userData.name === 'object' ? userData.name : undefined;
|
|
101
|
+
const apiDisplayName = typeof nameObj?.displayName === 'string' ? nameObj.displayName.trim() : '';
|
|
102
|
+
const displayName = apiDisplayName ||
|
|
103
|
+
getNormalizedUserHandle(userData) ||
|
|
104
|
+
getAccountDisplayName(null);
|
|
100
105
|
const userId = userData.id || (typeof userData._id === 'string' ? userData._id : userData._id?.toString());
|
|
101
106
|
// Preserve existing avatarUrl if avatar hasn't changed (prevents image reload)
|
|
102
107
|
let avatarUrl;
|
|
@@ -17,7 +17,30 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Classes captured (regexpu-core, u-mode):
|
|
19
19
|
* - DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES: the 30-script Script_Extensions
|
|
20
|
-
* allowlist (scx=Latin, scx=Greek, … scx=Han)
|
|
20
|
+
* allowlist (scx=Latin, scx=Greek, … scx=Han) INTERSECTED with
|
|
21
|
+
* General_Category L, MINUS an explicit code-point denylist. The
|
|
22
|
+
* intersection is load-bearing: `scx=X` also carries script X's digits,
|
|
23
|
+
* punctuation and symbols, so without it the class admitted 1831 non-letter
|
|
24
|
+
* code points — script digits, 1082 symbols, and 9 invisible format/control
|
|
25
|
+
* characters including the U+061C bidi control. The generator fails if any
|
|
26
|
+
* non-letter survives. The denylist covers the opposite case: code points
|
|
27
|
+
* that ARE letters of an allowlisted script yet function as hate symbols
|
|
28
|
+
* (卐 卍) — no script-level or category-level rule can exclude
|
|
29
|
+
* them without also rejecting every real Chinese, Japanese and Korean name.
|
|
30
|
+
* - DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES: that denylist on its own. NOT
|
|
31
|
+
* used to build any runtime regex — the code points are already subtracted
|
|
32
|
+
* from the allowlist above, so the policy enforces them with no extra probe.
|
|
33
|
+
* It is emitted so tests can enumerate what is denied and assert each entry
|
|
34
|
+
* is actually rejected.
|
|
35
|
+
* - DISPLAY_NAME_NAME_SEPARATORS_RANGES: the four punctuation code points that
|
|
36
|
+
* JOIN two letters inside one real name
|
|
37
|
+
* (· ־ ་ ・). All are General_Category P,
|
|
38
|
+
* so the letters intersection strips them by default; they are re-admitted
|
|
39
|
+
* ONLY between two letters. Unlike the denylist, this class DOES build a
|
|
40
|
+
* runtime regex — the conditional half lives in
|
|
41
|
+
* `DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE`, which strips any of them that
|
|
42
|
+
* is not letter-flanked on both sides, so decorative use
|
|
43
|
+
* (a trailing `Roberto ·`) keeps being trimmed.
|
|
21
44
|
* - DISPLAY_NAME_COMBINING_MARKS_RANGES: General_Category M (combining marks).
|
|
22
45
|
* - DISPLAY_NAME_SPACE_SEPARATORS_RANGES: General_Category Zs (space
|
|
23
46
|
* separators).
|
|
@@ -26,7 +49,9 @@
|
|
|
26
49
|
*
|
|
27
50
|
* REGENERATE: cd packages/core && bun run generate:display-name-policy
|
|
28
51
|
*/
|
|
29
|
-
export const DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES = 'A-Za-z\\xAA\\
|
|
52
|
+
export const DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES = 'A-Za-z\\xAA\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02B8\\u02BC\\u02C7\\u02C9-\\u02CB\\u02CD\\u02E0-\\u02E4\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03E1\\u03F0-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0750-\\u07A5\\u07B1\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C\\u0C5D\\u0C60\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2126\\u212A\\u212B\\u2132\\u214E\\u2183\\u2184\\u2C60-\\u2C7F\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\u534C\\u534E\\u534F\\u5351-\\u9FFF\\uA640-\\uA66E\\uA67F-\\uA69D\\uA722-\\uA787\\uA78B-\\uA7DC\\uA7F1-\\uA7FF\\uA8F2-\\uA8F7\\uA8FB\\uA8FD\\uA8FE\\uA960-\\uA97C\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA60-\\uAA76\\uAA7A\\uAA7E\\uAA7F\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABBF\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10EC2}-\\u{10EC7}\\u{16FE3}\\u{16FF2}\\u{16FF3}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}';
|
|
30
53
|
export const DISPLAY_NAME_COMBINING_MARKS_RANGES = '\\u0300-\\u036F\\u0483-\\u0489\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u07FD\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0859-\\u085B\\u0897-\\u089F\\u08CA-\\u08E1\\u08E3-\\u0903\\u093A-\\u093C\\u093E-\\u094F\\u0951-\\u0957\\u0962\\u0963\\u0981-\\u0983\\u09BC\\u09BE-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CD\\u09D7\\u09E2\\u09E3\\u09FE\\u0A01-\\u0A03\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81-\\u0A83\\u0ABC\\u0ABE-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AE2\\u0AE3\\u0AFA-\\u0AFF\\u0B01-\\u0B03\\u0B3C\\u0B3E-\\u0B44\\u0B47\\u0B48\\u0B4B-\\u0B4D\\u0B55-\\u0B57\\u0B62\\u0B63\\u0B82\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD7\\u0C00-\\u0C04\\u0C3C\\u0C3E-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0C81-\\u0C83\\u0CBC\\u0CBE-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0CE2\\u0CE3\\u0CF3\\u0D00-\\u0D03\\u0D3B\\u0D3C\\u0D3E-\\u0D44\\u0D46-\\u0D48\\u0D4A-\\u0D4D\\u0D57\\u0D62\\u0D63\\u0D81-\\u0D83\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EBC\\u0EC8-\\u0ECE\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F3E\\u0F3F\\u0F71-\\u0F84\\u0F86\\u0F87\\u0F8D-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102B-\\u103E\\u1056-\\u1059\\u105E-\\u1060\\u1062-\\u1064\\u1067-\\u106D\\u1071-\\u1074\\u1082-\\u108D\\u108F\\u109A-\\u109D\\u135D-\\u135F\\u1712-\\u1715\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B4-\\u17D3\\u17DD\\u180B-\\u180D\\u180F\\u1885\\u1886\\u18A9\\u1920-\\u192B\\u1930-\\u193B\\u1A17-\\u1A1B\\u1A55-\\u1A5E\\u1A60-\\u1A7C\\u1A7F\\u1AB0-\\u1ADD\\u1AE0-\\u1AEB\\u1B00-\\u1B04\\u1B34-\\u1B44\\u1B6B-\\u1B73\\u1B80-\\u1B82\\u1BA1-\\u1BAD\\u1BE6-\\u1BF3\\u1C24-\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE8\\u1CED\\u1CF4\\u1CF7-\\u1CF9\\u1DC0-\\u1DFF\\u20D0-\\u20F0\\u2CEF-\\u2CF1\\u2D7F\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F-\\uA672\\uA674-\\uA67D\\uA69E\\uA69F\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA823-\\uA827\\uA82C\\uA880\\uA881\\uA8B4-\\uA8C5\\uA8E0-\\uA8F1\\uA8FF\\uA926-\\uA92D\\uA947-\\uA953\\uA980-\\uA983\\uA9B3-\\uA9C0\\uA9E5\\uAA29-\\uAA36\\uAA43\\uAA4C\\uAA4D\\uAA7B-\\uAA7D\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uAAEB-\\uAAEF\\uAAF5\\uAAF6\\uABE3-\\uABEA\\uABEC\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE2F\\u{101FD}\\u{102E0}\\u{10376}-\\u{1037A}\\u{10A01}-\\u{10A03}\\u{10A05}\\u{10A06}\\u{10A0C}-\\u{10A0F}\\u{10A38}-\\u{10A3A}\\u{10A3F}\\u{10AE5}\\u{10AE6}\\u{10D24}-\\u{10D27}\\u{10D69}-\\u{10D6D}\\u{10EAB}\\u{10EAC}\\u{10EFA}-\\u{10EFF}\\u{10F46}-\\u{10F50}\\u{10F82}-\\u{10F85}\\u{11000}-\\u{11002}\\u{11038}-\\u{11046}\\u{11070}\\u{11073}\\u{11074}\\u{1107F}-\\u{11082}\\u{110B0}-\\u{110BA}\\u{110C2}\\u{11100}-\\u{11102}\\u{11127}-\\u{11134}\\u{11145}\\u{11146}\\u{11173}\\u{11180}-\\u{11182}\\u{111B3}-\\u{111C0}\\u{111C9}-\\u{111CC}\\u{111CE}\\u{111CF}\\u{1122C}-\\u{11237}\\u{1123E}\\u{11241}\\u{112DF}-\\u{112EA}\\u{11300}-\\u{11303}\\u{1133B}\\u{1133C}\\u{1133E}-\\u{11344}\\u{11347}\\u{11348}\\u{1134B}-\\u{1134D}\\u{11357}\\u{11362}\\u{11363}\\u{11366}-\\u{1136C}\\u{11370}-\\u{11374}\\u{113B8}-\\u{113C0}\\u{113C2}\\u{113C5}\\u{113C7}-\\u{113CA}\\u{113CC}-\\u{113D0}\\u{113D2}\\u{113E1}\\u{113E2}\\u{11435}-\\u{11446}\\u{1145E}\\u{114B0}-\\u{114C3}\\u{115AF}-\\u{115B5}\\u{115B8}-\\u{115C0}\\u{115DC}\\u{115DD}\\u{11630}-\\u{11640}\\u{116AB}-\\u{116B7}\\u{1171D}-\\u{1172B}\\u{1182C}-\\u{1183A}\\u{11930}-\\u{11935}\\u{11937}\\u{11938}\\u{1193B}-\\u{1193E}\\u{11940}\\u{11942}\\u{11943}\\u{119D1}-\\u{119D7}\\u{119DA}-\\u{119E0}\\u{119E4}\\u{11A01}-\\u{11A0A}\\u{11A33}-\\u{11A39}\\u{11A3B}-\\u{11A3E}\\u{11A47}\\u{11A51}-\\u{11A5B}\\u{11A8A}-\\u{11A99}\\u{11B60}-\\u{11B67}\\u{11C2F}-\\u{11C36}\\u{11C38}-\\u{11C3F}\\u{11C92}-\\u{11CA7}\\u{11CA9}-\\u{11CB6}\\u{11D31}-\\u{11D36}\\u{11D3A}\\u{11D3C}\\u{11D3D}\\u{11D3F}-\\u{11D45}\\u{11D47}\\u{11D8A}-\\u{11D8E}\\u{11D90}\\u{11D91}\\u{11D93}-\\u{11D97}\\u{11EF3}-\\u{11EF6}\\u{11F00}\\u{11F01}\\u{11F03}\\u{11F34}-\\u{11F3A}\\u{11F3E}-\\u{11F42}\\u{11F5A}\\u{13440}\\u{13447}-\\u{13455}\\u{1611E}-\\u{1612F}\\u{16AF0}-\\u{16AF4}\\u{16B30}-\\u{16B36}\\u{16F4F}\\u{16F51}-\\u{16F87}\\u{16F8F}-\\u{16F92}\\u{16FE4}\\u{16FF0}\\u{16FF1}\\u{1BC9D}\\u{1BC9E}\\u{1CF00}-\\u{1CF2D}\\u{1CF30}-\\u{1CF46}\\u{1D165}-\\u{1D169}\\u{1D16D}-\\u{1D172}\\u{1D17B}-\\u{1D182}\\u{1D185}-\\u{1D18B}\\u{1D1AA}-\\u{1D1AD}\\u{1D242}-\\u{1D244}\\u{1DA00}-\\u{1DA36}\\u{1DA3B}-\\u{1DA6C}\\u{1DA75}\\u{1DA84}\\u{1DA9B}-\\u{1DA9F}\\u{1DAA1}-\\u{1DAAF}\\u{1E000}-\\u{1E006}\\u{1E008}-\\u{1E018}\\u{1E01B}-\\u{1E021}\\u{1E023}\\u{1E024}\\u{1E026}-\\u{1E02A}\\u{1E08F}\\u{1E130}-\\u{1E136}\\u{1E2AE}\\u{1E2EC}-\\u{1E2EF}\\u{1E4EC}-\\u{1E4EF}\\u{1E5EE}\\u{1E5EF}\\u{1E6E3}\\u{1E6E6}\\u{1E6EE}\\u{1E6EF}\\u{1E6F5}\\u{1E8D0}-\\u{1E8D6}\\u{1E944}-\\u{1E94A}\\u{E0100}-\\u{E01EF}';
|
|
31
54
|
export const DISPLAY_NAME_SPACE_SEPARATORS_RANGES = ' \\xA0\\u1680\\u2000-\\u200A\\u202F\\u205F\\u3000';
|
|
32
55
|
export const DISPLAY_NAME_LETTERS_RANGES = 'A-Za-z\\xAA\\xB5\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C\\u0C5D\\u0C60\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}\\u{16FE1}\\u{16FE3}\\u{16FF2}\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}\\u{1D49F}\\u{1D4A2}\\u{1D4A5}\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}';
|
|
56
|
+
export const DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES = '\\u{534D}\\u{5350}';
|
|
57
|
+
export const DISPLAY_NAME_NAME_SEPARATORS_RANGES = '\\u{B7}\\u{5BE}\\u{F0B}\\u{30FB}';
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validation utilities for common data validation patterns
|
|
3
3
|
*/
|
|
4
|
-
import { DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES, DISPLAY_NAME_COMBINING_MARKS_RANGES, DISPLAY_NAME_SPACE_SEPARATORS_RANGES, DISPLAY_NAME_LETTERS_RANGES, } from './displayNamePolicyRanges.generated.js';
|
|
4
|
+
import { DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES, DISPLAY_NAME_COMBINING_MARKS_RANGES, DISPLAY_NAME_SPACE_SEPARATORS_RANGES, DISPLAY_NAME_LETTERS_RANGES, DISPLAY_NAME_NAME_SEPARATORS_RANGES, } from './displayNamePolicyRanges.generated.js';
|
|
5
|
+
/**
|
|
6
|
+
* Maximum stored length of a display name, in code units after cleaning.
|
|
7
|
+
* Shared by the API write path and client input surfaces.
|
|
8
|
+
*/
|
|
9
|
+
export const MAX_DISPLAY_NAME_LENGTH = 80;
|
|
5
10
|
/**
|
|
6
11
|
* Email validation regex
|
|
7
12
|
*/
|
|
@@ -48,7 +53,16 @@ export function isValidPassword(password) {
|
|
|
48
53
|
* ideographic space, …) — but NOT control whitespace such as tab, newline,
|
|
49
54
|
* or carriage return, which would break layout or enable multi-line
|
|
50
55
|
* spoofing,
|
|
51
|
-
* - the straight apostrophe (`'`, e.g. "O'Brien")
|
|
56
|
+
* - the straight apostrophe (`'`, e.g. "O'Brien"),
|
|
57
|
+
* - four name SEPARATORS — `·` U+00B7, `־` U+05BE, `་` U+0F0B, `・` U+30FB —
|
|
58
|
+
* but ONLY directly between two letters (`Codeur·euses`, `お坐・エガード`,
|
|
59
|
+
* `אברמסקי־קרוננברג`, `འོད་ཟེར`). Every one is General_Category P, so the
|
|
60
|
+
* letters intersection strips them by default; they are re-admitted because
|
|
61
|
+
* stripping them SPLITS a real name in two. The flanking condition is not a
|
|
62
|
+
* refinement but the whole point: the same characters are also used as
|
|
63
|
+
* ornament (a trailing `Roberto ·`), and unconditional admission would let
|
|
64
|
+
* that through. See {@link DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE}. The
|
|
65
|
+
* ASCII hyphen is NOT among them — `Jean-Luc` stays rejected.
|
|
52
66
|
*
|
|
53
67
|
* Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
|
|
54
68
|
* digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
|
|
@@ -58,7 +72,15 @@ export function isValidPassword(password) {
|
|
|
58
72
|
*
|
|
59
73
|
* The allowlist is expressed with Unicode Script_Extensions (scx=…) so a letter
|
|
60
74
|
* shared by several scripts (e.g. a Han ideograph used in both Chinese and
|
|
61
|
-
* Japanese) still matches
|
|
75
|
+
* Japanese) still matches, INTERSECTED with General_Category L so only the
|
|
76
|
+
* scripts' LETTERS are admitted. That intersection is load-bearing, not a
|
|
77
|
+
* refinement: `scx=X` also carries script X's own digits, punctuation and
|
|
78
|
+
* symbols, so the un-intersected allowlist admitted 1831 non-letter code points
|
|
79
|
+
* — script digits (`٠١٢`, `०१२`), 1082 symbols (`֍ ۞ ৳`), 180 punctuation marks
|
|
80
|
+
* (`։ ، ؛ ।`), and 9 INVISIBLE format/control characters, among them U+061C
|
|
81
|
+
* ARABIC LETTER MARK (a bidi control that can visually reorder a name) and
|
|
82
|
+
* U+180E MONGOLIAN VOWEL SEPARATOR. Without it the rejections listed above held
|
|
83
|
+
* for ASCII input only. It is the set of scripts Unicode UTS #39 marks
|
|
62
84
|
* "Recommended" for general interchange / identifiers, plus Cherokee and
|
|
63
85
|
* Mongolian (both in real modern name use). "Common" script is deliberately
|
|
64
86
|
* EXCLUDED — that is where ASCII digits and general punctuation live, and this
|
|
@@ -66,6 +88,29 @@ export function isValidPassword(password) {
|
|
|
66
88
|
* name needs are added back explicitly. Limited-use / excluded / historic
|
|
67
89
|
* scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
|
|
68
90
|
*
|
|
91
|
+
* CODE-POINT DENYLIST — a character policy classifies FORM, never MEANING, so a
|
|
92
|
+
* code point can be a perfectly ordinary letter to Unicode and a hate symbol to
|
|
93
|
+
* a reader. `卐` U+5350 and `卍` U+534D are the case in point: both are CJK
|
|
94
|
+
* Unified Ideographs (General_Category Lo, Script_Extensions Han), i.e. the same
|
|
95
|
+
* kind of thing as `山` in `山田太郎`, so NO script-level or category-level rule
|
|
96
|
+
* can separate them — every rule that would exclude these two also excludes Han
|
|
97
|
+
* itself, rejecting every real Chinese, Japanese and Korean name. They are
|
|
98
|
+
* therefore enumerated and SUBTRACTED from the allowlist at generation time (see
|
|
99
|
+
* `SYMBOL_LETTER_DENYLIST` in `scripts/generateDisplayNamePolicyRanges.mjs`).
|
|
100
|
+
* Because the subtraction happens inside the one emitted allowlist, every
|
|
101
|
+
* consumer enforces it with no extra probe and none can forget it; the generator
|
|
102
|
+
* additionally REFUSES an entry that an existing lever already excludes (e.g.
|
|
103
|
+
* the Tibetan svasti signs U+0FD5–U+0FD8, General_Category So, already dropped
|
|
104
|
+
* by the intersection above), so the list cannot silently accumulate dead
|
|
105
|
+
* weight.
|
|
106
|
+
*
|
|
107
|
+
* REMAINING LIMIT — that closes the "letter that reads as a symbol" gap, not the
|
|
108
|
+
* other one: slurs and abusive phrasing spelled in ordinary allowlisted letters
|
|
109
|
+
* pass by construction, because they are built from exactly the characters every
|
|
110
|
+
* real name needs. No character-level rule — allowlist, intersection, or
|
|
111
|
+
* denylist — can reject them; that needs a word-level moderation layer, which is
|
|
112
|
+
* deliberately not attempted here.
|
|
113
|
+
*
|
|
69
114
|
* HERMES / RANGES: the class bodies below are built from explicit Unicode
|
|
70
115
|
* code-point RANGES ({@link DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES} et al. from
|
|
71
116
|
* `./displayNamePolicyRanges.generated`), NOT from `scx`/General_Category
|
|
@@ -86,20 +131,27 @@ export function isValidPassword(password) {
|
|
|
86
131
|
*/
|
|
87
132
|
/**
|
|
88
133
|
* The curated allowlist of Unicode scripts permitted in a display name, as a
|
|
89
|
-
* character-class body of explicit code-point ranges
|
|
90
|
-
*
|
|
91
|
-
*
|
|
134
|
+
* character-class body of explicit code-point ranges: the union of the 30
|
|
135
|
+
* allowlisted Script_Extensions, INTERSECTED with General_Category L (so letters
|
|
136
|
+
* only), MINUS the symbol-letter denylist — 120823 code points. Interpolated
|
|
137
|
+
* into the negated class below, which is why both the reject gate here and the
|
|
138
|
+
* `@oxyhq/api` strip path inherit the denylist without a second pattern.
|
|
92
139
|
*/
|
|
93
140
|
export const DISPLAY_NAME_ALLOWED_SCRIPTS = DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES;
|
|
94
141
|
/**
|
|
95
142
|
* Source of the disallowed-character pattern: the negation of the full allowed
|
|
96
143
|
* set (allowlisted scripts + combining marks + space separators + the straight
|
|
97
|
-
* apostrophe). Consumers compile this with the `u` flag
|
|
98
|
-
* strip). The whitespace class is space separators only
|
|
99
|
-
* NOT `\s` — the latter would admit tab/newline/carriage
|
|
100
|
-
* layout and enable multi-line spoofing.
|
|
144
|
+
* apostrophe + the name separators). Consumers compile this with the `u` flag
|
|
145
|
+
* (and `g` for a global strip). The whitespace class is space separators only
|
|
146
|
+
* (General_Category Zs), NOT `\s` — the latter would admit tab/newline/carriage
|
|
147
|
+
* return, which break layout and enable multi-line spoofing.
|
|
148
|
+
*
|
|
149
|
+
* The name separators are admitted here only as CHARACTERS. Their position rule
|
|
150
|
+
* is a separate pattern ({@link DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE}), for
|
|
151
|
+
* the same reason combining marks are: a negated character class cannot express
|
|
152
|
+
* "allowed only in this context". Both halves must be applied together.
|
|
101
153
|
*/
|
|
102
|
-
export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}${DISPLAY_NAME_COMBINING_MARKS_RANGES}${DISPLAY_NAME_SPACE_SEPARATORS_RANGES}']`;
|
|
154
|
+
export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}${DISPLAY_NAME_COMBINING_MARKS_RANGES}${DISPLAY_NAME_SPACE_SEPARATORS_RANGES}${DISPLAY_NAME_NAME_SEPARATORS_RANGES}']`;
|
|
103
155
|
/**
|
|
104
156
|
* Source of the orphaned combining-mark pattern: a run of combining marks NOT
|
|
105
157
|
* attached to a base letter (preceded by string start, whitespace, the
|
|
@@ -112,26 +164,63 @@ export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}
|
|
|
112
164
|
* an allowlisted base letter is preserved.
|
|
113
165
|
*/
|
|
114
166
|
export const DISPLAY_NAME_ORPHANED_MARK_SOURCE = `(?<![${DISPLAY_NAME_LETTERS_RANGES}${DISPLAY_NAME_COMBINING_MARKS_RANGES}])[${DISPLAY_NAME_COMBINING_MARKS_RANGES}]+`;
|
|
167
|
+
/**
|
|
168
|
+
* Source of the unflanked name-separator pattern — the positional half of the
|
|
169
|
+
* name-separator rule, and the exact counterpart of
|
|
170
|
+
* {@link DISPLAY_NAME_ORPHANED_MARK_SOURCE}: a combining mark is allowed only
|
|
171
|
+
* when it rides a base letter, and a name separator is allowed only when it JOINS
|
|
172
|
+
* two letters. Matches a separator that is missing a letter on either side, so
|
|
173
|
+
* consumers can reject it (non-global probe) or strip it (`g` flag).
|
|
174
|
+
*
|
|
175
|
+
* Two alternatives, one per side, because JS has no "not surrounded by" atom:
|
|
176
|
+
* - `(?<![letters][marks])[sep]` — nothing letter-like immediately BEFORE.
|
|
177
|
+
* - `[sep](?![letters])` — no letter immediately AFTER.
|
|
178
|
+
*
|
|
179
|
+
* The left-hand class deliberately includes combining marks as well as letters:
|
|
180
|
+
* a base letter can carry an accent (`Renée·euses`, or a decomposed `e`+◌́), and
|
|
181
|
+
* that mark sits between the letter and the separator. Treating a mark as
|
|
182
|
+
* letter-like is what stops an accent from defeating the flanking test — the
|
|
183
|
+
* same reason the orphaned-mark lookbehind uses the identical pair of classes.
|
|
184
|
+
* The right-hand class is letters only: a mark AFTER a separator has no base and
|
|
185
|
+
* is removed by the orphaned-mark rule, so the separator is genuinely unflanked.
|
|
186
|
+
*
|
|
187
|
+
* Both lookarounds are single-character (no variable-length lookbehind), and the
|
|
188
|
+
* classes are explicit code-point ranges, so the compiled regex is within what
|
|
189
|
+
* Hermes accepts.
|
|
190
|
+
*
|
|
191
|
+
* `Codeur·euses` and `お坐・エガード` match NOTHING here and survive. A leading
|
|
192
|
+
* `·Roberto`, a trailing `Roberto ·`, and a doubled `a··b` each match and are
|
|
193
|
+
* stripped — which is what keeps a decorative middle dot from riding in on the
|
|
194
|
+
* back of the orthographic one.
|
|
195
|
+
*/
|
|
196
|
+
export const DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE = `(?<![${DISPLAY_NAME_LETTERS_RANGES}${DISPLAY_NAME_COMBINING_MARKS_RANGES}])[${DISPLAY_NAME_NAME_SEPARATORS_RANGES}]|[${DISPLAY_NAME_NAME_SEPARATORS_RANGES}](?![${DISPLAY_NAME_LETTERS_RANGES}])`;
|
|
115
197
|
/** Non-global probe for the presence of a disallowed character. */
|
|
116
198
|
const DISALLOWED_PROBE = new RegExp(DISPLAY_NAME_DISALLOWED_SOURCE, 'u');
|
|
117
199
|
/** Non-global probe for the presence of an orphaned combining mark. */
|
|
118
200
|
const ORPHANED_MARK_PROBE = new RegExp(DISPLAY_NAME_ORPHANED_MARK_SOURCE, 'u');
|
|
201
|
+
/** Non-global probe for the presence of an unflanked name separator. */
|
|
202
|
+
const UNFLANKED_SEPARATOR_PROBE = new RegExp(DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE, 'u');
|
|
119
203
|
/**
|
|
120
204
|
* Whether `raw` already satisfies the display-name policy, i.e. it contains no
|
|
121
|
-
* disallowed characters
|
|
122
|
-
* (signup / profile edit) names with a 400
|
|
123
|
-
* and to validate inline in the client
|
|
205
|
+
* disallowed characters, no orphaned combining marks, and no unflanked name
|
|
206
|
+
* separator. Used to REJECT native (signup / profile edit) names with a 400
|
|
207
|
+
* rather than silently stripping them, and to validate inline in the client
|
|
208
|
+
* editor.
|
|
124
209
|
*
|
|
125
|
-
* The
|
|
126
|
-
*
|
|
127
|
-
*
|
|
210
|
+
* The two positional probes run on the NFC-normalized form, the character probe
|
|
211
|
+
* on the raw input. Normalization matters for both: a legitimate decomposed
|
|
212
|
+
* accent (`e`+◌́) recomposes into `é`, so it is NOT rejected as an orphaned mark,
|
|
213
|
+
* and a separator after that accent sees a base letter rather than a mark.
|
|
128
214
|
*
|
|
129
|
-
* The function only checks the character set; an empty
|
|
130
|
-
* is considered valid (`true`). Call sites that require
|
|
131
|
-
* that separately.
|
|
215
|
+
* The function only checks the character set and separator placement; an empty
|
|
216
|
+
* or whitespace-only string is considered valid (`true`). Call sites that require
|
|
217
|
+
* a non-empty name enforce that separately.
|
|
132
218
|
*/
|
|
133
219
|
export function isValidDisplayName(raw) {
|
|
134
|
-
|
|
220
|
+
const normalized = raw.normalize('NFC');
|
|
221
|
+
return (!DISALLOWED_PROBE.test(raw) &&
|
|
222
|
+
!ORPHANED_MARK_PROBE.test(normalized) &&
|
|
223
|
+
!UNFLANKED_SEPARATOR_PROBE.test(normalized));
|
|
135
224
|
}
|
|
136
225
|
/**
|
|
137
226
|
* Validate required string
|