@oxyhq/core 12.3.0 → 12.4.1
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/index.js +5 -2
- package/dist/cjs/session/refresh.js +14 -3
- package/dist/cjs/utils/validationUtils.js +58 -21
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/session/refresh.js +14 -3
- package/dist/esm/utils/validationUtils.js +60 -23
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/validationUtils.d.ts +65 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/session/__tests__/refresh.test.ts +20 -0
- package/src/session/refresh.ts +12 -3
- package/src/utils/__tests__/validationUtils.test.ts +27 -0
- package/src/utils/validationUtils.ts +61 -20
package/dist/types/index.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export { buildSearchParams, buildUrl, buildPaginationParams, safeJsonParse, } fr
|
|
|
77
77
|
export type { PaginationParams, ApiResponse, ErrorResponse, } from './utils/apiUtils';
|
|
78
78
|
export { ErrorCodes, createApiError, handleHttpError, validateRequiredFields, } from './utils/errorUtils';
|
|
79
79
|
export { retryAsync } from './utils/asyncUtils';
|
|
80
|
-
export { EMAIL_REGEX, USERNAME_REGEX, PASSWORD_REGEX, isValidEmail, isValidUsername, isValidPassword, isValidDisplayName, isRequiredString, isRequiredNumber, isRequiredBoolean, isValidArray, isValidObject, isValidUUID, isValidURL, isValidDate, isValidFileSize, isValidFileType, sanitizeString, sanitizeHTML, isValidObjectId, validateAndSanitizeUserInput, } from './utils/validationUtils';
|
|
80
|
+
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';
|
|
81
81
|
export { normalizeInlineText, normalizeMultilineText, } from './utils/textNormalization';
|
|
82
82
|
export { logger, createLogger, configureLogger, getLoggerConfig, resetLoggerConfig, consoleSink, isDev, } from './logger';
|
|
83
83
|
export type { Logger, LogLevel, EmittableLogLevel, LogContext, LogEntry, LogSink, LoggerConfig, } from './logger';
|
|
@@ -25,6 +25,71 @@ export declare function isValidUsername(username: string): boolean;
|
|
|
25
25
|
* Validate password strength
|
|
26
26
|
*/
|
|
27
27
|
export declare function isValidPassword(password: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Display-name character policy.
|
|
30
|
+
*
|
|
31
|
+
* A clean display name is composed ONLY of:
|
|
32
|
+
* - letters from a curated ALLOWLIST of scripts that real names use
|
|
33
|
+
* ({@link DISPLAY_NAME_ALLOWED_SCRIPTS}) — NOT `\p{L}` (letters of ANY
|
|
34
|
+
* script), which admits decorative / historic / limited-use scripts whose
|
|
35
|
+
* characters are `\p{L}` yet never appear in a real name (e.g. `ᯅ` U+1BC5
|
|
36
|
+
* Batak, Runic, Deseret, dingbat letters),
|
|
37
|
+
* - combining marks / accents (`\p{M}`, e.g. the acute accent in a decomposed
|
|
38
|
+
* "é"),
|
|
39
|
+
* - Unicode space separators (`\p{Zs}`: the ASCII space, NBSP, ideographic
|
|
40
|
+
* space, …) — but NOT control whitespace such as tab, newline, or carriage
|
|
41
|
+
* return, which would break layout or enable multi-line spoofing,
|
|
42
|
+
* - the straight apostrophe (`'`, e.g. "O'Brien").
|
|
43
|
+
*
|
|
44
|
+
* Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
|
|
45
|
+
* digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
|
|
46
|
+
* non-allowlisted scripts, and any other punctuation. The allowed set never
|
|
47
|
+
* includes `<`, `>`, `&`, or `"`, so a value that passes this predicate can
|
|
48
|
+
* never contain an HTML/XSS vector.
|
|
49
|
+
*
|
|
50
|
+
* The allowlist is expressed with Unicode Script_Extensions (`\p{scx=…}`)
|
|
51
|
+
* escapes so a letter shared by several scripts (e.g. a Han ideograph used in
|
|
52
|
+
* both Chinese and Japanese) still matches. It is the set of scripts Unicode
|
|
53
|
+
* UTS #39 marks "Recommended" for general interchange / identifiers, plus
|
|
54
|
+
* Cherokee and Mongolian (both in real modern name use). "Common" script is
|
|
55
|
+
* deliberately EXCLUDED — that is where ASCII digits and general punctuation
|
|
56
|
+
* live, and this policy excludes those; the space separators, combining marks,
|
|
57
|
+
* and apostrophe a name needs are added back explicitly. Limited-use / excluded
|
|
58
|
+
* / historic scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
|
|
59
|
+
*
|
|
60
|
+
* This is the SINGLE definition of the policy: the character-class sources below
|
|
61
|
+
* are the ONE source of truth, shared between the API strip/gate
|
|
62
|
+
* (`@oxyhq/api` `displayNameSanitize.ts` builds its global-flag patterns from
|
|
63
|
+
* them) and client-side inline validation (the RN profile editor via
|
|
64
|
+
* {@link isValidDisplayName}) so the two can never drift. It is platform-agnostic
|
|
65
|
+
* (no react/react-native/expo).
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* The curated allowlist of Unicode scripts permitted in a display name, as a
|
|
69
|
+
* character-class body of Script_Extensions (`scx`) property escapes. Ordered by
|
|
70
|
+
* rough script family for readability; order has no semantic effect.
|
|
71
|
+
*/
|
|
72
|
+
export declare const DISPLAY_NAME_ALLOWED_SCRIPTS: string;
|
|
73
|
+
/**
|
|
74
|
+
* Source of the disallowed-character pattern: the negation of the full allowed
|
|
75
|
+
* set (allowlisted scripts + combining marks `\p{M}` + space separators `\p{Zs}`
|
|
76
|
+
* + the straight apostrophe). Consumers compile this with the `u` flag (and `g`
|
|
77
|
+
* for a global strip). The whitespace class is `\p{Zs}` (space separators only),
|
|
78
|
+
* NOT `\s` — the latter would admit tab/newline/carriage return, which break
|
|
79
|
+
* layout and enable multi-line spoofing.
|
|
80
|
+
*/
|
|
81
|
+
export declare const DISPLAY_NAME_DISALLOWED_SOURCE = "[^\\p{scx=Latin}\\p{scx=Greek}\\p{scx=Cyrillic}\\p{scx=Armenian}\\p{scx=Hebrew}\\p{scx=Arabic}\\p{scx=Thaana}\\p{scx=Devanagari}\\p{scx=Bengali}\\p{scx=Gurmukhi}\\p{scx=Gujarati}\\p{scx=Oriya}\\p{scx=Tamil}\\p{scx=Telugu}\\p{scx=Kannada}\\p{scx=Malayalam}\\p{scx=Sinhala}\\p{scx=Thai}\\p{scx=Lao}\\p{scx=Tibetan}\\p{scx=Myanmar}\\p{scx=Georgian}\\p{scx=Hangul}\\p{scx=Ethiopic}\\p{scx=Cherokee}\\p{scx=Khmer}\\p{scx=Mongolian}\\p{scx=Hiragana}\\p{scx=Katakana}\\p{scx=Bopomofo}\\p{scx=Han}\\p{M}\\p{Zs}']";
|
|
82
|
+
/**
|
|
83
|
+
* Source of the orphaned combining-mark pattern: a run of `\p{M}` NOT attached
|
|
84
|
+
* to a base letter (preceded by string start, whitespace, the apostrophe, or a
|
|
85
|
+
* position vacated by a stripped character). A mark preceded by `\p{L}` (a base
|
|
86
|
+
* letter, e.g. the decomposed accent in "Renée") or by another `\p{M}` (a
|
|
87
|
+
* multi-mark cluster) is NOT matched because the negative lookbehind fails at its
|
|
88
|
+
* position. Used both as a non-global probe (`.test`) and, with the `g` flag, to
|
|
89
|
+
* strip whole orphaned runs. The lookbehind intentionally still uses the broad
|
|
90
|
+
* `\p{L}` so that a mark riding on an allowlisted base letter is preserved.
|
|
91
|
+
*/
|
|
92
|
+
export declare const DISPLAY_NAME_ORPHANED_MARK_SOURCE = "(?<![\\p{L}\\p{M}])\\p{M}+";
|
|
28
93
|
/**
|
|
29
94
|
* Whether `raw` already satisfies the display-name policy, i.e. it contains no
|
|
30
95
|
* disallowed characters AND no orphaned combining marks. Used to REJECT native
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -457,6 +457,9 @@ export {
|
|
|
457
457
|
isValidUsername,
|
|
458
458
|
isValidPassword,
|
|
459
459
|
isValidDisplayName,
|
|
460
|
+
DISPLAY_NAME_ALLOWED_SCRIPTS,
|
|
461
|
+
DISPLAY_NAME_DISALLOWED_SOURCE,
|
|
462
|
+
DISPLAY_NAME_ORPHANED_MARK_SOURCE,
|
|
460
463
|
isRequiredString,
|
|
461
464
|
isRequiredNumber,
|
|
462
465
|
isRequiredBoolean,
|
|
@@ -159,6 +159,26 @@ describe('refreshPersistedSession — arm 1 (device-secret mint)', () => {
|
|
|
159
159
|
expect(await store.load()).toEqual(STORED);
|
|
160
160
|
expect(signInWithSharedIdentity).not.toHaveBeenCalled();
|
|
161
161
|
});
|
|
162
|
+
|
|
163
|
+
it('KEEPS the store on an UNRECOGNIZED 401 (proxy / middleware / deploy-window) — never wipes the credential on an ambiguous 401', async () => {
|
|
164
|
+
const store = createMemoryAuthStateStore();
|
|
165
|
+
await store.save(STORED);
|
|
166
|
+
const signInWithSharedIdentity = jest.fn(async () => null);
|
|
167
|
+
const { oxy } = makeOxy({
|
|
168
|
+
mintFromDeviceSecret: async () => {
|
|
169
|
+
// A 401 whose body is NEITHER `invalid_device_secret` NOR `no_active_session`
|
|
170
|
+
// — an auth-layer / proxy / starting-instance 401 common during a deploy
|
|
171
|
+
// window. It is NOT proof the secret diverged, so the durable device
|
|
172
|
+
// credential must survive (treated as transient) and a later attempt self-heals.
|
|
173
|
+
throw Object.assign(new Error('Unauthorized'), { status: 401 });
|
|
174
|
+
},
|
|
175
|
+
signInWithSharedIdentity,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true })).toBeNull();
|
|
179
|
+
expect(await store.load()).toEqual(STORED);
|
|
180
|
+
expect(signInWithSharedIdentity).not.toHaveBeenCalled();
|
|
181
|
+
});
|
|
162
182
|
});
|
|
163
183
|
|
|
164
184
|
describe('refreshPersistedSession — arm 2 (native shared-key fallback)', () => {
|
package/src/session/refresh.ts
CHANGED
|
@@ -136,9 +136,18 @@ export async function refreshDeviceSecretArm(deps: {
|
|
|
136
136
|
// Structural read (not `instanceof Error`): the thrown value can be a
|
|
137
137
|
// plain ApiError-shaped object or come from another realm.
|
|
138
138
|
const message = (error as { message?: unknown })?.message;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
const body = typeof message === 'string' ? message : '';
|
|
140
|
+
// ONLY the server's explicit `invalid_device_secret` proves the presented
|
|
141
|
+
// secret is bad and may clear the durable device credential. `no_active_session`
|
|
142
|
+
// is an authoritative signed-out. ANY OTHER 401 — a middleware/CSRF/proxy 401,
|
|
143
|
+
// an ALB/starting-instance 401, a CORS error page, etc., all common during a
|
|
144
|
+
// deploy/restart window — is NOT proof the secret diverged: treat it as
|
|
145
|
+
// transient and KEEP the credential so a later attempt self-heals. Wiping the
|
|
146
|
+
// credential on an ambiguous 401 is what logged users out on every deploy,
|
|
147
|
+
// ecosystem-wide.
|
|
148
|
+
if (body.includes('invalid_device_secret')) return { status: 'invalid-secret' };
|
|
149
|
+
if (body.includes('no_active_session')) return { status: 'no-session' };
|
|
150
|
+
return { status: 'transient' };
|
|
142
151
|
}
|
|
143
152
|
return { status: 'transient' };
|
|
144
153
|
}
|
|
@@ -148,6 +148,22 @@ describe('Validation Utils', () => {
|
|
|
148
148
|
expect(isValidDisplayName('')).toBe(true); // empty is valid; non-empty enforced elsewhere
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it.each([
|
|
152
|
+
['Владимир', 'Cyrillic'],
|
|
153
|
+
['مُحَمَد', 'Arabic with harakat'],
|
|
154
|
+
['נתן', 'Hebrew'],
|
|
155
|
+
['नमस्ते', 'Devanagari'],
|
|
156
|
+
['김철수', 'Hangul'],
|
|
157
|
+
['Αριστοτέλης', 'Greek'],
|
|
158
|
+
['Արամ', 'Armenian'],
|
|
159
|
+
['დავით', 'Georgian'],
|
|
160
|
+
['สมชาย', 'Thai'],
|
|
161
|
+
['ᏔᎳ', 'Cherokee'],
|
|
162
|
+
['ᠮᠣᠩᠭᠣᠯ', 'Mongolian'],
|
|
163
|
+
])('should return true for allowlisted-script real name %p (%s)', (name) => {
|
|
164
|
+
expect(isValidDisplayName(name)).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
151
167
|
it('should return false for emoji, symbols, digits, and punctuation', () => {
|
|
152
168
|
expect(isValidDisplayName('nixCraft \u{1f427}')).toBe(false); // penguin emoji
|
|
153
169
|
expect(isValidDisplayName('Agent007')).toBe(false);
|
|
@@ -155,6 +171,17 @@ describe('Validation Utils', () => {
|
|
|
155
171
|
expect(isValidDisplayName('J.R.')).toBe(false);
|
|
156
172
|
});
|
|
157
173
|
|
|
174
|
+
it.each([
|
|
175
|
+
['ᯅ', 'Batak U+1BC5 (Limited-Use script)'],
|
|
176
|
+
['ᚠ', 'Runic'],
|
|
177
|
+
['Miguel de Icaza ᯅ', 'a Latin name with a trailing Batak letter'],
|
|
178
|
+
])('should return false for non-allowlisted-script letter %p (%s)', (name) => {
|
|
179
|
+
// These characters are General_Category Lo (`\p{L}`), so the old
|
|
180
|
+
// all-scripts policy accepted them; the curated script allowlist rejects
|
|
181
|
+
// decorative / limited-use scripts a real name never uses.
|
|
182
|
+
expect(isValidDisplayName(name)).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
158
185
|
it('should return false for control whitespace (tab/newline/CR)', () => {
|
|
159
186
|
// \p{Zs} (space separators only) rejects layout-breaking / multi-line
|
|
160
187
|
// spoofing whitespace that \s would have admitted.
|
|
@@ -43,7 +43,11 @@ export function isValidPassword(password: string): boolean {
|
|
|
43
43
|
* Display-name character policy.
|
|
44
44
|
*
|
|
45
45
|
* A clean display name is composed ONLY of:
|
|
46
|
-
* - letters of
|
|
46
|
+
* - letters from a curated ALLOWLIST of scripts that real names use
|
|
47
|
+
* ({@link DISPLAY_NAME_ALLOWED_SCRIPTS}) — NOT `\p{L}` (letters of ANY
|
|
48
|
+
* script), which admits decorative / historic / limited-use scripts whose
|
|
49
|
+
* characters are `\p{L}` yet never appear in a real name (e.g. `ᯅ` U+1BC5
|
|
50
|
+
* Batak, Runic, Deseret, dingbat letters),
|
|
47
51
|
* - combining marks / accents (`\p{M}`, e.g. the acute accent in a decomposed
|
|
48
52
|
* "é"),
|
|
49
53
|
* - Unicode space separators (`\p{Zs}`: the ASCII space, NBSP, ideographic
|
|
@@ -52,34 +56,71 @@ export function isValidPassword(password: string): boolean {
|
|
|
52
56
|
* - the straight apostrophe (`'`, e.g. "O'Brien").
|
|
53
57
|
*
|
|
54
58
|
* Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
|
|
55
|
-
* digits, hyphens, dots, control whitespace (tab/newline/CR),
|
|
56
|
-
* punctuation. The allowed set
|
|
57
|
-
* `&`,
|
|
58
|
-
* HTML/XSS vector.
|
|
59
|
+
* digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
|
|
60
|
+
* non-allowlisted scripts, and any other punctuation. The allowed set never
|
|
61
|
+
* includes `<`, `>`, `&`, or `"`, so a value that passes this predicate can
|
|
62
|
+
* never contain an HTML/XSS vector.
|
|
59
63
|
*
|
|
60
|
-
*
|
|
61
|
-
* (
|
|
62
|
-
*
|
|
64
|
+
* The allowlist is expressed with Unicode Script_Extensions (`\p{scx=…}`)
|
|
65
|
+
* escapes so a letter shared by several scripts (e.g. a Han ideograph used in
|
|
66
|
+
* both Chinese and Japanese) still matches. It is the set of scripts Unicode
|
|
67
|
+
* UTS #39 marks "Recommended" for general interchange / identifiers, plus
|
|
68
|
+
* Cherokee and Mongolian (both in real modern name use). "Common" script is
|
|
69
|
+
* deliberately EXCLUDED — that is where ASCII digits and general punctuation
|
|
70
|
+
* live, and this policy excludes those; the space separators, combining marks,
|
|
71
|
+
* and apostrophe a name needs are added back explicitly. Limited-use / excluded
|
|
72
|
+
* / historic scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
|
|
73
|
+
*
|
|
74
|
+
* This is the SINGLE definition of the policy: the character-class sources below
|
|
75
|
+
* are the ONE source of truth, shared between the API strip/gate
|
|
76
|
+
* (`@oxyhq/api` `displayNameSanitize.ts` builds its global-flag patterns from
|
|
77
|
+
* them) and client-side inline validation (the RN profile editor via
|
|
78
|
+
* {@link isValidDisplayName}) so the two can never drift. It is platform-agnostic
|
|
63
79
|
* (no react/react-native/expo).
|
|
64
80
|
*/
|
|
65
81
|
|
|
66
82
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
83
|
+
* The curated allowlist of Unicode scripts permitted in a display name, as a
|
|
84
|
+
* character-class body of Script_Extensions (`scx`) property escapes. Ordered by
|
|
85
|
+
* rough script family for readability; order has no semantic effect.
|
|
86
|
+
*/
|
|
87
|
+
export const DISPLAY_NAME_ALLOWED_SCRIPTS =
|
|
88
|
+
'\\p{scx=Latin}\\p{scx=Greek}\\p{scx=Cyrillic}\\p{scx=Armenian}' +
|
|
89
|
+
'\\p{scx=Hebrew}\\p{scx=Arabic}\\p{scx=Thaana}\\p{scx=Devanagari}' +
|
|
90
|
+
'\\p{scx=Bengali}\\p{scx=Gurmukhi}\\p{scx=Gujarati}\\p{scx=Oriya}' +
|
|
91
|
+
'\\p{scx=Tamil}\\p{scx=Telugu}\\p{scx=Kannada}\\p{scx=Malayalam}' +
|
|
92
|
+
'\\p{scx=Sinhala}\\p{scx=Thai}\\p{scx=Lao}\\p{scx=Tibetan}' +
|
|
93
|
+
'\\p{scx=Myanmar}\\p{scx=Georgian}\\p{scx=Hangul}\\p{scx=Ethiopic}' +
|
|
94
|
+
'\\p{scx=Cherokee}\\p{scx=Khmer}\\p{scx=Mongolian}\\p{scx=Hiragana}' +
|
|
95
|
+
'\\p{scx=Katakana}\\p{scx=Bopomofo}\\p{scx=Han}';
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Source of the disallowed-character pattern: the negation of the full allowed
|
|
99
|
+
* set (allowlisted scripts + combining marks `\p{M}` + space separators `\p{Zs}`
|
|
100
|
+
* + the straight apostrophe). Consumers compile this with the `u` flag (and `g`
|
|
101
|
+
* for a global strip). The whitespace class is `\p{Zs}` (space separators only),
|
|
102
|
+
* NOT `\s` — the latter would admit tab/newline/carriage return, which break
|
|
103
|
+
* layout and enable multi-line spoofing.
|
|
71
104
|
*/
|
|
72
|
-
const
|
|
105
|
+
export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}\\p{M}\\p{Zs}']`;
|
|
73
106
|
|
|
74
107
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
108
|
+
* Source of the orphaned combining-mark pattern: a run of `\p{M}` NOT attached
|
|
109
|
+
* to a base letter (preceded by string start, whitespace, the apostrophe, or a
|
|
110
|
+
* position vacated by a stripped character). A mark preceded by `\p{L}` (a base
|
|
111
|
+
* letter, e.g. the decomposed accent in "Renée") or by another `\p{M}` (a
|
|
112
|
+
* multi-mark cluster) is NOT matched because the negative lookbehind fails at its
|
|
113
|
+
* position. Used both as a non-global probe (`.test`) and, with the `g` flag, to
|
|
114
|
+
* strip whole orphaned runs. The lookbehind intentionally still uses the broad
|
|
115
|
+
* `\p{L}` so that a mark riding on an allowlisted base letter is preserved.
|
|
81
116
|
*/
|
|
82
|
-
const
|
|
117
|
+
export const DISPLAY_NAME_ORPHANED_MARK_SOURCE = '(?<![\\p{L}\\p{M}])\\p{M}+';
|
|
118
|
+
|
|
119
|
+
/** Non-global probe for the presence of a disallowed character. */
|
|
120
|
+
const DISALLOWED_PROBE = new RegExp(DISPLAY_NAME_DISALLOWED_SOURCE, 'u');
|
|
121
|
+
|
|
122
|
+
/** Non-global probe for the presence of an orphaned combining mark. */
|
|
123
|
+
const ORPHANED_MARK_PROBE = new RegExp(DISPLAY_NAME_ORPHANED_MARK_SOURCE, 'u');
|
|
83
124
|
|
|
84
125
|
/**
|
|
85
126
|
* Whether `raw` already satisfies the display-name policy, i.e. it contains no
|