diff --git a/.gitignore b/.gitignore index 31437c4..6a7f75d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,10 @@ .old-reference/ .local/ dist/ -# dist-release/ # release/ node_modules/ # Build artifacts -dist-release/ dist-release.pem dist-release.crx diff --git a/dist-release/assets/icons/icon-128.png b/dist-release/assets/icons/icon-128.png deleted file mode 100644 index 8cc212d..0000000 Binary files a/dist-release/assets/icons/icon-128.png and /dev/null differ diff --git a/dist-release/assets/icons/icon-16.png b/dist-release/assets/icons/icon-16.png deleted file mode 100644 index b2d62c6..0000000 Binary files a/dist-release/assets/icons/icon-16.png and /dev/null differ diff --git a/dist-release/assets/icons/icon-32.png b/dist-release/assets/icons/icon-32.png deleted file mode 100644 index c87d772..0000000 Binary files a/dist-release/assets/icons/icon-32.png and /dev/null differ diff --git a/dist-release/assets/icons/icon-48.png b/dist-release/assets/icons/icon-48.png deleted file mode 100644 index ed5f38f..0000000 Binary files a/dist-release/assets/icons/icon-48.png and /dev/null differ diff --git a/dist-release/assets/icons/icon-source.svg b/dist-release/assets/icons/icon-source.svg deleted file mode 100644 index 4c81685..0000000 --- a/dist-release/assets/icons/icon-source.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/dist-release/background/index.js b/dist-release/background/index.js deleted file mode 100644 index 0ed7e76..0000000 --- a/dist-release/background/index.js +++ /dev/null @@ -1,3375 +0,0 @@ -"use strict"; -(() => { - // src/shared/auth-config.ts - var defaultAuthConfig = { - apiResource: "https://talent-search.intelligrow.cn", - appId: "i4jkllbvih0554r4n0fd3", - enableDevAuthPanel: false, - logtoEndpoint: "https://login-api.intelligrow.cn", - scopes: ["openid", "profile", "offline_access", "talent-search:read"] - }; - function readAuthConfig(overrides = {}) { - const nextConfig = { - ...defaultAuthConfig, - ...overrides - }; - if (!nextConfig.logtoEndpoint.trim()) { - throw new Error("auth config logtoEndpoint is required"); - } - if (!nextConfig.appId.trim()) { - throw new Error("auth config appId is required"); - } - if (!nextConfig.apiResource.trim()) { - throw new Error("auth config apiResource is required"); - } - return nextConfig; - } - - // src/background/auth/state.ts - function createLoggedOutAuthState(config, lastError) { - return { - isAuthenticated: false, - lastError: lastError ?? null, - resource: config?.apiResource ?? null - }; - } - function createLoggedInAuthState(claims, config) { - return { - accessTokenExpiresAt: null, - isAuthenticated: true, - resource: config?.apiResource ?? null, - scopes: config?.scopes ?? [], - tokenAvailable: true, - userInfo: { - email: readStringClaim(claims, "email"), - name: readStringClaim(claims, "name"), - sub: readStringClaim(claims, "sub"), - username: readStringClaim(claims, "username") - } - }; - } - function readStringClaim(claims, key) { - const value = claims?.[key]; - return typeof value === "string" ? value : void 0; - } - - // src/background/auth/controller.ts - function createAuthController(options) { - const config = options.config ?? readAuthConfig(); - return { - async getAccessToken() { - return options.authClient.getAccessToken(config.apiResource); - }, - async getAuthState() { - const isAuthenticated = await options.authClient.isAuthenticated(); - if (!isAuthenticated) { - return createLoggedOutAuthState(config); - } - try { - await options.authClient.getAccessToken(config.apiResource); - } catch (error) { - return createLoggedOutAuthState( - config, - error instanceof Error ? error.message : String(error) - ); - } - const claims = await options.authClient.getIdTokenClaims(); - return createLoggedInAuthState(claims, config); - }, - async signIn() { - await options.authClient.signIn(); - }, - async signOut() { - await options.authClient.signOut(); - } - }; - } - - // node_modules/map-obj/index.js - var isObject = (value) => typeof value === "object" && value !== null; - var isObjectCustom = (value) => isObject(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date); - var mapObjectSkip = /* @__PURE__ */ Symbol("mapObjectSkip"); - var _mapObject = (object, mapper, options, isSeen = /* @__PURE__ */ new WeakMap()) => { - options = { - deep: false, - target: {}, - ...options - }; - if (isSeen.has(object)) { - return isSeen.get(object); - } - isSeen.set(object, options.target); - const { target } = options; - delete options.target; - const mapArray = (array) => array.map((element) => isObjectCustom(element) ? _mapObject(element, mapper, options, isSeen) : element); - if (Array.isArray(object)) { - return mapArray(object); - } - for (const [key, value] of Object.entries(object)) { - const mapResult = mapper(key, value, object); - if (mapResult === mapObjectSkip) { - continue; - } - let [newKey, newValue, { shouldRecurse = true } = {}] = mapResult; - if (newKey === "__proto__") { - continue; - } - if (options.deep && shouldRecurse && isObjectCustom(newValue)) { - newValue = Array.isArray(newValue) ? mapArray(newValue) : _mapObject(newValue, mapper, options, isSeen); - } - target[newKey] = newValue; - } - return target; - }; - function mapObject(object, mapper, options) { - if (!isObject(object)) { - throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`); - } - return _mapObject(object, mapper, options); - } - - // node_modules/camelcase/index.js - var UPPERCASE = /[\p{Lu}]/u; - var LOWERCASE = /[\p{Ll}]/u; - var LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu; - var IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u; - var SEPARATORS = /[_.\- ]+/; - var LEADING_SEPARATORS = new RegExp("^" + SEPARATORS.source); - var SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, "gu"); - var NUMBERS_AND_IDENTIFIER = new RegExp("\\d+" + IDENTIFIER.source, "gu"); - var preserveCamelCase = (string, toLowerCase, toUpperCase, preserveConsecutiveUppercase2) => { - let isLastCharLower = false; - let isLastCharUpper = false; - let isLastLastCharUpper = false; - let isLastLastCharPreserved = false; - for (let index = 0; index < string.length; index++) { - const character = string[index]; - isLastLastCharPreserved = index > 2 ? string[index - 3] === "-" : true; - if (isLastCharLower && UPPERCASE.test(character)) { - string = string.slice(0, index) + "-" + string.slice(index); - isLastCharLower = false; - isLastLastCharUpper = isLastCharUpper; - isLastCharUpper = true; - index++; - } else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character) && (!isLastLastCharPreserved || preserveConsecutiveUppercase2)) { - string = string.slice(0, index - 1) + "-" + string.slice(index - 1); - isLastLastCharUpper = isLastCharUpper; - isLastCharUpper = false; - isLastCharLower = true; - } else { - isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character; - isLastLastCharUpper = isLastCharUpper; - isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character; - } - } - return string; - }; - var preserveConsecutiveUppercase = (input, toLowerCase) => { - LEADING_CAPITAL.lastIndex = 0; - return input.replaceAll(LEADING_CAPITAL, (match) => toLowerCase(match)); - }; - var postProcess = (input, toUpperCase) => { - SEPARATORS_AND_IDENTIFIER.lastIndex = 0; - NUMBERS_AND_IDENTIFIER.lastIndex = 0; - return input.replaceAll(NUMBERS_AND_IDENTIFIER, (match, pattern, offset) => ["_", "-"].includes(input.charAt(offset + match.length)) ? match : toUpperCase(match)).replaceAll(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier)); - }; - function camelCase(input, options) { - if (!(typeof input === "string" || Array.isArray(input))) { - throw new TypeError("Expected the input to be `string | string[]`"); - } - options = { - pascalCase: false, - preserveConsecutiveUppercase: false, - ...options - }; - if (Array.isArray(input)) { - input = input.map((x) => x.trim()).filter((x) => x.length).join("-"); - } else { - input = input.trim(); - } - if (input.length === 0) { - return ""; - } - const toLowerCase = options.locale === false ? (string) => string.toLowerCase() : (string) => string.toLocaleLowerCase(options.locale); - const toUpperCase = options.locale === false ? (string) => string.toUpperCase() : (string) => string.toLocaleUpperCase(options.locale); - if (input.length === 1) { - if (SEPARATORS.test(input)) { - return ""; - } - return options.pascalCase ? toUpperCase(input) : toLowerCase(input); - } - const hasUpperCase = input !== toLowerCase(input); - if (hasUpperCase) { - input = preserveCamelCase(input, toLowerCase, toUpperCase, options.preserveConsecutiveUppercase); - } - input = input.replace(LEADING_SEPARATORS, ""); - input = options.preserveConsecutiveUppercase ? preserveConsecutiveUppercase(input, toLowerCase) : toLowerCase(input); - if (options.pascalCase) { - input = toUpperCase(input.charAt(0)) + input.slice(1); - } - return postProcess(input, toUpperCase); - } - - // node_modules/quick-lru/index.js - var QuickLRU = class extends Map { - constructor(options = {}) { - super(); - if (!(options.maxSize && options.maxSize > 0)) { - throw new TypeError("`maxSize` must be a number greater than 0"); - } - if (typeof options.maxAge === "number" && options.maxAge === 0) { - throw new TypeError("`maxAge` must be a number greater than 0"); - } - this.maxSize = options.maxSize; - this.maxAge = options.maxAge || Number.POSITIVE_INFINITY; - this.onEviction = options.onEviction; - this.cache = /* @__PURE__ */ new Map(); - this.oldCache = /* @__PURE__ */ new Map(); - this._size = 0; - } - // TODO: Use private class methods when targeting Node.js 16. - _emitEvictions(cache2) { - if (typeof this.onEviction !== "function") { - return; - } - for (const [key, item] of cache2) { - this.onEviction(key, item.value); - } - } - _deleteIfExpired(key, item) { - if (typeof item.expiry === "number" && item.expiry <= Date.now()) { - if (typeof this.onEviction === "function") { - this.onEviction(key, item.value); - } - return this.delete(key); - } - return false; - } - _getOrDeleteIfExpired(key, item) { - const deleted = this._deleteIfExpired(key, item); - if (deleted === false) { - return item.value; - } - } - _getItemValue(key, item) { - return item.expiry ? this._getOrDeleteIfExpired(key, item) : item.value; - } - _peek(key, cache2) { - const item = cache2.get(key); - return this._getItemValue(key, item); - } - _set(key, value) { - this.cache.set(key, value); - this._size++; - if (this._size >= this.maxSize) { - this._size = 0; - this._emitEvictions(this.oldCache); - this.oldCache = this.cache; - this.cache = /* @__PURE__ */ new Map(); - } - } - _moveToRecent(key, item) { - this.oldCache.delete(key); - this._set(key, item); - } - *_entriesAscending() { - for (const item of this.oldCache) { - const [key, value] = item; - if (!this.cache.has(key)) { - const deleted = this._deleteIfExpired(key, value); - if (deleted === false) { - yield item; - } - } - } - for (const item of this.cache) { - const [key, value] = item; - const deleted = this._deleteIfExpired(key, value); - if (deleted === false) { - yield item; - } - } - } - get(key) { - if (this.cache.has(key)) { - const item = this.cache.get(key); - return this._getItemValue(key, item); - } - if (this.oldCache.has(key)) { - const item = this.oldCache.get(key); - if (this._deleteIfExpired(key, item) === false) { - this._moveToRecent(key, item); - return item.value; - } - } - } - set(key, value, { maxAge = this.maxAge } = {}) { - const expiry = typeof maxAge === "number" && maxAge !== Number.POSITIVE_INFINITY ? Date.now() + maxAge : void 0; - if (this.cache.has(key)) { - this.cache.set(key, { - value, - expiry - }); - } else { - this._set(key, { value, expiry }); - } - return this; - } - has(key) { - if (this.cache.has(key)) { - return !this._deleteIfExpired(key, this.cache.get(key)); - } - if (this.oldCache.has(key)) { - return !this._deleteIfExpired(key, this.oldCache.get(key)); - } - return false; - } - peek(key) { - if (this.cache.has(key)) { - return this._peek(key, this.cache); - } - if (this.oldCache.has(key)) { - return this._peek(key, this.oldCache); - } - } - delete(key) { - const deleted = this.cache.delete(key); - if (deleted) { - this._size--; - } - return this.oldCache.delete(key) || deleted; - } - clear() { - this.cache.clear(); - this.oldCache.clear(); - this._size = 0; - } - resize(newSize) { - if (!(newSize && newSize > 0)) { - throw new TypeError("`maxSize` must be a number greater than 0"); - } - const items = [...this._entriesAscending()]; - const removeCount = items.length - newSize; - if (removeCount < 0) { - this.cache = new Map(items); - this.oldCache = /* @__PURE__ */ new Map(); - this._size = items.length; - } else { - if (removeCount > 0) { - this._emitEvictions(items.slice(0, removeCount)); - } - this.oldCache = new Map(items.slice(removeCount)); - this.cache = /* @__PURE__ */ new Map(); - this._size = 0; - } - this.maxSize = newSize; - } - *keys() { - for (const [key] of this) { - yield key; - } - } - *values() { - for (const [, value] of this) { - yield value; - } - } - *[Symbol.iterator]() { - for (const item of this.cache) { - const [key, value] = item; - const deleted = this._deleteIfExpired(key, value); - if (deleted === false) { - yield [key, value.value]; - } - } - for (const item of this.oldCache) { - const [key, value] = item; - if (!this.cache.has(key)) { - const deleted = this._deleteIfExpired(key, value); - if (deleted === false) { - yield [key, value.value]; - } - } - } - } - *entriesDescending() { - let items = [...this.cache]; - for (let i = items.length - 1; i >= 0; --i) { - const item = items[i]; - const [key, value] = item; - const deleted = this._deleteIfExpired(key, value); - if (deleted === false) { - yield [key, value.value]; - } - } - items = [...this.oldCache]; - for (let i = items.length - 1; i >= 0; --i) { - const item = items[i]; - const [key, value] = item; - if (!this.cache.has(key)) { - const deleted = this._deleteIfExpired(key, value); - if (deleted === false) { - yield [key, value.value]; - } - } - } - } - *entriesAscending() { - for (const [key, value] of this._entriesAscending()) { - yield [key, value.value]; - } - } - get size() { - if (!this._size) { - return this.oldCache.size; - } - let oldCacheSize = 0; - for (const key of this.oldCache.keys()) { - if (!this.cache.has(key)) { - oldCacheSize++; - } - } - return Math.min(this._size + oldCacheSize, this.maxSize); - } - entries() { - return this.entriesAscending(); - } - forEach(callbackFunction, thisArgument = this) { - for (const [key, value] of this.entriesAscending()) { - callbackFunction.call(thisArgument, value, key, this); - } - } - get [Symbol.toStringTag]() { - return JSON.stringify([...this.entriesAscending()]); - } - }; - - // node_modules/camelcase-keys/index.js - var has = (array, key) => array.some((element) => { - if (typeof element === "string") { - return element === key; - } - element.lastIndex = 0; - return element.test(key); - }); - var cache = new QuickLRU({ maxSize: 1e5 }); - var isObject2 = (value) => typeof value === "object" && value !== null && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date); - var transform = (input, options = {}) => { - if (!isObject2(input)) { - return input; - } - const { - exclude, - pascalCase = false, - stopPaths, - deep = false, - preserveConsecutiveUppercase: preserveConsecutiveUppercase2 = false - } = options; - const stopPathsSet = new Set(stopPaths); - const makeMapper = (parentPath) => (key, value) => { - if (deep && isObject2(value)) { - const path = parentPath === void 0 ? key : `${parentPath}.${key}`; - if (!stopPathsSet.has(path)) { - value = mapObject(value, makeMapper(path)); - } - } - if (!(exclude && has(exclude, key))) { - const cacheKey = pascalCase ? `${key}_` : key; - if (cache.has(cacheKey)) { - key = cache.get(cacheKey); - } else { - const returnValue = camelCase(key, { pascalCase, locale: false, preserveConsecutiveUppercase: preserveConsecutiveUppercase2 }); - if (key.length < 100) { - cache.set(cacheKey, returnValue); - } - key = returnValue; - } - } - return [key, value]; - }; - return mapObject(input, makeMapper(void 0)); - }; - function camelcaseKeys(input, options) { - if (Array.isArray(input)) { - return Object.keys(input).map((key) => transform(input[key], options)); - } - return transform(input, options); - } - - // node_modules/@logto/js/lib/consts/openid.js - var ReservedScope; - (function(ReservedScope2) { - ReservedScope2["OpenId"] = "openid"; - ReservedScope2["OfflineAccess"] = "offline_access"; - })(ReservedScope || (ReservedScope = {})); - var ReservedResource; - (function(ReservedResource2) { - ReservedResource2["Organization"] = "urn:logto:resource:organizations"; - })(ReservedResource || (ReservedResource = {})); - var UserScope; - (function(UserScope2) { - UserScope2["Profile"] = "profile"; - UserScope2["Email"] = "email"; - UserScope2["Phone"] = "phone"; - UserScope2["Address"] = "address"; - UserScope2["CustomData"] = "custom_data"; - UserScope2["Identities"] = "identities"; - UserScope2["Roles"] = "roles"; - UserScope2["Organizations"] = "urn:logto:scope:organizations"; - UserScope2["OrganizationRoles"] = "urn:logto:scope:organization_roles"; - UserScope2["Sessions"] = "urn:logto:scope:sessions"; - })(UserScope || (UserScope = {})); - var idTokenClaims = Object.freeze({ - [UserScope.Profile]: ["name", "picture", "username"], - [UserScope.Email]: ["email", "email_verified"], - [UserScope.Phone]: ["phone_number", "phone_number_verified"], - [UserScope.Address]: [], - [UserScope.Roles]: ["roles"], - [UserScope.Organizations]: ["organizations"], - [UserScope.OrganizationRoles]: ["organization_roles"], - [UserScope.CustomData]: [], - [UserScope.Identities]: [], - [UserScope.Sessions]: [] - }); - var userinfoClaims = Object.freeze({ - [UserScope.Profile]: [], - [UserScope.Email]: [], - [UserScope.Phone]: [], - [UserScope.Address]: [], - [UserScope.Roles]: [], - [UserScope.Organizations]: [], - [UserScope.OrganizationRoles]: [], - [UserScope.CustomData]: ["custom_data"], - [UserScope.Identities]: ["identities"], - [UserScope.Sessions]: [] - }); - var userClaims = Object.freeze( - // Hard to infer type directly, use `as` for a workaround. - // eslint-disable-next-line no-restricted-syntax - Object.fromEntries(Object.values(UserScope).map((current) => [ - current, - [...idTokenClaims[current], ...userinfoClaims[current]] - ])) - ); - - // node_modules/@logto/js/lib/consts/index.js - var ContentType = { - formUrlEncoded: { "Content-Type": "application/x-www-form-urlencoded" } - }; - var TokenGrantType; - (function(TokenGrantType2) { - TokenGrantType2["AuthorizationCode"] = "authorization_code"; - TokenGrantType2["RefreshToken"] = "refresh_token"; - })(TokenGrantType || (TokenGrantType = {})); - var QueryKey; - (function(QueryKey2) { - QueryKey2["ClientId"] = "client_id"; - QueryKey2["Code"] = "code"; - QueryKey2["CodeChallenge"] = "code_challenge"; - QueryKey2["CodeChallengeMethod"] = "code_challenge_method"; - QueryKey2["CodeVerifier"] = "code_verifier"; - QueryKey2["Error"] = "error"; - QueryKey2["ErrorDescription"] = "error_description"; - QueryKey2["GrantType"] = "grant_type"; - QueryKey2["IdToken"] = "id_token"; - QueryKey2["IdTokenHint"] = "id_token_hint"; - QueryKey2["LoginHint"] = "login_hint"; - QueryKey2["PostLogoutRedirectUri"] = "post_logout_redirect_uri"; - QueryKey2["Prompt"] = "prompt"; - QueryKey2["RedirectUri"] = "redirect_uri"; - QueryKey2["RefreshToken"] = "refresh_token"; - QueryKey2["Resource"] = "resource"; - QueryKey2["ResponseType"] = "response_type"; - QueryKey2["Scope"] = "scope"; - QueryKey2["State"] = "state"; - QueryKey2["Token"] = "token"; - QueryKey2["InteractionMode"] = "interaction_mode"; - QueryKey2["OrganizationId"] = "organization_id"; - QueryKey2["FirstScreen"] = "first_screen"; - QueryKey2["Identifier"] = "identifier"; - QueryKey2["DirectSignIn"] = "direct_sign_in"; - QueryKey2["OneTimeToken"] = "one_time_token"; - })(QueryKey || (QueryKey = {})); - var Prompt; - (function(Prompt2) { - Prompt2["None"] = "none"; - Prompt2["Consent"] = "consent"; - Prompt2["Login"] = "login"; - })(Prompt || (Prompt = {})); - - // node_modules/@logto/js/lib/core/fetch-token.js - var fetchTokenByAuthorizationCode = async ({ clientId, tokenEndpoint, redirectUri, codeVerifier, code, resource }, requester) => { - const parameters = new URLSearchParams(); - parameters.append(QueryKey.ClientId, clientId); - parameters.append(QueryKey.Code, code); - parameters.append(QueryKey.CodeVerifier, codeVerifier); - parameters.append(QueryKey.RedirectUri, redirectUri); - parameters.append(QueryKey.GrantType, TokenGrantType.AuthorizationCode); - if (resource) { - parameters.append(QueryKey.Resource, resource); - } - const snakeCaseCodeTokenResponse = await requester(tokenEndpoint, { - method: "POST", - headers: ContentType.formUrlEncoded, - body: parameters.toString() - }); - return camelcaseKeys(snakeCaseCodeTokenResponse); - }; - var fetchTokenByRefreshToken = async (params, requester) => { - const { clientId, tokenEndpoint, refreshToken, resource, organizationId, scopes } = params; - const parameters = new URLSearchParams(); - parameters.append(QueryKey.ClientId, clientId); - parameters.append(QueryKey.RefreshToken, refreshToken); - parameters.append(QueryKey.GrantType, TokenGrantType.RefreshToken); - if (resource) { - parameters.append(QueryKey.Resource, resource); - } - if (organizationId) { - parameters.append(QueryKey.OrganizationId, organizationId); - } - if (scopes?.length) { - parameters.append(QueryKey.Scope, scopes.join(" ")); - } - const snakeCaseRefreshTokenTokenResponse = await requester(tokenEndpoint, { - method: "POST", - headers: ContentType.formUrlEncoded, - body: parameters.toString() - }); - return camelcaseKeys(snakeCaseRefreshTokenTokenResponse); - }; - - // node_modules/@logto/js/lib/core/oidc-config.js - var discoveryPath = "/oidc/.well-known/openid-configuration"; - var fetchOidcConfig = async (endpoint, requester) => camelcaseKeys(await requester(endpoint)); - - // node_modules/@logto/js/lib/core/revoke.js - var revoke = async (revocationEndpoint, clientId, token, requester) => requester(revocationEndpoint, { - method: "POST", - headers: ContentType.formUrlEncoded, - body: new URLSearchParams({ - [QueryKey.ClientId]: clientId, - [QueryKey.Token]: token - }).toString() - }); - - // node_modules/@logto/js/lib/utils/scopes.js - var withReservedScopes = (originalScopes) => { - const reservedScopes = Object.values(ReservedScope); - const uniqueScopes = /* @__PURE__ */ new Set([...reservedScopes, UserScope.Profile, ...originalScopes ?? []]); - return Array.from(uniqueScopes).join(" "); - }; - - // node_modules/@logto/js/lib/core/sign-in.js - var codeChallengeMethod = "S256"; - var responseType = "code"; - var buildPrompt = (prompt) => { - if (Array.isArray(prompt)) { - return prompt.join(" "); - } - return prompt ?? Prompt.Consent; - }; - var generateSignInUri = ({ authorizationEndpoint, clientId, redirectUri, codeChallenge, state, scopes, resources, prompt, firstScreen, identifiers: identifier, interactionMode, loginHint, directSignIn, oneTimeToken, extraParams, includeReservedScopes = true }) => { - const urlSearchParameters = new URLSearchParams({ - [QueryKey.ClientId]: clientId, - [QueryKey.RedirectUri]: redirectUri, - [QueryKey.CodeChallenge]: codeChallenge, - [QueryKey.CodeChallengeMethod]: codeChallengeMethod, - [QueryKey.State]: state, - [QueryKey.ResponseType]: responseType, - [QueryKey.Prompt]: buildPrompt(prompt) - }); - const computedScopes = includeReservedScopes ? withReservedScopes(scopes) : scopes?.join(" "); - if (computedScopes) { - urlSearchParameters.append(QueryKey.Scope, computedScopes); - } - if (loginHint) { - urlSearchParameters.append(QueryKey.LoginHint, loginHint); - } - if (directSignIn) { - urlSearchParameters.append(QueryKey.DirectSignIn, `${directSignIn.method}:${directSignIn.target}`); - } - for (const resource of resources ?? []) { - urlSearchParameters.append(QueryKey.Resource, resource); - } - if (firstScreen) { - urlSearchParameters.append(QueryKey.FirstScreen, firstScreen); - } else if (interactionMode) { - urlSearchParameters.append(QueryKey.InteractionMode, interactionMode); - } - if (identifier && identifier.length > 0) { - urlSearchParameters.append(QueryKey.Identifier, identifier.join(" ")); - } - if (oneTimeToken) { - urlSearchParameters.append(QueryKey.OneTimeToken, oneTimeToken); - } - if (extraParams) { - for (const [key, value] of Object.entries(extraParams)) { - urlSearchParameters.append(key, value); - } - } - return `${authorizationEndpoint}?${urlSearchParameters.toString()}`; - }; - - // node_modules/@logto/js/lib/core/sign-out.js - var generateSignOutUri = ({ endSessionEndpoint, clientId, postLogoutRedirectUri }) => { - const urlSearchParameters = new URLSearchParams({ [QueryKey.ClientId]: clientId }); - if (postLogoutRedirectUri) { - urlSearchParameters.append(QueryKey.PostLogoutRedirectUri, postLogoutRedirectUri); - } - return `${endSessionEndpoint}?${urlSearchParameters.toString()}`; - }; - - // node_modules/@logto/js/lib/core/user-info.js - var fetchUserInfo = async (userInfoEndpoint, accessToken, requester) => requester(userInfoEndpoint, { - headers: { Authorization: `Bearer ${accessToken}` } - }); - - // node_modules/@silverhand/essentials/lib/utilities/array.js - var deduplicate = (array) => [...new Set(array)]; - - // node_modules/@silverhand/essentials/lib/utilities/assertions.js - var notFalsy = (value) => Boolean(value); - - // node_modules/@silverhand/essentials/lib/utilities/conditional.js - var conditional = (exp) => notFalsy(exp) ? exp : void 0; - var conditionalString = (exp) => notFalsy(exp) ? String(exp) : ""; - - // node_modules/@silverhand/essentials/lib/utilities/function.js - var isPromise = (value) => value !== null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function"; - var trySafe = (exec, onError) => { - try { - const unwrapped = typeof exec === "function" ? exec() : exec; - return isPromise(unwrapped) ? ( - // eslint-disable-next-line promise/prefer-await-to-then - unwrapped.catch((error) => { - onError?.(error); - }) - ) : unwrapped; - } catch (error) { - onError?.(error); - } - }; - - // node_modules/@silverhand/essentials/lib/utilities/string.js - var replaceNonUrlSafeCharacters = (base64String) => base64String.replaceAll("+", "-").replaceAll("/", "_").replaceAll(/=+$/g, ""); - var restoreNonUrlSafeCharacters = (base64String) => base64String.replaceAll("-", "+").replaceAll("_", "/"); - var urlSafeBase64 = { - isSafe: (input) => /^[\w-]*$/.test(input), - encode: (rawString) => { - const encodedString = btoa(unescape(encodeURIComponent(rawString))); - return replaceNonUrlSafeCharacters(encodedString); - }, - decode: (encodedString) => { - const nonUrlSafeEncodedString = restoreNonUrlSafeCharacters(encodedString); - return decodeURIComponent(escape(atob(nonUrlSafeEncodedString))); - }, - replaceNonUrlSafeCharacters, - restoreNonUrlSafeCharacters - }; - - // node_modules/@silverhand/essentials/lib/utilities/url.js - var joinPath = (...segments) => { - const result = []; - for (const segment of segments.join("/").split("/")) { - if (!segment || segment === ".") { - continue; - } - if ([...segment].every((char) => char === ".")) { - result.pop(); - continue; - } - result.push(segment); - } - return "/" + result.join("/"); - }; - var appendPath = (baseUrl, ...pathnames) => new URL(joinPath(baseUrl.pathname, ...pathnames), baseUrl); - - // node_modules/@logto/js/lib/utils/arbitrary-object.js - var isArbitraryObject = (data) => typeof data === "object" && data !== null; - - // node_modules/@logto/js/lib/utils/errors.js - var logtoErrorCodes = Object.freeze({ - "id_token.invalid_iat": "Invalid issued at time in the ID token", - "id_token.invalid_token": "Invalid ID token", - "callback_uri_verification.redirect_uri_mismatched": "The callback URI mismatches the redirect URI.", - "callback_uri_verification.error_found": "Error found in the callback URI", - "callback_uri_verification.missing_state": "Missing state in the callback URI", - "callback_uri_verification.state_mismatched": "State mismatched in the callback URI", - "callback_uri_verification.missing_code": "Missing code in the callback URI", - crypto_subtle_unavailable: "Crypto.subtle is unavailable in insecure contexts (non-HTTPS).", - unexpected_response_error: "Unexpected response error from the server." - }); - var LogtoError = class extends Error { - constructor(code, data) { - super(logtoErrorCodes[code]); - this.code = code; - this.data = data; - this.name = "LogtoError"; - } - }; - var isLogtoRequestErrorJson = (data) => { - if (!isArbitraryObject(data)) { - return false; - } - return typeof data.code === "string" && typeof data.message === "string"; - }; - var LogtoRequestError = class extends Error { - constructor(code, message2, cause) { - super(message2); - this.code = code; - this.cause = cause; - this.name = "LogtoRequestError"; - } - }; - var OidcError = class { - constructor(error, errorDescription) { - this.error = error; - this.errorDescription = errorDescription; - this.name = "OidcError"; - } - }; - - // node_modules/@logto/js/lib/utils/callback-uri.js - var parseUriParameters = (uri) => { - const [, queryString = ""] = uri.split("?"); - return new URLSearchParams(queryString); - }; - var verifyAndParseCodeFromCallbackUri = (callbackUri, redirectUri, state) => { - if (!callbackUri.startsWith(redirectUri)) { - throw new LogtoError("callback_uri_verification.redirect_uri_mismatched"); - } - const uriParameters = parseUriParameters(callbackUri); - const error = conditional(uriParameters.get(QueryKey.Error)); - const errorDescription = conditional(uriParameters.get(QueryKey.ErrorDescription)); - if (error) { - throw new LogtoError("callback_uri_verification.error_found", new OidcError(error, errorDescription)); - } - const stateFromCallbackUri = uriParameters.get(QueryKey.State); - if (!stateFromCallbackUri) { - throw new LogtoError("callback_uri_verification.missing_state"); - } - if (stateFromCallbackUri !== state) { - throw new LogtoError("callback_uri_verification.state_mismatched"); - } - const code = uriParameters.get(QueryKey.Code); - if (!code) { - throw new LogtoError("callback_uri_verification.missing_code"); - } - return code; - }; - - // node_modules/@logto/js/lib/utils/id-token.js - function assertIdTokenClaims(data) { - if (!isArbitraryObject(data)) { - throw new TypeError("IdToken is expected to be an object"); - } - for (const key of ["iss", "sub", "aud"]) { - if (typeof data[key] !== "string") { - throw new TypeError(`At path: IdToken.${key}: expected a string`); - } - } - for (const key of ["exp", "iat"]) { - if (typeof data[key] !== "number") { - throw new TypeError(`At path: IdToken.${key}: expected a number`); - } - } - for (const key of ["at_hash", "name", "username", "picture", "email", "phone_number"]) { - if (data[key] === void 0) { - continue; - } - if (typeof data[key] !== "string" && data[key] !== null) { - throw new TypeError(`At path: IdToken.${key}: expected null or a string`); - } - } - for (const key of ["email_verified", "phone_number_verified"]) { - if (data[key] === void 0) { - continue; - } - if (typeof data[key] !== "boolean") { - throw new TypeError(`At path: IdToken.${key}: expected a boolean`); - } - } - } - var decodeIdToken = (token) => { - const { 1: encodedPayload } = token.split("."); - if (!encodedPayload) { - throw new LogtoError("id_token.invalid_token"); - } - const json = urlSafeBase64.decode(encodedPayload); - const idTokenClaims2 = JSON.parse(json); - assertIdTokenClaims(idTokenClaims2); - return idTokenClaims2; - }; - - // node_modules/@logto/js/lib/utils/access-token.js - function assertAccessTokenClaims(data) { - if (!isArbitraryObject(data)) { - throw new TypeError("AccessToken is expected to be an object"); - } - for (const key of ["jti", "iss", "sub", "aud", "client_id", "scope"]) { - if (data[key] === void 0) { - continue; - } - if (typeof data[key] !== "string" && data[key] !== null) { - throw new TypeError(`At path: AccessToken.${key}: expected null or a string`); - } - } - for (const key of ["exp", "iat"]) { - if (data[key] === void 0) { - continue; - } - if (typeof data[key] !== "number" && data[key] !== null) { - throw new TypeError(`At path: AccessToken.${key}: expected null or a number`); - } - } - } - var decodeAccessToken = (accessToken) => { - const { 1: encodedPayload } = accessToken.split("."); - if (!encodedPayload) { - return {}; - } - const json = urlSafeBase64.decode(encodedPayload); - const accessTokenClaims = JSON.parse(json); - assertAccessTokenClaims(accessTokenClaims); - return accessTokenClaims; - }; - - // node_modules/jose/dist/browser/runtime/webcrypto.js - var webcrypto_default = crypto; - var isCryptoKey = (key) => key instanceof CryptoKey; - - // node_modules/jose/dist/browser/lib/buffer_utils.js - var encoder = new TextEncoder(); - var decoder = new TextDecoder(); - var MAX_INT32 = 2 ** 32; - function concat(...buffers) { - const size = buffers.reduce((acc, { length }) => acc + length, 0); - const buf = new Uint8Array(size); - let i = 0; - for (const buffer of buffers) { - buf.set(buffer, i); - i += buffer.length; - } - return buf; - } - - // node_modules/jose/dist/browser/runtime/base64url.js - var decodeBase64 = (encoded) => { - const binary = atob(encoded); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - return bytes; - }; - var decode = (input) => { - let encoded = input; - if (encoded instanceof Uint8Array) { - encoded = decoder.decode(encoded); - } - encoded = encoded.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""); - try { - return decodeBase64(encoded); - } catch { - throw new TypeError("The input to be decoded is not correctly encoded."); - } - }; - - // node_modules/jose/dist/browser/util/errors.js - var JOSEError = class extends Error { - constructor(message2, options) { - super(message2, options); - this.code = "ERR_JOSE_GENERIC"; - this.name = this.constructor.name; - Error.captureStackTrace?.(this, this.constructor); - } - }; - JOSEError.code = "ERR_JOSE_GENERIC"; - var JWTClaimValidationFailed = class extends JOSEError { - constructor(message2, payload, claim = "unspecified", reason = "unspecified") { - super(message2, { cause: { claim, reason, payload } }); - this.code = "ERR_JWT_CLAIM_VALIDATION_FAILED"; - this.claim = claim; - this.reason = reason; - this.payload = payload; - } - }; - JWTClaimValidationFailed.code = "ERR_JWT_CLAIM_VALIDATION_FAILED"; - var JWTExpired = class extends JOSEError { - constructor(message2, payload, claim = "unspecified", reason = "unspecified") { - super(message2, { cause: { claim, reason, payload } }); - this.code = "ERR_JWT_EXPIRED"; - this.claim = claim; - this.reason = reason; - this.payload = payload; - } - }; - JWTExpired.code = "ERR_JWT_EXPIRED"; - var JOSEAlgNotAllowed = class extends JOSEError { - constructor() { - super(...arguments); - this.code = "ERR_JOSE_ALG_NOT_ALLOWED"; - } - }; - JOSEAlgNotAllowed.code = "ERR_JOSE_ALG_NOT_ALLOWED"; - var JOSENotSupported = class extends JOSEError { - constructor() { - super(...arguments); - this.code = "ERR_JOSE_NOT_SUPPORTED"; - } - }; - JOSENotSupported.code = "ERR_JOSE_NOT_SUPPORTED"; - var JWEDecryptionFailed = class extends JOSEError { - constructor(message2 = "decryption operation failed", options) { - super(message2, options); - this.code = "ERR_JWE_DECRYPTION_FAILED"; - } - }; - JWEDecryptionFailed.code = "ERR_JWE_DECRYPTION_FAILED"; - var JWEInvalid = class extends JOSEError { - constructor() { - super(...arguments); - this.code = "ERR_JWE_INVALID"; - } - }; - JWEInvalid.code = "ERR_JWE_INVALID"; - var JWSInvalid = class extends JOSEError { - constructor() { - super(...arguments); - this.code = "ERR_JWS_INVALID"; - } - }; - JWSInvalid.code = "ERR_JWS_INVALID"; - var JWTInvalid = class extends JOSEError { - constructor() { - super(...arguments); - this.code = "ERR_JWT_INVALID"; - } - }; - JWTInvalid.code = "ERR_JWT_INVALID"; - var JWKInvalid = class extends JOSEError { - constructor() { - super(...arguments); - this.code = "ERR_JWK_INVALID"; - } - }; - JWKInvalid.code = "ERR_JWK_INVALID"; - var JWKSInvalid = class extends JOSEError { - constructor() { - super(...arguments); - this.code = "ERR_JWKS_INVALID"; - } - }; - JWKSInvalid.code = "ERR_JWKS_INVALID"; - var JWKSNoMatchingKey = class extends JOSEError { - constructor(message2 = "no applicable key found in the JSON Web Key Set", options) { - super(message2, options); - this.code = "ERR_JWKS_NO_MATCHING_KEY"; - } - }; - JWKSNoMatchingKey.code = "ERR_JWKS_NO_MATCHING_KEY"; - var JWKSMultipleMatchingKeys = class extends JOSEError { - constructor(message2 = "multiple matching keys found in the JSON Web Key Set", options) { - super(message2, options); - this.code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS"; - } - }; - JWKSMultipleMatchingKeys.code = "ERR_JWKS_MULTIPLE_MATCHING_KEYS"; - var JWKSTimeout = class extends JOSEError { - constructor(message2 = "request timed out", options) { - super(message2, options); - this.code = "ERR_JWKS_TIMEOUT"; - } - }; - JWKSTimeout.code = "ERR_JWKS_TIMEOUT"; - var JWSSignatureVerificationFailed = class extends JOSEError { - constructor(message2 = "signature verification failed", options) { - super(message2, options); - this.code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED"; - } - }; - JWSSignatureVerificationFailed.code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED"; - - // node_modules/jose/dist/browser/lib/crypto_key.js - function unusable(name, prop = "algorithm.name") { - return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`); - } - function isAlgorithm(algorithm, name) { - return algorithm.name === name; - } - function getHashLength(hash) { - return parseInt(hash.name.slice(4), 10); - } - function getNamedCurve(alg) { - switch (alg) { - case "ES256": - return "P-256"; - case "ES384": - return "P-384"; - case "ES512": - return "P-521"; - default: - throw new Error("unreachable"); - } - } - function checkUsage(key, usages) { - if (usages.length && !usages.some((expected) => key.usages.includes(expected))) { - let msg = "CryptoKey does not support this operation, its usages must include "; - if (usages.length > 2) { - const last = usages.pop(); - msg += `one of ${usages.join(", ")}, or ${last}.`; - } else if (usages.length === 2) { - msg += `one of ${usages[0]} or ${usages[1]}.`; - } else { - msg += `${usages[0]}.`; - } - throw new TypeError(msg); - } - } - function checkSigCryptoKey(key, alg, ...usages) { - switch (alg) { - case "HS256": - case "HS384": - case "HS512": { - if (!isAlgorithm(key.algorithm, "HMAC")) - throw unusable("HMAC"); - const expected = parseInt(alg.slice(2), 10); - const actual = getHashLength(key.algorithm.hash); - if (actual !== expected) - throw unusable(`SHA-${expected}`, "algorithm.hash"); - break; - } - case "RS256": - case "RS384": - case "RS512": { - if (!isAlgorithm(key.algorithm, "RSASSA-PKCS1-v1_5")) - throw unusable("RSASSA-PKCS1-v1_5"); - const expected = parseInt(alg.slice(2), 10); - const actual = getHashLength(key.algorithm.hash); - if (actual !== expected) - throw unusable(`SHA-${expected}`, "algorithm.hash"); - break; - } - case "PS256": - case "PS384": - case "PS512": { - if (!isAlgorithm(key.algorithm, "RSA-PSS")) - throw unusable("RSA-PSS"); - const expected = parseInt(alg.slice(2), 10); - const actual = getHashLength(key.algorithm.hash); - if (actual !== expected) - throw unusable(`SHA-${expected}`, "algorithm.hash"); - break; - } - case "EdDSA": { - if (key.algorithm.name !== "Ed25519" && key.algorithm.name !== "Ed448") { - throw unusable("Ed25519 or Ed448"); - } - break; - } - case "Ed25519": { - if (!isAlgorithm(key.algorithm, "Ed25519")) - throw unusable("Ed25519"); - break; - } - case "ES256": - case "ES384": - case "ES512": { - if (!isAlgorithm(key.algorithm, "ECDSA")) - throw unusable("ECDSA"); - const expected = getNamedCurve(alg); - const actual = key.algorithm.namedCurve; - if (actual !== expected) - throw unusable(expected, "algorithm.namedCurve"); - break; - } - default: - throw new TypeError("CryptoKey does not support this operation"); - } - checkUsage(key, usages); - } - - // node_modules/jose/dist/browser/lib/invalid_key_input.js - function message(msg, actual, ...types2) { - types2 = types2.filter(Boolean); - if (types2.length > 2) { - const last = types2.pop(); - msg += `one of type ${types2.join(", ")}, or ${last}.`; - } else if (types2.length === 2) { - msg += `one of type ${types2[0]} or ${types2[1]}.`; - } else { - msg += `of type ${types2[0]}.`; - } - if (actual == null) { - msg += ` Received ${actual}`; - } else if (typeof actual === "function" && actual.name) { - msg += ` Received function ${actual.name}`; - } else if (typeof actual === "object" && actual != null) { - if (actual.constructor?.name) { - msg += ` Received an instance of ${actual.constructor.name}`; - } - } - return msg; - } - var invalid_key_input_default = (actual, ...types2) => { - return message("Key must be ", actual, ...types2); - }; - function withAlg(alg, actual, ...types2) { - return message(`Key for the ${alg} algorithm must be `, actual, ...types2); - } - - // node_modules/jose/dist/browser/runtime/is_key_like.js - var is_key_like_default = (key) => { - if (isCryptoKey(key)) { - return true; - } - return key?.[Symbol.toStringTag] === "KeyObject"; - }; - var types = ["CryptoKey"]; - - // node_modules/jose/dist/browser/lib/is_disjoint.js - var isDisjoint = (...headers) => { - const sources = headers.filter(Boolean); - if (sources.length === 0 || sources.length === 1) { - return true; - } - let acc; - for (const header of sources) { - const parameters = Object.keys(header); - if (!acc || acc.size === 0) { - acc = new Set(parameters); - continue; - } - for (const parameter of parameters) { - if (acc.has(parameter)) { - return false; - } - acc.add(parameter); - } - } - return true; - }; - var is_disjoint_default = isDisjoint; - - // node_modules/jose/dist/browser/lib/is_object.js - function isObjectLike(value) { - return typeof value === "object" && value !== null; - } - function isObject4(input) { - if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") { - return false; - } - if (Object.getPrototypeOf(input) === null) { - return true; - } - let proto = input; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - return Object.getPrototypeOf(input) === proto; - } - - // node_modules/jose/dist/browser/runtime/check_key_length.js - var check_key_length_default = (alg, key) => { - if (alg.startsWith("RS") || alg.startsWith("PS")) { - const { modulusLength } = key.algorithm; - if (typeof modulusLength !== "number" || modulusLength < 2048) { - throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`); - } - } - }; - - // node_modules/jose/dist/browser/lib/is_jwk.js - function isJWK(key) { - return isObject4(key) && typeof key.kty === "string"; - } - function isPrivateJWK(key) { - return key.kty !== "oct" && typeof key.d === "string"; - } - function isPublicJWK(key) { - return key.kty !== "oct" && typeof key.d === "undefined"; - } - function isSecretJWK(key) { - return isJWK(key) && key.kty === "oct" && typeof key.k === "string"; - } - - // node_modules/jose/dist/browser/runtime/jwk_to_key.js - function subtleMapping(jwk) { - let algorithm; - let keyUsages; - switch (jwk.kty) { - case "RSA": { - switch (jwk.alg) { - case "PS256": - case "PS384": - case "PS512": - algorithm = { name: "RSA-PSS", hash: `SHA-${jwk.alg.slice(-3)}` }; - keyUsages = jwk.d ? ["sign"] : ["verify"]; - break; - case "RS256": - case "RS384": - case "RS512": - algorithm = { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${jwk.alg.slice(-3)}` }; - keyUsages = jwk.d ? ["sign"] : ["verify"]; - break; - case "RSA-OAEP": - case "RSA-OAEP-256": - case "RSA-OAEP-384": - case "RSA-OAEP-512": - algorithm = { - name: "RSA-OAEP", - hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}` - }; - keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"]; - break; - default: - throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); - } - break; - } - case "EC": { - switch (jwk.alg) { - case "ES256": - algorithm = { name: "ECDSA", namedCurve: "P-256" }; - keyUsages = jwk.d ? ["sign"] : ["verify"]; - break; - case "ES384": - algorithm = { name: "ECDSA", namedCurve: "P-384" }; - keyUsages = jwk.d ? ["sign"] : ["verify"]; - break; - case "ES512": - algorithm = { name: "ECDSA", namedCurve: "P-521" }; - keyUsages = jwk.d ? ["sign"] : ["verify"]; - break; - case "ECDH-ES": - case "ECDH-ES+A128KW": - case "ECDH-ES+A192KW": - case "ECDH-ES+A256KW": - algorithm = { name: "ECDH", namedCurve: jwk.crv }; - keyUsages = jwk.d ? ["deriveBits"] : []; - break; - default: - throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); - } - break; - } - case "OKP": { - switch (jwk.alg) { - case "Ed25519": - algorithm = { name: "Ed25519" }; - keyUsages = jwk.d ? ["sign"] : ["verify"]; - break; - case "EdDSA": - algorithm = { name: jwk.crv }; - keyUsages = jwk.d ? ["sign"] : ["verify"]; - break; - case "ECDH-ES": - case "ECDH-ES+A128KW": - case "ECDH-ES+A192KW": - case "ECDH-ES+A256KW": - algorithm = { name: jwk.crv }; - keyUsages = jwk.d ? ["deriveBits"] : []; - break; - default: - throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value'); - } - break; - } - default: - throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value'); - } - return { algorithm, keyUsages }; - } - var parse = async (jwk) => { - if (!jwk.alg) { - throw new TypeError('"alg" argument is required when "jwk.alg" is not present'); - } - const { algorithm, keyUsages } = subtleMapping(jwk); - const rest = [ - algorithm, - jwk.ext ?? false, - jwk.key_ops ?? keyUsages - ]; - const keyData = { ...jwk }; - delete keyData.alg; - delete keyData.use; - return webcrypto_default.subtle.importKey("jwk", keyData, ...rest); - }; - var jwk_to_key_default = parse; - - // node_modules/jose/dist/browser/runtime/normalize_key.js - var exportKeyValue = (k) => decode(k); - var privCache; - var pubCache; - var isKeyObject = (key) => { - return key?.[Symbol.toStringTag] === "KeyObject"; - }; - var importAndCache = async (cache2, key, jwk, alg, freeze = false) => { - let cached = cache2.get(key); - if (cached?.[alg]) { - return cached[alg]; - } - const cryptoKey = await jwk_to_key_default({ ...jwk, alg }); - if (freeze) - Object.freeze(key); - if (!cached) { - cache2.set(key, { [alg]: cryptoKey }); - } else { - cached[alg] = cryptoKey; - } - return cryptoKey; - }; - var normalizePublicKey = (key, alg) => { - if (isKeyObject(key)) { - let jwk = key.export({ format: "jwk" }); - delete jwk.d; - delete jwk.dp; - delete jwk.dq; - delete jwk.p; - delete jwk.q; - delete jwk.qi; - if (jwk.k) { - return exportKeyValue(jwk.k); - } - pubCache || (pubCache = /* @__PURE__ */ new WeakMap()); - return importAndCache(pubCache, key, jwk, alg); - } - if (isJWK(key)) { - if (key.k) - return decode(key.k); - pubCache || (pubCache = /* @__PURE__ */ new WeakMap()); - const cryptoKey = importAndCache(pubCache, key, key, alg, true); - return cryptoKey; - } - return key; - }; - var normalizePrivateKey = (key, alg) => { - if (isKeyObject(key)) { - let jwk = key.export({ format: "jwk" }); - if (jwk.k) { - return exportKeyValue(jwk.k); - } - privCache || (privCache = /* @__PURE__ */ new WeakMap()); - return importAndCache(privCache, key, jwk, alg); - } - if (isJWK(key)) { - if (key.k) - return decode(key.k); - privCache || (privCache = /* @__PURE__ */ new WeakMap()); - const cryptoKey = importAndCache(privCache, key, key, alg, true); - return cryptoKey; - } - return key; - }; - var normalize_key_default = { normalizePublicKey, normalizePrivateKey }; - - // node_modules/jose/dist/browser/key/import.js - async function importJWK(jwk, alg) { - if (!isObject4(jwk)) { - throw new TypeError("JWK must be an object"); - } - alg || (alg = jwk.alg); - switch (jwk.kty) { - case "oct": - if (typeof jwk.k !== "string" || !jwk.k) { - throw new TypeError('missing "k" (Key Value) Parameter value'); - } - return decode(jwk.k); - case "RSA": - if ("oth" in jwk && jwk.oth !== void 0) { - throw new JOSENotSupported('RSA JWK "oth" (Other Primes Info) Parameter value is not supported'); - } - case "EC": - case "OKP": - return jwk_to_key_default({ ...jwk, alg }); - default: - throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value'); - } - } - - // node_modules/jose/dist/browser/lib/check_key_type.js - var tag = (key) => key?.[Symbol.toStringTag]; - var jwkMatchesOp = (alg, key, usage) => { - if (key.use !== void 0 && key.use !== "sig") { - throw new TypeError("Invalid key for this operation, when present its use must be sig"); - } - if (key.key_ops !== void 0 && key.key_ops.includes?.(usage) !== true) { - throw new TypeError(`Invalid key for this operation, when present its key_ops must include ${usage}`); - } - if (key.alg !== void 0 && key.alg !== alg) { - throw new TypeError(`Invalid key for this operation, when present its alg must be ${alg}`); - } - return true; - }; - var symmetricTypeCheck = (alg, key, usage, allowJwk) => { - if (key instanceof Uint8Array) - return; - if (allowJwk && isJWK(key)) { - if (isSecretJWK(key) && jwkMatchesOp(alg, key, usage)) - return; - throw new TypeError(`JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present`); - } - if (!is_key_like_default(key)) { - throw new TypeError(withAlg(alg, key, ...types, "Uint8Array", allowJwk ? "JSON Web Key" : null)); - } - if (key.type !== "secret") { - throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`); - } - }; - var asymmetricTypeCheck = (alg, key, usage, allowJwk) => { - if (allowJwk && isJWK(key)) { - switch (usage) { - case "sign": - if (isPrivateJWK(key) && jwkMatchesOp(alg, key, usage)) - return; - throw new TypeError(`JSON Web Key for this operation be a private JWK`); - case "verify": - if (isPublicJWK(key) && jwkMatchesOp(alg, key, usage)) - return; - throw new TypeError(`JSON Web Key for this operation be a public JWK`); - } - } - if (!is_key_like_default(key)) { - throw new TypeError(withAlg(alg, key, ...types, allowJwk ? "JSON Web Key" : null)); - } - if (key.type === "secret") { - throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`); - } - if (usage === "sign" && key.type === "public") { - throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`); - } - if (usage === "decrypt" && key.type === "public") { - throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`); - } - if (key.algorithm && usage === "verify" && key.type === "private") { - throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`); - } - if (key.algorithm && usage === "encrypt" && key.type === "private") { - throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`); - } - }; - function checkKeyType(allowJwk, alg, key, usage) { - const symmetric = alg.startsWith("HS") || alg === "dir" || alg.startsWith("PBES2") || /^A\d{3}(?:GCM)?KW$/.test(alg); - if (symmetric) { - symmetricTypeCheck(alg, key, usage, allowJwk); - } else { - asymmetricTypeCheck(alg, key, usage, allowJwk); - } - } - var check_key_type_default = checkKeyType.bind(void 0, false); - var checkKeyTypeWithJwk = checkKeyType.bind(void 0, true); - - // node_modules/jose/dist/browser/lib/validate_crit.js - function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) { - if (joseHeader.crit !== void 0 && protectedHeader?.crit === void 0) { - throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected'); - } - if (!protectedHeader || protectedHeader.crit === void 0) { - return /* @__PURE__ */ new Set(); - } - if (!Array.isArray(protectedHeader.crit) || protectedHeader.crit.length === 0 || protectedHeader.crit.some((input) => typeof input !== "string" || input.length === 0)) { - throw new Err('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present'); - } - let recognized; - if (recognizedOption !== void 0) { - recognized = new Map([...Object.entries(recognizedOption), ...recognizedDefault.entries()]); - } else { - recognized = recognizedDefault; - } - for (const parameter of protectedHeader.crit) { - if (!recognized.has(parameter)) { - throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`); - } - if (joseHeader[parameter] === void 0) { - throw new Err(`Extension Header Parameter "${parameter}" is missing`); - } - if (recognized.get(parameter) && protectedHeader[parameter] === void 0) { - throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`); - } - } - return new Set(protectedHeader.crit); - } - var validate_crit_default = validateCrit; - - // node_modules/jose/dist/browser/lib/validate_algorithms.js - var validateAlgorithms = (option, algorithms) => { - if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== "string"))) { - throw new TypeError(`"${option}" option must be an array of strings`); - } - if (!algorithms) { - return void 0; - } - return new Set(algorithms); - }; - var validate_algorithms_default = validateAlgorithms; - - // node_modules/jose/dist/browser/runtime/subtle_dsa.js - function subtleDsa(alg, algorithm) { - const hash = `SHA-${alg.slice(-3)}`; - switch (alg) { - case "HS256": - case "HS384": - case "HS512": - return { hash, name: "HMAC" }; - case "PS256": - case "PS384": - case "PS512": - return { hash, name: "RSA-PSS", saltLength: alg.slice(-3) >> 3 }; - case "RS256": - case "RS384": - case "RS512": - return { hash, name: "RSASSA-PKCS1-v1_5" }; - case "ES256": - case "ES384": - case "ES512": - return { hash, name: "ECDSA", namedCurve: algorithm.namedCurve }; - case "Ed25519": - return { name: "Ed25519" }; - case "EdDSA": - return { name: algorithm.name }; - default: - throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`); - } - } - - // node_modules/jose/dist/browser/runtime/get_sign_verify_key.js - async function getCryptoKey(alg, key, usage) { - if (usage === "sign") { - key = await normalize_key_default.normalizePrivateKey(key, alg); - } - if (usage === "verify") { - key = await normalize_key_default.normalizePublicKey(key, alg); - } - if (isCryptoKey(key)) { - checkSigCryptoKey(key, alg, usage); - return key; - } - if (key instanceof Uint8Array) { - if (!alg.startsWith("HS")) { - throw new TypeError(invalid_key_input_default(key, ...types)); - } - return webcrypto_default.subtle.importKey("raw", key, { hash: `SHA-${alg.slice(-3)}`, name: "HMAC" }, false, [usage]); - } - throw new TypeError(invalid_key_input_default(key, ...types, "Uint8Array", "JSON Web Key")); - } - - // node_modules/jose/dist/browser/runtime/verify.js - var verify = async (alg, key, signature, data) => { - const cryptoKey = await getCryptoKey(alg, key, "verify"); - check_key_length_default(alg, cryptoKey); - const algorithm = subtleDsa(alg, cryptoKey.algorithm); - try { - return await webcrypto_default.subtle.verify(algorithm, cryptoKey, signature, data); - } catch { - return false; - } - }; - var verify_default = verify; - - // node_modules/jose/dist/browser/jws/flattened/verify.js - async function flattenedVerify(jws, key, options) { - if (!isObject4(jws)) { - throw new JWSInvalid("Flattened JWS must be an object"); - } - if (jws.protected === void 0 && jws.header === void 0) { - throw new JWSInvalid('Flattened JWS must have either of the "protected" or "header" members'); - } - if (jws.protected !== void 0 && typeof jws.protected !== "string") { - throw new JWSInvalid("JWS Protected Header incorrect type"); - } - if (jws.payload === void 0) { - throw new JWSInvalid("JWS Payload missing"); - } - if (typeof jws.signature !== "string") { - throw new JWSInvalid("JWS Signature missing or incorrect type"); - } - if (jws.header !== void 0 && !isObject4(jws.header)) { - throw new JWSInvalid("JWS Unprotected Header incorrect type"); - } - let parsedProt = {}; - if (jws.protected) { - try { - const protectedHeader = decode(jws.protected); - parsedProt = JSON.parse(decoder.decode(protectedHeader)); - } catch { - throw new JWSInvalid("JWS Protected Header is invalid"); - } - } - if (!is_disjoint_default(parsedProt, jws.header)) { - throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint"); - } - const joseHeader = { - ...parsedProt, - ...jws.header - }; - const extensions = validate_crit_default(JWSInvalid, /* @__PURE__ */ new Map([["b64", true]]), options?.crit, parsedProt, joseHeader); - let b64 = true; - if (extensions.has("b64")) { - b64 = parsedProt.b64; - if (typeof b64 !== "boolean") { - throw new JWSInvalid('The "b64" (base64url-encode payload) Header Parameter must be a boolean'); - } - } - const { alg } = joseHeader; - if (typeof alg !== "string" || !alg) { - throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid'); - } - const algorithms = options && validate_algorithms_default("algorithms", options.algorithms); - if (algorithms && !algorithms.has(alg)) { - throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed'); - } - if (b64) { - if (typeof jws.payload !== "string") { - throw new JWSInvalid("JWS Payload must be a string"); - } - } else if (typeof jws.payload !== "string" && !(jws.payload instanceof Uint8Array)) { - throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance"); - } - let resolvedKey = false; - if (typeof key === "function") { - key = await key(parsedProt, jws); - resolvedKey = true; - checkKeyTypeWithJwk(alg, key, "verify"); - if (isJWK(key)) { - key = await importJWK(key, alg); - } - } else { - checkKeyTypeWithJwk(alg, key, "verify"); - } - const data = concat(encoder.encode(jws.protected ?? ""), encoder.encode("."), typeof jws.payload === "string" ? encoder.encode(jws.payload) : jws.payload); - let signature; - try { - signature = decode(jws.signature); - } catch { - throw new JWSInvalid("Failed to base64url decode the signature"); - } - const verified = await verify_default(alg, key, signature, data); - if (!verified) { - throw new JWSSignatureVerificationFailed(); - } - let payload; - if (b64) { - try { - payload = decode(jws.payload); - } catch { - throw new JWSInvalid("Failed to base64url decode the payload"); - } - } else if (typeof jws.payload === "string") { - payload = encoder.encode(jws.payload); - } else { - payload = jws.payload; - } - const result = { payload }; - if (jws.protected !== void 0) { - result.protectedHeader = parsedProt; - } - if (jws.header !== void 0) { - result.unprotectedHeader = jws.header; - } - if (resolvedKey) { - return { ...result, key }; - } - return result; - } - - // node_modules/jose/dist/browser/jws/compact/verify.js - async function compactVerify(jws, key, options) { - if (jws instanceof Uint8Array) { - jws = decoder.decode(jws); - } - if (typeof jws !== "string") { - throw new JWSInvalid("Compact JWS must be a string or Uint8Array"); - } - const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split("."); - if (length !== 3) { - throw new JWSInvalid("Invalid Compact JWS"); - } - const verified = await flattenedVerify({ payload, protected: protectedHeader, signature }, key, options); - const result = { payload: verified.payload, protectedHeader: verified.protectedHeader }; - if (typeof key === "function") { - return { ...result, key: verified.key }; - } - return result; - } - - // node_modules/jose/dist/browser/lib/epoch.js - var epoch_default = (date) => Math.floor(date.getTime() / 1e3); - - // node_modules/jose/dist/browser/lib/secs.js - var minute = 60; - var hour = minute * 60; - var day = hour * 24; - var week = day * 7; - var year = day * 365.25; - var REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i; - var secs_default = (str) => { - const matched = REGEX.exec(str); - if (!matched || matched[4] && matched[1]) { - throw new TypeError("Invalid time period format"); - } - const value = parseFloat(matched[2]); - const unit = matched[3].toLowerCase(); - let numericDate; - switch (unit) { - case "sec": - case "secs": - case "second": - case "seconds": - case "s": - numericDate = Math.round(value); - break; - case "minute": - case "minutes": - case "min": - case "mins": - case "m": - numericDate = Math.round(value * minute); - break; - case "hour": - case "hours": - case "hr": - case "hrs": - case "h": - numericDate = Math.round(value * hour); - break; - case "day": - case "days": - case "d": - numericDate = Math.round(value * day); - break; - case "week": - case "weeks": - case "w": - numericDate = Math.round(value * week); - break; - default: - numericDate = Math.round(value * year); - break; - } - if (matched[1] === "-" || matched[4] === "ago") { - return -numericDate; - } - return numericDate; - }; - - // node_modules/jose/dist/browser/lib/jwt_claims_set.js - var normalizeTyp = (value) => value.toLowerCase().replace(/^application\//, ""); - var checkAudiencePresence = (audPayload, audOption) => { - if (typeof audPayload === "string") { - return audOption.includes(audPayload); - } - if (Array.isArray(audPayload)) { - return audOption.some(Set.prototype.has.bind(new Set(audPayload))); - } - return false; - }; - var jwt_claims_set_default = (protectedHeader, encodedPayload, options = {}) => { - let payload; - try { - payload = JSON.parse(decoder.decode(encodedPayload)); - } catch { - } - if (!isObject4(payload)) { - throw new JWTInvalid("JWT Claims Set must be a top-level JSON object"); - } - const { typ } = options; - if (typ && (typeof protectedHeader.typ !== "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) { - throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, "typ", "check_failed"); - } - const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options; - const presenceCheck = [...requiredClaims]; - if (maxTokenAge !== void 0) - presenceCheck.push("iat"); - if (audience !== void 0) - presenceCheck.push("aud"); - if (subject !== void 0) - presenceCheck.push("sub"); - if (issuer !== void 0) - presenceCheck.push("iss"); - for (const claim of new Set(presenceCheck.reverse())) { - if (!(claim in payload)) { - throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, "missing"); - } - } - if (issuer && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) { - throw new JWTClaimValidationFailed('unexpected "iss" claim value', payload, "iss", "check_failed"); - } - if (subject && payload.sub !== subject) { - throw new JWTClaimValidationFailed('unexpected "sub" claim value', payload, "sub", "check_failed"); - } - if (audience && !checkAudiencePresence(payload.aud, typeof audience === "string" ? [audience] : audience)) { - throw new JWTClaimValidationFailed('unexpected "aud" claim value', payload, "aud", "check_failed"); - } - let tolerance; - switch (typeof options.clockTolerance) { - case "string": - tolerance = secs_default(options.clockTolerance); - break; - case "number": - tolerance = options.clockTolerance; - break; - case "undefined": - tolerance = 0; - break; - default: - throw new TypeError("Invalid clockTolerance option type"); - } - const { currentDate } = options; - const now = epoch_default(currentDate || /* @__PURE__ */ new Date()); - if ((payload.iat !== void 0 || maxTokenAge) && typeof payload.iat !== "number") { - throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, "iat", "invalid"); - } - if (payload.nbf !== void 0) { - if (typeof payload.nbf !== "number") { - throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, "nbf", "invalid"); - } - if (payload.nbf > now + tolerance) { - throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, "nbf", "check_failed"); - } - } - if (payload.exp !== void 0) { - if (typeof payload.exp !== "number") { - throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, "exp", "invalid"); - } - if (payload.exp <= now - tolerance) { - throw new JWTExpired('"exp" claim timestamp check failed', payload, "exp", "check_failed"); - } - } - if (maxTokenAge) { - const age = now - payload.iat; - const max = typeof maxTokenAge === "number" ? maxTokenAge : secs_default(maxTokenAge); - if (age - tolerance > max) { - throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, "iat", "check_failed"); - } - if (age < 0 - tolerance) { - throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, "iat", "check_failed"); - } - } - return payload; - }; - - // node_modules/jose/dist/browser/jwt/verify.js - async function jwtVerify(jwt, key, options) { - const verified = await compactVerify(jwt, key, options); - if (verified.protectedHeader.crit?.includes("b64") && verified.protectedHeader.b64 === false) { - throw new JWTInvalid("JWTs MUST NOT use unencoded payload"); - } - const payload = jwt_claims_set_default(verified.protectedHeader, verified.payload, options); - const result = { payload, protectedHeader: verified.protectedHeader }; - if (typeof key === "function") { - return { ...result, key: verified.key }; - } - return result; - } - - // node_modules/jose/dist/browser/jwks/local.js - function getKtyFromAlg(alg) { - switch (typeof alg === "string" && alg.slice(0, 2)) { - case "RS": - case "PS": - return "RSA"; - case "ES": - return "EC"; - case "Ed": - return "OKP"; - default: - throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set'); - } - } - function isJWKSLike(jwks) { - return jwks && typeof jwks === "object" && Array.isArray(jwks.keys) && jwks.keys.every(isJWKLike); - } - function isJWKLike(key) { - return isObject4(key); - } - function clone(obj) { - if (typeof structuredClone === "function") { - return structuredClone(obj); - } - return JSON.parse(JSON.stringify(obj)); - } - var LocalJWKSet = class { - constructor(jwks) { - this._cached = /* @__PURE__ */ new WeakMap(); - if (!isJWKSLike(jwks)) { - throw new JWKSInvalid("JSON Web Key Set malformed"); - } - this._jwks = clone(jwks); - } - async getKey(protectedHeader, token) { - const { alg, kid } = { ...protectedHeader, ...token?.header }; - const kty = getKtyFromAlg(alg); - const candidates = this._jwks.keys.filter((jwk2) => { - let candidate = kty === jwk2.kty; - if (candidate && typeof kid === "string") { - candidate = kid === jwk2.kid; - } - if (candidate && typeof jwk2.alg === "string") { - candidate = alg === jwk2.alg; - } - if (candidate && typeof jwk2.use === "string") { - candidate = jwk2.use === "sig"; - } - if (candidate && Array.isArray(jwk2.key_ops)) { - candidate = jwk2.key_ops.includes("verify"); - } - if (candidate) { - switch (alg) { - case "ES256": - candidate = jwk2.crv === "P-256"; - break; - case "ES256K": - candidate = jwk2.crv === "secp256k1"; - break; - case "ES384": - candidate = jwk2.crv === "P-384"; - break; - case "ES512": - candidate = jwk2.crv === "P-521"; - break; - case "Ed25519": - candidate = jwk2.crv === "Ed25519"; - break; - case "EdDSA": - candidate = jwk2.crv === "Ed25519" || jwk2.crv === "Ed448"; - break; - } - } - return candidate; - }); - const { 0: jwk, length } = candidates; - if (length === 0) { - throw new JWKSNoMatchingKey(); - } - if (length !== 1) { - const error = new JWKSMultipleMatchingKeys(); - const { _cached } = this; - error[Symbol.asyncIterator] = async function* () { - for (const jwk2 of candidates) { - try { - yield await importWithAlgCache(_cached, jwk2, alg); - } catch { - } - } - }; - throw error; - } - return importWithAlgCache(this._cached, jwk, alg); - } - }; - async function importWithAlgCache(cache2, jwk, alg) { - const cached = cache2.get(jwk) || cache2.set(jwk, {}).get(jwk); - if (cached[alg] === void 0) { - const key = await importJWK({ ...jwk, ext: true }, alg); - if (key instanceof Uint8Array || key.type !== "public") { - throw new JWKSInvalid("JSON Web Key Set members must be public keys"); - } - cached[alg] = key; - } - return cached[alg]; - } - function createLocalJWKSet(jwks) { - const set = new LocalJWKSet(jwks); - const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token); - Object.defineProperties(localJWKSet, { - jwks: { - value: () => clone(set._jwks), - enumerable: true, - configurable: false, - writable: false - } - }); - return localJWKSet; - } - - // node_modules/jose/dist/browser/runtime/fetch_jwks.js - var fetchJwks = async (url, timeout, options) => { - let controller; - let id; - let timedOut = false; - if (typeof AbortController === "function") { - controller = new AbortController(); - id = setTimeout(() => { - timedOut = true; - controller.abort(); - }, timeout); - } - const response = await fetch(url.href, { - signal: controller ? controller.signal : void 0, - redirect: "manual", - headers: options.headers - }).catch((err) => { - if (timedOut) - throw new JWKSTimeout(); - throw err; - }); - if (id !== void 0) - clearTimeout(id); - if (response.status !== 200) { - throw new JOSEError("Expected 200 OK from the JSON Web Key Set HTTP response"); - } - try { - return await response.json(); - } catch { - throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON"); - } - }; - var fetch_jwks_default = fetchJwks; - - // node_modules/jose/dist/browser/jwks/remote.js - function isCloudflareWorkers() { - return typeof WebSocketPair !== "undefined" || typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers" || typeof EdgeRuntime !== "undefined" && EdgeRuntime === "vercel"; - } - var USER_AGENT; - if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) { - const NAME = "jose"; - const VERSION = "v5.10.0"; - USER_AGENT = `${NAME}/${VERSION}`; - } - var jwksCache = /* @__PURE__ */ Symbol(); - function isFreshJwksCache(input, cacheMaxAge) { - if (typeof input !== "object" || input === null) { - return false; - } - if (!("uat" in input) || typeof input.uat !== "number" || Date.now() - input.uat >= cacheMaxAge) { - return false; - } - if (!("jwks" in input) || !isObject4(input.jwks) || !Array.isArray(input.jwks.keys) || !Array.prototype.every.call(input.jwks.keys, isObject4)) { - return false; - } - return true; - } - var RemoteJWKSet = class { - constructor(url, options) { - if (!(url instanceof URL)) { - throw new TypeError("url must be an instance of URL"); - } - this._url = new URL(url.href); - this._options = { agent: options?.agent, headers: options?.headers }; - this._timeoutDuration = typeof options?.timeoutDuration === "number" ? options?.timeoutDuration : 5e3; - this._cooldownDuration = typeof options?.cooldownDuration === "number" ? options?.cooldownDuration : 3e4; - this._cacheMaxAge = typeof options?.cacheMaxAge === "number" ? options?.cacheMaxAge : 6e5; - if (options?.[jwksCache] !== void 0) { - this._cache = options?.[jwksCache]; - if (isFreshJwksCache(options?.[jwksCache], this._cacheMaxAge)) { - this._jwksTimestamp = this._cache.uat; - this._local = createLocalJWKSet(this._cache.jwks); - } - } - } - coolingDown() { - return typeof this._jwksTimestamp === "number" ? Date.now() < this._jwksTimestamp + this._cooldownDuration : false; - } - fresh() { - return typeof this._jwksTimestamp === "number" ? Date.now() < this._jwksTimestamp + this._cacheMaxAge : false; - } - async getKey(protectedHeader, token) { - if (!this._local || !this.fresh()) { - await this.reload(); - } - try { - return await this._local(protectedHeader, token); - } catch (err) { - if (err instanceof JWKSNoMatchingKey) { - if (this.coolingDown() === false) { - await this.reload(); - return this._local(protectedHeader, token); - } - } - throw err; - } - } - async reload() { - if (this._pendingFetch && isCloudflareWorkers()) { - this._pendingFetch = void 0; - } - const headers = new Headers(this._options.headers); - if (USER_AGENT && !headers.has("User-Agent")) { - headers.set("User-Agent", USER_AGENT); - this._options.headers = Object.fromEntries(headers.entries()); - } - this._pendingFetch || (this._pendingFetch = fetch_jwks_default(this._url, this._timeoutDuration, this._options).then((json) => { - this._local = createLocalJWKSet(json); - if (this._cache) { - this._cache.uat = Date.now(); - this._cache.jwks = json; - } - this._jwksTimestamp = Date.now(); - this._pendingFetch = void 0; - }).catch((err) => { - this._pendingFetch = void 0; - throw err; - })); - await this._pendingFetch; - } - }; - function createRemoteJWKSet(url, options) { - const set = new RemoteJWKSet(url, options); - const remoteJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token); - Object.defineProperties(remoteJWKSet, { - coolingDown: { - get: () => set.coolingDown(), - enumerable: true, - configurable: false - }, - fresh: { - get: () => set.fresh(), - enumerable: true, - configurable: false - }, - reload: { - value: () => set.reload(), - enumerable: true, - configurable: false, - writable: false - }, - reloading: { - get: () => !!set._pendingFetch, - enumerable: true, - configurable: false - }, - jwks: { - value: () => set._local?.jwks(), - enumerable: true, - configurable: false, - writable: false - } - }); - return remoteJWKSet; - } - - // node_modules/@logto/client/lib/adapter/defaults.js - var defaultClockTolerance = 300; - var verifyIdToken = async (idToken, clientId, issuer, jwks, clockTolerance = defaultClockTolerance) => { - const result = await jwtVerify(idToken, jwks, { audience: clientId, issuer, clockTolerance }); - if (Math.abs((result.payload.iat ?? 0) - Date.now() / 1e3) > clockTolerance) { - throw new LogtoError("id_token.invalid_iat"); - } - }; - var DefaultJwtVerifier = class { - constructor(client, clockTolerance = defaultClockTolerance) { - this.client = client; - this.clockTolerance = clockTolerance; - } - async verifyIdToken(idToken) { - const { appId } = this.client.logtoConfig; - const { issuer, jwksUri } = await this.client.getOidcConfig(); - this.getJwtVerifyGetKey ||= createRemoteJWKSet(new URL(jwksUri)); - await verifyIdToken(idToken, appId, issuer, this.getJwtVerifyGetKey, this.clockTolerance); - } - }; - - // node_modules/@logto/client/lib/adapter/types.js - var PersistKey; - (function(PersistKey2) { - PersistKey2["IdToken"] = "idToken"; - PersistKey2["RefreshToken"] = "refreshToken"; - PersistKey2["AccessToken"] = "accessToken"; - PersistKey2["SignInSession"] = "signInSession"; - })(PersistKey || (PersistKey = {})); - var CacheKey; - (function(CacheKey2) { - CacheKey2["OpenidConfig"] = "openidConfiguration"; - CacheKey2["Jwks"] = "jwks"; - })(CacheKey || (CacheKey = {})); - - // node_modules/@logto/client/lib/adapter/index.js - var ClientAdapterInstance = class { - /* END OF IMPLEMENTATION */ - constructor(adapter) { - Object.assign(this, adapter); - } - async setStorageItem(key, value) { - if (!value) { - await this.storage.removeItem(key); - return; - } - await this.storage.setItem(key, value); - } - /** - * Try to get the string value from the cache and parse as JSON. - * Return the parsed value if it is an object, return `undefined` otherwise. - * - * @param key The cache key to get value from. - */ - async getCachedObject(key) { - const cached = await trySafe(async () => { - const data = await this.unstable_cache?.getItem(key); - return conditional(data && JSON.parse(data)); - }); - if (cached && typeof cached === "object") { - return cached; - } - } - /** - * Try to get the value from the cache first, if it doesn't exist in cache, - * run the getter function and store the result into cache. - * - * @param key The cache key to get value from. - */ - async getWithCache(key, getter) { - const cached = await this.getCachedObject(key); - if (cached) { - return cached; - } - const result = await getter(); - await this.unstable_cache?.setItem(key, JSON.stringify(result)); - return result; - } - }; - - // node_modules/@logto/client/lib/errors.js - var logtoClientErrorCodes = Object.freeze({ - "sign_in_session.invalid": "Invalid sign-in session.", - "sign_in_session.not_found": "Sign-in session not found.", - not_authenticated: "Not authenticated.", - fetch_user_info_failed: "Unable to fetch user info. The access token may be invalid.", - user_cancelled: "The user cancelled the action.", - missing_scope_organizations: `The \`${UserScope.Organizations}\` scope is required` - }); - var LogtoClientError = class extends Error { - constructor(code, data) { - super(logtoClientErrorCodes[code]); - this.name = "LogtoClientError"; - this.code = code; - this.data = data; - } - }; - - // node_modules/@logto/client/lib/types/index.js - var normalizeLogtoConfig = (config) => { - const { prompt = Prompt.Consent, scopes = [], resources, ...rest } = config; - const includeReservedScopes = config.includeReservedScopes ?? true; - return { - ...rest, - prompt, - scopes: includeReservedScopes ? withReservedScopes(scopes).split(" ") : scopes, - resources: scopes.includes(UserScope.Organizations) ? deduplicate([...resources ?? [], ReservedResource.Organization]) : resources, - includeReservedScopes - }; - }; - var isLogtoSignInSessionItem = (data) => { - if (!isArbitraryObject(data)) { - return false; - } - return ["redirectUri", "codeVerifier", "state"].every((key) => typeof data[key] === "string"); - }; - var isLogtoAccessTokenMap = (data) => { - if (!isArbitraryObject(data)) { - return false; - } - return Object.values(data).every((value) => { - if (!isArbitraryObject(value)) { - return false; - } - return typeof value.token === "string" && typeof value.scope === "string" && typeof value.expiresAt === "number"; - }); - }; - - // node_modules/@logto/client/lib/utils/index.js - var buildAccessTokenKey = (resource = "", organizationId, scopes = []) => `${scopes.slice().sort().join(" ")}@${resource}${conditionalString(organizationId && `#${organizationId}`)}`; - var getDiscoveryEndpoint = (endpoint) => appendPath(new URL(endpoint), discoveryPath).toString(); - - // node_modules/@logto/client/lib/utils/memoize.js - function memoize(run) { - const promiseCache = /* @__PURE__ */ new Map(); - const memoized = async function(...args) { - const promiseKey = JSON.stringify(args); - const cachedPromise = promiseCache.get(promiseKey); - if (cachedPromise) { - return cachedPromise; - } - const promise = (async () => { - try { - return await run.apply(this, args); - } finally { - promiseCache.delete(promiseKey); - } - })(); - promiseCache.set(promiseKey, promise); - return promise; - }; - return memoized; - } - - // node_modules/@logto/client/lib/utils/once.js - function once2(function_) { - let called = false; - let result; - return function(...args) { - if (!called) { - called = true; - result = function_.apply(this, args); - } - return result; - }; - } - - // node_modules/@logto/client/lib/client.js - var StandardLogtoClient = class { - get jwtVerifier() { - return this.jwtVerifierInstance; - } - constructor(logtoConfig, adapter, buildJwtVerifier) { - this.getOidcConfig = once2(this.#getOidcConfig); - this.getAccessToken = memoize(this.#getAccessToken); - this.getOrganizationToken = memoize(this.#getOrganizationToken); - this.clearAccessToken = memoize(this.#clearAccessToken); - this.clearAllTokens = memoize(this.#clearAllTokens); - this.handleSignInCallback = memoize(this.#handleSignInCallback); - this.accessTokenMap = /* @__PURE__ */ new Map(); - this.logtoConfig = normalizeLogtoConfig(logtoConfig); - this.adapter = new ClientAdapterInstance(adapter); - this.jwtVerifierInstance = buildJwtVerifier(this); - void this.loadAccessTokenMap(); - } - /** - * Set the JWT verifier for the client. - * @param buildJwtVerifier The JWT verifier instance or a function that returns the JWT verifier instance. - */ - setJwtVerifier(buildJwtVerifier) { - this.jwtVerifierInstance = typeof buildJwtVerifier === "function" ? buildJwtVerifier(this) : buildJwtVerifier; - } - /** - * Check if the user is authenticated by checking if the ID token exists. - */ - async isAuthenticated() { - return Boolean(await this.getIdToken()); - } - /** - * Get the Refresh Token from the storage. - */ - async getRefreshToken() { - return this.adapter.storage.getItem("refreshToken"); - } - /** - * Get the ID Token from the storage. If you want to get the ID Token claims, - * use {@link getIdTokenClaims} instead. - */ - async getIdToken() { - return this.adapter.storage.getItem("idToken"); - } - /** - * Get the ID Token claims. - */ - async getIdTokenClaims() { - const idToken = await this.getIdToken(); - if (!idToken) { - throw new LogtoClientError("not_authenticated", "ID token not found"); - } - return decodeIdToken(idToken); - } - /** - * Get the access token claims for the specified resource. - * - * @param resource The resource that the access token is granted for. If not - * specified, the access token will be used for OpenID Connect or the default - * resource, as specified in the Logto Console. - */ - async getAccessTokenClaims(resource) { - const accessToken = await this.getAccessToken(resource); - return decodeAccessToken(accessToken); - } - /** - * Get the organization token claims for the specified organization. - * - * @param organizationId The ID of the organization that the access token is granted for. - */ - async getOrganizationTokenClaims(organizationId) { - const accessToken = await this.getOrganizationToken(organizationId); - return decodeAccessToken(accessToken); - } - /** - * Get the user information from the Userinfo Endpoint. - * - * Note the Userinfo Endpoint will return more claims than the ID Token. See - * {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information} - * for more information. - * - * @returns The user information. - * @throws LogtoClientError if the user is not authenticated. - */ - async fetchUserInfo() { - const { userinfoEndpoint } = await this.getOidcConfig(); - const accessToken = await this.getAccessToken(); - if (!accessToken) { - throw new LogtoClientError("fetch_user_info_failed"); - } - return fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester); - } - async signIn(options, mode, hint) { - const { redirectUri: redirectUriUrl, postRedirectUri: postRedirectUriUrl, firstScreen, identifiers, interactionMode, loginHint, directSignIn, extraParams, prompt, clearTokens } = typeof options === "string" || options instanceof URL ? { - redirectUri: options, - postRedirectUri: void 0, - firstScreen: void 0, - identifiers: void 0, - interactionMode: mode, - loginHint: hint, - directSignIn: void 0, - extraParams: void 0, - prompt: void 0, - clearTokens: true - } : options; - const redirectUri = redirectUriUrl.toString(); - const postRedirectUri = postRedirectUriUrl?.toString(); - const { appId: clientId, prompt: promptViaConfig, resources, scopes, includeReservedScopes } = this.logtoConfig; - const { authorizationEndpoint } = await this.getOidcConfig(); - const [codeVerifier, state] = await Promise.all([ - this.adapter.generateCodeVerifier(), - this.adapter.generateState() - ]); - const codeChallenge = await this.adapter.generateCodeChallenge(codeVerifier); - const signInUri = generateSignInUri({ - authorizationEndpoint, - clientId, - redirectUri: redirectUri.toString(), - codeChallenge, - state, - scopes, - resources, - prompt: prompt ?? promptViaConfig, - firstScreen, - identifiers, - interactionMode, - loginHint, - directSignIn, - extraParams, - includeReservedScopes - }); - await Promise.all([ - this.setSignInSession({ redirectUri, postRedirectUri, codeVerifier, state }), - clearTokens === false ? void 0 : this.clearAllTokens() - ]); - await this.adapter.navigate(signInUri, { redirectUri, for: "sign-in" }); - } - /** - * Check if the user is redirected from the sign-in page by checking if the - * current URL matches the redirect URI in the sign-in session. - * - * If there's no sign-in session, it will return `false`. - * - * @param url The current URL. - */ - async isSignInRedirected(url) { - const signInSession = await this.getSignInSession(); - if (!signInSession) { - return false; - } - const { redirectUri } = signInSession; - const { origin, pathname } = new URL(url); - return `${origin}${pathname}` === redirectUri; - } - /** - * Start the sign-out flow with the specified redirect URI. The URI must be - * registered in the Logto Console. - * - * It will also revoke all the tokens and clean up the storage. - * - * The user will be redirected that URI after the sign-out flow is completed. - * If the `postLogoutRedirectUri` is not specified, the user will be redirected - * to a default page. - */ - async signOut(postLogoutRedirectUri) { - const { appId: clientId } = this.logtoConfig; - const { endSessionEndpoint, revocationEndpoint } = await this.getOidcConfig(); - const refreshToken = await this.getRefreshToken(); - if (refreshToken) { - try { - await revoke(revocationEndpoint, clientId, refreshToken, this.adapter.requester); - } catch { - } - } - const url = generateSignOutUri({ - endSessionEndpoint, - postLogoutRedirectUri, - clientId - }); - await this.clearAllTokens(); - await this.adapter.navigate(url, { redirectUri: postLogoutRedirectUri, for: "sign-out" }); - } - async getSignInSession() { - const jsonItem = await this.adapter.storage.getItem("signInSession"); - if (!jsonItem) { - return null; - } - const item = JSON.parse(jsonItem); - if (!isLogtoSignInSessionItem(item)) { - throw new LogtoClientError("sign_in_session.invalid"); - } - return item; - } - async setSignInSession(value) { - return this.adapter.setStorageItem(PersistKey.SignInSession, value && JSON.stringify(value)); - } - async setIdToken(value) { - return this.adapter.setStorageItem(PersistKey.IdToken, value); - } - async setRefreshToken(value) { - return this.adapter.setStorageItem(PersistKey.RefreshToken, value); - } - async getAccessTokenByRefreshToken(resource, organizationId) { - const currentRefreshToken = await this.getRefreshToken(); - if (!currentRefreshToken) { - throw new LogtoClientError("not_authenticated", "Refresh token not found"); - } - const accessTokenKey = buildAccessTokenKey(resource, organizationId); - const { appId: clientId } = this.logtoConfig; - const { tokenEndpoint } = await this.getOidcConfig(); - const requestedAt = Math.round(Date.now() / 1e3); - const { accessToken, refreshToken, idToken, scope, expiresIn } = await fetchTokenByRefreshToken({ - clientId, - tokenEndpoint, - refreshToken: currentRefreshToken, - resource, - organizationId - }, this.adapter.requester); - this.accessTokenMap.set(accessTokenKey, { - token: accessToken, - scope, - /** The `expiresAt` variable provides an approximate estimation of the actual `exp` property - * in the token claims. It is utilized by the client to determine if the cached access token - * has expired and when a new access token should be requested. - */ - expiresAt: requestedAt + expiresIn - }); - await this.saveAccessTokenMap(); - if (refreshToken) { - await this.setRefreshToken(refreshToken); - } - if (idToken) { - await this.jwtVerifier.verifyIdToken(idToken); - await this.setIdToken(idToken); - } - return accessToken; - } - async saveAccessTokenMap() { - const data = {}; - for (const [key, accessToken] of this.accessTokenMap.entries()) { - data[key] = accessToken; - } - await this.adapter.storage.setItem("accessToken", JSON.stringify(data)); - } - async loadAccessTokenMap() { - const raw = await this.adapter.storage.getItem("accessToken"); - if (!raw) { - return; - } - try { - const json = JSON.parse(raw); - if (!isLogtoAccessTokenMap(json)) { - return; - } - this.accessTokenMap.clear(); - for (const [key, accessToken] of Object.entries(json)) { - this.accessTokenMap.set(key, accessToken); - } - } catch (error) { - console.warn(error); - } - } - async #getOidcConfig() { - return this.adapter.getWithCache(CacheKey.OpenidConfig, async () => { - return fetchOidcConfig(getDiscoveryEndpoint(this.logtoConfig.endpoint), this.adapter.requester); - }); - } - async #getAccessToken(resource, organizationId) { - if (!await this.isAuthenticated()) { - throw new LogtoClientError("not_authenticated"); - } - const accessTokenKey = buildAccessTokenKey(resource, organizationId); - const accessToken = this.accessTokenMap.get(accessTokenKey); - if (accessToken && accessToken.expiresAt > Date.now() / 1e3) { - return accessToken.token; - } - if (accessToken) { - this.accessTokenMap.delete(accessTokenKey); - } - return this.getAccessTokenByRefreshToken(resource, organizationId); - } - async #getOrganizationToken(organizationId) { - if (!this.logtoConfig.scopes?.includes(UserScope.Organizations)) { - throw new LogtoClientError("missing_scope_organizations"); - } - return this.getAccessToken(void 0, organizationId); - } - async #clearAccessToken() { - this.accessTokenMap.clear(); - await this.adapter.storage.removeItem("accessToken"); - } - async #clearAllTokens() { - await Promise.all([this.setRefreshToken(null), this.setIdToken(null), this.clearAccessToken()]); - } - async #handleSignInCallback(callbackUri) { - const signInSession = await this.getSignInSession(); - if (!signInSession) { - throw new LogtoClientError("sign_in_session.not_found"); - } - const { redirectUri, postRedirectUri, state, codeVerifier } = signInSession; - const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state); - const accessTokenKey = buildAccessTokenKey(); - const { appId: clientId } = this.logtoConfig; - const { tokenEndpoint } = await this.getOidcConfig(); - const requestedAt = Math.round(Date.now() / 1e3); - const { idToken, refreshToken, accessToken, scope, expiresIn } = await fetchTokenByAuthorizationCode({ - clientId, - tokenEndpoint, - redirectUri, - codeVerifier, - code - }, this.adapter.requester); - await this.jwtVerifier.verifyIdToken(idToken); - await this.setRefreshToken(refreshToken ?? null); - await this.setIdToken(idToken); - this.accessTokenMap.set(accessTokenKey, { - token: accessToken, - scope, - /** The `expiresAt` variable provides an approximate estimation of the actual `exp` property - * in the token claims. It is utilized by the client to determine if the cached access token - * has expired and when a new access token should be requested. - */ - expiresAt: requestedAt + expiresIn - }); - await this.saveAccessTokenMap(); - await this.setSignInSession(null); - if (postRedirectUri) { - await this.adapter.navigate(postRedirectUri, { for: "post-sign-in" }); - } - } - }; - - // node_modules/@logto/client/lib/utils/requester.js - var createRequester = (fetchFunction) => { - return async (...args) => { - const response = await fetchFunction(...args); - if (!response.ok) { - const cloned = response.clone(); - const responseJson = await response.json(); - console.error(`Logto requester error: [status=${response.status}]`, responseJson); - if (!isLogtoRequestErrorJson(responseJson)) { - throw new LogtoError("unexpected_response_error", responseJson); - } - const { code, message: message2 } = responseJson; - throw new LogtoRequestError(code, message2, cloned); - } - return response.json(); - }; - }; - - // node_modules/@logto/client/lib/index.js - var LogtoClient = class extends StandardLogtoClient { - constructor(logtoConfig, adapter, buildJwtVerifier) { - super(logtoConfig, adapter, buildJwtVerifier ?? ((client) => new DefaultJwtVerifier(client))); - } - }; - - // node_modules/js-base64/base64.mjs - var _hasBuffer = typeof Buffer === "function"; - var _TD = typeof TextDecoder === "function" ? new TextDecoder() : void 0; - var _TE = typeof TextEncoder === "function" ? new TextEncoder() : void 0; - var b64ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - var b64chs = Array.prototype.slice.call(b64ch); - var b64tab = ((a) => { - let tab = {}; - a.forEach((c, i) => tab[c] = i); - return tab; - })(b64chs); - var _fromCC = String.fromCharCode.bind(String); - var _U8Afrom = typeof Uint8Array.from === "function" ? Uint8Array.from.bind(Uint8Array) : (it) => new Uint8Array(Array.prototype.slice.call(it, 0)); - var _mkUriSafe = (src) => src.replace(/=/g, "").replace(/[+\/]/g, (m0) => m0 == "+" ? "-" : "_"); - var btoaPolyfill = (bin) => { - let u32, c0, c1, c2, asc = ""; - const pad = bin.length % 3; - for (let i = 0; i < bin.length; ) { - if ((c0 = bin.charCodeAt(i++)) > 255 || (c1 = bin.charCodeAt(i++)) > 255 || (c2 = bin.charCodeAt(i++)) > 255) - throw new TypeError("invalid character found"); - u32 = c0 << 16 | c1 << 8 | c2; - asc += b64chs[u32 >> 18 & 63] + b64chs[u32 >> 12 & 63] + b64chs[u32 >> 6 & 63] + b64chs[u32 & 63]; - } - return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc; - }; - var _btoa = typeof btoa === "function" ? (bin) => btoa(bin) : _hasBuffer ? (bin) => Buffer.from(bin, "binary").toString("base64") : btoaPolyfill; - var _fromUint8Array = _hasBuffer ? (u8a) => Buffer.from(u8a).toString("base64") : (u8a) => { - const maxargs = 4096; - let strs = []; - for (let i = 0, l = u8a.length; i < l; i += maxargs) { - strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs))); - } - return _btoa(strs.join("")); - }; - var fromUint8Array = (u8a, urlsafe = false) => urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a); - - // node_modules/@logto/browser/lib/utils/generators.js - var generateRandomString = (length = 64) => fromUint8Array(crypto.getRandomValues(new Uint8Array(length)), true); - var generateState = () => generateRandomString(); - var generateCodeVerifier = () => generateRandomString(); - var generateCodeChallenge = async (codeVerifier) => { - if (crypto.subtle === void 0) { - throw new LogtoError("crypto_subtle_unavailable"); - } - const encodedCodeVerifier = new TextEncoder().encode(codeVerifier); - const codeChallenge = new Uint8Array(await crypto.subtle.digest("SHA-256", encodedCodeVerifier)); - return fromUint8Array(codeChallenge, true); - }; - - // node_modules/@logto/chrome-extension/lib/storage.js - var keyPrefix = `logto`; - var ChromeExtensionStorage = class { - constructor(appId) { - this.appId = appId; - } - getKey(item) { - if (item === void 0) { - return `${keyPrefix}:${this.appId}`; - } - return `${keyPrefix}:${this.appId}:${item}`; - } - // eslint-disable-next-line @typescript-eslint/ban-types - async getItem(key) { - const storageKey = this.getKey(key); - const object = await chrome.storage.local.get(storageKey); - return object[storageKey] ? String(object[storageKey]) : null; - } - async setItem(key, value) { - await chrome.storage.local.set({ [this.getKey(key)]: value }); - } - async removeItem(key) { - await chrome.storage.local.remove(this.getKey(key)); - } - }; - - // node_modules/@logto/chrome-extension/lib/index.js - var LogtoClient2 = class extends LogtoClient { - /** - * @param config The configuration object for the client. - */ - constructor(config) { - const requester = createRequester(fetch); - const navigate = async (url, params) => { - switch (params.for) { - case "sign-in": { - const responseUrl = await chrome.identity.launchWebAuthFlow({ url, interactive: true }); - if (!responseUrl) { - throw new LogtoClientError("user_cancelled"); - } - await this.handleSignInCallback(responseUrl); - break; - } - case "sign-out": { - await chrome.identity.launchWebAuthFlow({ - url, - interactive: false, - abortOnLoadForNonInteractive: false, - timeoutMsForNonInteractive: 1e4 - }); - break; - } - default: { - throw new Error(`Unsupported navigation for ${params.for}`); - } - } - }; - super(config, { - requester, - navigate, - storage: new ChromeExtensionStorage(config.appId), - generateCodeChallenge, - generateCodeVerifier, - generateState - }); - } - }; - - // src/background/auth/client.ts - function createLogtoAuthClient() { - const config = readAuthConfig(); - const client = new LogtoClient2({ - appId: config.appId, - endpoint: config.logtoEndpoint, - resources: [config.apiResource], - scopes: config.scopes - }); - return { - getAccessToken(resource) { - return client.getAccessToken(resource); - }, - getIdTokenClaims() { - return client.getIdTokenClaims(); - }, - isAuthenticated() { - return client.isAuthenticated(); - }, - signIn() { - return client.signIn(readChromeIdentity().getRedirectURL("/callback")); - }, - signOut() { - return client.signOut(readChromeIdentity().getRedirectURL()); - } - }; - } - function readChromeIdentity() { - const identity = globalThis.chrome?.identity; - if (typeof identity?.getRedirectURL !== "function") { - throw new Error("chrome.identity.getRedirectURL is unavailable"); - } - return { - getRedirectURL: identity.getRedirectURL.bind(identity) - }; - } - - // src/shared/auth-messages.ts - var authRequestTypes = /* @__PURE__ */ new Set([ - "auth:get-state", - "auth:sign-in", - "auth:sign-out", - "auth:get-access-token" - ]); - function isAuthRequestMessage(value) { - if (!value || typeof value !== "object") { - return false; - } - const candidate = value; - return typeof candidate.type === "string" && authRequestTypes.has(candidate.type); - } - function isAuthResponseMessage(value) { - if (!value || typeof value !== "object") { - return false; - } - const candidate = value; - if (candidate.ok === false) { - return candidate.type === "auth:error" && typeof candidate.error === "string"; - } - if (candidate.ok !== true || typeof candidate.type !== "string") { - return false; - } - if (candidate.type === "auth:ack") { - return true; - } - if (candidate.type === "auth:token") { - return Boolean( - candidate.value && typeof candidate.value === "object" && typeof candidate.value.accessToken === "string" - ); - } - if (candidate.type === "auth:state") { - return Boolean( - candidate.value && typeof candidate.value === "object" && typeof candidate.value.isAuthenticated === "boolean" - ); - } - return false; - } - - // src/shared/batch-submit-config.ts - var DEFAULT_BATCH_SUBMIT_BASE_URL = "http://192.168.31.21:8083"; - - // src/shared/batch-submit-client.ts - function createBatchSubmitClient(options) { - const baseUrl = options.baseUrl ?? DEFAULT_BATCH_SUBMIT_BASE_URL; - const fetchImpl = options.fetchImpl ?? fetch; - const getAccessToken = options.getAccessToken ?? (() => readAccessToken(options.sendMessage)); - return { - async submitBatch(payload) { - const token = await getAccessToken(); - const response = await fetchImpl( - buildBatchSubmitUrl(baseUrl), - { - body: JSON.stringify(payload), - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json" - }, - method: "POST" - } - ); - if (response.status === 401 || response.status === 403) { - throw new Error("batch submit unauthorized"); - } - if (!response.ok) { - throw new Error(`batch submit failed: ${response.status}`); - } - return readBatchSubmitResponse(await response.json()); - } - }; - } - function buildBatchSubmitUrl(baseUrl) { - return new URL("/api/v1/batch-status/batches", baseUrl).toString(); - } - async function readAccessToken(sendMessage) { - const response = await sendMessage({ type: "auth:get-access-token" }); - if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:token" || !response.value.accessToken.trim()) { - throw new Error("batch submit token unavailable"); - } - return response.value.accessToken; - } - function readBatchSubmitResponse(payload) { - if (!isRecord(payload)) { - throw new Error("batch submit response is invalid"); - } - if (payload.success !== true) { - const message2 = typeof payload.msg === "string" && payload.msg.trim() ? payload.msg : "batch submit failed"; - throw new Error(message2); - } - return "data" in payload ? payload.data : payload; - } - function isRecord(value) { - return typeof value === "object" && value !== null; - } - - // src/shared/backend-metrics-config.ts - var DEFAULT_BACKEND_METRICS_BASE_URL = "https://talent-search.intelligrow.cn"; - - // src/shared/backend-metrics-client.ts - function createBackendMetricsClient(options) { - const baseUrl = options.baseUrl ?? DEFAULT_BACKEND_METRICS_BASE_URL; - const fetchImpl = options.fetchImpl ?? defaultFetch; - return { - async searchByStarIds(starIds) { - const response = await fetchImpl(buildBackendMetricsSearchUrl(baseUrl), { - body: JSON.stringify(buildBackendMetricsSearchRequestBody(starIds)), - headers: { - Authorization: `Bearer ${await options.getAccessToken()}`, - "Content-Type": "application/json" - }, - method: "POST" - }); - if (!response.ok) { - throw new Error("backend metrics request failed"); - } - return mapBackendMetricsSearchResponse(await response.json()); - } - }; - } - function buildBackendMetricsSearchUrl(baseUrl) { - return new URL("/api/v1/history/talents/search", baseUrl).toString(); - } - function buildBackendMetricsSearchRequestBody(starIds) { - return { - page: 1, - size: Math.max(20, starIds.length), - type: "star_id", - values: starIds - }; - } - function mapBackendMetricsSearchResponse(payload) { - const rows = readResponseRows(payload); - if (!rows) { - throw new Error("backend metrics response is invalid"); - } - return rows.flatMap((row) => { - if (!isRecord2(row) || typeof row.star_id !== "string") { - return []; - } - return [ - { - a3IncreaseCount: formatDecimalValue( - readAverageA3IncreaseCount(row) - ), - afterViewSearchCount: formatDecimalValue(row.avg_after_view_search_cnt), - afterViewSearchRate: formatRateValue(row.avg_after_view_search_rate), - cpSearch: formatDecimalValue(row.cp_search), - cpa3: formatDecimalValue(readCpa3Value(row)), - newA3Rate: formatRateValue(row.avg_new_a3_rate), - starId: row.star_id - } - ]; - }); - } - function readAverageA3IncreaseCount(row) { - const directAverage = readFiniteNumber(row.avg_a3_increase_cnt); - if (directAverage !== null) { - return directAverage; - } - const totalNewA3 = readTotalNewA3Value(row); - const videoCount = readFiniteNumber(row.video_count) ?? readNestedVideoCount(row.videos); - if (totalNewA3 === null || videoCount === null || videoCount <= 0) { - return null; - } - return totalNewA3 / videoCount; - } - function readCpa3Value(row) { - const directCpa3 = readFiniteNumber(row.cpa3); - if (directCpa3 !== null) { - return directCpa3; - } - const totalCost = readFiniteNumber(row.total_estimated_video_cost); - const totalNewA3 = readTotalNewA3Value(row); - if (totalCost === null || totalNewA3 === null || totalNewA3 <= 0) { - return null; - } - return totalCost / totalNewA3; - } - function readTotalNewA3Value(row) { - const derivedFromTotals = deriveTotalNewA3FromTotals(row); - if (derivedFromTotals !== null) { - return derivedFromTotals; - } - return deriveTotalNewA3FromVideos(row.videos); - } - function deriveTotalNewA3FromTotals(row) { - const totalPlayCount = readFiniteNumber(row.total_play_cnt); - const averageNewA3Rate = readFiniteNumber(row.avg_new_a3_rate); - if (totalPlayCount === null || averageNewA3Rate === null) { - return null; - } - return totalPlayCount * averageNewA3Rate; - } - function deriveTotalNewA3FromVideos(value) { - if (!Array.isArray(value)) { - return null; - } - let total = 0; - let hasFiniteValue = false; - value.forEach((video) => { - if (!isRecord2(video)) { - return; - } - const newA3 = readFiniteNumber(video.new_a3); - if (newA3 === null) { - return; - } - hasFiniteValue = true; - total += newA3; - }); - return hasFiniteValue ? total : null; - } - function readNestedVideoCount(value) { - return Array.isArray(value) ? value.length : null; - } - function readResponseRows(payload) { - if (!isRecord2(payload) || payload.success !== true) { - return null; - } - const topLevelData = isRecord2(payload.data) ? payload.data : null; - return Array.isArray(topLevelData?.data) ? topLevelData.data : null; - } - function formatRateValue(value) { - const number = typeof value === "number" ? value : Number(value); - if (Number.isFinite(number)) { - const percentage = number * 100; - const formatted = new Intl.NumberFormat("en-US", { - maximumFractionDigits: 2, - minimumFractionDigits: percentage % 1 === 0 ? 0 : 2 - }).format(percentage); - return `${formatted}%`; - } - return ""; - } - function formatDecimalValue(value) { - const number = typeof value === "number" ? value : Number(value); - if (!Number.isFinite(number)) { - return ""; - } - return new Intl.NumberFormat("en-US", { - maximumFractionDigits: 2, - minimumFractionDigits: 2 - }).format(number); - } - async function defaultFetch(input, init) { - return fetch(input, init); - } - function isRecord2(value) { - return typeof value === "object" && value !== null; - } - function readFiniteNumber(value) { - const number = typeof value === "number" ? value : Number(value); - return Number.isFinite(number) ? number : null; - } - - // src/shared/backend-metrics-messages.ts - function isBackendMetricsSearchRequestMessage(value) { - if (!value || typeof value !== "object") { - return false; - } - const candidate = value; - return candidate.type === "backend-metrics:search" && Boolean( - candidate.value && typeof candidate.value === "object" && Array.isArray(candidate.value.starIds) && candidate.value.starIds.every( - (starId) => typeof starId === "string" - ) - ); - } - - // src/background/index.ts - function registerBackgroundMessageHandler(chromeLike = readChromeLike(), dependencies = {}) { - let authController = dependencies.authController; - let searchBackendMetrics = dependencies.searchBackendMetrics; - let submitBatch = dependencies.submitBatch; - chromeLike.runtime?.onMessage?.addListener((message2, _sender, sendResponse) => { - if (isDownloadMarketCsvMessage(message2)) { - void triggerCsvDownload(chromeLike, message2).then(() => { - sendResponse({ ok: true }); - }).catch((error) => { - sendResponse({ - error: error instanceof Error ? error.message : String(error), - ok: false - }); - }); - return true; - } - if (isDownloadUpdateMessage(message2)) { - void triggerUpdateDownload(chromeLike, message2).then(() => { - sendResponse({ ok: true, type: "update:download-ack" }); - }).catch((error) => { - sendResponse({ - error: error instanceof Error ? error.message : String(error), - ok: false, - type: "update:download-error" - }); - }); - return true; - } - if (isBatchSubmitMessage(message2)) { - authController ??= createAuthController({ - authClient: createLogtoAuthClient() - }); - submitBatch ??= createBatchSubmitClient({ - baseUrl: DEFAULT_BATCH_SUBMIT_BASE_URL, - getAccessToken: () => authController.getAccessToken(), - sendMessage: () => Promise.reject(new Error("background batch submit does not use sendMessage")) - }).submitBatch; - void submitBatch(message2.payload).then((value) => { - sendResponse({ - ok: true, - type: "batch:ack", - value - }); - }).catch((error) => { - sendResponse({ - error: error instanceof Error ? error.message : String(error), - ok: false, - type: "batch:error" - }); - }); - return true; - } - if (isBackendMetricsSearchRequestMessage(message2)) { - authController ??= createAuthController({ - authClient: createLogtoAuthClient() - }); - searchBackendMetrics ??= createBackendMetricsClient({ - baseUrl: DEFAULT_BACKEND_METRICS_BASE_URL, - getAccessToken: () => authController.getAccessToken() - }).searchByStarIds; - void searchBackendMetrics(message2.value.starIds).then((rows) => { - sendResponse({ - ok: true, - type: "backend-metrics:result", - value: { - rows - } - }); - }).catch((error) => { - sendResponse({ - error: error instanceof Error ? error.message : String(error), - ok: false, - type: "backend-metrics:error" - }); - }); - return true; - } - if (!isAuthRequestMessage(message2)) { - return; - } - authController ??= createAuthController({ - authClient: createLogtoAuthClient() - }); - void handleAuthMessage(authController, message2).then((response) => { - sendResponse(response); - }).catch((error) => { - sendResponse({ - error: error instanceof Error ? error.message : String(error), - ok: false, - type: "auth:error" - }); - }); - return true; - }); - } - async function triggerUpdateDownload(chromeLike, message2) { - if (!chromeLike.downloads?.download) { - throw new Error("chrome.downloads.download is unavailable"); - } - await Promise.resolve( - chromeLike.downloads.download({ - filename: message2.filename, - saveAs: true, - url: message2.url - }) - ); - } - async function handleAuthMessage(authController, message2) { - if (message2.type === "auth:get-state") { - return { - ok: true, - type: "auth:state", - value: await authController.getAuthState() - }; - } - if (message2.type === "auth:get-access-token") { - return { - ok: true, - type: "auth:token", - value: { - accessToken: await authController.getAccessToken() - } - }; - } - if (message2.type === "auth:sign-in") { - await authController.signIn(); - return { - ok: true, - type: "auth:ack" - }; - } - await authController.signOut(); - return { - ok: true, - type: "auth:ack" - }; - } - function readChromeLike() { - return globalThis.chrome ?? {}; - } - async function triggerCsvDownload(chromeLike, message2) { - if (!chromeLike.downloads?.download) { - throw new Error("chrome.downloads.download is unavailable"); - } - const csvUrl = `data:text/csv;charset=utf-8,${encodeURIComponent(`\uFEFF${message2.csv}`)}`; - await Promise.resolve( - chromeLike.downloads.download({ - filename: message2.filename, - saveAs: false, - url: csvUrl - }) - ); - } - function isDownloadMarketCsvMessage(message2) { - if (!message2 || typeof message2 !== "object") { - return false; - } - const candidate = message2; - return candidate.type === "download-market-csv" && typeof candidate.csv === "string" && typeof candidate.filename === "string"; - } - function isDownloadUpdateMessage(message2) { - if (!message2 || typeof message2 !== "object") { - return false; - } - const candidate = message2; - return candidate.type === "update:download" && typeof candidate.filename === "string" && typeof candidate.url === "string" && candidate.url.startsWith("https://"); - } - function isBatchSubmitMessage(message2) { - if (!message2 || typeof message2 !== "object") { - return false; - } - const candidate = message2; - return candidate.type === "batch:submit" && "payload" in candidate; - } - registerBackgroundMessageHandler(); -})(); -/*! Bundled license information: - -@silverhand/essentials/lib/utilities/assertions.js: - (*! - * is-plain-object - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - *) -*/ diff --git a/dist-release/content/index.js b/dist-release/content/index.js deleted file mode 100644 index 4202fce..0000000 --- a/dist-release/content/index.js +++ /dev/null @@ -1,5935 +0,0 @@ -"use strict"; -(() => { - // src/shared/rate-normalizer.ts - function normalizeRateDisplay(value) { - const trimmedValue = value.trim(); - const rangeMatch = trimmedValue.match( - /^([0-9]+(?:\.[0-9]+)?)\s*%?\s*-\s*([0-9]+(?:\.[0-9]+)?)\s*%$/ - ); - if (rangeMatch) { - const [, lowerBound, upperBound] = rangeMatch; - return `${lowerBound}% - ${upperBound}%`; - } - return trimmedValue.replace(/\s+/g, ""); - } - function normalizeFractionRateDisplay(value) { - const numericValue = Number(value); - if (!Number.isFinite(numericValue)) { - return null; - } - const percentageValue = numericValue * 100; - return `${trimTrailingZeros(percentageValue.toFixed(6))}%`; - } - function parseRateLowerBound(value) { - const comparableRate = toComparableRate(value); - return comparableRate?.numeric ?? null; - } - function compareRateValues(leftValue, rightValue) { - const leftComparable = toComparableRate(leftValue); - const rightComparable = toComparableRate(rightValue); - if (!leftComparable && !rightComparable) { - return 0; - } - if (!leftComparable) { - return 1; - } - if (!rightComparable) { - return -1; - } - if (leftComparable.numeric !== rightComparable.numeric) { - return leftComparable.numeric - rightComparable.numeric; - } - if (leftComparable.isLessThan === rightComparable.isLessThan) { - return 0; - } - return leftComparable.isLessThan ? -1 : 1; - } - function toComparableRate(value) { - if (!value) { - return null; - } - const normalizedValue = normalizeRateDisplay(value); - const lessThanMatch = normalizedValue.match(/^<\s*([0-9]+(?:\.[0-9]+)?)%$/); - if (lessThanMatch) { - return { - isLessThan: true, - numeric: Number(lessThanMatch[1]) - }; - } - const rangeMatch = normalizedValue.match( - /^([0-9]+(?:\.[0-9]+)?)%\s*-\s*([0-9]+(?:\.[0-9]+)?)%$/ - ); - if (rangeMatch) { - return { - isLessThan: false, - numeric: Number(rangeMatch[1]) - }; - } - const exactMatch = normalizedValue.match(/^([0-9]+(?:\.[0-9]+)?)%$/); - if (exactMatch) { - return { - isLessThan: false, - numeric: Number(exactMatch[1]) - }; - } - return null; - } - function trimTrailingZeros(value) { - return value.replace(/\.?0+$/, ""); - } - - // src/shared/csv.ts - function escapeCsvCell(value) { - if (/[",\n]/.test(value)) { - return `"${value.replace(/"/g, '""')}"`; - } - return value; - } - - // src/content/market/csv-exporter.ts - var FALLBACK_BASE_COLUMNS = [ - { - header: "\u8FBE\u4EBAID", - readValue: (record) => record.authorId - }, - { - header: "\u8FBE\u4EBA\u540D\u79F0", - readValue: (record) => record.authorName - }, - { - header: "\u5730\u533A", - readValue: (record) => record.location ?? "" - }, - { - header: "21-60s\u62A5\u4EF7", - readValue: (record) => record.price21To60s ?? "" - } - ]; - var RATE_COLUMNS = [ - { - header: "\u5546\u5355\u89C6\u9891\u770B\u540E\u641C\u7387", - readValue: (record) => record.rates?.singleVideoAfterSearchRate ? normalizeRateDisplay(record.rates.singleVideoAfterSearchRate) : "" - }, - { - header: "\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387", - readValue: (record) => record.rates?.personalVideoAfterSearchRate ? normalizeRateDisplay(record.rates.personalVideoAfterSearchRate) : "" - } - ]; - var BACKEND_METRIC_COLUMNS = [ - { - header: "\u79D2\u601Dapi-\u770B\u540E\u641C\u7387", - readValue: (record) => record.backendMetrics?.afterViewSearchRate ?? "" - }, - { - header: "\u79D2\u601Dapi-\u770B\u540E\u641C\u6570", - readValue: (record) => record.backendMetrics?.afterViewSearchCount ?? "" - }, - { - header: "\u79D2\u601Dapi-\u65B0\u589EA3\u6570", - readValue: (record) => record.backendMetrics?.a3IncreaseCount ?? "" - }, - { - header: "\u79D2\u601Dapi-\u65B0\u589EA3\u7387", - readValue: (record) => record.backendMetrics?.newA3Rate ?? "" - }, - { - header: "\u79D2\u601Dapi-CPA3", - readValue: (record) => record.backendMetrics?.cpa3 ?? "" - }, - { - header: "\u79D2\u601Dapi-cp_search", - readValue: (record) => record.backendMetrics?.cpSearch ?? "" - } - ]; - function listRateCsvHeaders() { - return RATE_COLUMNS.map((column) => column.header); - } - function listBackendMetricCsvHeaders() { - return BACKEND_METRIC_COLUMNS.map((column) => column.header); - } - function buildMarketCsv(records) { - const csvColumns = buildMarketCsvColumns(records); - const headerLine = csvColumns.map((column) => column.header).join(","); - const rowLines = records.map( - (record) => csvColumns.map((column) => escapeCsvCell(column.readValue(record))).join(",") - ); - return [headerLine, ...rowLines].join("\n"); - } - function buildMarketCsvColumns(records) { - const baseColumns = buildBaseColumns(records); - return [...baseColumns, ...RATE_COLUMNS, ...BACKEND_METRIC_COLUMNS]; - } - function buildBaseColumns(records) { - const orderedHeaders = []; - const seenHeaders = /* @__PURE__ */ new Set(); - const excludedHeaders = /* @__PURE__ */ new Set(["\u4EE3\u8868\u89C6\u9891"]); - records.forEach((record) => { - Object.keys(record.exportFields ?? {}).forEach((header) => { - if (seenHeaders.has(header) || excludedHeaders.has(header)) { - return; - } - seenHeaders.add(header); - orderedHeaders.push(header); - }); - }); - if (orderedHeaders.length === 0) { - return FALLBACK_BASE_COLUMNS; - } - return orderedHeaders.map((header) => ({ - header, - readValue: (record) => record.exportFields?.[header] ?? "" - })); - } - - // src/content/market/audience-profile-csv.ts - var PROFILE_LAYOUTS = [ - { includeGender: true, kind: "audience", label: "\u89C2\u4F17\u753B\u50CF" }, - { includeGender: true, kind: "fans", label: "\u7C89\u4E1D\u753B\u50CF" }, - { includeGender: false, kind: "longtimeFans", label: "\u94C1\u7C89\u753B\u50CF" } - ]; - var GENDER_LABELS = ["\u7537\u6027", "\u5973\u6027"]; - var AGE_LABELS = ["18-23", "24-30", "31-40", "41-50", "50+"]; - var CITY_TIER_LABELS = [ - "\u4E00\u7EBF\u57CE\u5E02", - "\u4E8C\u7EBF\u57CE\u5E02", - "\u4E09\u7EBF\u57CE\u5E02", - "\u56DB\u7EBF\u57CE\u5E02", - "\u4E94\u7EBF\u57CE\u5E02" - ]; - var CROWD_LABELS = [ - "\u7CBE\u81F4\u5988\u5988", - "\u90FD\u5E02\u94F6\u53D1", - "\u65B0\u9510\u767D\u9886", - "\u8D44\u6DF1\u4E2D\u4EA7", - "\u90FD\u5E02\u84DD\u9886", - "Z\u4E16\u4EE3", - "\u5C0F\u9547\u4E2D\u8001\u5E74", - "\u5C0F\u9547\u9752\u5E74" - ]; - var BUSINESS_VIDEO_LAYOUTS = [ - { key: "personalVideo", label: "\u4E2A\u4EBA\u89C6\u9891" }, - { key: "xingtuVideo", label: "\u661F\u56FE\u89C6\u9891" } - ]; - var BUSINESS_VIDEO_METRIC_LAYOUTS = [ - { key: "medianPlay", label: "\u64AD\u653E\u91CF\u4E2D\u4F4D\u6570" }, - { key: "finishRate", label: "\u5B8C\u64AD\u7387" }, - { key: "interactionRate", label: "\u4E92\u52A8\u7387" }, - { key: "publishedItems", label: "\u53D1\u5E03\u4F5C\u54C1" }, - { key: "averageDuration", label: "\u5E73\u5747\u65F6\u957F" }, - { key: "averageLike", label: "\u5E73\u5747\u70B9\u8D5E" }, - { key: "averageComment", label: "\u5E73\u5747\u8BC4\u8BBA" }, - { key: "averageShare", label: "\u5E73\u5747\u8F6C\u53D1" } - ]; - var BUSINESS_VIDEO_SECTION_LABEL = "\u5185\u5BB9\u6570\u636E"; - var BUSINESS_ESTIMATE_SECTION_LABEL = "\u6548\u679C\u9884\u4F30"; - var BUSINESS_ESTIMATE_LAYOUTS = [ - { key: "oneToTwenty", label: "1-20s\u89C6\u9891" }, - { key: "twentyToSixty", label: "20-60s\u89C6\u9891" }, - { key: "overSixty", label: "60s\u4EE5\u4E0A\u89C6\u9891" } - ]; - var BUSINESS_ESTIMATE_METRIC_LAYOUTS = [ - { key: "expectedCpm", label: "\u9884\u671FCPM" }, - { key: "expectedCpe", label: "\u9884\u671FCPE" }, - { key: "expectedPlay", label: "\u9884\u671F\u64AD\u653E\u91CF" }, - { key: "hotRate", label: "\u7206\u6587\u7387" } - ]; - function buildAudienceProfileCsv(rows, options = {}) { - const marketColumns = buildMarketCsvColumns(rows.map((row) => row.record)); - const csvColumns = filterAudienceProfileCsvColumns([ - ...marketColumns.map(toMarketColumn), - ...buildBusinessAbilityColumns(), - ...PROFILE_LAYOUTS.flatMap((layout) => buildProfileColumns(layout)) - ], options.selectedHeaders); - const headerLine = csvColumns.map((column) => column.header).join(","); - const rowLines = rows.map( - (row) => csvColumns.map((column) => escapeCsvCell(column.readValue(row))).join(",") - ); - return [headerLine, ...rowLines].join("\n"); - } - function listAudienceProfileSelectableFieldGroups() { - return [ - { - headers: listRateCsvHeaders(), - label: "\u770B\u540E\u641C\u7387" - }, - { - headers: listBackendMetricCsvHeaders(), - label: "\u79D2\u601Dapi\u6570\u636E" - }, - { - headers: buildBusinessVideoColumns().map((column) => column.header), - label: "\u5185\u5BB9\u6570\u636E" - }, - { - headers: buildBusinessEstimateColumns().map((column) => column.header), - label: "\u6548\u679C\u9884\u4F30" - }, - ...PROFILE_LAYOUTS.map((layout) => ({ - headers: buildProfileColumns(layout).map((column) => column.header), - label: layout.label - })) - ]; - } - function filterAudienceProfileCsvColumns(columns, selectedHeaders) { - if (!selectedHeaders) { - return columns; - } - const selectableHeaderSet = new Set(listAudienceProfileSelectableHeaders()); - const selectedHeaderSet = new Set(selectedHeaders); - return columns.filter( - (column) => !selectableHeaderSet.has(column.header) || selectedHeaderSet.has(column.header) - ); - } - function listAudienceProfileSelectableHeaders() { - return listAudienceProfileSelectableFieldGroups().flatMap( - (group) => group.headers - ); - } - function buildBusinessAbilityColumns() { - return [...buildBusinessVideoColumns(), ...buildBusinessEstimateColumns()]; - } - function buildBusinessVideoColumns() { - return [ - ...BUSINESS_VIDEO_LAYOUTS.flatMap( - (videoLayout) => BUSINESS_VIDEO_METRIC_LAYOUTS.map((metricLayout) => ({ - header: `${BUSINESS_VIDEO_SECTION_LABEL}-${videoLayout.label}-${metricLayout.label}`, - readValue: (row) => readBusinessVideoValue(row, videoLayout.key, metricLayout.key) - })) - ) - ]; - } - function buildBusinessEstimateColumns() { - return [ - ...BUSINESS_ESTIMATE_LAYOUTS.flatMap( - (durationLayout) => BUSINESS_ESTIMATE_METRIC_LAYOUTS.map((metricLayout) => ({ - header: `${BUSINESS_ESTIMATE_SECTION_LABEL}-${durationLayout.label}-${metricLayout.label}`, - readValue: (row) => readBusinessEstimateValue(row, durationLayout.key, metricLayout.key) - })) - ) - ]; - } - function readBusinessVideoValue(row, videoKey, metricKey) { - const businessAbility = row.businessAbility; - if (!businessAbility || businessAbility.status !== "success") { - return ""; - } - return businessAbility.videos[videoKey]?.[metricKey] ?? ""; - } - function readBusinessEstimateValue(row, durationKey, metricKey) { - const businessAbility = row.businessAbility; - if (!businessAbility || businessAbility.status !== "success") { - return ""; - } - return businessAbility.estimates[durationKey]?.[metricKey] ?? ""; - } - function toMarketColumn(column) { - return { - header: column.header, - readValue: (row) => column.readValue(row.record) - }; - } - function buildProfileColumns(layout) { - const columns = []; - if (layout.includeGender) { - columns.push( - ...buildFixedDistributionColumns( - layout.label, - layout.kind, - "gender", - GENDER_LABELS - ) - ); - } - columns.push( - ...buildFixedDistributionColumns(layout.label, layout.kind, "age", AGE_LABELS), - ...buildFixedDistributionColumns( - layout.label, - layout.kind, - "cityTier", - CITY_TIER_LABELS - ), - ...buildFixedDistributionColumns(layout.label, layout.kind, "crowd", CROWD_LABELS) - ); - return columns; - } - function buildFixedDistributionColumns(prefix, kind, key, labels) { - return labels.map((label) => ({ - header: `${prefix}-${label}\u5360\u6BD4`, - readValue: (row) => readDistributionValue(row.profiles[kind], key, label) - })); - } - function readDistributionValue(profile, key, label) { - if (profile.status !== "success") { - return ""; - } - return readProfileDistributionItems(profile, key).find( - (candidate) => candidate.label === label - )?.value ?? "0%"; - } - function readProfileDistributionItems(profile, key) { - return profile.status === "success" ? profile[key] ?? [] : []; - } - - // src/content/market/audience-profile-client.ts - var SECTION_BY_DISPLAY = [ - [/性别/, "gender"], - [/年龄/, "age"], - [/省份|全国省份/, "province"], - [/城市分布|地域/, "cityTop"], - [/城市等级/, "cityTier"], - [/兴趣/, "interest"], - [/八大人群/, "crowd"] - ]; - var GENDER_LABELS2 = { - female: "\u5973\u6027", - male: "\u7537\u6027" - }; - var AGE_ORDER = ["18-23", "24-30", "31-40", "41-50", "50+"]; - var CITY_TIER_ORDER = ["\u4E00\u7EBF", "\u65B0\u4E00\u7EBF", "\u4E8C\u7EBF", "\u4E09\u7EBF", "\u56DB\u7EBF", "\u4E94\u7EBF"]; - var AUDIENCE_PROFILE_TARGETS = { - audience: { linkType: 5, source: "audienceDistribution" }, - fans: { authorType: 1, source: "fansDistribution" }, - longtimeFans: { authorType: 5, source: "fansDistribution" } - }; - function createAudienceProfileClient(options = {}) { - const baseUrl = options.baseUrl ?? resolveBaseUrl(); - const fetchImpl = options.fetchImpl ?? defaultFetch; - const timeoutMs = options.timeoutMs ?? 8e3; - return { - async loadAudienceProfile(record, target) { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await fetchImpl( - buildAudienceProfileUrl(record.authorId, baseUrl, target), - { - credentials: "include", - method: "GET", - signal: controller.signal - } - ); - if (!response.ok) { - return { - failureReason: "request-failed", - status: "failed" - }; - } - return mapAudienceProfileResponse(await response.json()); - } catch (error) { - return { - failureReason: error instanceof Error && error.name === "AbortError" ? "timeout" : "request-failed", - status: "failed" - }; - } finally { - clearTimeout(timeoutId); - } - } - }; - } - function buildAudienceProfileUrl(authorId, baseUrl, target) { - const url = new URL( - target.source === "audienceDistribution" ? "/gw/api/data_sp/author_audience_distribution" : "/gw/api/data_sp/get_author_fans_distribution", - baseUrl - ); - url.searchParams.set("o_author_id", authorId); - url.searchParams.set("platform_source", "1"); - if (target.source === "audienceDistribution") { - url.searchParams.set("platform_channel", "1"); - url.searchParams.set("link_type", String(target.linkType)); - } else { - url.searchParams.set("author_type", String(target.authorType)); - } - return url.toString(); - } - function mapAudienceProfileResponse(payload) { - if (!isRecord(payload) || !Array.isArray(payload.distributions)) { - return { - failureReason: "bad-response", - status: "failed" - }; - } - const profile = { - status: "success" - }; - payload.distributions.forEach((section) => { - if (!isRecord(section)) { - return; - } - const display = readString(section.type_display); - const sectionName = resolveSection(display); - if (!sectionName || !Array.isArray(section.distribution_list)) { - return; - } - profile[sectionName] = normalizeDistributionItems( - section.distribution_list, - sectionName - ); - }); - if (Object.keys(profile).length === 1) { - return { - failureReason: "missing-profile", - status: "failed" - }; - } - return profile; - } - function normalizeDistributionItems(rawItems, sectionName) { - const parsedItems = rawItems.map((item) => { - if (!isRecord(item)) { - return null; - } - const key = readString(item.distribution_key); - const value = readNumber(item.distribution_value); - if (!key || value === null) { - return null; - } - return { - label: normalizeLabel(key, sectionName), - rawLabel: key, - value - }; - }).filter( - (item) => Boolean(item) - ); - const total = parsedItems.reduce((sum, item) => sum + item.value, 0); - if (total <= 0) { - return []; - } - return parsedItems.sort((left, right) => compareDistributionItems(left, right, sectionName)).map((item) => ({ - label: item.label, - value: formatPercent(item.value / total) - })); - } - function compareDistributionItems(left, right, sectionName) { - if (sectionName === "age") { - return orderIndex(AGE_ORDER, left.rawLabel) - orderIndex(AGE_ORDER, right.rawLabel); - } - if (sectionName === "cityTier") { - return orderIndex(CITY_TIER_ORDER, left.rawLabel) - orderIndex(CITY_TIER_ORDER, right.rawLabel); - } - return right.value - left.value; - } - function orderIndex(order, value) { - const index = order.indexOf(value); - return index === -1 ? order.length : index; - } - function normalizeLabel(label, sectionName) { - if (sectionName === "gender") { - return GENDER_LABELS2[label] ?? label; - } - if (sectionName === "cityTier" && !label.endsWith("\u57CE\u5E02")) { - return `${label}\u57CE\u5E02`; - } - return label; - } - function resolveSection(display) { - if (!display) { - return null; - } - return SECTION_BY_DISPLAY.find(([pattern]) => pattern.test(display))?.[1] ?? null; - } - function formatPercent(value) { - const percent = Math.round(value * 1e3) / 10; - return `${Number.isInteger(percent) ? percent.toFixed(0) : percent.toFixed(1)}%`; - } - function readString(value) { - return typeof value === "string" && value.trim() ? value.trim() : null; - } - function readNumber(value) { - if (typeof value === "number" && Number.isFinite(value)) { - return value; - } - if (typeof value === "string" && value.trim()) { - const numericValue = Number(value); - return Number.isFinite(numericValue) ? numericValue : null; - } - return null; - } - function resolveBaseUrl() { - if (typeof location !== "undefined" && location.origin) { - return location.origin; - } - return "https://xingtu.cn"; - } - async function defaultFetch(input, init) { - return fetch(input, init); - } - function isRecord(value) { - return typeof value === "object" && value !== null; - } - - // src/content/market/author-base-client.ts - function createAuthorBaseClient(options = {}) { - const baseUrl = options.baseUrl ?? resolveBaseUrl2(); - const fetchImpl = options.fetchImpl ?? defaultFetch2; - const timeoutMs = options.timeoutMs ?? 8e3; - return { - async loadAuthorBaseInfo(authorId) { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await fetchImpl( - buildAuthorBaseInfoUrl(authorId, baseUrl), - { - credentials: "include", - method: "GET", - signal: controller.signal - } - ); - if (!response.ok) { - return buildFailedRecord(authorId, "request-failed"); - } - return mapAuthorBaseInfoResponse(authorId, await response.json()); - } catch (error) { - return buildFailedRecord( - authorId, - error instanceof Error && error.name === "AbortError" ? "timeout" : "request-failed" - ); - } finally { - clearTimeout(timeoutId); - } - } - }; - } - function buildAuthorBaseInfoUrl(authorId, baseUrl) { - const url = new URL("/gw/api/author/get_author_base_info", baseUrl); - url.searchParams.set("o_author_id", authorId); - url.searchParams.set("platform_source", "1"); - url.searchParams.set("platform_channel", "1"); - url.searchParams.set("recommend", "true"); - url.searchParams.set("need_sec_uid", "true"); - url.searchParams.set("need_linkage_info", "true"); - return url.toString(); - } - function mapAuthorBaseInfoResponse(authorId, payload) { - if (!isRecord2(payload)) { - return buildFailedRecord(authorId, "bad-response"); - } - const authorName = readString2(payload.nick_name); - if (!authorName) { - return buildFailedRecord(authorId, "missing-rate"); - } - return { - authorId, - authorName, - status: "success" - }; - } - function buildFailedRecord(authorId, failureReason) { - return { - authorId, - authorName: "", - failureReason, - status: "failed" - }; - } - function readString2(value) { - return typeof value === "string" && value.trim() ? value.trim() : null; - } - function resolveBaseUrl2() { - if (typeof location !== "undefined" && location.origin) { - return location.origin; - } - return "https://xingtu.cn"; - } - async function defaultFetch2(input, init) { - return fetch(input, init); - } - function isRecord2(value) { - return typeof value === "object" && value !== null; - } - - // src/content/market/author-id-input.ts - var AUTHOR_ID_PATTERN = /^\d{16,20}$/; - function parseAuthorIds(input) { - const ids = []; - const duplicates = []; - const invalidTokens = []; - const seen = /* @__PURE__ */ new Set(); - input.split(/[\s,,;;]+/).map((token) => token.trim()).filter(Boolean).forEach((token) => { - if (!/^\d+$/.test(token) || !AUTHOR_ID_PATTERN.test(token)) { - invalidTokens.push(token); - return; - } - if (seen.has(token)) { - duplicates.push(token); - return; - } - seen.add(token); - ids.push(token); - }); - return { - duplicates, - ids, - invalidTokens - }; - } - - // src/content/market/business-ability-client.ts - var VIDEO_TYPES = { - personalVideo: 1, - xingtuVideo: 2 - }; - function createBusinessAbilityClient(options = {}) { - const baseUrl = options.baseUrl ?? resolveBaseUrl3(); - const fetchImpl = options.fetchImpl ?? defaultFetch3; - const timeoutMs = options.timeoutMs ?? 8e3; - return { - async loadBusinessAbility(record) { - const personalVideo = await loadJson( - buildBusinessAbilityVideoUrl(record.authorId, baseUrl, VIDEO_TYPES.personalVideo) - ); - const xingtuVideo = await loadJson( - buildBusinessAbilityVideoUrl(record.authorId, baseUrl, VIDEO_TYPES.xingtuVideo) - ); - const estimates = await loadJson( - buildBusinessAbilityEstimateUrl(record.authorId, baseUrl) - ); - if (!personalVideo.ok || !xingtuVideo.ok || !estimates.ok) { - return { - failureReason: personalVideo.failureReason ?? xingtuVideo.failureReason ?? estimates.failureReason, - status: "failed" - }; - } - return { - estimates: mapBusinessAbilityEstimateResponse(estimates.payload), - status: "success", - videos: { - personalVideo: mapBusinessAbilityVideoResponse(personalVideo.payload), - xingtuVideo: mapBusinessAbilityVideoResponse(xingtuVideo.payload) - } - }; - } - }; - async function loadJson(url) { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await fetchImpl(url, { - credentials: "include", - method: "GET", - signal: controller.signal - }); - if (!response.ok) { - return { failureReason: "request-failed", ok: false }; - } - return { ok: true, payload: await response.json() }; - } catch (error) { - return { - failureReason: error instanceof Error && error.name === "AbortError" ? "timeout" : "request-failed", - ok: false - }; - } finally { - clearTimeout(timeoutId); - } - } - } - function buildBusinessAbilityVideoUrl(authorId, baseUrl, videoType) { - const url = new URL("/gw/api/data_sp/get_author_spread_info", baseUrl); - url.searchParams.set("o_author_id", authorId); - url.searchParams.set("platform_source", "1"); - url.searchParams.set("platform_channel", "1"); - url.searchParams.set("type", String(videoType)); - url.searchParams.set("flow_type", "0"); - url.searchParams.set("only_assign", "true"); - url.searchParams.set("range", "2"); - return url.toString(); - } - function buildBusinessAbilityEstimateUrl(authorId, baseUrl) { - const url = new URL( - "/gw/api/aggregator/get_author_commerce_spread_info", - baseUrl - ); - url.searchParams.set("o_author_id", authorId); - return url.toString(); - } - function mapBusinessAbilityVideoResponse(payload) { - const data = getPayloadData(payload); - return { - averageComment: formatWan(readNumber2(data?.comment_avg)), - averageDuration: formatDuration(readNumber2(data?.avg_duration)), - averageLike: formatWan(readNumber2(data?.like_avg)), - averageShare: formatWan(readNumber2(data?.share_avg)), - finishRate: formatBasisPointRate(readNestedNumber(data, "play_over_rate", "value")), - interactionRate: formatBasisPointRate( - readNestedNumber(data, "interact_rate", "value") - ), - medianPlay: formatWan(readNumber2(data?.play_mid)), - publishedItems: formatPublishedItems(readNumber2(data?.item_num)) - }; - } - function mapBusinessAbilityEstimateResponse(payload) { - const data = getPayloadData(payload); - const expectedPlay = formatWan(readNumber2(data?.vv)); - const hotRate = formatDecimalRate(readNumber2(data?.platform_hot_rate)); - return { - oneToTwenty: { - expectedCpe: formatDecimal(readNumber2(data?.cpe_1_20), 1), - expectedCpm: formatFixedDecimal(readNumber2(data?.cpm_1_20), 1), - expectedPlay, - hotRate - }, - overSixty: { - expectedCpe: formatDecimal(readNumber2(data?.cpe_60), 1), - expectedCpm: formatFixedDecimal(readNumber2(data?.cpm_60), 1), - expectedPlay, - hotRate - }, - twentyToSixty: { - expectedCpe: formatDecimal(readNumber2(data?.cpe_20_60), 1), - expectedCpm: formatFixedDecimal(readNumber2(data?.cpm_20_60), 1), - expectedPlay, - hotRate - } - }; - } - function formatPublishedItems(value) { - if (value === null) { - return ""; - } - return value > 0 && value < 5 ? "<5" : formatDecimal(value, 0); - } - function formatDuration(value) { - if (value === null) { - return ""; - } - return `${formatDecimal(value / 100, 0)}s`; - } - function formatBasisPointRate(value) { - if (value === null) { - return ""; - } - return `${formatDecimal(value / 100, 1)}%`; - } - function formatDecimalRate(value) { - if (value === null) { - return "\u7F3A\u5931"; - } - return `${formatDecimal(value * 100, 0)}%`; - } - function formatWan(value) { - if (value === null) { - return ""; - } - if (Math.abs(value) >= 1e4) { - return `${formatDecimal(value / 1e4, 1)}w`; - } - return formatDecimal(value, 0); - } - function formatDecimal(value, digits) { - if (value === null || !Number.isFinite(value)) { - return ""; - } - const fixed = value.toFixed(digits); - return fixed.replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1"); - } - function formatFixedDecimal(value, digits) { - if (value === null || !Number.isFinite(value)) { - return ""; - } - return value.toFixed(digits); - } - function readNestedNumber(data, objectKey, valueKey) { - const objectValue = data?.[objectKey]; - if (!isRecord3(objectValue)) { - return null; - } - return readNumber2(objectValue[valueKey]); - } - function readNumber2(value) { - if (typeof value === "number" && Number.isFinite(value)) { - return value; - } - if (typeof value === "string" && value.trim()) { - const numericValue = Number(value); - return Number.isFinite(numericValue) ? numericValue : null; - } - return null; - } - function getPayloadData(payload) { - if (!isRecord3(payload)) { - return null; - } - return isRecord3(payload.data) ? payload.data : payload; - } - function resolveBaseUrl3() { - if (typeof location !== "undefined" && location.origin) { - return location.origin; - } - return "https://xingtu.cn"; - } - async function defaultFetch3(input, init) { - return fetch(input, init); - } - function isRecord3(value) { - return typeof value === "object" && value !== null; - } - - // src/content/market/audience-profile-field-dialog.ts - function promptForAudienceProfileFields(document2, groups, selectedHeaders) { - return new Promise((resolve) => { - const selectableHeaders = groups.flatMap((group) => group.headers); - const selectedHeaderSet = new Set( - selectedHeaders.filter((header) => selectableHeaders.includes(header)) - ); - if (selectedHeaderSet.size === 0) { - selectableHeaders.forEach((header) => selectedHeaderSet.add(header)); - } - const overlay = document2.createElement("div"); - overlay.dataset.audienceProfileFieldDialog = "overlay"; - applyOverlayStyles(overlay); - const dialog = document2.createElement("section"); - applyDialogStyles(dialog); - const title = document2.createElement("h2"); - applyTitleStyles(title); - const hint = document2.createElement("p"); - hint.textContent = "\u57FA\u7840\u5B57\u6BB5\u4F1A\u56FA\u5B9A\u4FDD\u7559\u3002\u53D6\u6D88\u52FE\u9009\u540E\uFF0C\u672C\u6B21\u53CA\u540E\u7EEDCSV\u5C06\u4E0D\u5305\u542B\u5BF9\u5E94\u5217\u3002"; - applyHintStyles(hint); - const toolbar = document2.createElement("div"); - applyToolbarStyles(toolbar); - const selectAllButton = document2.createElement("button"); - selectAllButton.type = "button"; - selectAllButton.textContent = "\u5168\u9009"; - applySecondaryButtonStyles(selectAllButton); - const resetButton = document2.createElement("button"); - resetButton.type = "button"; - resetButton.textContent = "\u6062\u590D\u9ED8\u8BA4"; - applySecondaryButtonStyles(resetButton); - toolbar.append(selectAllButton, resetButton); - const groupContainer = document2.createElement("div"); - applyGroupContainerStyles(groupContainer); - const fieldInputs = []; - groups.forEach((group) => { - const groupSection = document2.createElement("section"); - groupSection.dataset.audienceProfileFieldDialogGroup = "section"; - applyGroupSectionStyles(groupSection); - const groupHeader = document2.createElement("label"); - applyGroupHeaderStyles(groupHeader); - const groupInput = document2.createElement("input"); - groupInput.type = "checkbox"; - const groupTitle = document2.createElement("span"); - groupTitle.textContent = group.label; - groupHeader.append(groupInput, groupTitle); - const fieldList = document2.createElement("div"); - applyFieldListStyles(fieldList); - const groupFieldInputs = group.headers.map((header) => { - const fieldLabel = document2.createElement("label"); - applyFieldLabelStyles(fieldLabel); - const input = document2.createElement("input"); - input.type = "checkbox"; - input.value = header; - input.dataset.audienceProfileFieldDialogField = "checkbox"; - input.checked = selectedHeaderSet.has(header); - const text = document2.createElement("span"); - text.textContent = header; - fieldLabel.append(input, text); - fieldList.append(fieldLabel); - fieldInputs.push(input); - return input; - }); - const syncGroupInput = () => { - const checkedCount = groupFieldInputs.filter((input) => input.checked).length; - groupInput.checked = checkedCount === groupFieldInputs.length; - groupInput.indeterminate = checkedCount > 0 && checkedCount < groupFieldInputs.length; - }; - groupInput.addEventListener("change", () => { - groupFieldInputs.forEach((input) => { - input.checked = groupInput.checked; - }); - syncTitle(); - }); - groupFieldInputs.forEach((input) => { - input.addEventListener("change", () => { - syncGroupInput(); - syncTitle(); - }); - }); - syncGroupInput(); - groupSection.append(groupHeader, fieldList); - groupContainer.append(groupSection); - }); - const actions = document2.createElement("div"); - applyActionsStyles(actions); - const cancelButton = document2.createElement("button"); - cancelButton.type = "button"; - cancelButton.textContent = "\u53D6\u6D88"; - applySecondaryButtonStyles(cancelButton); - const confirmButton = document2.createElement("button"); - confirmButton.type = "button"; - confirmButton.dataset.audienceProfileFieldDialogSave = "button"; - confirmButton.textContent = "\u4FDD\u5B58"; - applyPrimaryButtonStyles(confirmButton); - actions.append(cancelButton, confirmButton); - dialog.append(title, hint, toolbar, groupContainer, actions); - overlay.append(dialog); - document2.body.appendChild(overlay); - function syncTitle() { - const checkedCount = fieldInputs.filter((input) => input.checked).length; - title.textContent = `\u53EF\u9009\u5B57\u6BB5\uFF08\u5DF2\u9009 ${checkedCount}/${fieldInputs.length} \u4E2A\u5B57\u6BB5\uFF09`; - } - function close(value) { - overlay.remove(); - resolve(value); - } - selectAllButton.addEventListener("click", () => { - fieldInputs.forEach((input) => { - input.checked = true; - }); - syncTitle(); - syncAllGroupInputs(dialog); - }); - resetButton.addEventListener("click", () => { - fieldInputs.forEach((input) => { - input.checked = true; - }); - syncTitle(); - syncAllGroupInputs(dialog); - }); - cancelButton.addEventListener("click", () => close(null)); - confirmButton.addEventListener("click", () => { - const nextHeaders = fieldInputs.filter((input) => input.checked).map((input) => input.value); - close(nextHeaders); - }); - overlay.addEventListener("click", (event) => { - if (event.target === overlay) { - close(null); - } - }); - syncTitle(); - }); - } - function syncAllGroupInputs(dialog) { - dialog.querySelectorAll('[data-audience-profile-field-dialog-group="section"]').forEach((section) => { - const groupInput = section.querySelector(":scope > label > input"); - const fieldInputs = Array.from( - section.querySelectorAll(":scope > div input") - ); - if (!(groupInput instanceof HTMLInputElement) || fieldInputs.length === 0) { - return; - } - const checkedCount = fieldInputs.filter((input) => input.checked).length; - groupInput.checked = checkedCount === fieldInputs.length; - groupInput.indeterminate = checkedCount > 0 && checkedCount < fieldInputs.length; - }); - } - function applyOverlayStyles(overlay) { - overlay.style.position = "fixed"; - overlay.style.inset = "0"; - overlay.style.zIndex = "2147483647"; - overlay.style.display = "flex"; - overlay.style.alignItems = "center"; - overlay.style.justifyContent = "center"; - overlay.style.background = "rgba(15, 23, 42, 0.38)"; - } - function applyDialogStyles(dialog) { - dialog.style.width = "680px"; - dialog.style.maxWidth = "calc(100vw - 32px)"; - dialog.style.maxHeight = "calc(100vh - 48px)"; - dialog.style.display = "flex"; - dialog.style.flexDirection = "column"; - dialog.style.background = "#ffffff"; - dialog.style.borderRadius = "8px"; - dialog.style.boxShadow = "0 18px 45px rgba(15, 23, 42, 0.22)"; - dialog.style.padding = "20px"; - dialog.style.boxSizing = "border-box"; - } - function applyTitleStyles(title) { - title.style.margin = "0 0 8px"; - title.style.fontSize = "18px"; - title.style.fontWeight = "700"; - title.style.color = "#1f2329"; - } - function applyHintStyles(hint) { - hint.style.margin = "0 0 12px"; - hint.style.fontSize = "13px"; - hint.style.lineHeight = "20px"; - hint.style.color = "#64748b"; - } - function applyToolbarStyles(toolbar) { - toolbar.style.display = "flex"; - toolbar.style.gap = "8px"; - toolbar.style.marginBottom = "12px"; - } - function applyGroupContainerStyles(container) { - container.style.display = "flex"; - container.style.flexDirection = "column"; - container.style.gap = "10px"; - container.style.overflow = "auto"; - container.style.paddingRight = "4px"; - } - function applyGroupSectionStyles(section) { - section.style.border = "1px solid #e5e7eb"; - section.style.borderRadius = "8px"; - section.style.padding = "10px"; - } - function applyGroupHeaderStyles(label) { - label.style.display = "flex"; - label.style.alignItems = "center"; - label.style.gap = "8px"; - label.style.fontWeight = "700"; - label.style.color = "#1f2329"; - label.style.marginBottom = "8px"; - } - function applyFieldListStyles(list) { - list.style.display = "grid"; - list.style.gridTemplateColumns = "repeat(auto-fit, minmax(220px, 1fr))"; - list.style.gap = "8px"; - } - function applyFieldLabelStyles(label) { - label.style.display = "flex"; - label.style.alignItems = "center"; - label.style.gap = "6px"; - label.style.fontSize = "13px"; - label.style.lineHeight = "18px"; - label.style.color = "#374151"; - } - function applyActionsStyles(actions) { - actions.style.display = "flex"; - actions.style.justifyContent = "flex-end"; - actions.style.columnGap = "8px"; - actions.style.marginTop = "14px"; - } - function applyPrimaryButtonStyles(button) { - button.style.height = "32px"; - button.style.padding = "0 15px"; - button.style.border = "1px solid #7f1d2d"; - button.style.borderRadius = "8px"; - button.style.background = "#7f1d2d"; - button.style.color = "#ffffff"; - button.style.fontWeight = "600"; - } - function applySecondaryButtonStyles(button) { - button.style.height = "32px"; - button.style.padding = "0 15px"; - button.style.border = "1px solid #d0d7de"; - button.style.borderRadius = "8px"; - button.style.background = "#ffffff"; - button.style.color = "#1f2329"; - button.style.fontWeight = "600"; - } - - // src/content/market/author-id-dialog.ts - function promptForAuthorIds(document2) { - return new Promise((resolve) => { - const overlay = document2.createElement("div"); - overlay.dataset.authorIdDialog = "overlay"; - applyOverlayStyles2(overlay); - const dialog = document2.createElement("section"); - applyDialogStyles2(dialog); - const title = document2.createElement("h2"); - title.textContent = "\u6309\u661F\u56FEID\u5BFC\u51FA"; - applyTitleStyles2(title); - const textarea = document2.createElement("textarea"); - textarea.dataset.authorIdDialogInput = "textarea"; - textarea.placeholder = "\u6BCF\u884C\u4E00\u4E2A\u661F\u56FEID\uFF0C\u4E5F\u652F\u6301\u9017\u53F7\u3001\u7A7A\u683C\u5206\u9694"; - applyTextareaStyles(textarea); - const hint = document2.createElement("p"); - hint.textContent = "\u7C98\u8D34\u5BA2\u6237\u63D0\u4F9B\u7684\u8FBE\u4EBA\u661F\u56FEID\uFF0C\u786E\u8BA4\u540E\u5C06\u6279\u91CF\u5BFC\u51FA\u8FBE\u4EBA\u6570\u636E\u3002"; - applyHintStyles2(hint); - const actions = document2.createElement("div"); - applyActionsStyles2(actions); - const cancelButton = document2.createElement("button"); - cancelButton.type = "button"; - cancelButton.textContent = "\u53D6\u6D88"; - applySecondaryButtonStyles2(cancelButton); - const confirmButton = document2.createElement("button"); - confirmButton.type = "button"; - confirmButton.textContent = "\u5F00\u59CB\u5BFC\u51FA"; - applyPrimaryButtonStyles2(confirmButton); - actions.append(cancelButton, confirmButton); - dialog.append(title, hint, textarea, actions); - overlay.append(dialog); - document2.body.appendChild(overlay); - const close = (value) => { - overlay.remove(); - resolve(value); - }; - cancelButton.addEventListener("click", () => close(null)); - confirmButton.addEventListener("click", () => close(textarea.value)); - overlay.addEventListener("click", (event) => { - if (event.target === overlay) { - close(null); - } - }); - textarea.focus(); - }); - } - function applyOverlayStyles2(overlay) { - overlay.style.position = "fixed"; - overlay.style.inset = "0"; - overlay.style.zIndex = "2147483647"; - overlay.style.display = "flex"; - overlay.style.alignItems = "center"; - overlay.style.justifyContent = "center"; - overlay.style.background = "rgba(15, 23, 42, 0.38)"; - } - function applyDialogStyles2(dialog) { - dialog.style.width = "520px"; - dialog.style.maxWidth = "calc(100vw - 32px)"; - dialog.style.background = "#ffffff"; - dialog.style.borderRadius = "8px"; - dialog.style.boxShadow = "0 18px 45px rgba(15, 23, 42, 0.22)"; - dialog.style.padding = "20px"; - dialog.style.boxSizing = "border-box"; - } - function applyTitleStyles2(title) { - title.style.margin = "0 0 8px"; - title.style.fontSize = "18px"; - title.style.fontWeight = "700"; - title.style.color = "#1f2329"; - } - function applyHintStyles2(hint) { - hint.style.margin = "0 0 12px"; - hint.style.fontSize = "13px"; - hint.style.lineHeight = "20px"; - hint.style.color = "#64748b"; - } - function applyTextareaStyles(textarea) { - textarea.style.width = "100%"; - textarea.style.height = "220px"; - textarea.style.resize = "vertical"; - textarea.style.border = "1px solid #d0d7de"; - textarea.style.borderRadius = "6px"; - textarea.style.padding = "10px"; - textarea.style.boxSizing = "border-box"; - textarea.style.fontSize = "13px"; - textarea.style.lineHeight = "20px"; - textarea.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, monospace"; - textarea.style.color = "#1f2329"; - } - function applyActionsStyles2(actions) { - actions.style.display = "flex"; - actions.style.justifyContent = "flex-end"; - actions.style.columnGap = "8px"; - actions.style.marginTop = "14px"; - } - function applyPrimaryButtonStyles2(button) { - button.style.height = "32px"; - button.style.padding = "0 15px"; - button.style.border = "1px solid #7f1d2d"; - button.style.borderRadius = "8px"; - button.style.background = "#7f1d2d"; - button.style.color = "#ffffff"; - button.style.fontWeight = "600"; - } - function applySecondaryButtonStyles2(button) { - button.style.height = "32px"; - button.style.padding = "0 15px"; - button.style.border = "1px solid #d0d7de"; - button.style.borderRadius = "8px"; - button.style.background = "#ffffff"; - button.style.color = "#1f2329"; - button.style.fontWeight = "600"; - } - - // src/content/market/batch-name-dialog.ts - var DIALOG_STYLE_ID = "sces-batch-name-dialog-style"; - var activeDialogs = /* @__PURE__ */ new WeakMap(); - function promptForBatchName(document2) { - const existingDialog = activeDialogs.get(document2); - if (existingDialog) { - existingDialog.input.focus(); - existingDialog.input.select(); - return existingDialog.promise; - } - ensureDialogStyles(document2); - const dialogRoot = document2.createElement("div"); - dialogRoot.dataset.pluginBatchNameDialog = "root"; - dialogRoot.setAttribute("role", "dialog"); - dialogRoot.setAttribute("aria-modal", "true"); - dialogRoot.setAttribute("aria-labelledby", "sces-batch-name-title"); - applyOverlayStyles3(dialogRoot); - const dialogPanel = document2.createElement("div"); - applyPanelStyles(dialogPanel); - const title = document2.createElement("h2"); - title.id = "sces-batch-name-title"; - title.textContent = "\u63D0\u4EA4\u6279\u6B21"; - applyTitleStyles3(title); - const description = document2.createElement("p"); - description.textContent = "\u8BF7\u8F93\u5165\u6279\u6B21\u540D\u79F0\uFF0C\u4FBF\u4E8E\u540E\u7EED\u5728\u7CFB\u7EDF\u4E2D\u8BC6\u522B\u548C\u8FFD\u8E2A\u3002"; - applyDescriptionStyles(description); - const input = document2.createElement("input"); - input.type = "text"; - input.dataset.pluginBatchNameInput = "input"; - input.placeholder = "\u4F8B\u5982\uFF1A618\u8FBE\u4EBA\u7B5B\u9009\u7B2C\u4E00\u6279"; - input.maxLength = 60; - applyInputStyles(input); - const errorText = document2.createElement("p"); - errorText.dataset.pluginBatchNameError = "text"; - applyErrorStyles(errorText); - const buttonRow = document2.createElement("div"); - applyButtonRowStyles(buttonRow); - const cancelButton = document2.createElement("button"); - cancelButton.type = "button"; - cancelButton.dataset.pluginBatchNameCancel = "button"; - cancelButton.textContent = "\u53D6\u6D88"; - applySecondaryButtonStyles3(cancelButton); - const confirmButton = document2.createElement("button"); - confirmButton.type = "button"; - confirmButton.dataset.pluginBatchNameConfirm = "button"; - confirmButton.textContent = "\u786E\u8BA4\u63D0\u4EA4"; - applyPrimaryButtonStyles3(confirmButton); - buttonRow.append(cancelButton, confirmButton); - dialogPanel.append(title, description, input, errorText, buttonRow); - dialogRoot.appendChild(dialogPanel); - document2.body.appendChild(dialogRoot); - const dialogPromise = new Promise((resolve) => { - const closeDialog = (value) => { - activeDialogs.delete(document2); - dialogRoot.remove(); - document2.removeEventListener("keydown", handleDocumentKeydown, true); - resolve(value); - }; - const submitValue = () => { - const value = input.value.trim(); - if (!value) { - errorText.textContent = "\u8BF7\u8F93\u5165\u6279\u6B21\u540D\u79F0"; - input.setAttribute("aria-invalid", "true"); - input.focus(); - return; - } - closeDialog(value); - }; - const handleDocumentKeydown = (event) => { - if (event.key === "Escape") { - event.preventDefault(); - closeDialog(null); - return; - } - if (event.key === "Enter") { - event.preventDefault(); - submitValue(); - } - }; - input.addEventListener("input", () => { - if (!input.value.trim()) { - return; - } - errorText.textContent = ""; - input.removeAttribute("aria-invalid"); - }); - cancelButton.addEventListener("click", () => { - closeDialog(null); - }); - confirmButton.addEventListener("click", () => { - submitValue(); - }); - dialogRoot.addEventListener("click", (event) => { - if (event.target === dialogRoot) { - closeDialog(null); - } - }); - document2.addEventListener("keydown", handleDocumentKeydown, true); - }); - activeDialogs.set(document2, { - input, - promise: dialogPromise - }); - input.focus(); - return dialogPromise; - } - function ensureDialogStyles(document2) { - if (document2.getElementById(DIALOG_STYLE_ID)) { - return; - } - const style = document2.createElement("style"); - style.id = DIALOG_STYLE_ID; - style.textContent = ` - [data-plugin-batch-name-dialog="root"] { - animation: sces-batch-name-fade-in 0.16s ease; - } - - @keyframes sces-batch-name-fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - `; - document2.head.appendChild(style); - } - function applyOverlayStyles3(root) { - root.style.position = "fixed"; - root.style.inset = "0"; - root.style.background = "rgba(15, 23, 42, 0.38)"; - root.style.display = "flex"; - root.style.alignItems = "center"; - root.style.justifyContent = "center"; - root.style.padding = "24px"; - root.style.zIndex = "2147483647"; - } - function applyPanelStyles(panel) { - panel.style.width = "min(420px, calc(100vw - 32px))"; - panel.style.background = "#fffaf9"; - panel.style.border = "1px solid rgba(127, 29, 45, 0.14)"; - panel.style.borderRadius = "18px"; - panel.style.boxShadow = "0 28px 70px rgba(15, 23, 42, 0.22)"; - panel.style.padding = "24px"; - panel.style.boxSizing = "border-box"; - } - function applyTitleStyles3(title) { - title.style.margin = "0"; - title.style.color = "#4c0519"; - title.style.fontSize = "20px"; - title.style.fontWeight = "700"; - title.style.lineHeight = "28px"; - } - function applyDescriptionStyles(description) { - description.style.margin = "10px 0 0"; - description.style.color = "#64748b"; - description.style.fontSize = "13px"; - description.style.lineHeight = "20px"; - } - function applyInputStyles(input) { - input.style.width = "100%"; - input.style.height = "42px"; - input.style.marginTop = "18px"; - input.style.padding = "0 14px"; - input.style.boxSizing = "border-box"; - input.style.border = "1px solid #d8c1c6"; - input.style.borderRadius = "12px"; - input.style.background = "#ffffff"; - input.style.color = "#1f2937"; - input.style.fontSize = "14px"; - input.style.outline = "none"; - } - function applyErrorStyles(errorText) { - errorText.style.minHeight = "20px"; - errorText.style.margin = "8px 0 0"; - errorText.style.color = "#b91c1c"; - errorText.style.fontSize = "12px"; - errorText.style.lineHeight = "18px"; - } - function applyButtonRowStyles(buttonRow) { - buttonRow.style.display = "flex"; - buttonRow.style.justifyContent = "flex-end"; - buttonRow.style.gap = "10px"; - buttonRow.style.marginTop = "18px"; - } - function applySecondaryButtonStyles3(button) { - button.style.height = "36px"; - button.style.padding = "0 16px"; - button.style.border = "1px solid #d7dde6"; - button.style.borderRadius = "10px"; - button.style.background = "#ffffff"; - button.style.color = "#334155"; - button.style.fontWeight = "600"; - button.style.cursor = "pointer"; - } - function applyPrimaryButtonStyles3(button) { - button.style.height = "36px"; - button.style.padding = "0 16px"; - button.style.border = "1px solid #7f1d2d"; - button.style.borderRadius = "10px"; - button.style.background = "#7f1d2d"; - button.style.color = "#ffffff"; - button.style.fontWeight = "600"; - button.style.cursor = "pointer"; - } - - // src/content/market/batch-payload.ts - function createBatchPayload(options) { - const logtoUserId = options.authState.userInfo?.sub?.trim(); - if (!logtoUserId) { - throw new Error("batch submit user id unavailable"); - } - const resource = options.authState.resource?.trim(); - if (!resource) { - throw new Error("batch submit resource unavailable"); - } - const batchName = options.batchName.trim(); - if (!batchName) { - throw new Error("batch submit batch name is required"); - } - return { - authors: options.records.map((record) => ({ - authorId: record.authorId, - authorName: record.authorName, - ...record.coreUserId ? { authorUid: record.coreUserId } : {} - })), - batchName, - createdAt: options.createdAt, - creatorName: options.authState.userInfo?.name ?? options.authState.userInfo?.username ?? logtoUserId, - logtoUserId, - resource - }; - } - - // src/content/market/market-list-row.ts - var PAGE_NUMBER_KEYS = [ - "currentPage", - "page", - "pageNo", - "pageNum", - "page_no", - "page_num" - ]; - var PAGE_SIZE_KEYS = [ - "limit", - "pageSize", - "page_size", - "size" - ]; - var TOTAL_COUNT_KEYS = [ - "total", - "totalCount", - "total_count" - ]; - var TOTAL_PAGE_KEYS = [ - "pageCount", - "page_count", - "totalPage", - "totalPages", - "total_page", - "total_pages" - ]; - function mapMarketListRow(row) { - const attributeDatas = readMarketAttributeDatas(row); - const singleVideoAfterSearchRate = normalizeMarketListRate( - readMarketFieldValue(row, attributeDatas, "avg_search_after_view_rate_30d") - ); - return { - authorId: readString3(readMarketFieldValue(row, attributeDatas, "star_id")) ?? readString3(readMarketFieldValue(row, attributeDatas, "id")) ?? "", - authorName: readString3(readMarketFieldValue(row, attributeDatas, "nickname")) ?? readString3(readMarketFieldValue(row, attributeDatas, "nick_name")) ?? "", - coreUserId: readString3(readMarketFieldValue(row, attributeDatas, "core_user_id")) ?? void 0, - exportFields: buildMarketExportFieldFallbacks(row, attributeDatas), - hasDirectRatesSource: true, - location: readMarketLocation(row, attributeDatas), - price21To60s: readMarketPrice21To60s(row, attributeDatas), - rates: singleVideoAfterSearchRate ? { - singleVideoAfterSearchRate - } : void 0 - }; - } - function parseMarketListResponse(payload) { - const container = findMarketListContainer(payload); - if (!container) { - return null; - } - const marketList = readMarketListArray(container); - if (!marketList) { - return null; - } - return { - currentPage: readKnownNumberDeep(container, PAGE_NUMBER_KEYS) ?? void 0, - pageSize: readKnownNumberDeep(container, PAGE_SIZE_KEYS) ?? void 0, - records: marketList.map((row) => isRecord4(row) ? mapMarketListRow(row) : null).filter( - (row) => row !== null && Boolean(row.authorId || row.authorName) - ), - totalCount: readKnownNumberDeep(container, TOTAL_COUNT_KEYS) ?? void 0, - totalPages: readKnownNumberDeep(container, TOTAL_PAGE_KEYS) ?? void 0 - }; - } - function readKnownPaginationNumber(value, kind) { - if (!isRecord4(value)) { - return null; - } - return readKnownNumberDeep(value, kind === "page" ? PAGE_NUMBER_KEYS : PAGE_SIZE_KEYS); - } - function findMarketListContainer(value) { - const queue = [value]; - while (queue.length > 0) { - const current = queue.shift(); - if (!isRecord4(current)) { - continue; - } - if (readMarketListArray(current)) { - return current; - } - Object.values(current).forEach((entry) => { - queue.push(unwrapVueRef(entry)); - }); - } - return null; - } - function readMarketListArray(record) { - const marketList = unwrapVueRef(record.marketList); - if (Array.isArray(marketList)) { - return marketList; - } - const authors = unwrapVueRef(record.authors); - if (Array.isArray(authors)) { - return authors; - } - return null; - } - function unwrapVueRef(value) { - if (isRecord4(value) && "value" in value) { - return value.value; - } - return value; - } - function isRecord4(value) { - return typeof value === "object" && value !== null; - } - function readMarketAttributeDatas(record) { - return isRecord4(record.attribute_datas) ? record.attribute_datas : {}; - } - function readMarketFieldValue(record, attributeDatas, field) { - return record[field] ?? attributeDatas[field]; - } - function readString3(value) { - return typeof value === "string" ? value : null; - } - function normalizeMarketListRate(value) { - if (typeof value === "number") { - return normalizeFractionRateDisplay(String(value)); - } - return typeof value === "string" ? normalizeFractionRateDisplay(value) : null; - } - function normalizeExportCellText(value) { - return value?.replace(/\s+/g, " ").trim() ?? ""; - } - function buildMarketExportFieldFallbacks(record, attributeDatas) { - const exportFields = {}; - const authorInfo = buildMarketAuthorInfo(record, attributeDatas); - const authorType = buildMarketAuthorType(record, attributeDatas); - const contentTheme = buildMarketContentTheme(record, attributeDatas); - const connectedUsers = formatWanValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "link_link_cnt_by_industry")) - ); - const followerCount = formatWanValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "follower")) - ); - const expectedCpm = formatDecimalDisplay( - readNumericValue(readMarketFieldValue(record, attributeDatas, "prospective_20_60_cpm")) - ); - const expectedPlayCount = formatWanValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "expected_play_num")) - ); - const interactionRate = formatFractionPercent( - readNumericValue(readMarketFieldValue(record, attributeDatas, "interact_rate_within_30d")) - ); - const finishRate = formatFractionPercent( - readNumericValue(readMarketFieldValue(record, attributeDatas, "play_over_rate_within_30d")) - ); - const burstRate = readBurstRateDisplay( - readNumericValue(readMarketFieldValue(record, attributeDatas, "burst_text_rate")) - ); - const price21To60s = readMarketPrice21To60s(record, attributeDatas); - const representativeVideo = readMarketRepresentativeVideo(record, attributeDatas); - assignExportField(exportFields, "\u8FBE\u4EBA\u4FE1\u606F", authorInfo); - assignExportField(exportFields, "\u4EE3\u8868\u89C6\u9891", representativeVideo); - assignExportField(exportFields, "\u8FBE\u4EBA\u7C7B\u578B", authorType); - assignExportField(exportFields, "\u5185\u5BB9\u4E3B\u9898", contentTheme); - assignExportField(exportFields, "\u8FDE\u63A5\u7528\u6237\u6570", connectedUsers); - assignExportField(exportFields, "\u7C89\u4E1D\u6570", followerCount); - assignExportField(exportFields, "\u9884\u671FCPM", expectedCpm); - assignExportField(exportFields, "\u9884\u671F\u64AD\u653E\u91CF", expectedPlayCount); - assignExportField(exportFields, "\u4E92\u52A8\u7387", interactionRate); - assignExportField(exportFields, "\u5B8C\u64AD\u7387", finishRate); - assignExportField(exportFields, "\u7206\u6587\u7387", burstRate); - assignExportField(exportFields, "21-60s\u62A5\u4EF7", price21To60s); - return Object.keys(exportFields).length > 0 ? exportFields : void 0; - } - function assignExportField(exportFields, key, value) { - if (hasTextValue(value)) { - exportFields[key] = value; - } - } - function hasTextValue(value) { - return Boolean(value && value.trim().length > 0); - } - function buildMarketAuthorInfo(record, attributeDatas) { - const nickname = readString3(readMarketFieldValue(record, attributeDatas, "nickname")) ?? readString3(readMarketFieldValue(record, attributeDatas, "nick_name")) ?? ""; - const parts = [ - nickname, - readMarketGenderLabel(readMarketFieldValue(record, attributeDatas, "gender")), - readString3(readMarketFieldValue(record, attributeDatas, "city")) ?? "" - ].filter((value) => Boolean(value)); - return parts.length > 0 ? parts.join(" ") : void 0; - } - function buildMarketAuthorType(record, attributeDatas) { - const tagsRelation = readRecordLike( - readMarketFieldValue(record, attributeDatas, "tags_relation") - ); - if (tagsRelation) { - const primaryTag = Object.keys(tagsRelation)[0]; - if (hasTextValue(primaryTag)) { - return primaryTag; - } - } - return void 0; - } - function buildMarketContentTheme(record, attributeDatas) { - const themes = readStringArray( - readMarketFieldValue(record, attributeDatas, "content_theme_labels_180d") - ); - if (themes.length === 0) { - return void 0; - } - if (themes.length <= 2) { - return themes.join(" "); - } - return `${themes.slice(0, 2).join(" ")} ${themes.length - 2}+`; - } - function readMarketLocation(record, attributeDatas) { - return readString3(readMarketFieldValue(record, attributeDatas, "city")) ?? void 0; - } - function readMarketPrice21To60s(record, attributeDatas) { - return formatCurrencyValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "price_20_60")) - ); - } - function readMarketRepresentativeVideo(record, attributeDatas) { - const items = readArrayLike(readMarketFieldValue(record, attributeDatas, "items")); - for (const item of items) { - if (!isRecord4(item)) { - continue; - } - const title = readString3(item.title); - if (hasTextValue(title)) { - return normalizeExportCellText(title); - } - } - return void 0; - } - function readMarketGenderLabel(value) { - const rawValue = typeof value === "number" ? String(value) : readString3(value); - if (rawValue === "1") { - return "\u7537"; - } - if (rawValue === "2") { - return "\u5973"; - } - return void 0; - } - function readBurstRateDisplay(value) { - if (value === null) { - return void 0; - } - if (value <= 0) { - return "-"; - } - return formatFractionPercent(value); - } - function formatCurrencyValue(value) { - if (value === null) { - return void 0; - } - return `\xA5${value.toLocaleString("en-US", { - maximumFractionDigits: 0 - })}`; - } - function formatWanValue(value) { - if (value === null) { - return void 0; - } - return `${formatDecimalWithGrouping(value / 1e4)}w`; - } - function formatFractionPercent(value) { - if (value === null) { - return void 0; - } - return `${formatDecimalDisplay(value * 100)}%`; - } - function formatDecimalDisplay(value) { - if (value === null) { - return void 0; - } - return value.toLocaleString("en-US", { - maximumFractionDigits: 1, - minimumFractionDigits: 0, - useGrouping: false - }); - } - function formatDecimalWithGrouping(value) { - return value.toLocaleString("en-US", { - maximumFractionDigits: 1, - minimumFractionDigits: 0 - }); - } - function readNumericValue(value) { - if (typeof value === "number" && Number.isFinite(value)) { - return value; - } - if (typeof value === "string") { - const trimmedValue = value.trim(); - if (!trimmedValue) { - return null; - } - const parsedValue = Number(trimmedValue); - return Number.isFinite(parsedValue) ? parsedValue : null; - } - return null; - } - function readStringArray(value) { - if (Array.isArray(value)) { - return value.filter((item) => typeof item === "string"); - } - if (typeof value === "string") { - try { - const parsedValue = JSON.parse(value); - return Array.isArray(parsedValue) ? parsedValue.filter((item) => typeof item === "string") : []; - } catch { - return []; - } - } - return []; - } - function readArrayLike(value) { - if (Array.isArray(value)) { - return value; - } - if (typeof value === "string") { - try { - const parsedValue = JSON.parse(value); - return Array.isArray(parsedValue) ? parsedValue : []; - } catch { - return []; - } - } - return []; - } - function readRecordLike(value) { - if (isRecord4(value)) { - return value; - } - if (typeof value === "string") { - try { - const parsedValue = JSON.parse(value); - return isRecord4(parsedValue) ? parsedValue : null; - } catch { - return null; - } - } - return null; - } - function readKnownNumber(record, keys) { - for (const key of keys) { - const value = readNumericValue(record[key]); - if (value !== null) { - return value; - } - } - return void 0; - } - function readKnownNumberDeep(value, keys) { - if (!isRecord4(value)) { - return null; - } - const directValue = readKnownNumber(value, keys); - if (typeof directValue === "number") { - return directValue; - } - for (const nestedValue of Object.values(value)) { - const candidate = readKnownNumberDeep(unwrapVueRef(nestedValue), keys); - if (typeof candidate === "number") { - return candidate; - } - } - return null; - } - - // src/content/market/dom-sync.ts - var BACKEND_COLUMN_KEY = "backendMetrics"; - var SELECTION_COLUMN_KEY = "selection"; - var SINGLE_COLUMN_KEY = "singleVideoAfterSearchRate"; - var PERSONAL_COLUMN_KEY = "personalVideoAfterSearchRate"; - var ACTION_HEADER_TEXT = "\u64CD\u4F5C"; - var AUTHOR_HEADER_TEXT = "\u8FBE\u4EBA\u4FE1\u606F"; - var BACKEND_HEADER_TEXT = "\u79D2\u63A2\u6307\u6807"; - var MARKET_SCROLL_HINT_TEXT = "\u6A2A\u5411\u6EDA\u52A8\u53EF\u67E5\u770B\u770B\u540E\u641C\u7387\u3001\u79D2\u63A2\u6307\u6807"; - var MARKET_SCROLLBAR_STYLE_ID = "sces-market-scrollbar-style"; - var UNAVAILABLE_RATE_TEXT = "\u6682\u65E0\u6765\u6E90"; - var UNAVAILABLE_BACKEND_METRICS_TEXT = "\u6682\u65E0\u6570\u636E"; - var SERIALIZED_MARKET_ROWS_ATTRIBUTE = "data-sces-market-rows"; - var SORTABLE_RATE_FIELDS = [SINGLE_COLUMN_KEY, PERSONAL_COLUMN_KEY]; - var BACKEND_METRIC_COLUMNS2 = [ - { - field: "afterViewSearchRate", - label: "\u770B\u540E\u641C\u7387" - }, - { - field: "afterViewSearchCount", - label: "\u770B\u540E\u641C\u6570" - }, - { - field: "a3IncreaseCount", - label: "\u65B0\u589EA3\u6570" - }, - { - field: "newA3Rate", - label: "\u65B0\u589EA3\u7387" - }, - { - field: "cpa3", - label: "CPA3" - }, - { - field: "cpSearch", - label: "cp_search" - } - ]; - var SORTABLE_MARKET_FIELDS = [ - ...SORTABLE_RATE_FIELDS, - ...BACKEND_METRIC_COLUMNS2.map((column) => column.field) - ]; - function syncMarketTable(root) { - return syncSyntheticMarketTable(root) ?? syncDivGridMarketTable(root); - } - function readMarketPageSignature(root) { - const document2 = getOwnerDocument(root); - const explicitPageIndex = document2?.documentElement.getAttribute("data-test-page-index") ?? ""; - const activePageIndex = document2?.querySelector(".el-pagination .number.active, .xt-pagination .number.active")?.textContent?.trim() ?? ""; - const authorIds = readRawAuthorIds(root).join("|"); - return `${explicitPageIndex || activePageIndex}::${authorIds}`; - } - function findNextPageControl(root) { - const document2 = getOwnerDocument(root); - if (!document2) { - return null; - } - const explicitControl = document2.querySelector('[data-testid="next-page"]'); - if (explicitControl instanceof document2.defaultView.HTMLElement) { - return explicitControl; - } - const paginationNextControl = document2.querySelector( - ".el-pagination .btn-next, .xt-pagination .btn-next" - ); - if (paginationNextControl instanceof document2.defaultView.HTMLElement) { - return paginationNextControl; - } - const candidates = Array.from( - document2.querySelectorAll("button, a, [role='button']") - ).filter( - (element) => element instanceof document2.defaultView.HTMLElement - ); - return candidates.find( - (element) => /下一页|next/i.test(normalizeExportCellText2(element.textContent)) - ) ?? null; - } - function isPageControlDisabled(control) { - if (!control) { - return true; - } - if (control instanceof HTMLButtonElement) { - return control.disabled; - } - return control.getAttribute("aria-disabled") === "true"; - } - function renderMarketRowState(rowDom, record) { - renderBackendMetricsCells(rowDom.backendMetricsCells, record); - if (record.status === "success" && record.rates) { - rowDom.singleCell.textContent = readRateCellText( - record.rates.singleVideoAfterSearchRate - ); - rowDom.personalCell.textContent = readRateCellText( - record.rates.personalVideoAfterSearchRate - ); - return; - } - if (record.status === "loading") { - rowDom.singleCell.textContent = "\u52A0\u8F7D\u4E2D..."; - rowDom.personalCell.textContent = "\u52A0\u8F7D\u4E2D..."; - return; - } - if (record.status === "failed") { - rowDom.singleCell.textContent = "\u52A0\u8F7D\u5931\u8D25"; - rowDom.personalCell.textContent = "\u52A0\u8F7D\u5931\u8D25"; - return; - } - rowDom.singleCell.textContent = ""; - rowDom.personalCell.textContent = ""; - } - function applyRowVisibility(table, visibleAuthorIds) { - table.rows.forEach((rowDom) => { - const isVisible = visibleAuthorIds.has(rowDom.authorId); - rowDom.visibilityTargets.forEach((target) => { - target.hidden = !isVisible; - }); - }); - } - function applyRowOrder(table, orderedAuthorIds) { - const rowById = new Map(table.rows.map((rowDom) => [rowDom.authorId, rowDom])); - const orderByAuthorId = new Map( - orderedAuthorIds.map((authorId, index) => [authorId, index]) - ); - orderedAuthorIds.forEach((authorId) => { - const rowDom = rowById.get(authorId); - if (!rowDom) { - return; - } - rowDom.orderTargets.forEach(({ container, mode, node }) => { - const visualOrder = orderByAuthorId.get(authorId) ?? orderedAuthorIds.length; - if (mode === "css") { - container.dataset.marketOrderMode = "css"; - container.style.display = "flex"; - container.style.flexDirection = "column"; - node.style.order = String(visualOrder); - return; - } - container.dataset.marketOrderMode = "dom"; - container.appendChild(node); - }); - }); - } - function syncPluginSortHeaders(root, options) { - SORTABLE_MARKET_FIELDS.forEach((field) => { - const cell = root.querySelector( - `[data-market-header-cell="${field}"]` - ); - if (!cell) { - return; - } - syncSortableHeaderCell(cell, { - direction: options.activeSort?.field === field ? options.activeSort.direction : "none", - field, - onToggleSort: options.onToggleSort - }); - }); - } - function syncMarketSelectionState(table, selectedAuthorIds) { - table.rows.forEach((rowDom) => { - rowDom.selectionCheckbox.dataset.marketSelectionAuthorId = rowDom.authorId; - rowDom.selectionCheckbox.checked = selectedAuthorIds.has(rowDom.authorId); - }); - if (!table.headerSelectionCheckbox) { - return; - } - const visibleRows = table.rows.filter( - (rowDom) => rowDom.visibilityTargets.some((target) => !target.hidden) - ); - const scopedRows = visibleRows.length > 0 ? visibleRows : table.rows; - const selectedCount = scopedRows.filter( - (rowDom) => selectedAuthorIds.has(rowDom.authorId) - ).length; - table.headerSelectionCheckbox.indeterminate = selectedCount > 0 && selectedCount < scopedRows.length; - table.headerSelectionCheckbox.checked = scopedRows.length > 0 && selectedCount === scopedRows.length; - table.headerSelectionCheckbox.disabled = scopedRows.length === 0; - } - function syncSyntheticMarketTable(root) { - const header = root.querySelector("[data-market-header]"); - const body = root.querySelector("[data-market-body]"); - if (!header || !body) { - return null; - } - const selectionHeader = ensureSyntheticHeaderCell(header, SELECTION_COLUMN_KEY, ""); - const headerSelectionCheckbox = ensureSelectionHeaderControl(selectionHeader); - ensureSyntheticHeaderCell(header, SINGLE_COLUMN_KEY, "\u5546\u5355\u89C6\u9891\u770B\u540E\u641C\u7387"); - ensureSyntheticHeaderCell(header, PERSONAL_COLUMN_KEY, "\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387"); - BACKEND_METRIC_COLUMNS2.forEach(({ field, label }) => { - ensureSyntheticHeaderCell(header, field, label); - }); - const headerLabelsByField = readSyntheticHeaderLabels(header); - const rows = Array.from(body.querySelectorAll("[data-market-row]")).map( - (rowElement) => { - const row = rowElement; - const selectionCell = ensureSyntheticRowCell(row, SELECTION_COLUMN_KEY); - const selectionCheckbox = ensureSelectionRowControl(selectionCell); - const singleCell = ensureSyntheticRowCell(row, SINGLE_COLUMN_KEY); - const personalCell = ensureSyntheticRowCell(row, PERSONAL_COLUMN_KEY); - const backendMetricsCells = Object.fromEntries( - BACKEND_METRIC_COLUMNS2.map(({ field }) => [field, ensureSyntheticRowCell(row, field)]) - ); - const authorId = row.dataset.authorId ?? ""; - selectionCheckbox.dataset.marketSelectionAuthorId = authorId; - return { - authorId, - authorName: row.querySelector('[data-market-field="authorName"]')?.textContent?.trim() ?? "", - backendMetricsCells, - exportFields: readSyntheticExportFields(row, headerLabelsByField), - hasDirectRatesSource: false, - location: row.querySelector('[data-market-field="location"]')?.textContent?.trim() ?? "", - orderTargets: [ - { - container: body, - mode: "dom", - node: row - } - ], - personalCell, - price21To60s: row.querySelector('[data-market-field="price21To60s"]')?.textContent?.trim() ?? "", - rates: void 0, - row, - selectionCheckbox, - singleCell, - visibilityTargets: [row] - }; - } - ); - return { - headerSelectionCheckbox, - rows - }; - } - function syncDivGridMarketTable(root) { - const document2 = getOwnerDocument(root); - if (!document2) { - return null; - } - for (const marketRoot of document2.querySelectorAll(".base-author-list")) { - if (!(marketRoot instanceof document2.defaultView.HTMLElement)) { - continue; - } - const syncedTable = syncDivGridRoot(marketRoot); - if (syncedTable) { - return syncedTable; - } - } - return null; - } - function readRawAuthorIds(root) { - const document2 = getOwnerDocument(root); - const syntheticAuthorIds = readSyntheticAuthorIds(root); - if (syntheticAuthorIds && syntheticAuthorIds.length > 0) { - return syntheticAuthorIds; - } - const divGridAuthorIds = readDivGridAuthorIds(root); - if (divGridAuthorIds && divGridAuthorIds.length > 0) { - return divGridAuthorIds; - } - if (!document2) { - return []; - } - return readSerializedMarketRows(document2).map((row) => row.authorId).filter((authorId) => Boolean(authorId)); - } - function readSyntheticAuthorIds(root) { - const body = root.querySelector("[data-market-body]"); - if (!body) { - return null; - } - return Array.from(body.querySelectorAll("[data-market-row]")).map( - (row) => row instanceof HTMLElement ? row.dataset.authorId ?? "" : "" - ).filter((authorId) => Boolean(authorId)); - } - function readDivGridAuthorIds(root) { - const document2 = getOwnerDocument(root); - if (!document2) { - return null; - } - const marketRoot = document2.querySelector(".base-author-list"); - if (!(marketRoot instanceof document2.defaultView.HTMLElement)) { - return null; - } - const bodySection = Array.from(marketRoot.querySelectorAll(".section-wrapper")).find( - (section) => section instanceof document2.defaultView.HTMLElement && !section.classList.contains("sticky-header") - ); - const authorSection = bodySection ? Array.from(bodySection.children).find( - (child) => child instanceof document2.defaultView.HTMLElement && child.querySelector(".content-column .content-cell") - ) ?? null : null; - const authorColumn = authorSection ? getNativeAuthorColumn(authorSection) : null; - if (!authorColumn) { - return null; - } - return getDirectContentCells(authorColumn).map((cell) => extractAuthorId(cell)).filter((authorId) => Boolean(authorId)); - } - function syncDivGridRoot(root) { - const headerSection = root.querySelector( - ".section-wrapper.sticky-header" - ); - const bodySection = Array.from(root.querySelectorAll(".section-wrapper")).find( - (section) => section instanceof root.ownerDocument.defaultView.HTMLElement && !section.classList.contains("sticky-header") - ); - if (!headerSection || !bodySection) { - return null; - } - const authorHeader = findCellByText(getDirectHeaderCells(headerSection), AUTHOR_HEADER_TEXT); - const actionHeader = findCellByText(getDirectHeaderCells(headerSection), ACTION_HEADER_TEXT); - if (!authorHeader || !actionHeader) { - return null; - } - const rightHeaderSection = actionHeader.parentElement; - if (!(rightHeaderSection instanceof root.ownerDocument.defaultView.HTMLElement)) { - return null; - } - const middleHeaderSection = findPreviousNativeSection(rightHeaderSection) ?? rightHeaderSection; - const authorSection = getIndexedChild( - bodySection, - getDirectChildIndex(headerSection, authorHeader) - ); - const authorHeaderSection = getIndexedChild( - headerSection, - getDirectChildIndex(headerSection, authorHeader) - ); - const rightSection = getIndexedChild( - bodySection, - getDirectChildIndex(headerSection, actionHeader) - ); - if (!authorSection || !authorHeaderSection || !rightSection) { - return null; - } - const middleBodySection = findPreviousNativeSection(rightSection) ?? rightSection; - const pluginHeaderSection = ensurePluginSection(headerSection, rightHeaderSection, { - testId: "plugin-header", - type: "header" - }); - const pluginBodySection = ensurePluginSection(bodySection, rightSection, { - testId: "plugin-section", - type: "body" - }); - const authorColumn = getNativeAuthorColumn(authorSection); - const actionColumn = getActionColumn(rightSection); - if (!authorColumn || !actionColumn) { - return null; - } - const rowCount = getDirectContentCells(authorColumn).length; - const selectionHeaderCell = ensureDivHeaderCell( - authorHeaderSection, - authorHeader, - SELECTION_COLUMN_KEY, - "" - ); - const headerSelectionCheckbox = ensureSelectionHeaderControl(selectionHeaderCell); - const selectionColumn = ensureDivBodyColumn( - authorSection, - authorColumn, - SELECTION_COLUMN_KEY, - rowCount - ); - const headerTemplateCell = getDirectHeaderCells(middleHeaderSection).at(-1) ?? findPreviousHeaderCell(actionHeader) ?? actionHeader; - const bodyTemplateColumn = getDirectContentColumns(middleBodySection).at(-1) ?? findPreviousColumn(actionColumn) ?? actionColumn; - ensureDivHeaderCell( - pluginHeaderSection, - headerTemplateCell, - SINGLE_COLUMN_KEY, - "\u5546\u5355\u89C6\u9891\u770B\u540E\u641C\u7387" - ); - ensureDivHeaderCell( - pluginHeaderSection, - headerTemplateCell, - PERSONAL_COLUMN_KEY, - "\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387" - ); - const singleColumn = ensureDivBodyColumn( - pluginBodySection, - bodyTemplateColumn, - SINGLE_COLUMN_KEY, - rowCount - ); - const personalColumn = ensureDivBodyColumn( - pluginBodySection, - bodyTemplateColumn, - PERSONAL_COLUMN_KEY, - rowCount - ); - const backendMetricColumns = Object.fromEntries( - BACKEND_METRIC_COLUMNS2.map(({ field, label }) => { - ensureDivHeaderCell(pluginHeaderSection, headerTemplateCell, field, label); - return [ - field, - ensureDivBodyColumn( - pluginBodySection, - bodyTemplateColumn, - field, - rowCount - ) - ]; - }) - ); - syncContainerWidth(pluginHeaderSection); - syncContainerWidth(pluginBodySection); - syncContainerWidth(authorHeaderSection); - syncContainerWidth(authorSection); - ensureVisibleHorizontalScroll(headerSection); - ensureVisibleHorizontalScroll(bodySection); - ensureScrollHint(root, headerSection); - const allBodyColumns = Array.from(bodySection.children).flatMap( - (section) => section instanceof root.ownerDocument.defaultView.HTMLElement ? getDirectContentColumns(section) : [] - ); - const allHeaderCells = Array.from(headerSection.children).flatMap( - (section) => section instanceof root.ownerDocument.defaultView.HTMLElement ? getDirectHeaderCells(section) : [] - ); - const authorCells = getDirectContentCells(authorColumn); - const selectionCells = getDirectContentCells(selectionColumn); - const singleCells = getDirectContentCells(singleColumn); - const personalCells = getDirectContentCells(personalColumn); - const backendMetricCellsByField = Object.fromEntries( - BACKEND_METRIC_COLUMNS2.map(({ field }) => [ - field, - getDirectContentCells(backendMetricColumns[field]) - ]) - ); - const priceColumn = findPreviousColumn(actionColumn); - const priceCells = priceColumn ? getDirectContentCells(priceColumn) : []; - const remainingVueMarketRows = [...readVueMarketRows(root)]; - const remainingSerializedMarketRows = [...readSerializedMarketRows(root.ownerDocument)]; - const rows = authorCells.flatMap((authorCell, index) => { - const selectionCell = selectionCells[index] ?? null; - const singleCell = singleCells[index] ?? null; - const personalCell = personalCells[index] ?? null; - const backendMetricsCells = Object.fromEntries( - BACKEND_METRIC_COLUMNS2.map(({ field }) => [ - field, - backendMetricCellsByField[field][index] ?? null - ]) - ); - if (!selectionCell || !singleCell || !personalCell || Object.values(backendMetricsCells).some((cell) => cell === null)) { - return []; - } - const selectionCheckbox = ensureSelectionRowControl(selectionCell); - const alignedRowCells = allBodyColumns.map( - (column) => getDirectContentCells(column)[index] ?? null - ); - const rowCells = alignedRowCells.filter( - (cell) => cell !== null - ); - const directAuthorId = extractAuthorId(authorCell) || ""; - const directAuthorName = extractAuthorName(authorCell) || ""; - const vueMarketRow = takeMatchedMarketDataRow( - remainingVueMarketRows, - directAuthorId, - directAuthorName - ); - const serializedMarketRow = takeMatchedMarketDataRow( - remainingSerializedMarketRows, - directAuthorId, - directAuthorName - ); - const fallbackMarketRow = mergeMarketDataRows(serializedMarketRow, vueMarketRow); - const exportFields = mergeExportFieldMaps( - readExportFieldsForDivGridRow(allHeaderCells, alignedRowCells), - fallbackMarketRow?.exportFields - ); - const authorId = directAuthorId || fallbackMarketRow?.authorId || ""; - const authorName = directAuthorName || fallbackMarketRow?.authorName || ""; - const price21To60s = mergeNonEmptyString( - readDivGridPriceDisplay(priceCells[index]?.textContent), - fallbackMarketRow?.price21To60s - ); - selectionCheckbox.dataset.marketSelectionAuthorId = authorId; - return [ - { - authorId, - authorName, - backendMetricsCells, - exportFields, - hasDirectRatesSource: fallbackMarketRow?.hasDirectRatesSource ?? false, - location: fallbackMarketRow?.location, - orderTargets: rowCells.map((cell) => { - const container = cell.parentElement; - if (!(container instanceof root.ownerDocument.defaultView.HTMLElement)) { - return null; - } - return { - container, - mode: "css", - node: cell - }; - }).filter((target) => target !== null), - personalCell, - price21To60s, - rates: fallbackMarketRow?.rates, - row: authorCell, - selectionCheckbox, - singleCell, - visibilityTargets: rowCells - } - ]; - }); - return { - headerSelectionCheckbox, - rows - }; - } - function ensureSyntheticHeaderCell(header, field, label) { - const existingCell = header.querySelector( - `[data-market-header-cell="${field}"]` - ); - if (existingCell) { - existingCell.textContent = label; - return existingCell; - } - const nextCell = header.ownerDocument.createElement("div"); - nextCell.dataset.marketHeaderCell = field; - nextCell.textContent = label; - if (field === SELECTION_COLUMN_KEY) { - header.insertBefore(nextCell, header.firstChild); - } else { - header.appendChild(nextCell); - } - return nextCell; - } - function ensureSyntheticRowCell(row, field) { - const existingCell = row.querySelector( - `[data-market-row-cell="${field}"]` - ); - if (existingCell) { - return existingCell; - } - const nextCell = row.ownerDocument.createElement(field === BACKEND_COLUMN_KEY ? "div" : "span"); - nextCell.dataset.marketRowCell = field; - if (field === SELECTION_COLUMN_KEY) { - row.insertBefore(nextCell, row.firstChild); - } else { - row.appendChild(nextCell); - } - return nextCell; - } - function ensureDivHeaderCell(container, templateCell, field, label) { - const existingCell = container.querySelector( - `[data-market-header-cell="${field}"]` - ); - if (existingCell) { - existingCell.textContent = label; - applyPluginHeaderCellStyles(existingCell); - return existingCell; - } - const nextCell = cloneElementShallow(templateCell); - nextCell.dataset.marketHeaderCell = field; - nextCell.textContent = label; - applyColumnWidth(nextCell, field); - applyPluginHeaderCellStyles(nextCell); - if (field === SELECTION_COLUMN_KEY) { - container.insertBefore(nextCell, templateCell); - } else { - container.appendChild(nextCell); - } - return nextCell; - } - function ensureDivBodyColumn(container, templateColumn, field, rowCount) { - const existingColumn = container.querySelector( - `[data-market-column-group="${field}"]` - ); - if (existingColumn) { - syncDivColumnCells(existingColumn, templateColumn, field, rowCount); - return existingColumn; - } - const nextColumn = cloneElementShallow(templateColumn); - nextColumn.dataset.marketColumnGroup = field; - applyColumnWidth(nextColumn, field); - syncDivColumnCells(nextColumn, templateColumn, field, rowCount); - if (field === SELECTION_COLUMN_KEY) { - container.insertBefore(nextColumn, templateColumn); - } else { - container.appendChild(nextColumn); - } - return nextColumn; - } - function syncDivColumnCells(column, templateColumn, field, rowCount) { - const currentCells = getDirectContentCells(column); - while (currentCells.length > rowCount) { - currentCells.pop()?.remove(); - } - const templateCells = getDirectContentCells(templateColumn); - for (let index = 0; index < rowCount; index += 1) { - const existingCell = getDirectContentCells(column)[index] ?? null; - const templateCell = templateCells[index] ?? templateCells[templateCells.length - 1] ?? null; - if (existingCell) { - existingCell.dataset.marketRowCell = field; - applyPluginContentCellStyles(existingCell); - syncContentCellHeight(existingCell, templateCell); - continue; - } - const nextCell = field === SELECTION_COLUMN_KEY ? templateCell ? createSelectionContentCell(templateCell) : createBareContentCell(column.ownerDocument) : templateCell ? cloneElementShallow(templateCell) : createBareContentCell(column.ownerDocument); - nextCell.dataset.marketRowCell = field; - applyColumnWidth(nextCell, field); - applyPluginContentCellStyles(nextCell); - syncContentCellHeight(nextCell, templateCell); - nextCell.textContent = ""; - column.appendChild(nextCell); - } - } - function syncContentCellHeight(cell, templateCell) { - if (!templateCell) { - return; - } - const measuredHeight = Math.round(templateCell.getBoundingClientRect().height); - const nextHeight = measuredHeight > 0 ? `${measuredHeight}px` : templateCell.style.height; - if (nextHeight) { - cell.style.height = nextHeight; - } else { - cell.style.removeProperty("height"); - } - } - function applyPluginHeaderCellStyles(cell) { - cell.style.display = "flex"; - cell.style.alignItems = "center"; - cell.style.justifyContent = "normal"; - cell.style.cursor = "pointer"; - cell.style.whiteSpace = "nowrap"; - } - function applyPluginContentCellStyles(cell) { - cell.style.display = "flex"; - cell.style.alignItems = "center"; - cell.style.justifyContent = "normal"; - cell.style.paddingTop = "12px"; - cell.style.paddingBottom = "12px"; - cell.style.boxSizing = "border-box"; - cell.style.whiteSpace = "nowrap"; - } - function ensureSelectionHeaderControl(cell) { - cell.textContent = ""; - cell.style.gap = "6px"; - cell.style.justifyContent = "center"; - const checkbox = ensureSelectionCheckbox(cell, "header"); - const label = cell.querySelector( - '[data-market-selection-label="header"]' - ); - if (label) { - label.textContent = "\u5168\u9009"; - return checkbox; - } - const nextLabel = cell.ownerDocument.createElement("span"); - nextLabel.dataset.marketSelectionLabel = "header"; - nextLabel.textContent = "\u5168\u9009"; - nextLabel.style.fontSize = "12px"; - cell.appendChild(nextLabel); - return checkbox; - } - function ensureSelectionRowControl(cell) { - cell.textContent = ""; - cell.style.justifyContent = "center"; - return ensureSelectionCheckbox(cell, "row"); - } - function ensureSelectionCheckbox(container, kind) { - const existingCheckbox = container.querySelector( - `[data-market-selection-checkbox="${kind}"]` - ); - if (existingCheckbox) { - existingCheckbox.type = "checkbox"; - return existingCheckbox; - } - const checkbox = container.ownerDocument.createElement("input"); - checkbox.type = "checkbox"; - checkbox.dataset.marketSelectionCheckbox = kind; - checkbox.style.cursor = "pointer"; - container.appendChild(checkbox); - return checkbox; - } - function getOwnerDocument(root) { - if ("ownerDocument" in root && root.ownerDocument) { - return root.ownerDocument; - } - return "nodeType" in root && root.nodeType === 9 ? root : null; - } - function readSyntheticHeaderLabels(header) { - return Array.from(header.querySelectorAll("[data-market-header-cell]")).reduce((labels, cell) => { - if (!(cell instanceof header.ownerDocument.defaultView.HTMLElement)) { - return labels; - } - const field = cell.dataset.marketHeaderCell; - if (!field) { - return labels; - } - labels[field] = normalizeExportCellText2(cell.textContent); - return labels; - }, {}); - } - function readSyntheticExportFields(row, headerLabelsByField) { - const exportFields = {}; - for (const cell of row.querySelectorAll("[data-market-field]")) { - if (!(cell instanceof row.ownerDocument.defaultView.HTMLElement)) { - continue; - } - const field = cell.dataset.marketField; - const headerLabel = field ? headerLabelsByField[field] : ""; - if (!shouldExportColumn(headerLabel)) { - continue; - } - exportFields[headerLabel] = normalizeExportCellText2(cell.textContent); - } - return exportFields; - } - function readExportFieldsForDivGridRow(headerCells, rowCells) { - const exportFields = {}; - rowCells.forEach((cell, index) => { - const headerLabel = normalizeExportCellText2(headerCells[index]?.textContent); - if (!shouldExportColumn(headerLabel)) { - return; - } - exportFields[headerLabel] = headerLabel === "21-60s\u62A5\u4EF7" ? readDivGridPriceDisplay(cell?.textContent) ?? "" : normalizeExportCellText2(cell?.textContent); - }); - return exportFields; - } - function findPreviousHeaderCell(cell) { - let current = cell.previousElementSibling; - while (current) { - if (current instanceof cell.ownerDocument.defaultView.HTMLElement && current.classList.contains("header-cell")) { - return current; - } - current = current.previousElementSibling; - } - return null; - } - function findPreviousColumn(column) { - let current = column.previousElementSibling; - while (current) { - if (current instanceof column.ownerDocument.defaultView.HTMLElement && current.classList.contains("content-column")) { - return current; - } - current = current.previousElementSibling; - } - return null; - } - function ensurePluginSection(rootSection, referenceSection, options) { - const existingSection = rootSection.querySelector( - `[data-market-plugin-section="${options.type}"]` - ); - if (existingSection) { - existingSection.dataset.testid = options.testId; - existingSection.setAttribute("data-testid", options.testId); - return existingSection; - } - const templateSection = findPreviousSection(referenceSection) ?? referenceSection; - const nextSection = cloneElementShallow(templateSection); - nextSection.dataset.marketPluginSection = options.type; - nextSection.dataset.testid = options.testId; - nextSection.setAttribute("data-testid", options.testId); - resetStickySectionStyles(nextSection); - rootSection.insertBefore(nextSection, referenceSection); - return nextSection; - } - function ensureVisibleHorizontalScroll(section) { - ensureVisibleScrollbarStyles(section.ownerDocument); - section.classList.remove("hide-scrollbar"); - section.dataset.marketScrollbar = "visible"; - section.style.overflowX = "auto"; - section.style.scrollbarWidth = "thin"; - section.style.scrollbarColor = "rgba(148, 163, 184, 0.95) rgba(226, 232, 240, 0.9)"; - } - function ensureVisibleScrollbarStyles(document2) { - if (document2.getElementById(MARKET_SCROLLBAR_STYLE_ID)) { - return; - } - const style = document2.createElement("style"); - style.id = MARKET_SCROLLBAR_STYLE_ID; - style.textContent = ` - [data-market-scrollbar="visible"]::-webkit-scrollbar { - display: block !important; - height: 10px !important; - } - - [data-market-scrollbar="visible"]::-webkit-scrollbar-track { - background: rgba(226, 232, 240, 0.9) !important; - border-radius: 999px; - } - - [data-market-scrollbar="visible"]::-webkit-scrollbar-thumb { - background: rgba(148, 163, 184, 0.95) !important; - border: 2px solid rgba(226, 232, 240, 0.9); - border-radius: 999px; - } - `; - document2.head.appendChild(style); - } - function ensureScrollHint(root, headerSection) { - const existingHint = root.querySelector( - '[data-testid="market-scroll-hint"]' - ); - if (existingHint) { - existingHint.textContent = MARKET_SCROLL_HINT_TEXT; - return; - } - const hint = root.ownerDocument.createElement("div"); - hint.dataset.testid = "market-scroll-hint"; - hint.setAttribute("data-testid", "market-scroll-hint"); - hint.textContent = MARKET_SCROLL_HINT_TEXT; - hint.style.color = "#64748b"; - hint.style.display = "flex"; - hint.style.fontSize = "12px"; - hint.style.justifyContent = "flex-end"; - hint.style.lineHeight = "18px"; - hint.style.padding = "0 12px 8px"; - root.insertBefore(hint, headerSection); - } - function findPreviousSection(section) { - let current = section.previousElementSibling; - while (current) { - if (current instanceof section.ownerDocument.defaultView.HTMLElement) { - return current; - } - current = current.previousElementSibling; - } - return null; - } - function findPreviousNativeSection(section) { - let current = section.previousElementSibling; - while (current) { - if (current instanceof section.ownerDocument.defaultView.HTMLElement && !current.hasAttribute("data-market-plugin-section")) { - return current; - } - current = current.previousElementSibling; - } - return null; - } - function resetStickySectionStyles(section) { - section.style.position = ""; - section.style.left = ""; - section.style.right = ""; - section.style.zIndex = ""; - section.style.width = ""; - section.style.minWidth = ""; - } - function getActionColumn(bodySection) { - const columns = getDirectContentColumns(bodySection); - return columns[columns.length - 1] ?? null; - } - function getNativeAuthorColumn(authorSection) { - return getDirectContentColumns(authorSection).find( - (column) => !column.dataset.marketColumnGroup && getDirectContentCells(column).some( - (cell) => cell.querySelector("a") || cell.querySelector(".author-nickname") || Boolean(cell.dataset.authorId) - ) - ) ?? null; - } - function getDirectHeaderCells(section) { - return Array.from(section.querySelectorAll(".header-cell")).filter( - (cell) => cell instanceof section.ownerDocument.defaultView.HTMLElement - ); - } - function getDirectContentColumns(section) { - return Array.from(section.children).filter( - (child) => child instanceof section.ownerDocument.defaultView.HTMLElement && child.classList.contains("content-column") - ); - } - function getDirectContentCells(column) { - return Array.from(column.children).filter( - (child) => child instanceof column.ownerDocument.defaultView.HTMLElement && child.classList.contains("content-cell") - ); - } - function getDirectChildIndex(root, descendant) { - const directChild = Array.from(root.children).find((child) => child.contains(descendant)); - return directChild ? Array.from(root.children).indexOf(directChild) : -1; - } - function getIndexedChild(root, index) { - if (index < 0) { - return null; - } - const child = root.children[index] ?? null; - return child instanceof root.ownerDocument.defaultView.HTMLElement ? child : null; - } - function findCellByText(cells, text) { - return cells.find((cell) => cell.textContent?.trim() === text) ?? null; - } - function cloneElementShallow(reference) { - const clone = reference.ownerDocument.createElement(reference.tagName); - Array.from(reference.attributes).forEach((attribute) => { - clone.setAttribute(attribute.name, attribute.value); - }); - return clone; - } - function createBareContentCell(document2) { - const cell = document2.createElement("div"); - cell.className = "content-cell"; - return cell; - } - function createSelectionContentCell(templateCell) { - const cell = cloneElementShallow(templateCell); - cell.removeAttribute("data-testid"); - cell.removeAttribute("data-author-id"); - return cell; - } - function extractAuthorId(authorCell) { - const explicitAuthorId = authorCell.dataset.authorId; - if (explicitAuthorId) { - return explicitAuthorId; - } - const linkedAuthorId = Array.from(authorCell.querySelectorAll("a")).map((link) => extractAuthorIdFromHref(link.href)).find((value) => Boolean(value)); - if (linkedAuthorId) { - return linkedAuthorId; - } - const fallbackAuthorId = authorCell.querySelector("[data-author-id]")?.getAttribute("data-author-id"); - return fallbackAuthorId ?? ""; - } - function extractAuthorName(authorCell) { - return authorCell.querySelector(".author-nickname")?.textContent?.trim() ?? authorCell.textContent?.trim() ?? ""; - } - function extractAuthorIdFromHref(href) { - const match = href.match(/\/author-homepage\/[^/]+\/(\d+)/); - return match?.[1] ?? null; - } - function readVueMarketRows(marketRoot) { - const vueRoot = marketRoot.__vue__; - const setupStates = collectVueSetupStates(vueRoot); - for (const setupState of setupStates) { - for (const value of Object.values(setupState)) { - const candidate = unwrapVueRef2(value); - if (!candidate || typeof candidate !== "object") { - continue; - } - const marketList = unwrapVueRef2( - candidate.marketList - ); - if (!Array.isArray(marketList)) { - continue; - } - return marketList.map((row) => isRecord5(row) ? mapMarketListRow(row) : null).filter((row) => row !== null); - } - } - return []; - } - function collectVueSetupStates(vueRoot) { - if (!vueRoot) { - return []; - } - const queue = [vueRoot]; - const setupStates = []; - while (queue.length > 0) { - const current = queue.shift(); - if (!isRecord5(current)) { - continue; - } - if (isRecord5(current._setupState)) { - setupStates.push(current._setupState); - } - const children = Array.isArray(current.$children) ? current.$children : []; - queue.push(...children); - } - return setupStates; - } - function readSerializedMarketRows(document2) { - const serializedRows = document2.documentElement.getAttribute( - SERIALIZED_MARKET_ROWS_ATTRIBUTE - ); - if (!serializedRows) { - return []; - } - try { - const parsedRows = JSON.parse(serializedRows); - if (!Array.isArray(parsedRows)) { - return []; - } - return parsedRows.map((row) => { - const record = isRecord5(row) ? row : {}; - const singleVideoAfterSearchRate = readString4( - record.singleVideoAfterSearchRate - ); - return { - authorId: readString4(record.authorId) ?? "", - authorName: readString4(record.authorName) ?? "", - coreUserId: readString4(record.coreUserId) ?? void 0, - exportFields: readSerializedExportFields(record), - hasDirectRatesSource: Boolean(singleVideoAfterSearchRate), - location: readString4(record.location) ?? void 0, - price21To60s: readString4(record.price21To60s) ?? void 0, - rates: singleVideoAfterSearchRate ? { - singleVideoAfterSearchRate - } : void 0 - }; - }).filter((row) => Boolean(row.authorId || row.authorName)); - } catch { - return []; - } - } - function unwrapVueRef2(value) { - if (isRecord5(value) && "value" in value) { - return value.value; - } - return value; - } - function isRecord5(value) { - return typeof value === "object" && value !== null; - } - function readString4(value) { - return typeof value === "string" ? value : null; - } - function normalizeExportCellText2(value) { - return value?.replace(/\s+/g, " ").trim() ?? ""; - } - function readDivGridPriceDisplay(value) { - const normalizedValue = normalizeExportCellText2(value); - if (!normalizedValue) { - return void 0; - } - const match = normalizedValue.match(/^¥?\s*([\d,]+(?:\.\d+)?)$/); - if (!match) { - return void 0; - } - const numericValue = Number(match[1].replace(/,/g, "")); - if (!Number.isFinite(numericValue)) { - return void 0; - } - return formatCurrencyValue2(numericValue); - } - function shouldExportColumn(label) { - const excludedBackendLabels = new Set(BACKEND_METRIC_COLUMNS2.map((column) => column.label)); - return Boolean( - label && label !== ACTION_HEADER_TEXT && label !== BACKEND_HEADER_TEXT && !excludedBackendLabels.has(label) && label !== "\u5546\u5355\u89C6\u9891\u770B\u540E\u641C\u7387" && label !== "\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387" - ); - } - function syncSortableHeaderCell(cell, options) { - const label = readSortableHeaderLabel(cell); - const sorterRoot = ensureHeaderSorterRoot(cell); - const text = ensureHeaderSorterText(sorterRoot); - const icon = ensureHeaderSorterIcon(sorterRoot); - const upTriangle = ensureHeaderTriangle(icon, "up"); - const downTriangle = ensureHeaderTriangle(icon, "down"); - text.textContent = label; - cell.dataset.marketSortField = options.field; - cell.dataset.marketSortDirection = options.direction; - cell.setAttribute("role", "button"); - cell.tabIndex = 0; - cell.onclick = () => { - options.onToggleSort(options.field); - }; - cell.onkeydown = (event) => { - if (event.key !== "Enter" && event.key !== " ") { - return; - } - event.preventDefault(); - options.onToggleSort(options.field); - }; - syncTriangleStyles(upTriangle, { - active: options.direction === "asc", - direction: "up" - }); - syncTriangleStyles(downTriangle, { - active: options.direction === "desc", - direction: "down" - }); - } - function readSortableHeaderLabel(cell) { - return cell.dataset.marketHeaderLabel ?? normalizeExportCellText2(cell.textContent) ?? ""; - } - function ensureHeaderSorterRoot(cell) { - const existingRoot = cell.querySelector( - '[data-market-sorter="root"]' - ); - if (existingRoot) { - return existingRoot; - } - cell.dataset.marketHeaderLabel = normalizeExportCellText2(cell.textContent); - cell.replaceChildren(); - const root = cell.ownerDocument.createElement("span"); - root.dataset.marketSorter = "root"; - root.style.alignItems = "center"; - root.style.display = "inline-flex"; - root.style.gap = "4px"; - root.style.maxWidth = "100%"; - cell.appendChild(root); - return root; - } - function ensureHeaderSorterText(sorterRoot) { - const existingText = sorterRoot.querySelector( - '[data-market-sorter="text"]' - ); - if (existingText) { - return existingText; - } - const text = sorterRoot.ownerDocument.createElement("span"); - text.dataset.marketSorter = "text"; - text.style.display = "inline-block"; - text.style.lineHeight = "20px"; - text.style.whiteSpace = "nowrap"; - sorterRoot.appendChild(text); - return text; - } - function ensureHeaderSorterIcon(sorterRoot) { - const existingIcon = sorterRoot.querySelector( - '[data-market-sorter="icon"]' - ); - if (existingIcon) { - return existingIcon; - } - const icon = sorterRoot.ownerDocument.createElement("span"); - icon.dataset.marketSorter = "icon"; - icon.style.display = "inline-flex"; - icon.style.flexDirection = "column"; - icon.style.gap = "2px"; - icon.style.justifyContent = "center"; - icon.style.minWidth = "8px"; - sorterRoot.appendChild(icon); - return icon; - } - function ensureHeaderTriangle(iconRoot, direction) { - const existingTriangle = iconRoot.querySelector( - `[data-market-sorter-triangle="${direction}"]` - ); - if (existingTriangle) { - return existingTriangle; - } - const triangle = iconRoot.ownerDocument.createElement("span"); - triangle.dataset.marketSorterTriangle = direction; - triangle.style.display = "block"; - triangle.style.height = "0"; - triangle.style.width = "0"; - triangle.style.borderLeft = "4px solid transparent"; - triangle.style.borderRight = "4px solid transparent"; - iconRoot.appendChild(triangle); - return triangle; - } - function syncTriangleStyles(triangle, options) { - const activeColor = "#1f2329"; - const inactiveColor = "#c9cdd4"; - if (options.direction === "up") { - triangle.style.borderBottom = `5px solid ${options.active ? activeColor : inactiveColor}`; - triangle.style.borderTop = "0 solid transparent"; - } else { - triangle.style.borderTop = `5px solid ${options.active ? activeColor : inactiveColor}`; - triangle.style.borderBottom = "0 solid transparent"; - } - } - function readRateCellText(value) { - return value ? normalizeRateDisplay(value) : UNAVAILABLE_RATE_TEXT; - } - function applyColumnWidth(element, field) { - if (field === SELECTION_COLUMN_KEY) { - element.style.minWidth = "56px"; - element.style.width = "56px"; - } - if (field === BACKEND_COLUMN_KEY) { - element.style.minWidth = "240px"; - element.style.width = "240px"; - } - if (field === SINGLE_COLUMN_KEY || field === PERSONAL_COLUMN_KEY) { - element.style.minWidth = "160px"; - element.style.width = "160px"; - } - if (BACKEND_METRIC_COLUMNS2.some((column) => column.field === field)) { - element.style.minWidth = "120px"; - element.style.width = "120px"; - } - } - function syncContainerWidth(container) { - if (!(container instanceof HTMLElement)) { - return; - } - const directChildren = Array.from(container.children).filter( - (child) => child instanceof HTMLElement - ); - const totalWidth = directChildren.reduce((sum, child) => { - return sum + readElementWidth(child); - }, 0); - if (totalWidth <= 0) { - return; - } - container.style.width = `${totalWidth}px`; - container.style.minWidth = `${totalWidth}px`; - } - function readElementWidth(element) { - const styleWidth = Number.parseFloat(element.style.width || ""); - if (Number.isFinite(styleWidth) && styleWidth > 0) { - return styleWidth; - } - const minWidth = Number.parseFloat(element.style.minWidth || ""); - if (Number.isFinite(minWidth) && minWidth > 0) { - return minWidth; - } - return 0; - } - function mergeMarketDataRows(baseRow, preferredRow) { - if (!baseRow && !preferredRow) { - return null; - } - if (!baseRow) { - return preferredRow; - } - if (!preferredRow) { - return baseRow; - } - return { - authorId: preferredRow.authorId || baseRow.authorId, - authorName: preferredRow.authorName || baseRow.authorName, - coreUserId: mergeNonEmptyString(baseRow.coreUserId, preferredRow.coreUserId), - exportFields: mergeExportFieldMaps(baseRow.exportFields, preferredRow.exportFields), - hasDirectRatesSource: preferredRow.hasDirectRatesSource || baseRow.hasDirectRatesSource, - location: mergeNonEmptyString(baseRow.location, preferredRow.location), - price21To60s: mergeNonEmptyString( - baseRow.price21To60s, - preferredRow.price21To60s - ), - rates: mergeRates(baseRow.rates, preferredRow.rates) - }; - } - function takeMatchedMarketDataRow(remainingRows, authorId, authorName) { - if (remainingRows.length === 0) { - return null; - } - const matchedIndex = remainingRows.findIndex((row) => { - if (authorId && row.authorId === authorId) { - return true; - } - if (authorName && row.authorName === authorName) { - return true; - } - return false; - }); - if (matchedIndex >= 0) { - return remainingRows.splice(matchedIndex, 1)[0] ?? null; - } - if (!authorId && !authorName) { - return remainingRows.shift() ?? null; - } - return null; - } - function mergeExportFieldMaps(current, fallback) { - if (!current && !fallback) { - return void 0; - } - const nextFields = { - ...current ?? {} - }; - Object.entries(fallback ?? {}).forEach(([key, value]) => { - if (!hasTextValue2(nextFields[key]) && hasTextValue2(value)) { - nextFields[key] = value; - } - }); - return nextFields; - } - function mergeRates(current, fallback) { - if (!current && !fallback) { - return void 0; - } - return { - singleVideoAfterSearchRate: current?.singleVideoAfterSearchRate ?? fallback?.singleVideoAfterSearchRate, - personalVideoAfterSearchRate: current?.personalVideoAfterSearchRate ?? fallback?.personalVideoAfterSearchRate - }; - } - function mergeNonEmptyString(current, fallback) { - return hasTextValue2(current) ? current : fallback; - } - function formatCurrencyValue2(value) { - if (value === null) { - return void 0; - } - return `\xA5${value.toLocaleString("en-US", { - maximumFractionDigits: 0 - })}`; - } - function readSerializedExportFields(record) { - if (!isRecord5(record.exportFields)) { - return void 0; - } - const entries = Object.entries(record.exportFields).flatMap( - ([key, value]) => typeof value === "string" ? [[key, value]] : [] - ); - return entries.length > 0 ? Object.fromEntries(entries) : void 0; - } - function hasTextValue2(value) { - return typeof value === "string" && value.trim().length > 0; - } - function renderBackendMetricsCells(cells, record) { - if (record.backendMetricsStatus === "loading" || record.status === "loading" && !record.backendMetricsStatus) { - fillBackendMetricCells(cells, "\u52A0\u8F7D\u4E2D..."); - return; - } - if (record.backendMetricsStatus === "failed") { - fillBackendMetricCells(cells, "\u52A0\u8F7D\u5931\u8D25"); - return; - } - if (record.backendMetricsStatus === "missing") { - fillBackendMetricCells(cells, UNAVAILABLE_BACKEND_METRICS_TEXT); - return; - } - if (record.backendMetricsStatus !== "success" || !record.backendMetrics) { - fillBackendMetricCells(cells, ""); - return; - } - BACKEND_METRIC_COLUMNS2.forEach(({ field }) => { - cells[field].textContent = record.backendMetrics?.[field] ?? ""; - }); - } - function fillBackendMetricCells(cells, value) { - BACKEND_METRIC_COLUMNS2.forEach(({ field }) => { - cells[field].textContent = value; - }); - } - - // src/content/market/filter-sort-controller.ts - function applyFilterAndSort(records, options = {}) { - const filteredRecords = records.filter( - (record) => matchesFilters(record, options.filters) - ); - if (!options.sort) { - return filteredRecords; - } - return [...filteredRecords].sort( - (leftRecord, rightRecord) => compareRecords(leftRecord, rightRecord, options.sort) - ); - } - function matchesFilters(record, filters) { - if (!filters) { - return true; - } - return meetsThreshold( - record.rates?.singleVideoAfterSearchRate, - filters.singleVideoAfterSearchRateMin - ) && meetsThreshold( - record.rates?.personalVideoAfterSearchRate, - filters.personalVideoAfterSearchRateMin - ); - } - function meetsThreshold(rateValue, minValue) { - if (minValue == null) { - return true; - } - const lowerBound = parseRateLowerBound(rateValue ?? null); - return lowerBound != null && lowerBound >= minValue; - } - function compareRecords(leftRecord, rightRecord, sort) { - if (isRateSortField(sort.field)) { - return compareRateSortRecords(leftRecord, rightRecord, sort); - } - return compareBackendMetricRecords(leftRecord, rightRecord, sort); - } - function compareRateSortRecords(leftRecord, rightRecord, sort) { - const field = sort.field; - const leftValue = leftRecord.rates?.[field]; - const rightValue = rightRecord.rates?.[field]; - const leftLowerBound = parseRateLowerBound(leftValue ?? null); - const rightLowerBound = parseRateLowerBound(rightValue ?? null); - if (leftLowerBound == null && rightLowerBound == null) { - return compareRecordIdentity(leftRecord, rightRecord); - } - if (leftLowerBound == null) { - return 1; - } - if (rightLowerBound == null) { - return -1; - } - if (leftLowerBound !== rightLowerBound) { - return sort.direction === "asc" ? leftLowerBound - rightLowerBound : rightLowerBound - leftLowerBound; - } - const tieBreak = compareRateValues(leftValue, rightValue); - if (tieBreak !== 0) { - return sort.direction === "asc" ? tieBreak : -tieBreak; - } - return compareRecordIdentity(leftRecord, rightRecord); - } - function compareBackendMetricRecords(leftRecord, rightRecord, sort) { - const field = sort.field; - const leftValue = parseBackendMetricValue(leftRecord.backendMetrics?.[field]); - const rightValue = parseBackendMetricValue(rightRecord.backendMetrics?.[field]); - if (leftValue == null && rightValue == null) { - return compareRecordIdentity(leftRecord, rightRecord); - } - if (leftValue == null) { - return 1; - } - if (rightValue == null) { - return -1; - } - if (leftValue !== rightValue) { - return sort.direction === "asc" ? leftValue - rightValue : rightValue - leftValue; - } - return compareRecordIdentity(leftRecord, rightRecord); - } - function parseBackendMetricValue(value) { - if (!value) { - return null; - } - const normalizedValue = value.replace(/,/g, "").replace(/%/g, "").trim(); - if (!normalizedValue) { - return null; - } - const numericValue = Number(normalizedValue); - return Number.isFinite(numericValue) ? numericValue : null; - } - function isRateSortField(field) { - return field === "singleVideoAfterSearchRate" || field === "personalVideoAfterSearchRate"; - } - function compareRecordIdentity(leftRecord, rightRecord) { - const authorIdCompare = leftRecord.authorId.localeCompare(rightRecord.authorId); - if (authorIdCompare !== 0) { - return authorIdCompare; - } - return leftRecord.authorName.localeCompare(rightRecord.authorName); - } - - // src/content/market/api-client.ts - function createMarketApiClient(options = {}) { - const baseUrl = options.baseUrl ?? resolveBaseUrl4(); - const fetchImpl = options.fetchImpl ?? defaultFetch4; - const timeoutMs = options.timeoutMs ?? 8e3; - return { - async loadAuthorAseInfo(authorId) { - const primaryResult = await loadAuthorMetricsFromUrl( - buildAuthorCommerceSeedBaseInfoUrl(authorId, baseUrl) - ); - if (primaryResult.success || primaryResult.reason === "timeout") { - return primaryResult; - } - return loadAuthorMetricsFromUrl(buildAuthorAseInfoUrl(authorId, baseUrl)); - } - }; - async function loadAuthorMetricsFromUrl(url) { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await fetchImpl(url, { - credentials: "include", - method: "GET", - signal: controller.signal - }); - if (!response.ok) { - return { - success: false, - reason: "request-failed" - }; - } - return mapAuthorAseInfoResponse(await response.json()); - } catch (error) { - if (isAbortError(error) || controller.signal.aborted) { - return { - success: false, - reason: "timeout" - }; - } - return { - success: false, - reason: "request-failed" - }; - } finally { - clearTimeout(timeoutId); - } - } - } - function buildAuthorAseInfoUrl(authorId, baseUrl) { - const url = new URL("/gw/api/aggregator/get_author_ase_info", baseUrl); - url.searchParams.set("author_id", authorId); - url.searchParams.set("range", "30"); - return url.toString(); - } - function buildAuthorCommerceSeedBaseInfoUrl(authorId, baseUrl) { - const url = new URL( - "/gw/api/aggregator/get_author_commerce_seed_base_info", - baseUrl - ); - url.searchParams.set("o_author_id", authorId); - url.searchParams.set("range", "90"); - return url.toString(); - } - function mapAuthorAseInfoResponse(payload) { - const data = getPayloadData2(payload); - if (!data) { - return { - success: false, - reason: "bad-response" - }; - } - const singleVideoAfterSearchRate = readNormalizedRate( - data.avg_search_after_view_rate - ); - const personalVideoAfterSearchRate = readNormalizedRate( - data.personal_avg_search_after_view_rate - ); - if (!singleVideoAfterSearchRate && !personalVideoAfterSearchRate) { - return { - success: false, - reason: "missing-rate" - }; - } - return { - success: true, - rates: { - ...singleVideoAfterSearchRate ? { singleVideoAfterSearchRate } : {}, - ...personalVideoAfterSearchRate ? { personalVideoAfterSearchRate } : {} - } - }; - } - function getPayloadData2(payload) { - if (!isRecord6(payload)) { - return null; - } - return isRecord6(payload.data) ? payload.data : payload; - } - function readNormalizedRate(value) { - return typeof value === "string" ? normalizeRateDisplay(value) : null; - } - function resolveBaseUrl4() { - if (typeof location !== "undefined" && location.origin) { - return location.origin; - } - return "https://xingtu.cn"; - } - async function defaultFetch4(input, init) { - return fetch(input, init); - } - function isAbortError(error) { - return error instanceof Error && error.name === "AbortError"; - } - function isRecord6(value) { - return typeof value === "object" && value !== null; - } - - // src/content/market/export-range-controller.ts - function createExportRangeController(options) { - return { - async exportRecords(target) { - const mergedRecords = /* @__PURE__ */ new Map(); - let currentPage = 0; - let expectedMinimumRowCount; - while (true) { - currentPage += 1; - options.onProgress?.({ - currentPage, - totalPages: target.mode === "count" ? target.pageCount : void 0 - }); - const currentPageRecords = await preparePageRecords(expectedMinimumRowCount); - if (!currentPageRecords) { - throw new Error(`\u7B2C ${currentPage} \u9875\u52A0\u8F7D\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`); - } - currentPageRecords.forEach((record) => { - const existingRecord = mergedRecords.get(record.authorId); - mergedRecords.set(record.authorId, mergeMarketRecord(existingRecord, record)); - }); - expectedMinimumRowCount = Math.max( - expectedMinimumRowCount ?? 0, - currentPageRecords.length - ); - if (target.mode === "count" && currentPage >= target.pageCount) { - break; - } - const previousSignature = readMarketPageSignature(options.document); - const nextPageControl = findNextPageControl(options.document); - if (!nextPageControl || isPageControlDisabled(nextPageControl)) { - break; - } - nextPageControl.click(); - const pageChanged = await waitForPageChange(previousSignature); - if (!pageChanged) { - throw new Error(`\u7B2C ${currentPage + 1} \u9875\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`); - } - } - return Array.from(mergedRecords.values()); - } - }; - async function preparePageRecords(expectedMinimumRowCount) { - for (let attempt = 0; attempt < 4; attempt += 1) { - const currentPageReady = await waitForCurrentPageReady(); - if (!currentPageReady) { - return null; - } - await options.prepareCurrentPageForExport(); - const currentPageRecords = options.readCurrentPageRecords(); - if (currentPageRecords.length > 0 && (typeof expectedMinimumRowCount !== "number" || expectedMinimumRowCount <= 0 || isCurrentPageTerminal() || currentPageRecords.length >= expectedMinimumRowCount)) { - return currentPageRecords; - } - } - return null; - } - async function waitForPageChange(previousSignature) { - const previousPageState = parsePageSignature(previousSignature); - for (let attempt = 0; attempt < 60; attempt += 1) { - await new Promise((resolve) => { - options.window.setTimeout(resolve, 50); - }); - await Promise.resolve(); - const nextSignature = readMarketPageSignature(options.document); - const nextPageState = parsePageSignature(nextSignature); - if (hasLoadedNextPage(previousPageState, nextPageState)) { - return true; - } - } - return false; - } - async function waitForCurrentPageReady() { - let stableAttemptCount = 0; - let lastReadyFingerprint = ""; - for (let attempt = 0; attempt < 80; attempt += 1) { - await new Promise((resolve) => { - options.window.setTimeout(resolve, 150); - }); - await Promise.resolve(); - const pageState = readCurrentPageState(); - if (!pageState.authorIds || pageState.rowCount <= 0) { - stableAttemptCount = 0; - lastReadyFingerprint = ""; - continue; - } - const readyFingerprint = [ - pageState.pageToken, - pageState.authorIds, - String(pageState.rowCount), - pageState.isTerminalPage ? "terminal" : "paged" - ].join("::"); - if (readyFingerprint === lastReadyFingerprint) { - stableAttemptCount += 1; - } else { - lastReadyFingerprint = readyFingerprint; - stableAttemptCount = 1; - } - if (stableAttemptCount >= 6) { - return true; - } - } - return false; - } - function readCurrentPageState() { - const pageSignature = parsePageSignature(readMarketPageSignature(options.document)); - const nextPageControl = findNextPageControl(options.document); - return { - authorIds: pageSignature.authorIds, - isTerminalPage: isPageControlDisabled(nextPageControl), - pageToken: pageSignature.pageToken, - rowCount: options.readCurrentPageRowCount() - }; - } - function isCurrentPageTerminal() { - return isPageControlDisabled(findNextPageControl(options.document)); - } - } - function parsePageSignature(signature) { - const separatorIndex = signature.indexOf("::"); - if (separatorIndex < 0) { - return { - authorIds: "", - pageToken: signature.trim() - }; - } - return { - authorIds: signature.slice(separatorIndex + 2).trim(), - pageToken: signature.slice(0, separatorIndex).trim() - }; - } - function hasLoadedNextPage(previousPageState, nextPageState) { - if (!nextPageState.authorIds) { - return false; - } - if (nextPageState.pageToken || previousPageState.pageToken) { - return nextPageState.pageToken !== previousPageState.pageToken; - } - return nextPageState.authorIds !== previousPageState.authorIds; - } - function mergeMarketRecord(existingRecord, incomingRecord) { - if (!existingRecord) { - return { - ...incomingRecord, - exportFields: mergeFieldMap(void 0, incomingRecord.exportFields), - rates: mergeFieldMap(void 0, incomingRecord.rates) - }; - } - return { - ...existingRecord, - ...incomingRecord, - authorName: mergeStringValue(existingRecord.authorName, incomingRecord.authorName) ?? "", - coreUserId: mergeStringValue(existingRecord.coreUserId, incomingRecord.coreUserId), - exportFields: mergeFieldMap( - existingRecord.exportFields, - incomingRecord.exportFields - ), - failureReason: incomingRecord.failureReason ?? existingRecord.failureReason, - hasDirectRatesSource: existingRecord.hasDirectRatesSource || incomingRecord.hasDirectRatesSource, - location: mergeStringValue(existingRecord.location, incomingRecord.location), - price21To60s: mergeStringValue( - existingRecord.price21To60s, - incomingRecord.price21To60s - ), - rates: mergeFieldMap(existingRecord.rates, incomingRecord.rates), - status: mergeStatus(existingRecord.status, incomingRecord.status) - }; - } - function mergeFieldMap(current, incoming) { - if (!current && !incoming) { - return void 0; - } - const merged = { - ...current ?? {} - }; - Object.entries(incoming ?? {}).forEach(([key, value]) => { - const currentValue = merged[key]; - if (hasTextValue3(value) || !hasTextValue3(currentValue)) { - merged[key] = value; - } - }); - return merged; - } - function mergeStatus(current, incoming) { - const priority = { - failed: 1, - idle: 0, - loading: 2, - missing: -1, - success: 3 - }; - return priority[incoming] >= priority[current] ? incoming : current; - } - function mergeStringValue(current, incoming) { - if (hasTextValue3(incoming) || !hasTextValue3(current)) { - return incoming ?? current; - } - return current; - } - function hasTextValue3(value) { - return typeof value === "string" && value.trim().length > 0; - } - - // src/content/market/plugin-toolbar.ts - var PLUGIN_ACTION_BUTTON_STYLE_ID = "sces-plugin-action-button-style"; - function isPluginToolbarMounted(root, document2) { - const actionRow = findNativeActionRow(document2); - return Boolean(actionRow && root.parentElement === actionRow && !root.hidden); - } - function ensurePluginToolbar(document2, handlers) { - ensurePluginActionButtonTheme(document2); - const existingRoot = document2.querySelector( - "[data-plugin-toolbar='root']" - ); - if (existingRoot) { - if (existingRoot.querySelector( - '[data-plugin-export-audience-profile-by-id="button"]' - )) { - ensureToolbarMounted(existingRoot, document2); - return readToolbarDom(existingRoot); - } - existingRoot.remove(); - } - const root = document2.createElement("section"); - root.dataset.pluginToolbar = "root"; - applyToolbarRootStyles(root); - const exportRangeSelect = document2.createElement("select"); - exportRangeSelect.dataset.pluginExportRange = "select"; - exportRangeSelect.hidden = true; - appendOption(exportRangeSelect, "current", "\u5F53\u524D\u9875"); - appendOption(exportRangeSelect, "first-5", "\u524D5\u9875"); - appendOption(exportRangeSelect, "first-10", "\u524D10\u9875"); - appendOption(exportRangeSelect, "all", "\u5168\u90E8"); - appendOption(exportRangeSelect, "custom", "\u81EA\u5B9A\u4E49"); - exportRangeSelect.value = "first-5"; - const exportCustomPagesInput = document2.createElement("input"); - exportCustomPagesInput.type = "number"; - exportCustomPagesInput.min = "1"; - exportCustomPagesInput.step = "1"; - exportCustomPagesInput.hidden = true; - exportCustomPagesInput.placeholder = "\u9875\u6570"; - exportCustomPagesInput.dataset.pluginExportCustomPages = "input"; - const exportButton = document2.createElement("button"); - exportButton.type = "button"; - exportButton.dataset.pluginExport = "button"; - exportButton.hidden = true; - exportButton.tabIndex = -1; - const audienceProfileExportButton = document2.createElement("button"); - audienceProfileExportButton.type = "button"; - audienceProfileExportButton.dataset.pluginExportAudienceProfile = "button"; - audienceProfileExportButton.textContent = "\u5BFC\u51FA\u9009\u4E2D\u8FBE\u4EBA\u6570\u636E"; - audienceProfileExportButton.title = "\u4EC5\u5BFC\u51FA\u5DF2\u52FE\u9009\u8FBE\u4EBA\uFF0C\u5305\u542B\u5185\u5BB9\u6570\u636E\u3001\u6548\u679C\u9884\u4F30\u3001\u753B\u50CF\u7B49\u7EF4\u5EA6"; - const audienceProfileByIdExportButton = document2.createElement("button"); - audienceProfileByIdExportButton.type = "button"; - audienceProfileByIdExportButton.dataset.pluginExportAudienceProfileById = "button"; - audienceProfileByIdExportButton.textContent = "\u6309\u661F\u56FEID\u5BFC\u51FA"; - audienceProfileByIdExportButton.title = "\u7C98\u8D34\u8FBE\u4EBA\u661F\u56FEID\u540E\u6279\u91CF\u5BFC\u51FA\u8FBE\u4EBA\u6570\u636E\uFF0C\u4E0D\u4F9D\u8D56\u5F53\u524D\u5217\u8868\u52FE\u9009"; - const audienceProfileFieldButton = document2.createElement("button"); - audienceProfileFieldButton.type = "button"; - audienceProfileFieldButton.dataset.pluginAudienceProfileFields = "button"; - audienceProfileFieldButton.textContent = "\u9009\u62E9\u5B57\u6BB5"; - audienceProfileFieldButton.title = "\u52FE\u9009\u672C\u6B21CSV\u9700\u8981\u5BFC\u51FA\u7684\u5B57\u6BB5\uFF0C\u8BBE\u7F6E\u4F1A\u81EA\u52A8\u4FDD\u5B58"; - const batchSubmitButton = document2.createElement("button"); - batchSubmitButton.type = "button"; - batchSubmitButton.dataset.pluginBatchSubmit = "button"; - batchSubmitButton.textContent = "\u63D0\u4EA4\u6279\u6B21"; - batchSubmitButton.title = "\u5C06\u5F53\u524D\u9009\u4E2D\u7684\u8FBE\u4EBA\u63D0\u4EA4\u5230\u540E\u7EED\u4E1A\u52A1\u6279\u6B21"; - const exportStatusText = document2.createElement("span"); - exportStatusText.dataset.pluginExportStatus = "text"; - applyStatusStyles(exportStatusText); - root.append( - exportRangeSelect, - exportCustomPagesInput, - exportButton, - audienceProfileExportButton, - audienceProfileByIdExportButton, - audienceProfileFieldButton, - batchSubmitButton, - exportStatusText - ); - document2.body.appendChild(root); - applyNativeControlStyles(document2, { - audienceProfileExportButton, - audienceProfileByIdExportButton, - audienceProfileFieldButton, - batchSubmitButton, - exportButton, - exportCustomPagesInput, - exportRangeSelect - }); - ensureToolbarMounted(root, document2); - exportButton.addEventListener("click", () => { - void handlers.onExport(); - }); - audienceProfileExportButton.addEventListener("click", () => { - void handlers.onExportAudienceProfile(); - }); - audienceProfileByIdExportButton.addEventListener("click", () => { - void handlers.onExportAudienceProfileByIds(); - }); - audienceProfileFieldButton.addEventListener("click", () => { - void handlers.onConfigureAudienceProfileFields(); - }); - batchSubmitButton.addEventListener("click", () => { - void handlers.onSubmitBatch(); - }); - exportRangeSelect.addEventListener("change", () => { - syncCustomPagesInputVisibility({ - batchSubmitButton, - audienceProfileFieldButton, - audienceProfileByIdExportButton, - audienceProfileExportButton, - exportButton, - exportCustomPagesInput, - exportRangeSelect, - exportStatusText, - root - }); - }); - const toolbarDom = { - audienceProfileExportButton, - audienceProfileByIdExportButton, - audienceProfileFieldButton, - batchSubmitButton, - exportButton, - exportCustomPagesInput, - exportRangeSelect, - exportStatusText, - root - }; - syncCustomPagesInputVisibility(toolbarDom); - return toolbarDom; - } - function appendOption(select, value, label) { - const option = select.ownerDocument.createElement("option"); - option.value = value; - option.textContent = label; - select.appendChild(option); - } - function readToolbarDom(root) { - const toolbarDom = { - audienceProfileByIdExportButton: root.querySelector( - '[data-plugin-export-audience-profile-by-id="button"]' - ), - audienceProfileExportButton: root.querySelector( - '[data-plugin-export-audience-profile="button"]' - ), - audienceProfileFieldButton: root.querySelector( - '[data-plugin-audience-profile-fields="button"]' - ), - batchSubmitButton: root.querySelector( - '[data-plugin-batch-submit="button"]' - ), - exportButton: root.querySelector( - '[data-plugin-export="button"]' - ), - exportCustomPagesInput: root.querySelector( - '[data-plugin-export-custom-pages="input"]' - ), - exportRangeSelect: root.querySelector( - '[data-plugin-export-range="select"]' - ), - exportStatusText: root.querySelector( - '[data-plugin-export-status="text"]' - ), - root - }; - syncCustomPagesInputVisibility(toolbarDom); - return toolbarDom; - } - function readToolbarExportTarget(toolbar) { - const scope = toolbar.exportRangeSelect.value; - if (scope === "all") { - return { - target: { - mode: "all" - } - }; - } - if (scope === "current") { - return { - target: { - mode: "count", - pageCount: 1 - } - }; - } - if (scope === "first-5") { - return { - target: { - mode: "count", - pageCount: 5 - } - }; - } - if (scope === "first-10") { - return { - target: { - mode: "count", - pageCount: 10 - } - }; - } - const pageCount = Number(toolbar.exportCustomPagesInput.value); - if (!Number.isInteger(pageCount) || pageCount < 1) { - return { - error: "\u8BF7\u8F93\u5165\u6709\u6548\u9875\u6570" - }; - } - return { - target: { - mode: "count", - pageCount - } - }; - } - function setToolbarBusyState(toolbar, isBusy) { - [ - toolbar.batchSubmitButton, - toolbar.audienceProfileFieldButton, - toolbar.audienceProfileByIdExportButton, - toolbar.audienceProfileExportButton, - toolbar.exportButton, - toolbar.exportRangeSelect, - toolbar.exportCustomPagesInput - ].forEach((element) => { - element.disabled = isBusy; - }); - } - function setToolbarExportStatus(toolbar, text) { - toolbar.exportStatusText.textContent = text; - } - function syncCustomPagesInputVisibility(toolbar) { - toolbar.exportRangeSelect.hidden = true; - toolbar.exportCustomPagesInput.hidden = true; - } - function ensureToolbarMounted(root, document2) { - const actionRow = findNativeActionRow(document2); - if (!actionRow) { - root.hidden = true; - return; - } - const customizeButton = findNativeActionButton(actionRow, "\u81EA\u5B9A\u4E49\u6307\u6807"); - const insertionAnchor = customizeButton ? findDirectChildAnchor(actionRow, customizeButton) : null; - if (insertionAnchor) { - actionRow.insertBefore(root, insertionAnchor); - } else if (root.parentElement !== actionRow) { - actionRow.prepend(root); - } - root.hidden = false; - } - function findNativeActionRow(document2) { - const customizeButton = findNativeActionButton(document2, "\u81EA\u5B9A\u4E49\u6307\u6807"); - const exportButton = findNativeActionButton(document2, "\u5BFC\u51FA"); - const header = findHeaderContainer(customizeButton, exportButton); - const sharedActionRow = customizeButton && exportButton ? findSmallestSharedActionRow(customizeButton, exportButton, header) : null; - if (sharedActionRow) { - return sharedActionRow; - } - const scope = header ?? document2; - const candidates = Array.from( - scope.querySelectorAll(".xt-space.xt-space--medium, .search-content--header") - ).filter( - (element) => element instanceof document2.defaultView.HTMLElement - ); - const rankedCandidates = candidates.filter( - (candidate) => isNativeActionRowCandidate(candidate, customizeButton, exportButton) - ).sort((left, right) => { - const depthDelta = getDepthWithinAncestor(right, header) - getDepthWithinAncestor(left, header); - if (depthDelta !== 0) { - return depthDelta; - } - return normalizeText(left.textContent).length - normalizeText(right.textContent).length; - }); - return rankedCandidates[0] ?? null; - } - function findHeaderContainer(customizeButton, exportButton) { - return customizeButton?.closest(".search-content--header") ?? exportButton?.closest(".search-content--header"); - } - function findSmallestSharedActionRow(customizeButton, exportButton, boundary) { - const exportAncestors = new Set(collectAncestorChain(exportButton, boundary)); - for (const candidate of collectAncestorChain(customizeButton, boundary)) { - if (exportAncestors.has(candidate) && isNativeActionRowCandidate(candidate, customizeButton, exportButton)) { - return candidate; - } - } - return null; - } - function collectAncestorChain(element, boundary) { - const ancestors = []; - let current = element.parentElement; - while (current) { - ancestors.push(current); - if (current === boundary) { - break; - } - current = current.parentElement; - } - return ancestors; - } - function isNativeActionRowCandidate(candidate, customizeButton, exportButton) { - if (customizeButton && !candidate.contains(customizeButton)) { - return false; - } - if (exportButton && !candidate.contains(exportButton)) { - return false; - } - const directChildLabels = Array.from(candidate.children).flatMap((child) => { - const buttons = []; - if (child instanceof candidate.ownerDocument.defaultView.HTMLButtonElement) { - buttons.push(child); - } - buttons.push(...Array.from(child.querySelectorAll("button"))); - return buttons; - }).map((button) => normalizeText(button.textContent)); - return directChildLabels.includes("\u5BFC\u51FA") && (directChildLabels.includes("\u81EA\u5B9A\u4E49\u6307\u6807") || Boolean(customizeButton)); - } - function getDepthWithinAncestor(element, boundary) { - let depth = 0; - let current = element.parentElement; - while (current && current !== boundary) { - depth += 1; - current = current.parentElement; - } - return depth; - } - function findNativeActionButton(root, text) { - const document2 = root instanceof Document ? root : root.ownerDocument; - if (!document2) { - return null; - } - const candidates = Array.from(root.querySelectorAll("button")).filter( - (element) => element instanceof document2.defaultView.HTMLElement - ); - return candidates.find((element) => normalizeText(element.textContent) === text) ?? null; - } - function applyToolbarRootStyles(root) { - root.style.display = "inline-flex"; - root.style.alignItems = "center"; - root.style.columnGap = "8px"; - root.style.flexWrap = "wrap"; - } - function applyNativeControlStyles(document2, controls) { - const primaryButton = findButtonContainingText(document2, "\u53D1\u5E03\u4EFB\u52A1") ?? findButtonContainingText(document2, "+\u53D1\u5E03\u4EFB\u52A1"); - const nativeButton = primaryButton ?? findNativeActionButton(document2, "\u81EA\u5B9A\u4E49\u6307\u6807") ?? findNativeActionButton(document2, "\u5BFC\u51FA"); - if (nativeButton) { - controls.audienceProfileExportButton.className = nativeButton.className; - controls.audienceProfileByIdExportButton.className = nativeButton.className; - controls.audienceProfileFieldButton.className = nativeButton.className; - controls.batchSubmitButton.className = nativeButton.className; - } - [ - controls.audienceProfileExportButton, - controls.audienceProfileByIdExportButton, - controls.audienceProfileFieldButton, - controls.batchSubmitButton - ].forEach((button) => { - applyPrimaryButtonStyles4(button); - button.style.whiteSpace = "nowrap"; - }); - [controls.exportRangeSelect, controls.exportCustomPagesInput].forEach((element) => { - element.style.height = "32px"; - element.style.border = "1px solid #d0d7de"; - element.style.borderRadius = "6px"; - element.style.padding = "0 10px"; - element.style.background = "#fff"; - element.style.color = "#1f2329"; - element.style.boxSizing = "border-box"; - }); - controls.exportRangeSelect.style.minWidth = "104px"; - controls.exportCustomPagesInput.style.width = "72px"; - } - function applyPrimaryButtonStyles4(button) { - button.style.backgroundColor = "#7f1d2d"; - button.style.border = "1px solid #7f1d2d"; - button.style.borderRadius = "8px"; - button.style.color = "#ffffff"; - button.style.height = "32px"; - button.style.padding = "0 15px"; - button.style.boxSizing = "border-box"; - button.style.fontWeight = "600"; - button.style.transition = "background-color 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease"; - } - function applyStatusStyles(statusText) { - statusText.style.color = "#64748b"; - statusText.style.fontSize = "12px"; - statusText.style.lineHeight = "20px"; - statusText.style.marginLeft = "4px"; - statusText.style.whiteSpace = "nowrap"; - } - function ensurePluginActionButtonTheme(document2) { - if (document2.getElementById(PLUGIN_ACTION_BUTTON_STYLE_ID)) { - return; - } - const style = document2.createElement("style"); - style.id = PLUGIN_ACTION_BUTTON_STYLE_ID; - style.textContent = ` - [data-plugin-export-audience-profile="button"]:hover:not(:disabled), - [data-plugin-export-audience-profile-by-id="button"]:hover:not(:disabled), - [data-plugin-audience-profile-fields="button"]:hover:not(:disabled), - [data-plugin-batch-submit="button"]:hover:not(:disabled) { - background-color: #6d1627 !important; - border-color: #6d1627 !important; - } - - [data-plugin-export-audience-profile="button"]:active:not(:disabled), - [data-plugin-export-audience-profile-by-id="button"]:active:not(:disabled), - [data-plugin-audience-profile-fields="button"]:active:not(:disabled), - [data-plugin-batch-submit="button"]:active:not(:disabled) { - background-color: #58111f !important; - border-color: #58111f !important; - transform: translateY(1px); - } - - [data-plugin-export-audience-profile="button"]:focus-visible, - [data-plugin-export-audience-profile-by-id="button"]:focus-visible, - [data-plugin-audience-profile-fields="button"]:focus-visible, - [data-plugin-batch-submit="button"]:focus-visible { - outline: none !important; - box-shadow: 0 0 0 3px rgba(127, 29, 45, 0.2) !important; - } - - [data-plugin-export-audience-profile="button"]:disabled, - [data-plugin-export-audience-profile-by-id="button"]:disabled, - [data-plugin-audience-profile-fields="button"]:disabled, - [data-plugin-batch-submit="button"]:disabled { - background-color: #c89ca4 !important; - border-color: #c89ca4 !important; - color: rgba(255, 255, 255, 0.95) !important; - cursor: not-allowed !important; - opacity: 1 !important; - transform: none !important; - box-shadow: none !important; - } - `; - document2.head.appendChild(style); - } - function normalizeText(value) { - return value?.replace(/\s+/g, " ").trim() ?? ""; - } - function findButtonContainingText(root, text) { - const document2 = root instanceof Document ? root : root.ownerDocument; - if (!document2) { - return null; - } - const candidates = Array.from(root.querySelectorAll("button")).filter( - (element) => element instanceof document2.defaultView.HTMLElement - ); - return candidates.find((element) => normalizeText(element.textContent).includes(text)) ?? null; - } - function findDirectChildAnchor(ancestor, descendant) { - let current = descendant; - let previous = null; - while (current && current !== ancestor) { - previous = current; - current = current.parentElement; - } - return current === ancestor ? previous : null; - } - - // src/content/market/market-list-request-snapshot.ts - var MARKET_REQUEST_SNAPSHOT_ATTRIBUTE = "data-sces-market-request-snapshot"; - var MARKET_SEARCH_ENDPOINT_PATH = "/gw/api/gsearch/search_for_author_square"; - function readMarketListRequestSnapshot(document2) { - const serializedSnapshot = document2.documentElement.getAttribute( - MARKET_REQUEST_SNAPSHOT_ATTRIBUTE - ); - if (!serializedSnapshot) { - return readMarketListRequestSnapshotFromPageState(document2); - } - try { - const parsedSnapshot = normalizeMarketListRequestSnapshot( - JSON.parse(serializedSnapshot) - ); - if (!parsedSnapshot) { - return readMarketListRequestSnapshotFromPageState(document2); - } - return parsedSnapshot; - } catch { - return readMarketListRequestSnapshotFromPageState(document2); - } - } - function isMarketListRequestSnapshot(value) { - if (!value || typeof value !== "object") { - return false; - } - const candidate = value; - return typeof candidate.method === "string" && typeof candidate.url === "string" && (!("body" in candidate) || typeof candidate.body === "string") && (!("headers" in candidate) || isStringRecord(candidate.headers)); - } - function normalizeMarketListRequestSnapshot(value) { - if (!value || typeof value !== "object") { - return null; - } - const candidate = value; - const normalizedSnapshot = { - body: typeof candidate.body === "string" ? candidate.body : void 0, - method: typeof candidate.method === "string" ? candidate.method : void 0, - url: typeof candidate.url === "string" ? candidate.url : void 0 - }; - if (candidate.headers && typeof candidate.headers === "object") { - normalizedSnapshot.headers = Object.fromEntries( - Object.entries(candidate.headers).filter( - ([, entry]) => ["string", "number", "boolean"].includes(typeof entry) - ).map(([key, entry]) => [key, String(entry)]) - ); - } - return isMarketListRequestSnapshot(normalizedSnapshot) ? normalizedSnapshot : null; - } - function isStringRecord(value) { - if (!value || typeof value !== "object") { - return false; - } - return Object.values(value).every((entry) => typeof entry === "string"); - } - function readMarketListRequestSnapshotFromPageState(document2) { - const reqParams = findMarketReqParams(document2); - if (!reqParams) { - return null; - } - return { - body: JSON.stringify(reqParams), - method: "POST", - url: buildMarketSearchUrl(document2) - }; - } - function findMarketReqParams(document2) { - const marketRoot = document2.querySelector(".base-author-list"); - const setupState = marketRoot?.__vue__?._setupState; - if (!setupState) { - return null; - } - const queue = Object.values(setupState); - while (queue.length > 0) { - const current = unwrapVueRef3(queue.shift()); - if (!isRecord7(current)) { - continue; - } - const reqParams = unwrapVueRef3(current.reqParams); - if (isRecord7(reqParams)) { - return reqParams; - } - Object.values(current).forEach((value) => { - queue.push(value); - }); - } - return null; - } - function buildMarketSearchUrl(document2) { - if (document2.location?.origin && document2.location.origin !== "null" && document2.location.origin !== "about:blank") { - return document2.location.origin.includes("xingtu.cn") ? MARKET_SEARCH_ENDPOINT_PATH : new URL(MARKET_SEARCH_ENDPOINT_PATH, document2.location.origin).toString(); - } - return MARKET_SEARCH_ENDPOINT_PATH; - } - function unwrapVueRef3(value) { - if (isRecord7(value) && "value" in value) { - return value.value; - } - return value; - } - function isRecord7(value) { - return typeof value === "object" && value !== null; - } - - // src/content/market/silent-export-controller.ts - var PAGE_NUMBER_KEYS2 = [ - "currentPage", - "page", - "pageNo", - "pageNum", - "page_no", - "page_num" - ]; - function createSilentExportController(options) { - const fetchImpl = options.fetchImpl ?? defaultFetch5; - return { - async exportRecords(target) { - const snapshot = readMarketListRequestSnapshot(options.document); - if (!snapshot) { - return null; - } - const baseRequest = createPagedRequest(snapshot); - if (!baseRequest) { - return null; - } - const mergedRecords = /* @__PURE__ */ new Map(); - const maxPageCount = target.mode === "count" ? target.pageCount : 200; - let totalPagesHint; - for (let offset = 0; offset < maxPageCount; offset += 1) { - const pageNumber = baseRequest.initialPage + offset; - options.onProgress?.({ - currentPage: offset + 1, - totalPages: target.mode === "count" ? target.pageCount : totalPagesHint - }); - const payload = await fetchPagePayload(fetchImpl, baseRequest, pageNumber); - const parsedResponse = parseMarketListResponse(payload); - if (!parsedResponse) { - return null; - } - totalPagesHint = parsedResponse.totalPages ?? totalPagesHint; - if (parsedResponse.records.length === 0) { - break; - } - parsedResponse.records.forEach((record) => { - const existingRecord = mergedRecords.get(record.authorId); - mergedRecords.set(record.authorId, mergeMarketRecord2(existingRecord, record)); - }); - if (target.mode === "count" && offset + 1 >= target.pageCount) { - break; - } - if (target.mode === "all") { - if (typeof parsedResponse.totalPages === "number" && pageNumber >= parsedResponse.totalPages) { - break; - } - if (typeof parsedResponse.pageSize === "number" && parsedResponse.records.length < parsedResponse.pageSize) { - break; - } - } - } - return Array.from(mergedRecords.values()); - } - }; - } - function createPagedRequest(snapshot) { - const bodyPage = readPageFromBody(snapshot.body); - if (bodyPage !== null) { - return { - initialPage: bodyPage, - pageSource: "body", - snapshot - }; - } - const urlPage = readPageFromUrl(snapshot.url); - if (urlPage !== null) { - return { - initialPage: urlPage, - pageSource: "url", - snapshot - }; - } - return { - initialPage: 1, - pageSource: "none", - snapshot - }; - } - async function fetchPagePayload(fetchImpl, request, pageNumber) { - const nextUrl = request.pageSource === "url" ? mutateUrlPage(request.snapshot.url, pageNumber) : request.snapshot.url; - const nextBody = mutateBodyPage(request.snapshot.body, pageNumber); - const response = await fetchImpl(nextUrl, { - body: nextBody, - credentials: "include", - headers: filterReplayHeaders(request.snapshot.headers, nextBody), - method: request.snapshot.method - }); - if (!response.ok) { - throw new Error("\u9759\u9ED8\u5BFC\u51FA\u8BF7\u6C42\u5931\u8D25"); - } - return response.json(); - } - function readPageFromUrl(url) { - try { - const parsedUrl = new URL(url); - for (const key of PAGE_NUMBER_KEYS2) { - const value = readNumericString(parsedUrl.searchParams.get(key)); - if (value !== null) { - return value; - } - } - } catch { - return null; - } - return null; - } - function mutateUrlPage(url, pageNumber) { - try { - const parsedUrl = new URL(url); - for (const key of PAGE_NUMBER_KEYS2) { - if (!parsedUrl.searchParams.has(key)) { - continue; - } - parsedUrl.searchParams.set(key, String(pageNumber)); - return parsedUrl.toString(); - } - parsedUrl.searchParams.set("page", String(pageNumber)); - return parsedUrl.toString(); - } catch { - return url; - } - } - function readPageFromBody(body) { - const parsedBody = parseBody(body); - if (!parsedBody) { - return null; - } - return readKnownPaginationNumber(parsedBody, "page"); - } - function mutateBodyPage(body, pageNumber) { - if (!body) { - return body; - } - const trimmedBody = body.trim(); - if (!trimmedBody) { - return body; - } - try { - const parsedJson = JSON.parse(trimmedBody); - if (!replacePageNumberInValue(parsedJson, pageNumber) && isRecord8(parsedJson)) { - parsedJson.page = pageNumber; - } - return JSON.stringify(parsedJson); - } catch { - const searchParams = new URLSearchParams(trimmedBody); - for (const key of PAGE_NUMBER_KEYS2) { - if (!searchParams.has(key)) { - continue; - } - searchParams.set(key, String(pageNumber)); - return searchParams.toString(); - } - searchParams.set("page", String(pageNumber)); - return searchParams.toString(); - } - } - function parseBody(body) { - if (!body) { - return null; - } - const trimmedBody = body.trim(); - if (!trimmedBody) { - return null; - } - try { - const parsedBody = JSON.parse(trimmedBody); - return isRecord8(parsedBody) ? parsedBody : null; - } catch { - const searchParams = new URLSearchParams(trimmedBody); - const payload = {}; - searchParams.forEach((value, key) => { - payload[key] = value; - }); - return payload; - } - } - function replacePageNumberInValue(value, pageNumber) { - if (!isRecord8(value)) { - return false; - } - let replaced = false; - PAGE_NUMBER_KEYS2.forEach((key) => { - if (!(key in value)) { - return; - } - value[key] = pageNumber; - replaced = true; - }); - if (replaced) { - return true; - } - return Object.values(value).some((entry) => replacePageNumberInValue(entry, pageNumber)); - } - function filterReplayHeaders(headers, body) { - const filteredHeaders = Object.fromEntries( - Object.entries(headers ?? {}).filter(([key]) => { - const normalizedKey = key.toLowerCase(); - return normalizedKey !== "content-length" && normalizedKey !== "host"; - }) - ); - if (body) { - if (!hasHeader(filteredHeaders, "accept")) { - filteredHeaders.Accept = "application/json, text/plain, */*"; - } - if (!hasHeader(filteredHeaders, "content-type")) { - filteredHeaders["Content-Type"] = "application/json"; - } - if (!hasHeader(filteredHeaders, "x-login-source")) { - filteredHeaders["x-login-source"] = "1"; - } - if (!hasHeader(filteredHeaders, "agw-js-conv")) { - filteredHeaders["Agw-Js-Conv"] = "str"; - } - } - return Object.keys(filteredHeaders).length > 0 ? filteredHeaders : void 0; - } - function hasHeader(headers, key) { - return Object.keys(headers).some((headerKey) => headerKey.toLowerCase() === key); - } - function readNumericString(value) { - if (!value) { - return null; - } - const parsedValue = Number(value); - return Number.isFinite(parsedValue) ? parsedValue : null; - } - async function defaultFetch5(input, init) { - return fetch(input, init); - } - function isRecord8(value) { - return typeof value === "object" && value !== null; - } - function mergeMarketRecord2(existingRecord, incomingRecord) { - if (!existingRecord) { - return { - ...incomingRecord, - exportFields: mergeFieldMap2(void 0, incomingRecord.exportFields), - rates: mergeFieldMap2(void 0, incomingRecord.rates), - status: incomingRecord.status ?? "idle" - }; - } - return { - ...existingRecord, - ...incomingRecord, - authorName: mergeStringValue2(existingRecord.authorName, incomingRecord.authorName) ?? "", - coreUserId: mergeStringValue2(existingRecord.coreUserId, incomingRecord.coreUserId), - exportFields: mergeFieldMap2( - existingRecord.exportFields, - incomingRecord.exportFields - ), - failureReason: incomingRecord.failureReason ?? existingRecord.failureReason, - hasDirectRatesSource: existingRecord.hasDirectRatesSource || incomingRecord.hasDirectRatesSource, - location: mergeStringValue2(existingRecord.location, incomingRecord.location), - price21To60s: mergeStringValue2( - existingRecord.price21To60s, - incomingRecord.price21To60s - ), - rates: mergeFieldMap2(existingRecord.rates, incomingRecord.rates), - status: incomingRecord.status ?? existingRecord.status - }; - } - function mergeFieldMap2(current, incoming) { - if (!current && !incoming) { - return void 0; - } - const merged = { - ...current ?? {} - }; - Object.entries(incoming ?? {}).forEach(([key, value]) => { - const currentValue = merged[key]; - if (hasTextValue4(value) || !hasTextValue4(currentValue)) { - merged[key] = value; - } - }); - return merged; - } - function mergeStringValue2(current, incoming) { - return hasTextValue4(incoming) ? incoming : current; - } - function hasTextValue4(value) { - return Boolean(value && value.trim().length > 0); - } - - // src/content/market/result-store.ts - function createMarketResultStore() { - const records = /* @__PURE__ */ new Map(); - return { - getRecord(authorId) { - return records.get(authorId) ?? null; - }, - listRecords() { - return Array.from(records.values()); - }, - setAuthorFailed(authorId, reason) { - const existingRecord = ensureRecord(authorId); - existingRecord.status = "failed"; - existingRecord.failureReason = reason; - }, - setAuthorLoading(authorId) { - const existingRecord = ensureRecord(authorId); - existingRecord.status = "loading"; - delete existingRecord.failureReason; - }, - setBackendMetricsFailed(authorId) { - const existingRecord = ensureRecord(authorId); - existingRecord.backendMetricsStatus = "failed"; - }, - setBackendMetricsLoading(authorId) { - const existingRecord = ensureRecord(authorId); - existingRecord.backendMetricsStatus = "loading"; - }, - setBackendMetricsMissing(authorId) { - const existingRecord = ensureRecord(authorId); - existingRecord.backendMetricsStatus = "missing"; - }, - setBackendMetricsSuccess(authorId, backendMetrics) { - const existingRecord = ensureRecord(authorId); - existingRecord.backendMetricsStatus = "success"; - existingRecord.backendMetrics = { - ...existingRecord.backendMetrics, - ...backendMetrics - }; - }, - setAuthorSuccess(authorId, rates) { - const existingRecord = ensureRecord(authorId); - existingRecord.status = "success"; - existingRecord.rates = { - ...existingRecord.rates, - ...rates - }; - delete existingRecord.failureReason; - }, - upsertMarketRow(row) { - const existingRecord = records.get(row.authorId); - if (existingRecord) { - existingRecord.authorName = mergeStringValue3(existingRecord.authorName, row.authorName) ?? existingRecord.authorName; - existingRecord.coreUserId = mergeStringValue3( - existingRecord.coreUserId, - row.coreUserId - ); - existingRecord.location = mergeStringValue3( - existingRecord.location, - row.location - ); - existingRecord.price21To60s = mergeStringValue3( - existingRecord.price21To60s, - row.price21To60s - ); - existingRecord.exportFields = mergeFieldMap3( - existingRecord.exportFields, - row.exportFields - ); - existingRecord.backendMetrics = mergeFieldMap3( - existingRecord.backendMetrics, - row.backendMetrics - ); - existingRecord.hasDirectRatesSource = existingRecord.hasDirectRatesSource || row.hasDirectRatesSource; - existingRecord.rates = mergeFieldMap3(existingRecord.rates, row.rates); - return existingRecord; - } - const nextRecord = { - ...row, - backendMetricsStatus: "idle", - status: "idle" - }; - records.set(row.authorId, nextRecord); - return nextRecord; - } - }; - function ensureRecord(authorId) { - const existingRecord = records.get(authorId); - if (existingRecord) { - return existingRecord; - } - const nextRecord = { - authorId, - authorName: authorId, - backendMetricsStatus: "idle", - status: "idle" - }; - records.set(authorId, nextRecord); - return nextRecord; - } - } - function mergeFieldMap3(current, incoming) { - if (!current && !incoming) { - return void 0; - } - const merged = { - ...current ?? {} - }; - Object.entries(incoming ?? {}).forEach(([key, value]) => { - const currentValue = merged[key]; - if (!hasTextValue5(currentValue)) { - merged[key] = value; - } - }); - return merged; - } - function mergeStringValue3(current, incoming) { - if (!hasTextValue5(current)) { - return incoming ?? current; - } - return current; - } - function hasTextValue5(value) { - return typeof value === "string" && value.trim().length > 0; - } - - // src/shared/auth-messages.ts - function isAuthResponseMessage(value) { - if (!value || typeof value !== "object") { - return false; - } - const candidate = value; - if (candidate.ok === false) { - return candidate.type === "auth:error" && typeof candidate.error === "string"; - } - if (candidate.ok !== true || typeof candidate.type !== "string") { - return false; - } - if (candidate.type === "auth:ack") { - return true; - } - if (candidate.type === "auth:token") { - return Boolean( - candidate.value && typeof candidate.value === "object" && typeof candidate.value.accessToken === "string" - ); - } - if (candidate.type === "auth:state") { - return Boolean( - candidate.value && typeof candidate.value === "object" && typeof candidate.value.isAuthenticated === "boolean" - ); - } - return false; - } - - // src/shared/backend-metrics-messages.ts - function isBackendMetricsResponseMessage(value) { - if (!value || typeof value !== "object") { - return false; - } - const candidate = value; - if (candidate.ok === false) { - return candidate.type === "backend-metrics:error" && typeof candidate.error === "string"; - } - return Boolean( - candidate.ok === true && candidate.type === "backend-metrics:result" && candidate.value && typeof candidate.value === "object" && Array.isArray(candidate.value.rows) - ); - } - - // src/content/market/index.ts - var AUDIENCE_PROFILE_FIELD_SELECTION_STORAGE_KEY = "sces:audience-profile:selectedHeaders"; - function createMarketController(options) { - const marketApiClient = createMarketApiClient(); - const audienceProfileClient = createAudienceProfileClient(); - const authorBaseClient = createAuthorBaseClient(); - const businessAbilityClient = createBusinessAbilityClient(); - const sendRuntimeMessage = createRuntimeMessageSender(); - const resultStore = options.resultStore ?? createMarketResultStore(); - const loadAuthorMetrics = options.loadAuthorMetrics ?? marketApiClient.loadAuthorAseInfo; - const searchBackendMetrics = options.searchBackendMetrics ?? (hasRuntimeMessageSender() ? (starIds) => readBackendMetrics(sendRuntimeMessage, starIds) : null); - const buildCsv = options.buildCsv ?? buildMarketCsv; - const buildAudienceCsv = options.buildAudienceProfileCsv ?? buildAudienceProfileCsv; - const loadAudienceProfile = options.loadAudienceProfile ?? audienceProfileClient.loadAudienceProfile; - const loadAuthorBaseInfo = options.loadAuthorBaseInfo ?? authorBaseClient.loadAuthorBaseInfo; - const loadBusinessAbility = options.loadBusinessAbility ?? businessAbilityClient.loadBusinessAbility; - const getAuthState = options.getAuthState ?? (() => readAuthState(sendRuntimeMessage)); - const mutationObserverFactory = options.mutationObserverFactory ?? ((callback) => new MutationObserver(callback)); - const promptBatchName = options.promptBatchName ?? (() => promptForBatchName(options.document)); - const promptAuthorIds = options.promptAuthorIds ?? (() => promptForAuthorIds(options.document)); - const submitBatch = options.submitBatch ?? ((payload) => readBatchSubmitAck(sendRuntimeMessage, payload)); - const audienceProfileTargets = [ - { kind: "audience", target: AUDIENCE_PROFILE_TARGETS.audience }, - { kind: "fans", target: AUDIENCE_PROFILE_TARGETS.fans }, - { - kind: "longtimeFans", - target: AUDIENCE_PROFILE_TARGETS.longtimeFans - } - ]; - let activeProgressLabel = "\u5BFC\u51FA\u4E2D"; - let shouldShowDetailedProgress = true; - const exportRangeController = createExportRangeController({ - document: options.document, - onProgress: ({ currentPage, totalPages }) => { - updateToolbarProgress(currentPage, totalPages); - }, - prepareCurrentPageForExport, - readCurrentPageRecords: () => getVisibleOrderedRecords(), - readCurrentPageRowCount: () => countCurrentPageRows(options.document), - window: options.window - }); - const silentExportController = createSilentExportController({ - document: options.document, - onProgress: ({ currentPage, totalPages }) => { - updateToolbarProgress(currentPage, totalPages); - } - }); - let activeSort; - let isDisposed = false; - let isSyncRunning = false; - let isSyncScheduled = false; - let lastKnownPageSignature = ""; - let needsResync = false; - let scheduledSyncTimeoutId = null; - const selectedAuthorIds = /* @__PURE__ */ new Set(); - let toolbar; - const observer = mutationObserverFactory(() => { - if (isDisposed) { - return; - } - let nextPageSignature = lastKnownPageSignature; - try { - nextPageSignature = readMarketPageSignature(options.document); - } catch { - return; - } - const toolbarNeedsRemount = !toolbar || !isPluginToolbarMounted(toolbar.root, options.document); - const selectionControlsMissing = !options.document.querySelector('[data-market-selection-checkbox="row"]') || !options.document.querySelector('[data-market-selection-checkbox="header"]'); - if (nextPageSignature === lastKnownPageSignature && !toolbarNeedsRemount && !selectionControlsMissing) { - return; - } - scheduleSync(); - }); - const observationRoot = options.document.body ?? options.document.documentElement; - startObserving(); - const toolbarHandlers = { - onExport: async () => { - syncSelectionStateFromDom(); - const exportTarget = readToolbarExportTarget(toolbar); - if (!exportTarget.target) { - setToolbarExportStatus(toolbar, exportTarget.error ?? "\u5BFC\u51FA\u914D\u7F6E\u65E0\u6548"); - return; - } - setToolbarBusyState(toolbar, true); - try { - const records = filterRecordsBySelection( - await exportRecords(exportTarget.target, "\u5BFC\u51FA\u4E2D", { - showDetailedProgress: selectedAuthorIds.size === 0 - }) - ); - options.onCsvReady?.(buildCsv(records)); - setToolbarExportStatus(toolbar, ""); - } catch (error) { - setToolbarExportStatus( - toolbar, - error instanceof Error ? error.message : "\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" - ); - } finally { - setToolbarBusyState(toolbar, false); - } - }, - onExportAudienceProfile: async () => { - syncSelectionStateFromDom(); - if (selectedAuthorIds.size === 0) { - setToolbarExportStatus(toolbar, "\u8BF7\u5148\u52FE\u9009\u9700\u8981\u5BFC\u51FA\u6570\u636E\u7684\u8FBE\u4EBA"); - return; - } - const exportTarget = readToolbarExportTarget(toolbar); - if (!exportTarget.target) { - setToolbarExportStatus(toolbar, exportTarget.error ?? "\u5BFC\u51FA\u914D\u7F6E\u65E0\u6548"); - return; - } - setToolbarBusyState(toolbar, true); - try { - const selectedRecords = filterRecordsBySelectionStrict( - await exportRecords(exportTarget.target, "\u753B\u50CF\u5BFC\u51FA\u4E2D", { - showDetailedProgress: false - }) - ); - if (selectedRecords.length === 0) { - setToolbarExportStatus(toolbar, "\u5F53\u524D\u5BFC\u51FA\u8303\u56F4\u5185\u6CA1\u6709\u9009\u4E2D\u7684\u8FBE\u4EBA"); - return; - } - const rows = []; - for (let index = 0; index < selectedRecords.length; index += 1) { - const record = selectedRecords[index]; - setToolbarExportStatus( - toolbar, - `\u753B\u50CF\u5BFC\u51FA\u4E2D ${index + 1}/${selectedRecords.length}...` - ); - const [profiles, businessAbility] = await Promise.all([ - loadAudienceProfileSet(record), - loadBusinessAbilitySafe(record) - ]); - rows.push({ - businessAbility, - profiles, - record - }); - } - if (rows.every( - (row) => Object.values(row.profiles).every((profile) => profile.status === "failed") - )) { - setToolbarExportStatus(toolbar, "\u753B\u50CF\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"); - return; - } - options.onCsvReady?.( - buildAudienceCsv(rows, { - selectedHeaders: readAudienceProfileSelectedHeaders() - }), - buildAudienceProfileFilename() - ); - setToolbarExportStatus(toolbar, ""); - } catch (error) { - setToolbarExportStatus( - toolbar, - error instanceof Error ? error.message : "\u753B\u50CF\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" - ); - } finally { - setToolbarBusyState(toolbar, false); - } - }, - onExportAudienceProfileByIds: async () => { - const input = await promptAuthorIds(); - if (input === null) { - return; - } - const parsed = parseAuthorIds(input); - if (parsed.ids.length === 0) { - setToolbarExportStatus(toolbar, "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u8FBE\u4EBA\u661F\u56FEID"); - return; - } - setToolbarBusyState(toolbar, true); - try { - setToolbarExportStatus( - toolbar, - `\u8BC6\u522B ${parsed.ids.length + parsed.duplicates.length + parsed.invalidTokens.length} \u4E2A\uFF0C\u53BB\u91CD\u540E ${parsed.ids.length} \u4E2A\uFF0C\u975E\u6CD5 ${parsed.invalidTokens.length} \u4E2A` - ); - const backendMetricsByAuthorId = await loadBackendMetricsMap(parsed.ids); - const rows = []; - for (let index = 0; index < parsed.ids.length; index += 1) { - const authorId = parsed.ids[index]; - setToolbarExportStatus( - toolbar, - `\u6309ID\u753B\u50CF\u5BFC\u51FA\u4E2D ${index + 1}/${parsed.ids.length}...` - ); - rows.push( - await loadAudienceProfileRowById( - authorId, - backendMetricsByAuthorId.get(authorId) - ) - ); - } - options.onCsvReady?.( - buildAudienceCsv(rows, { - selectedHeaders: readAudienceProfileSelectedHeaders() - }), - buildAudienceProfileFilename(/* @__PURE__ */ new Date(), "\u6309ID\u5BFC\u51FA") - ); - setToolbarExportStatus(toolbar, ""); - } catch (error) { - setToolbarExportStatus( - toolbar, - error instanceof Error ? error.message : "\u6309ID\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" - ); - } finally { - setToolbarBusyState(toolbar, false); - } - }, - onConfigureAudienceProfileFields: async () => { - const groups = listAudienceProfileSelectableFieldGroups(); - const selectedHeaders = readAudienceProfileSelectedHeaders(); - const nextHeaders = await promptForAudienceProfileFields( - options.document, - groups, - selectedHeaders - ); - if (nextHeaders === null) { - return; - } - saveAudienceProfileSelectedHeaders(nextHeaders); - setToolbarExportStatus( - toolbar, - `\u5B57\u6BB5\u5DF2\u4FDD\u5B58\uFF08\u5DF2\u9009 ${nextHeaders.length}/${readAudienceProfileSelectableHeaders().length} \u4E2A\u5B57\u6BB5\uFF09` - ); - }, - onSubmitBatch: async () => { - syncSelectionStateFromDom(); - const exportTarget = readToolbarExportTarget(toolbar); - if (!exportTarget.target) { - setToolbarExportStatus(toolbar, exportTarget.error ?? "\u5BFC\u51FA\u914D\u7F6E\u65E0\u6548"); - return; - } - const batchName = await promptBatchName(); - if (batchName === null) { - return; - } - if (!batchName.trim()) { - setToolbarExportStatus(toolbar, "\u8BF7\u8F93\u5165\u6279\u6B21\u540D\u79F0"); - return; - } - setToolbarBusyState(toolbar, true); - try { - const hasSelectedAuthors = selectedAuthorIds.size > 0; - const records = filterRecordsBySelection( - await exportRecords( - exportTarget.target, - hasSelectedAuthors ? "\u63D0\u4EA4\u5DF2\u9009\u8FBE\u4EBA\u4E2D" : "\u63D0\u4EA4\u4E2D", - { - showDetailedProgress: !hasSelectedAuthors - } - ) - ); - const authState = await getAuthState(); - if (!authState.isAuthenticated) { - throw new Error("\u8BF7\u5148\u767B\u5F55\u63D2\u4EF6"); - } - const payload = createBatchPayload({ - authState, - batchName, - createdAt: (/* @__PURE__ */ new Date()).toISOString(), - records - }); - await submitBatch(payload); - setToolbarExportStatus(toolbar, "\u6279\u6B21\u63D0\u4EA4\u6210\u529F"); - } catch (error) { - setToolbarExportStatus( - toolbar, - error instanceof Error ? error.message : "\u6279\u6B21\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" - ); - } finally { - setToolbarBusyState(toolbar, false); - } - } - }; - toolbar = ensurePluginToolbar(options.document, toolbarHandlers); - const ready = runSyncCycle(); - return { - dispose() { - isDisposed = true; - observer.disconnect(); - if (scheduledSyncTimeoutId !== null) { - options.window.clearTimeout(scheduledSyncTimeoutId); - scheduledSyncTimeoutId = null; - } - }, - ready - }; - async function hydrateCurrentPage() { - const table = syncMarketTable(options.document); - if (!table) { - return; - } - const pageRows = []; - for (const rowDom of table.rows) { - const rowSnapshot = readRowSnapshot(rowDom); - if (!rowSnapshot.authorId || !hasTextValue6(rowSnapshot.authorName)) { - continue; - } - pageRows.push({ - rowDom, - rowSnapshot - }); - resultStore.upsertMarketRow(rowSnapshot); - } - const pendingRateRows = []; - const rowsNeedingBackendMetrics = []; - pageRows.forEach(({ rowDom, rowSnapshot }) => { - if (rowSnapshot.hasDirectRatesSource) { - resultStore.setAuthorSuccess(rowSnapshot.authorId, rowSnapshot.rates ?? {}); - } - const existingRecord = resultStore.getRecord(rowSnapshot.authorId); - const needsRateFetch = !hasSettledRateState(existingRecord) && !hasCompleteRates(existingRecord?.rates); - const needsBackendMetrics = Boolean(searchBackendMetrics) && !hasSettledBackendMetricsState(existingRecord); - if (needsRateFetch) { - resultStore.setAuthorLoading(rowSnapshot.authorId); - pendingRateRows.push({ - rowDom, - rowSnapshot - }); - } - if (needsBackendMetrics) { - resultStore.setBackendMetricsLoading(rowSnapshot.authorId); - rowsNeedingBackendMetrics.push({ - rowDom, - rowSnapshot - }); - } - if (needsRateFetch || needsBackendMetrics) { - renderMarketRowState(rowDom, { - ...existingRecord ?? { - authorId: rowSnapshot.authorId, - authorName: rowSnapshot.authorName, - status: "idle" - }, - ...rowSnapshot, - backendMetricsStatus: needsBackendMetrics ? "loading" : existingRecord?.backendMetricsStatus, - rates: existingRecord?.rates, - status: needsRateFetch || needsBackendMetrics ? "loading" : existingRecord?.status ?? "idle" - }); - return; - } - if (existingRecord) { - renderMarketRowState(rowDom, existingRecord); - } - }); - await Promise.all([ - hydrateRatesForRows(pendingRateRows), - hydrateBackendMetricsForPage(rowsNeedingBackendMetrics) - ]); - pageRows.forEach(({ rowDom, rowSnapshot }) => { - const record = resultStore.getRecord(rowSnapshot.authorId); - if (!record) { - return; - } - renderMarketRowState(rowDom, record); - }); - } - async function hydrateRatesForRows(pageRows) { - if (pageRows.length === 0) { - return; - } - await Promise.all( - pageRows.map(async ({ rowSnapshot }) => { - const metricsResult = await loadAuthorMetrics(rowSnapshot.authorId); - if (metricsResult.success) { - resultStore.setAuthorSuccess(rowSnapshot.authorId, metricsResult.rates); - return; - } - resultStore.setAuthorFailed(rowSnapshot.authorId, metricsResult.reason); - }) - ); - } - async function hydrateBackendMetricsForPage(pageRows) { - if (!searchBackendMetrics || pageRows.length === 0) { - return; - } - try { - const rows = await searchBackendMetrics( - pageRows.map(({ rowSnapshot }) => rowSnapshot.authorId) - ); - const rowMap = new Map(rows.map((row) => [row.starId, row])); - pageRows.forEach(({ rowSnapshot }) => { - const backendMetrics = rowMap.get(rowSnapshot.authorId); - if (backendMetrics) { - resultStore.setBackendMetricsSuccess(rowSnapshot.authorId, backendMetrics); - } else { - resultStore.setBackendMetricsMissing(rowSnapshot.authorId); - } - }); - } catch { - pageRows.forEach(({ rowSnapshot }) => { - resultStore.setBackendMetricsFailed(rowSnapshot.authorId); - }); - } - } - function applyCurrentView() { - runWithoutMutationSync(() => { - toolbar = ensurePluginToolbar(options.document, toolbarHandlers); - const table = syncMarketTable(options.document); - if (!table) { - return; - } - syncPluginSortHeaders(options.document, { - activeSort, - onToggleSort: toggleSortFromHeader - }); - const records = getVisibleOrderedRecords(table); - applyRowVisibility(table, new Set(records.map((record) => record.authorId))); - applyRowOrder(table, records.map((record) => record.authorId)); - bindSelectionControls(table); - syncMarketSelectionState(table, selectedAuthorIds); - lastKnownPageSignature = readMarketPageSignature(options.document); - }); - } - function bindSelectionControls(table) { - if (!table) { - return; - } - table.rows.forEach((rowDom) => { - rowDom.selectionCheckbox.dataset.marketSelectionAuthorId = rowDom.authorId; - if (rowDom.selectionCheckbox.dataset.marketSelectionBound === "true") { - return; - } - rowDom.selectionCheckbox.dataset.marketSelectionBound = "true"; - rowDom.selectionCheckbox.addEventListener("change", () => { - if (rowDom.selectionCheckbox.checked) { - selectedAuthorIds.add(rowDom.authorId); - } else { - selectedAuthorIds.delete(rowDom.authorId); - } - refreshSelectionControls(); - }); - }); - if (!table.headerSelectionCheckbox) { - return; - } - if (table.headerSelectionCheckbox.dataset.marketSelectionBound === "true") { - return; - } - table.headerSelectionCheckbox.dataset.marketSelectionBound = "true"; - table.headerSelectionCheckbox.addEventListener("change", () => { - const currentTable = syncMarketTable(options.document); - if (!currentTable) { - return; - } - const visibleRows = currentTable.rows.filter( - (rowDom) => rowDom.visibilityTargets.some((target) => !target.hidden) - ); - const scopedRows = visibleRows.length > 0 ? visibleRows : currentTable.rows; - if (table.headerSelectionCheckbox?.checked) { - scopedRows.forEach((rowDom) => { - selectedAuthorIds.add(rowDom.authorId); - }); - } else { - scopedRows.forEach((rowDom) => { - selectedAuthorIds.delete(rowDom.authorId); - }); - } - refreshSelectionControls(); - }); - } - function refreshSelectionControls() { - const table = syncMarketTable(options.document); - if (!table) { - return; - } - bindSelectionControls(table); - syncMarketSelectionState(table, selectedAuthorIds); - } - function syncSelectionStateFromDom() { - const rowSelectionCheckboxes = Array.from( - options.document.querySelectorAll('[data-market-selection-checkbox="row"]') - ).filter( - (element) => element instanceof HTMLInputElement - ); - if (rowSelectionCheckboxes.length === 0) { - return; - } - rowSelectionCheckboxes.forEach((checkbox) => { - const authorId = checkbox.dataset.marketSelectionAuthorId?.trim(); - if (!authorId) { - return; - } - if (checkbox.checked) { - selectedAuthorIds.add(authorId); - } else { - selectedAuthorIds.delete(authorId); - } - }); - refreshSelectionControls(); - } - function toggleSortFromHeader(field) { - activeSort = getNextSortState(activeSort, field); - applyCurrentView(); - } - function getVisibleOrderedRecords(table = syncMarketTable(options.document)) { - const currentPageRecords = readCurrentPageRecords(table); - return applyFilterAndSort(currentPageRecords, { - sort: activeSort - }); - } - async function exportRecords(target, inProgressLabel = "\u5BFC\u51FA\u4E2D", progressOptions = {}) { - activeProgressLabel = inProgressLabel; - shouldShowDetailedProgress = progressOptions.showDetailedProgress ?? true; - setToolbarExportStatus(toolbar, `${inProgressLabel}...`); - if (target.mode === "count" && target.pageCount <= 1) { - await prepareCurrentPageForExport(); - return getVisibleOrderedRecords(); - } - const silentExportRecords = await silentExportController.exportRecords(target); - if (silentExportRecords) { - return hydrateExportRecords( - silentExportRecords.map((record) => ({ - ...record, - status: record.status ?? "idle" - })) - ); - } - return exportRangeController.exportRecords(target); - } - function updateToolbarProgress(currentPage, totalPages) { - if (!shouldShowDetailedProgress) { - setToolbarExportStatus(toolbar, `${activeProgressLabel}...`); - return; - } - setToolbarExportStatus( - toolbar, - totalPages ? `${activeProgressLabel} ${currentPage}/${totalPages} \u9875...` : `${activeProgressLabel} \u7B2C${currentPage}\u9875...` - ); - } - function filterRecordsBySelection(records) { - if (selectedAuthorIds.size === 0) { - return records; - } - const selectedRecords = records.filter( - (record) => selectedAuthorIds.has(record.authorId) - ); - return selectedRecords.length > 0 ? selectedRecords : records; - } - function filterRecordsBySelectionStrict(records) { - if (selectedAuthorIds.size === 0) { - return []; - } - return records.filter((record) => selectedAuthorIds.has(record.authorId)); - } - async function loadAudienceProfileSet(record) { - const profiles = {}; - for (const { kind, target } of audienceProfileTargets) { - try { - profiles[kind] = await loadAudienceProfile(record, target); - } catch (error) { - profiles[kind] = { - failureReason: error instanceof Error ? error.message : "request-failed", - status: "failed" - }; - } - } - return profiles; - } - async function loadBusinessAbilitySafe(record) { - try { - return await loadBusinessAbility(record); - } catch (error) { - return { - failureReason: error instanceof Error ? error.message : "request-failed", - status: "failed" - }; - } - } - async function loadAudienceProfileRowById(authorId, backendMetrics) { - const [baseRecord, metricsResult] = await Promise.all([ - loadAuthorBaseInfoSafe(authorId), - loadAuthorMetricsSafe(authorId) - ]); - const recordForRequests = { - ...baseRecord, - authorName: baseRecord.authorName || authorId, - ...metricsResult.success ? { rates: metricsResult.rates } : {}, - ...backendMetrics ? { backendMetrics, backendMetricsStatus: "success" } : {} - }; - const [profiles, businessAbility] = await Promise.all([ - loadAudienceProfileSet(recordForRequests), - loadBusinessAbilitySafe(recordForRequests) - ]); - const failureReasons = collectAudienceProfileRowFailures( - baseRecord, - profiles, - businessAbility - ); - const rowStatus = failureReasons.length === 0 ? "\u6210\u529F" : hasAudienceProfileRowSuccess(baseRecord, profiles, businessAbility) ? "\u90E8\u5206\u6210\u529F" : "\u5931\u8D25"; - const authorName = baseRecord.authorName || ""; - return { - businessAbility, - profiles, - record: { - ...recordForRequests, - exportFields: { - \u8FBE\u4EBAID: authorId, - \u8FBE\u4EBA\u540D\u79F0: authorName, - \u5BFC\u51FA\u72B6\u6001: rowStatus, - \u5931\u8D25\u539F\u56E0: failureReasons.join("; ") - } - } - }; - } - async function loadAuthorBaseInfoSafe(authorId) { - try { - return await loadAuthorBaseInfo(authorId); - } catch (error) { - return { - authorId, - authorName: "", - failureReason: error instanceof Error ? "request-failed" : "request-failed", - status: "failed" - }; - } - } - async function loadAuthorMetricsSafe(authorId) { - try { - return await loadAuthorMetrics(authorId); - } catch { - return { - reason: "request-failed", - success: false - }; - } - } - async function loadBackendMetricsMap(authorIds) { - const metricsMap = /* @__PURE__ */ new Map(); - if (!searchBackendMetrics || authorIds.length === 0) { - return metricsMap; - } - try { - const rows = await searchBackendMetrics(authorIds); - rows.forEach((row) => { - const { starId, ...backendMetrics } = row; - metricsMap.set(starId, backendMetrics); - }); - } catch { - return metricsMap; - } - return metricsMap; - } - function collectAudienceProfileRowFailures(baseRecord, profiles, businessAbility) { - const failures = []; - if (baseRecord.status === "failed") { - failures.push(`\u57FA\u7840\u4FE1\u606F:${baseRecord.failureReason ?? "request-failed"}`); - } - Object.entries(profiles).forEach(([kind, profile]) => { - if (profile.status === "failed") { - failures.push(`${readAudienceProfileKindLabel(kind)}:${profile.failureReason ?? "request-failed"}`); - } - }); - if (businessAbility.status === "failed") { - failures.push(`\u5546\u4E1A\u80FD\u529B:${businessAbility.failureReason ?? "request-failed"}`); - } - return failures; - } - function hasAudienceProfileRowSuccess(baseRecord, profiles, businessAbility) { - return baseRecord.status === "success" || businessAbility.status === "success" || Object.values(profiles).some((profile) => profile.status === "success"); - } - function readAudienceProfileKindLabel(kind) { - if (kind === "audience") { - return "\u89C2\u4F17\u753B\u50CF"; - } - if (kind === "fans") { - return "\u7C89\u4E1D\u753B\u50CF"; - } - return "\u94C1\u7C89\u753B\u50CF"; - } - function readAudienceProfileSelectableHeaders() { - return listAudienceProfileSelectableFieldGroups().flatMap( - (group) => group.headers - ); - } - function readAudienceProfileSelectedHeaders() { - const selectableHeaders = readAudienceProfileSelectableHeaders(); - const selectableHeaderSet = new Set(selectableHeaders); - try { - const rawValue = options.window.localStorage?.getItem( - AUDIENCE_PROFILE_FIELD_SELECTION_STORAGE_KEY - ); - if (!rawValue) { - return selectableHeaders; - } - const parsedValue = JSON.parse(rawValue); - if (!Array.isArray(parsedValue)) { - return selectableHeaders; - } - const selectedHeaders = parsedValue.filter( - (header) => typeof header === "string" && selectableHeaderSet.has(header) - ); - return selectedHeaders.length > 0 ? selectedHeaders : selectableHeaders; - } catch { - return selectableHeaders; - } - } - function saveAudienceProfileSelectedHeaders(headers) { - const selectableHeaderSet = new Set(readAudienceProfileSelectableHeaders()); - const selectedHeaders = headers.filter( - (header) => selectableHeaderSet.has(header) - ); - try { - options.window.localStorage?.setItem( - AUDIENCE_PROFILE_FIELD_SELECTION_STORAGE_KEY, - JSON.stringify(selectedHeaders) - ); - } catch { - } - } - async function prepareCurrentPageForExport() { - await runSyncCycle(); - await harvestCurrentPageForExport(); - await runSyncCycle(); - } - async function hydrateExportRecords(records) { - for (const record of records) { - resultStore.upsertMarketRow(record); - const existingRecord = resultStore.getRecord(record.authorId); - if (existingRecord?.status === "success" && existingRecord.rates) { - continue; - } - if (record.hasDirectRatesSource) { - const directRates = record.rates ?? {}; - const hasAllRates = Boolean(directRates.singleVideoAfterSearchRate) && Boolean(directRates.personalVideoAfterSearchRate); - resultStore.setAuthorSuccess(record.authorId, directRates); - if (hasAllRates) { - continue; - } - } else { - resultStore.setAuthorLoading(record.authorId); - } - const metricsResult = await loadAuthorMetrics(record.authorId); - if (metricsResult.success) { - resultStore.setAuthorSuccess(record.authorId, metricsResult.rates); - } else { - resultStore.setAuthorFailed(record.authorId, metricsResult.reason); - } - } - if (searchBackendMetrics) { - const backendTargetRecords = records.filter((record) => { - const existingRecord = resultStore.getRecord(record.authorId); - return !(existingRecord?.backendMetricsStatus === "success" || existingRecord?.backendMetricsStatus === "missing"); - }); - if (backendTargetRecords.length > 0) { - backendTargetRecords.forEach((record) => { - resultStore.setBackendMetricsLoading(record.authorId); - }); - try { - const backendRows = await searchBackendMetrics( - backendTargetRecords.map((record) => record.authorId) - ); - const backendRowMap = new Map(backendRows.map((row) => [row.starId, row])); - backendTargetRecords.forEach((record) => { - const backendMetrics = backendRowMap.get(record.authorId); - if (backendMetrics) { - resultStore.setBackendMetricsSuccess(record.authorId, backendMetrics); - } else { - resultStore.setBackendMetricsMissing(record.authorId); - } - }); - } catch { - backendTargetRecords.forEach((record) => { - resultStore.setBackendMetricsFailed(record.authorId); - }); - } - } - } - return records.map((record) => toMarketRecord(record)); - } - async function harvestCurrentPageForExport() { - let hydrationSnapshot = await collectCurrentPageSnapshotsUntilSettled(); - if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount === 0) { - return; - } - const table = syncMarketTable(options.document); - const scrollContainer = findCurrentPageScrollContainer(table); - if (!scrollContainer) { - return; - } - const originalScrollTop = scrollContainer.scrollTop; - const maxScrollTop = Math.max( - 0, - scrollContainer.scrollHeight - scrollContainer.clientHeight - ); - if (maxScrollTop <= 0) { - return; - } - const step = Math.max(scrollContainer.clientHeight, 240); - for (let nextScrollTop = Math.min(originalScrollTop + step, maxScrollTop); nextScrollTop > originalScrollTop && nextScrollTop <= maxScrollTop; nextScrollTop = Math.min(nextScrollTop + step, maxScrollTop)) { - setScrollTop(scrollContainer, nextScrollTop); - hydrationSnapshot = await collectCurrentPageSnapshotsUntilSettled(); - if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount === 0) { - break; - } - if (nextScrollTop === maxScrollTop) { - break; - } - } - if (scrollContainer.scrollTop !== originalScrollTop) { - setScrollTop(scrollContainer, originalScrollTop); - } - } - function readCurrentPageRecords(table) { - if (!table) { - return []; - } - return table.rows.map((rowDom) => { - const rowSnapshot = readRowSnapshot(rowDom); - if (!rowSnapshot.authorId || !hasTextValue6(rowSnapshot.authorName)) { - return null; - } - return toMarketRecord(rowSnapshot); - }).filter((record) => record !== null); - } - function toMarketRecord(rowSnapshot) { - const existingRecord = resultStore.getRecord(rowSnapshot.authorId); - const authorName = mergeStringValue4(existingRecord?.authorName, rowSnapshot.authorName) ?? ""; - const coreUserId = mergeStringValue4( - existingRecord?.coreUserId, - rowSnapshot.coreUserId - ); - const location2 = mergeStringValue4(existingRecord?.location, rowSnapshot.location); - const price21To60s = mergeStringValue4( - existingRecord?.price21To60s, - rowSnapshot.price21To60s - ); - return { - ...existingRecord, - ...rowSnapshot, - authorName, - backendMetrics: mergeFieldMap4( - existingRecord?.backendMetrics, - rowSnapshot.backendMetrics - ), - backendMetricsStatus: existingRecord?.backendMetricsStatus ?? "idle", - coreUserId, - exportFields: withExportFieldFallbacks( - mergeFieldMap4(existingRecord?.exportFields, rowSnapshot.exportFields), - { - authorName, - location: location2, - price21To60s - } - ), - location: location2, - price21To60s, - rates: mergeFieldMap4(existingRecord?.rates, rowSnapshot.rates), - status: existingRecord?.status ?? "idle" - }; - } - function collectCurrentPageSnapshots() { - readCurrentPageRows(options.document).forEach((rowSnapshot) => { - resultStore.upsertMarketRow(rowSnapshot); - }); - } - function findCurrentPageScrollContainer(table) { - if (!table) { - return null; - } - const candidateScores = /* @__PURE__ */ new Map(); - const candidateRoots = table.rows.map((rowDom) => rowDom.row).filter((row) => row instanceof options.window.HTMLElement); - for (const rootElement of candidateRoots) { - let currentElement = rootElement.parentElement; - let depth = 0; - while (currentElement) { - if (isScrollableContainer(currentElement)) { - const scrollRange = currentElement.scrollHeight - currentElement.clientHeight; - const existingScore = candidateScores.get(currentElement); - if (!existingScore || depth < existingScore.depth) { - candidateScores.set(currentElement, { - depth, - scrollRange - }); - } - } - depth += 1; - currentElement = currentElement.parentElement; - } - } - const rankedCandidates = Array.from(candidateScores.entries()).sort((left, right) => { - const [, leftScore] = left; - const [, rightScore] = right; - if (rightScore.scrollRange !== leftScore.scrollRange) { - return rightScore.scrollRange - leftScore.scrollRange; - } - return leftScore.depth - rightScore.depth; - }); - return rankedCandidates[0]?.[0] ?? null; - } - function isScrollableContainer(element) { - const computedStyle = options.window.getComputedStyle(element); - return /auto|scroll|overlay/.test(computedStyle.overflowY) && element.scrollHeight > element.clientHeight; - } - async function waitForDomSettled() { - await new Promise((resolve) => { - options.window.setTimeout(resolve, 0); - }); - await Promise.resolve(); - } - async function collectCurrentPageSnapshotsUntilSettled() { - let previousFingerprint = ""; - let stablePassCount = 0; - let fingerprintStableSince = 0; - let lastSnapshot = { - blankExportFieldCount: 0, - fingerprint: "", - missingDefaultFieldCount: 0 - }; - for (let attempt = 0; attempt < 16; attempt += 1) { - await waitForDomSettled(); - if (attempt > 0) { - await new Promise((resolve) => { - options.window.setTimeout( - resolve, - previousFingerprint.includes("|missing:0") ? 25 : 50 - ); - }); - await Promise.resolve(); - } - collectCurrentPageSnapshots(); - const hydrationSnapshot = readVisibleRowHydrationSnapshot(); - lastSnapshot = hydrationSnapshot; - if (!hydrationSnapshot.fingerprint) { - stablePassCount = 0; - previousFingerprint = ""; - continue; - } - if (hydrationSnapshot.fingerprint === previousFingerprint) { - stablePassCount += 1; - } else { - previousFingerprint = hydrationSnapshot.fingerprint; - stablePassCount = 1; - fingerprintStableSince = options.window.Date.now(); - } - const stableForMs = options.window.Date.now() - fingerprintStableSince; - if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount === 0 && stablePassCount >= 2) { - return hydrationSnapshot; - } - if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount > 0 && stablePassCount >= 2 && stableForMs >= 500) { - return hydrationSnapshot; - } - } - return lastSnapshot; - } - function readVisibleRowHydrationSnapshot() { - const table = syncMarketTable(options.document); - if (!table || table.rows.length === 0) { - return { - blankExportFieldCount: 0, - fingerprint: "", - missingDefaultFieldCount: 0 - }; - } - const parts = table.rows.map((rowDom) => { - const rowSnapshot = readRowSnapshot(rowDom); - const populatedFieldCount = Object.values(rowSnapshot.exportFields ?? {}).filter( - (value) => typeof value === "string" && value.trim().length > 0 - ).length; - const blankExportFieldCount = Object.values(rowSnapshot.exportFields ?? {}).filter( - (value) => typeof value !== "string" || value.trim().length === 0 - ).length; - const hasAuthorField = hasTextValue6(rowSnapshot.exportFields?.["\u8FBE\u4EBA\u4FE1\u606F"]); - const hasRepresentativeVideo = hasTextValue6( - rowSnapshot.exportFields?.["\u4EE3\u8868\u89C6\u9891"] - ); - const hasPriceField = hasTextValue6(rowSnapshot.price21To60s) || hasTextValue6(rowSnapshot.exportFields?.["21-60s\u62A5\u4EF7"]); - const missingDefaultFieldCount = Number(!hasAuthorField) + Number(!hasRepresentativeVideo) + Number(!hasPriceField); - return [ - rowSnapshot.authorId, - populatedFieldCount, - `blank:${blankExportFieldCount}`, - hasAuthorField ? "author" : "no-author", - hasRepresentativeVideo ? "video" : "no-video", - hasPriceField ? "price" : "no-price", - `missing:${missingDefaultFieldCount}` - ].join(":"); - }); - return { - blankExportFieldCount: parts.reduce((count, part) => { - const match = part.match(/:blank:(\d+):/); - return count + Number(match?.[1] ?? 0); - }, 0), - fingerprint: parts.join("|"), - missingDefaultFieldCount: parts.reduce((count, part) => { - const match = part.match(/missing:(\d+)$/); - return count + Number(match?.[1] ?? 0); - }, 0) - }; - } - function scheduleSync() { - if (isDisposed) { - return; - } - if (isSyncRunning) { - needsResync = true; - return; - } - if (isSyncScheduled) { - return; - } - isSyncScheduled = true; - scheduledSyncTimeoutId = options.window.setTimeout(() => { - scheduledSyncTimeoutId = null; - isSyncScheduled = false; - if (isDisposed) { - return; - } - void runSyncCycle(); - }, 0); - } - function runWithoutMutationSync(callback) { - if (isDisposed) { - return; - } - observer.disconnect(); - try { - callback(); - } finally { - startObserving(); - } - } - function startObserving() { - if (isDisposed || !observationRoot) { - return; - } - observer.observe(observationRoot, { - childList: true, - subtree: true - }); - } - async function runSyncCycle() { - if (isDisposed) { - return; - } - if (isSyncRunning) { - needsResync = true; - return; - } - isSyncRunning = true; - try { - toolbar = ensurePluginToolbar(options.document, toolbarHandlers); - await hydrateCurrentPage(); - applyCurrentView(); - lastKnownPageSignature = readMarketPageSignature(options.document); - } finally { - isSyncRunning = false; - if (isDisposed) { - return; - } - if (needsResync) { - needsResync = false; - scheduleSync(); - } - } - } - } - function setScrollTop(element, top) { - element.scrollTop = top; - element.dispatchEvent(new Event("scroll")); - } - function readCurrentPageRows(document2) { - const table = syncMarketTable(document2); - if (!table) { - return []; - } - return table.rows.map((rowDom) => readRowSnapshot(rowDom)).filter( - (row) => Boolean(row.authorId) && hasTextValue6(row.authorName) - ); - } - function countCurrentPageRows(document2) { - const table = syncMarketTable(document2); - if (!table) { - return 0; - } - return table.rows.filter((rowDom) => Boolean(rowDom.authorId)).length; - } - function readRowSnapshot(rowDom) { - return { - authorId: rowDom.authorId, - authorName: rowDom.authorName, - exportFields: rowDom.exportFields, - hasDirectRatesSource: rowDom.hasDirectRatesSource, - location: rowDom.location, - price21To60s: rowDom.price21To60s, - rates: rowDom.rates - }; - } - function getNextSortState(currentSort, field) { - if (!currentSort || currentSort.field !== field) { - return { - direction: "desc", - field - }; - } - if (currentSort.direction === "desc") { - return { - direction: "asc", - field - }; - } - return void 0; - } - function hasCompleteRates(rates) { - return Boolean( - rates?.singleVideoAfterSearchRate && rates?.personalVideoAfterSearchRate - ); - } - function hasSettledRateState(record) { - if (!record) { - return false; - } - return record.status === "failed" || hasCompleteRates(record.rates); - } - function hasSettledBackendMetricsState(record) { - if (!record) { - return false; - } - return record.backendMetricsStatus === "success" || record.backendMetricsStatus === "missing" || record.backendMetricsStatus === "failed"; - } - function mergeFieldMap4(current, incoming) { - if (!current && !incoming) { - return void 0; - } - const merged = { - ...current ?? {} - }; - Object.entries(incoming ?? {}).forEach(([key, value]) => { - const currentValue = merged[key]; - if (hasTextValue6(value) || !hasTextValue6(currentValue)) { - merged[key] = value; - } - }); - return merged; - } - function createRuntimeMessageSender() { - return (message) => Promise.resolve( - globalThis.chrome?.runtime?.sendMessage?.(message) - ); - } - async function readAuthState(sendMessage) { - const response = await sendMessage({ type: "auth:get-state" }); - if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") { - throw new Error("\u8BF7\u5148\u767B\u5F55\u63D2\u4EF6"); - } - return response.value; - } - async function readBatchSubmitAck(sendMessage, payload) { - const response = await sendMessage({ - payload, - type: "batch:submit" - }); - if (response && typeof response === "object" && response.ok === true) { - return response.value; - } - if (response && typeof response === "object" && response.ok === false && typeof response.error === "string") { - throw new Error(response.error); - } - throw new Error("\u6279\u6B21\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"); - } - async function readBackendMetrics(sendMessage, starIds) { - const response = await sendMessage({ - type: "backend-metrics:search", - value: { - starIds - } - }); - if (isBackendMetricsResponseMessage(response) && response.ok && response.type === "backend-metrics:result") { - return response.value.rows; - } - throw new Error("\u540E\u7AEF\u6307\u6807\u52A0\u8F7D\u5931\u8D25"); - } - function mergeStringValue4(current, incoming) { - if (hasTextValue6(incoming) || !hasTextValue6(current)) { - return incoming ?? current; - } - return current; - } - function withExportFieldFallbacks(exportFields, fallbackValues) { - if (!exportFields) { - return void 0; - } - const nextExportFields = { - ...exportFields - }; - if ("\u8FBE\u4EBA\u4FE1\u606F" in nextExportFields && !hasTextValue6(nextExportFields["\u8FBE\u4EBA\u4FE1\u606F"]) && hasTextValue6(fallbackValues.authorName)) { - nextExportFields["\u8FBE\u4EBA\u4FE1\u606F"] = fallbackValues.authorName; - } - if ("\u5730\u533A" in nextExportFields && !hasTextValue6(nextExportFields["\u5730\u533A"]) && hasTextValue6(fallbackValues.location)) { - nextExportFields["\u5730\u533A"] = fallbackValues.location; - } - if ("21-60s\u62A5\u4EF7" in nextExportFields && !hasTextValue6(nextExportFields["21-60s\u62A5\u4EF7"]) && hasTextValue6(fallbackValues.price21To60s)) { - nextExportFields["21-60s\u62A5\u4EF7"] = fallbackValues.price21To60s; - } - return nextExportFields; - } - function hasTextValue6(value) { - return typeof value === "string" && value.trim().length > 0; - } - function hasRuntimeMessageSender() { - return Boolean( - globalThis.chrome?.runtime?.sendMessage - ); - } - function buildAudienceProfileFilename(date = /* @__PURE__ */ new Date(), label) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - const hour = String(date.getHours()).padStart(2, "0"); - const minute = String(date.getMinutes()).padStart(2, "0"); - const labelPart = label ? `_${label}` : ""; - return `\u8FBE\u4EBA\u8FDE\u63A5\u7528\u6237\u753B\u50CF${labelPart}_${year}${month}${day}_${hour}${minute}.csv`; - } - - // src/content/market/auth-gate.ts - function renderMarketAuthGate(document2, currentWindow, message = "\u8BF7\u5148\u767B\u5F55\u63D2\u4EF6") { - const existingGate = document2.querySelector( - '[data-market-auth-gate="root"]' - ); - if (existingGate) { - return existingGate; - } - const root = document2.createElement("section"); - root.dataset.marketAuthGate = "root"; - root.innerHTML = ` - -

\u6253\u5F00\u6269\u5C55\u5F39\u7A97\u5B8C\u6210\u767B\u5F55\u540E\u5237\u65B0\u672C\u9875

- - `; - const title = root.querySelector("strong"); - if (title) { - title.textContent = message; - } - root.querySelector('[data-market-auth-help="button"]')?.addEventListener("click", () => { - currentWindow.alert("\u8BF7\u70B9\u51FB\u6D4F\u89C8\u5668\u5DE5\u5177\u680F\u4E2D\u7684\u6269\u5C55\u56FE\u6807\u5B8C\u6210\u767B\u5F55"); - }); - document2.body.prepend(root); - return root; - } - - // src/content/index.ts - var DOWNLOAD_MARKET_CSV_MESSAGE = "download-market-csv"; - async function bootContentScript(options = {}) { - const currentWindow = options.window ?? window; - const currentDocument = options.document ?? document; - const controllerFactory = options.createMarketController ?? createMarketController; - const sendAuthMessage = options.sendAuthMessage ?? createRuntimeMessageSender2(); - if (!isMarketPage(currentWindow.location.href)) { - return null; - } - installMarketPageBridge(currentDocument); - const authState = await readAuthState2(sendAuthMessage); - if (!authState?.isAuthenticated) { - await waitForBodyReady(currentDocument, currentWindow); - renderMarketAuthGate( - currentDocument, - currentWindow, - isExpiredAuthState(authState) ? "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" : void 0 - ); - return { - ready: Promise.resolve() - }; - } - await waitForBodyReady(currentDocument, currentWindow); - return controllerFactory({ - document: currentDocument, - onCsvReady: (csv, filename) => { - if (filename) { - downloadCsv(currentDocument, currentWindow, csv, filename); - return; - } - if (requestCsvDownload(csv)) { - return; - } - downloadCsv(currentDocument, currentWindow, csv, filename); - }, - window: currentWindow - }); - } - async function readAuthState2(sendMessage) { - const response = await sendMessage({ type: "auth:get-state" }); - if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") { - return null; - } - return response.value; - } - function isMarketPage(url) { - const parsedUrl = new URL(url); - const isXingtuHost = parsedUrl.hostname === "xingtu.cn" || parsedUrl.hostname.endsWith(".xingtu.cn"); - return isXingtuHost && parsedUrl.pathname.startsWith("/ad/creator/market"); - } - function bootstrapContentScript() { - const runtime = globalThis.chrome?.runtime; - if (!runtime || typeof window === "undefined" || typeof document === "undefined") { - return; - } - const marker = "__starChartSearchEnhancerContentController"; - const scopedWindow = window; - if (scopedWindow[marker]) { - return; - } - scopedWindow[marker] = true; - void bootContentScript().then((controller) => { - scopedWindow[marker] = controller; - }); - } - bootstrapContentScript(); - function requestCsvDownload(csv, filename) { - const runtime = globalThis.chrome?.runtime; - if (!runtime?.id || typeof runtime.sendMessage !== "function") { - return false; - } - runtime.sendMessage({ - csv, - filename: filename ?? `star-chart-search-enhancer-${formatTimestampForFilename()}.csv`, - type: DOWNLOAD_MARKET_CSV_MESSAGE - }); - return true; - } - function createRuntimeMessageSender2() { - return async (message) => { - const runtime = globalThis.chrome?.runtime; - if (typeof runtime?.sendMessage !== "function") { - return null; - } - return runtime.sendMessage(message); - }; - } - async function waitForBodyReady(document2, currentWindow) { - if (document2.body) { - return; - } - await new Promise((resolve) => { - const handleReady = () => { - if (document2.body) { - document2.removeEventListener("DOMContentLoaded", handleReady); - resolve(); - } - }; - document2.addEventListener("DOMContentLoaded", handleReady); - currentWindow.setTimeout(handleReady, 0); - }); - } - function downloadCsv(document2, window2, csv, filename) { - const blob = new Blob(["\uFEFF", csv], { - type: "text/csv;charset=utf-8" - }); - const objectUrl = window2.URL.createObjectURL(blob); - const link = document2.createElement("a"); - link.href = objectUrl; - link.download = filename ?? `star-chart-search-enhancer-${formatTimestampForFilename()}.csv`; - document2.body.appendChild(link); - link.click(); - link.remove(); - window2.URL.revokeObjectURL(objectUrl); - } - function formatTimestampForFilename() { - return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"); - } - function isExpiredAuthState(authState) { - const lastError = authState?.lastError; - return typeof lastError === "string" && (/token/i.test(lastError) || lastError.includes("\u8FC7\u671F")); - } - function installMarketPageBridge(document2) { - if (document2.documentElement.querySelector( - '[data-sces-market-bridge="script"]' - )) { - return; - } - const script = document2.createElement("script"); - script.dataset.scesMarketBridge = "script"; - const runtime = globalThis.chrome?.runtime; - const bridgeUrl = runtime?.getURL?.("content/market-page-bridge.js"); - if (bridgeUrl) { - script.src = bridgeUrl; - } else { - script.textContent = ""; - } - (document2.head ?? document2.documentElement).appendChild(script); - } -})(); diff --git a/dist-release/content/market-page-bridge.js b/dist-release/content/market-page-bridge.js deleted file mode 100644 index f8799fe..0000000 --- a/dist-release/content/market-page-bridge.js +++ /dev/null @@ -1,585 +0,0 @@ -"use strict"; -(() => { - // src/shared/rate-normalizer.ts - function normalizeFractionRateDisplay(value) { - const numericValue = Number(value); - if (!Number.isFinite(numericValue)) { - return null; - } - const percentageValue = numericValue * 100; - return `${trimTrailingZeros(percentageValue.toFixed(6))}%`; - } - function trimTrailingZeros(value) { - return value.replace(/\.?0+$/, ""); - } - - // src/content/market/market-list-request-snapshot.ts - var MARKET_REQUEST_SNAPSHOT_ATTRIBUTE = "data-sces-market-request-snapshot"; - function writeMarketListRequestSnapshot(document2, snapshot) { - document2.documentElement.setAttribute( - MARKET_REQUEST_SNAPSHOT_ATTRIBUTE, - JSON.stringify(snapshot) - ); - } - - // src/content/market/market-list-row.ts - var PAGE_NUMBER_KEYS = [ - "currentPage", - "page", - "pageNo", - "pageNum", - "page_no", - "page_num" - ]; - var PAGE_SIZE_KEYS = [ - "limit", - "pageSize", - "page_size", - "size" - ]; - var TOTAL_COUNT_KEYS = [ - "total", - "totalCount", - "total_count" - ]; - var TOTAL_PAGE_KEYS = [ - "pageCount", - "page_count", - "totalPage", - "totalPages", - "total_page", - "total_pages" - ]; - function mapMarketListRow(row) { - const attributeDatas = readMarketAttributeDatas(row); - const singleVideoAfterSearchRate = normalizeMarketListRate( - readMarketFieldValue(row, attributeDatas, "avg_search_after_view_rate_30d") - ); - return { - authorId: readString(readMarketFieldValue(row, attributeDatas, "star_id")) ?? readString(readMarketFieldValue(row, attributeDatas, "id")) ?? "", - authorName: readString(readMarketFieldValue(row, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(row, attributeDatas, "nick_name")) ?? "", - coreUserId: readString(readMarketFieldValue(row, attributeDatas, "core_user_id")) ?? void 0, - exportFields: buildMarketExportFieldFallbacks(row, attributeDatas), - hasDirectRatesSource: true, - location: readMarketLocation(row, attributeDatas), - price21To60s: readMarketPrice21To60s(row, attributeDatas), - rates: singleVideoAfterSearchRate ? { - singleVideoAfterSearchRate - } : void 0 - }; - } - function parseMarketListResponse(payload) { - const container = findMarketListContainer(payload); - if (!container) { - return null; - } - const marketList = readMarketListArray(container); - if (!marketList) { - return null; - } - return { - currentPage: readKnownNumberDeep(container, PAGE_NUMBER_KEYS) ?? void 0, - pageSize: readKnownNumberDeep(container, PAGE_SIZE_KEYS) ?? void 0, - records: marketList.map((row) => isRecord(row) ? mapMarketListRow(row) : null).filter( - (row) => row !== null && Boolean(row.authorId || row.authorName) - ), - totalCount: readKnownNumberDeep(container, TOTAL_COUNT_KEYS) ?? void 0, - totalPages: readKnownNumberDeep(container, TOTAL_PAGE_KEYS) ?? void 0 - }; - } - function findMarketListContainer(value) { - const queue = [value]; - while (queue.length > 0) { - const current = queue.shift(); - if (!isRecord(current)) { - continue; - } - if (readMarketListArray(current)) { - return current; - } - Object.values(current).forEach((entry) => { - queue.push(unwrapVueRef(entry)); - }); - } - return null; - } - function readMarketListArray(record) { - const marketList = unwrapVueRef(record.marketList); - if (Array.isArray(marketList)) { - return marketList; - } - const authors = unwrapVueRef(record.authors); - if (Array.isArray(authors)) { - return authors; - } - return null; - } - function unwrapVueRef(value) { - if (isRecord(value) && "value" in value) { - return value.value; - } - return value; - } - function isRecord(value) { - return typeof value === "object" && value !== null; - } - function readMarketAttributeDatas(record) { - return isRecord(record.attribute_datas) ? record.attribute_datas : {}; - } - function readMarketFieldValue(record, attributeDatas, field) { - return record[field] ?? attributeDatas[field]; - } - function readString(value) { - return typeof value === "string" ? value : null; - } - function normalizeMarketListRate(value) { - if (typeof value === "number") { - return normalizeFractionRateDisplay(String(value)); - } - return typeof value === "string" ? normalizeFractionRateDisplay(value) : null; - } - function normalizeExportCellText(value) { - return value?.replace(/\s+/g, " ").trim() ?? ""; - } - function buildMarketExportFieldFallbacks(record, attributeDatas) { - const exportFields = {}; - const authorInfo = buildMarketAuthorInfo(record, attributeDatas); - const authorType = buildMarketAuthorType(record, attributeDatas); - const contentTheme = buildMarketContentTheme(record, attributeDatas); - const connectedUsers = formatWanValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "link_link_cnt_by_industry")) - ); - const followerCount = formatWanValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "follower")) - ); - const expectedCpm = formatDecimalDisplay( - readNumericValue(readMarketFieldValue(record, attributeDatas, "prospective_20_60_cpm")) - ); - const expectedPlayCount = formatWanValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "expected_play_num")) - ); - const interactionRate = formatFractionPercent( - readNumericValue(readMarketFieldValue(record, attributeDatas, "interact_rate_within_30d")) - ); - const finishRate = formatFractionPercent( - readNumericValue(readMarketFieldValue(record, attributeDatas, "play_over_rate_within_30d")) - ); - const burstRate = readBurstRateDisplay( - readNumericValue(readMarketFieldValue(record, attributeDatas, "burst_text_rate")) - ); - const price21To60s = readMarketPrice21To60s(record, attributeDatas); - const representativeVideo = readMarketRepresentativeVideo(record, attributeDatas); - assignExportField(exportFields, "\u8FBE\u4EBA\u4FE1\u606F", authorInfo); - assignExportField(exportFields, "\u4EE3\u8868\u89C6\u9891", representativeVideo); - assignExportField(exportFields, "\u8FBE\u4EBA\u7C7B\u578B", authorType); - assignExportField(exportFields, "\u5185\u5BB9\u4E3B\u9898", contentTheme); - assignExportField(exportFields, "\u8FDE\u63A5\u7528\u6237\u6570", connectedUsers); - assignExportField(exportFields, "\u7C89\u4E1D\u6570", followerCount); - assignExportField(exportFields, "\u9884\u671FCPM", expectedCpm); - assignExportField(exportFields, "\u9884\u671F\u64AD\u653E\u91CF", expectedPlayCount); - assignExportField(exportFields, "\u4E92\u52A8\u7387", interactionRate); - assignExportField(exportFields, "\u5B8C\u64AD\u7387", finishRate); - assignExportField(exportFields, "\u7206\u6587\u7387", burstRate); - assignExportField(exportFields, "21-60s\u62A5\u4EF7", price21To60s); - return Object.keys(exportFields).length > 0 ? exportFields : void 0; - } - function assignExportField(exportFields, key, value) { - if (hasTextValue(value)) { - exportFields[key] = value; - } - } - function hasTextValue(value) { - return Boolean(value && value.trim().length > 0); - } - function buildMarketAuthorInfo(record, attributeDatas) { - const nickname = readString(readMarketFieldValue(record, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(record, attributeDatas, "nick_name")) ?? ""; - const parts = [ - nickname, - readMarketGenderLabel(readMarketFieldValue(record, attributeDatas, "gender")), - readString(readMarketFieldValue(record, attributeDatas, "city")) ?? "" - ].filter((value) => Boolean(value)); - return parts.length > 0 ? parts.join(" ") : void 0; - } - function buildMarketAuthorType(record, attributeDatas) { - const tagsRelation = readRecordLike( - readMarketFieldValue(record, attributeDatas, "tags_relation") - ); - if (tagsRelation) { - const primaryTag = Object.keys(tagsRelation)[0]; - if (hasTextValue(primaryTag)) { - return primaryTag; - } - } - return void 0; - } - function buildMarketContentTheme(record, attributeDatas) { - const themes = readStringArray( - readMarketFieldValue(record, attributeDatas, "content_theme_labels_180d") - ); - if (themes.length === 0) { - return void 0; - } - if (themes.length <= 2) { - return themes.join(" "); - } - return `${themes.slice(0, 2).join(" ")} ${themes.length - 2}+`; - } - function readMarketLocation(record, attributeDatas) { - return readString(readMarketFieldValue(record, attributeDatas, "city")) ?? void 0; - } - function readMarketPrice21To60s(record, attributeDatas) { - return formatCurrencyValue( - readNumericValue(readMarketFieldValue(record, attributeDatas, "price_20_60")) - ); - } - function readMarketRepresentativeVideo(record, attributeDatas) { - const items = readArrayLike(readMarketFieldValue(record, attributeDatas, "items")); - for (const item of items) { - if (!isRecord(item)) { - continue; - } - const title = readString(item.title); - if (hasTextValue(title)) { - return normalizeExportCellText(title); - } - } - return void 0; - } - function readMarketGenderLabel(value) { - const rawValue = typeof value === "number" ? String(value) : readString(value); - if (rawValue === "1") { - return "\u7537"; - } - if (rawValue === "2") { - return "\u5973"; - } - return void 0; - } - function readBurstRateDisplay(value) { - if (value === null) { - return void 0; - } - if (value <= 0) { - return "-"; - } - return formatFractionPercent(value); - } - function formatCurrencyValue(value) { - if (value === null) { - return void 0; - } - return `\xA5${value.toLocaleString("en-US", { - maximumFractionDigits: 0 - })}`; - } - function formatWanValue(value) { - if (value === null) { - return void 0; - } - return `${formatDecimalWithGrouping(value / 1e4)}w`; - } - function formatFractionPercent(value) { - if (value === null) { - return void 0; - } - return `${formatDecimalDisplay(value * 100)}%`; - } - function formatDecimalDisplay(value) { - if (value === null) { - return void 0; - } - return value.toLocaleString("en-US", { - maximumFractionDigits: 1, - minimumFractionDigits: 0, - useGrouping: false - }); - } - function formatDecimalWithGrouping(value) { - return value.toLocaleString("en-US", { - maximumFractionDigits: 1, - minimumFractionDigits: 0 - }); - } - function readNumericValue(value) { - if (typeof value === "number" && Number.isFinite(value)) { - return value; - } - if (typeof value === "string") { - const trimmedValue = value.trim(); - if (!trimmedValue) { - return null; - } - const parsedValue = Number(trimmedValue); - return Number.isFinite(parsedValue) ? parsedValue : null; - } - return null; - } - function readStringArray(value) { - if (Array.isArray(value)) { - return value.filter((item) => typeof item === "string"); - } - if (typeof value === "string") { - try { - const parsedValue = JSON.parse(value); - return Array.isArray(parsedValue) ? parsedValue.filter((item) => typeof item === "string") : []; - } catch { - return []; - } - } - return []; - } - function readArrayLike(value) { - if (Array.isArray(value)) { - return value; - } - if (typeof value === "string") { - try { - const parsedValue = JSON.parse(value); - return Array.isArray(parsedValue) ? parsedValue : []; - } catch { - return []; - } - } - return []; - } - function readRecordLike(value) { - if (isRecord(value)) { - return value; - } - if (typeof value === "string") { - try { - const parsedValue = JSON.parse(value); - return isRecord(parsedValue) ? parsedValue : null; - } catch { - return null; - } - } - return null; - } - function readKnownNumber(record, keys) { - for (const key of keys) { - const value = readNumericValue(record[key]); - if (value !== null) { - return value; - } - } - return void 0; - } - function readKnownNumberDeep(value, keys) { - if (!isRecord(value)) { - return null; - } - const directValue = readKnownNumber(value, keys); - if (typeof directValue === "number") { - return directValue; - } - for (const nestedValue of Object.values(value)) { - const candidate = readKnownNumberDeep(unwrapVueRef(nestedValue), keys); - if (typeof candidate === "number") { - return candidate; - } - } - return null; - } - - // src/content/market/page-bridge.ts - var BRIDGE_MARKER = "__SCES_MARKET_PAGE_BRIDGE_INSTALLED__"; - var MARKET_SEARCH_REQUEST_PATH = "/gw/api/gsearch/search_for_author_square"; - var SERIALIZED_MARKET_ROWS_ATTRIBUTE = "data-sces-market-rows"; - installMarketPageBridge(); - function installMarketPageBridge() { - if (window[BRIDGE_MARKER]) { - syncSerializedMarketRows(); - return; - } - window[BRIDGE_MARKER] = true; - installMarketRequestSnapshotBridge(); - syncSerializedMarketRows(); - const observer = new MutationObserver(() => { - syncSerializedMarketRows(); - }); - observer.observe(document.documentElement, { - childList: true, - subtree: true - }); - window.setInterval(() => { - syncSerializedMarketRows(); - }, 1e3); - } - function installMarketRequestSnapshotBridge() { - installFetchSnapshotBridge(); - installXmlHttpRequestSnapshotBridge(); - } - function syncSerializedMarketRows() { - if (typeof document === "undefined") { - return; - } - const nextSerializedRows = JSON.stringify(readSerializedMarketRows()); - if (document.documentElement.getAttribute(SERIALIZED_MARKET_ROWS_ATTRIBUTE) !== nextSerializedRows) { - document.documentElement.setAttribute( - SERIALIZED_MARKET_ROWS_ATTRIBUTE, - nextSerializedRows - ); - } - } - function installFetchSnapshotBridge() { - if (typeof window.fetch !== "function") { - return; - } - const originalFetch = window.fetch.bind(window); - window.fetch = async (input, init) => { - const requestSnapshot = readFetchSnapshot(input, init); - const response = await originalFetch(input, init); - if (requestSnapshot) { - const clonedResponse = response.clone(); - void captureMarketSnapshotFromResponse( - requestSnapshot, - () => clonedResponse.json() - ); - } - return response; - }; - } - function installXmlHttpRequestSnapshotBridge() { - const OriginalXmlHttpRequest = window.XMLHttpRequest; - if (!OriginalXmlHttpRequest) { - return; - } - const originalOpen = OriginalXmlHttpRequest.prototype.open; - const originalSend = OriginalXmlHttpRequest.prototype.send; - const originalSetRequestHeader = OriginalXmlHttpRequest.prototype.setRequestHeader; - OriginalXmlHttpRequest.prototype.open = function(method, url, ...rest) { - this.__scesMarketSnapshot = { - headers: {}, - method, - url: String(url) - }; - return originalOpen.call(this, method, url, ...rest); - }; - OriginalXmlHttpRequest.prototype.setRequestHeader = function(name, value) { - this.__scesMarketSnapshot?.headers && (this.__scesMarketSnapshot.headers[name] = value); - return originalSetRequestHeader.call(this, name, value); - }; - OriginalXmlHttpRequest.prototype.send = function(body) { - const snapshotState = this.__scesMarketSnapshot; - if (snapshotState) { - snapshotState.body = typeof body === "string" ? body : void 0; - this.addEventListener("load", () => { - if (this.status < 200 || this.status >= 300 || typeof this.responseText !== "string") { - return; - } - void captureMarketSnapshotFromResponse( - snapshotState, - async () => JSON.parse(this.responseText) - ); - }); - } - return originalSend.call(this, body); - }; - } - async function captureMarketSnapshotFromResponse(snapshot, readPayload) { - if (!isMarketSearchRequest(snapshot.url)) { - return; - } - try { - const payload = await readPayload(); - if (!parseMarketListResponse(payload)) { - return; - } - writeMarketListRequestSnapshot(document, { - body: snapshot.body, - headers: snapshot.headers, - method: snapshot.method, - url: snapshot.url - }); - } catch { - } - } - function readSerializedMarketRows() { - const marketList = readMarketList(); - return marketList.map((row) => { - const attributeDatas = isRecord2(row.attribute_datas) ? row.attribute_datas : {}; - const singleVideoAfterSearchRate = readNormalizedFractionRate( - attributeDatas.avg_search_after_view_rate_30d - ); - return { - authorId: readString2(row.star_id) ?? readString2(attributeDatas.id) ?? "", - authorName: readString2(attributeDatas.nickname) ?? readString2(row.nick_name) ?? "", - coreUserId: readString2(attributeDatas.core_user_id) ?? void 0, - singleVideoAfterSearchRate - }; - }).filter((row) => Boolean(row.authorId || row.authorName)); - } - function readFetchSnapshot(input, init) { - const request = input instanceof Request ? input : null; - const method = init?.method ?? request?.method ?? "GET"; - const url = request?.url ?? String(input); - const body = typeof init?.body === "string" ? init.body : typeof request?.bodyUsed === "boolean" && request.bodyUsed ? void 0 : void 0; - const headers = serializeHeaders(init?.headers ?? request?.headers); - return { - body, - headers, - method, - url - }; - } - function serializeHeaders(headers) { - if (!headers) { - return void 0; - } - if (headers instanceof Headers) { - return Object.fromEntries(headers.entries()); - } - if (Array.isArray(headers)) { - return Object.fromEntries(headers); - } - return Object.fromEntries( - Object.entries(headers).map(([key, value]) => [key, String(value)]) - ); - } - function readMarketList() { - if (typeof document === "undefined") { - return []; - } - const marketRoot = document.querySelector(".base-author-list"); - const setupState = marketRoot?.__vue__?._setupState; - if (!setupState) { - return []; - } - for (const value of Object.values(setupState)) { - const candidate = unwrapVueRef2(value); - if (Array.isArray(candidate) && looksLikeMarketList(candidate)) { - return candidate; - } - if (!isRecord2(candidate) || !Array.isArray(candidate.marketList)) { - continue; - } - if (looksLikeMarketList(candidate.marketList)) { - return candidate.marketList; - } - } - return []; - } - function isMarketSearchRequest(url) { - return url === MARKET_SEARCH_REQUEST_PATH || url.startsWith(`${MARKET_SEARCH_REQUEST_PATH}?`) || url.includes(`${MARKET_SEARCH_REQUEST_PATH}?`) || url.endsWith(MARKET_SEARCH_REQUEST_PATH); - } - function looksLikeMarketList(value) { - const firstRow = value[0]; - return isRecord2(firstRow) && ("star_id" in firstRow || "attribute_datas" in firstRow); - } - function unwrapVueRef2(value) { - if (isRecord2(value) && "value" in value) { - return value.value; - } - return value; - } - function isRecord2(value) { - return typeof value === "object" && value !== null; - } - function readString2(value) { - return typeof value === "string" ? value : null; - } - function readNormalizedFractionRate(value) { - return typeof value === "string" ? normalizeFractionRateDisplay(value) ?? void 0 : void 0; - } -})(); diff --git a/dist-release/manifest.json b/dist-release/manifest.json deleted file mode 100644 index ef39e2b..0000000 --- a/dist-release/manifest.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "action": { - "default_icon": { - "16": "assets/icons/icon-16.png", - "32": "assets/icons/icon-32.png" - }, - "default_popup": "popup/index.html" - }, - "background": { - "service_worker": "background/index.js" - }, - "content_scripts": [ - { - "js": [ - "content/index.js" - ], - "matches": [ - "https://xingtu.cn/ad/creator/market*", - "https://*.xingtu.cn/ad/creator/market*" - ], - "run_at": "document_start" - } - ], - "description": "Bootstraps the Xingtu creator market content script.", - "icons": { - "16": "assets/icons/icon-16.png", - "32": "assets/icons/icon-32.png", - "48": "assets/icons/icon-48.png", - "128": "assets/icons/icon-128.png" - }, - "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0CaZJxcX97TbRXCR08L10t9EZFV31+wPnUgDf21j2f0qYaWdblzWXfVkeU9jGb2Hr2Etpp7F/XuBa6pcipUXkzMMBkJ42KOkciAwbuzTBoAtGB8o9aoWigtax+gGfSz+T3BjqxKBJtXqeqbIAKCDIlxRKIrY+KcY1Z+mD5BKcBHKsUDQPlHsrjc1g0wIBD5doz9LoOk1Wso6gK5cSeOp9lw5YHcu4TImR4yqxGiL6pZwnpciuX/g7qjWBZXn5gf0YBlDsBDDTt5upbP3NguUKgO2qA9M77LyeUwXl3aqbIxYi/VwsQ2t5w9PGWtnOUQQDWUcEg/9dfTb89esZXKATwIDAQAB", - "manifest_version": 3, - "name": "Star Chart Search Enhancer", - "permissions": [ - "downloads", - "identity", - "storage" - ], - "web_accessible_resources": [ - { - "matches": [ - "https://xingtu.cn/*", - "https://*.xingtu.cn/*" - ], - "resources": [ - "content/market-page-bridge.js" - ] - } - ], - "version": "0.0525.6", - "host_permissions": [ - "https://xingtu.cn/ad/creator/market*", - "https://*.xingtu.cn/ad/creator/market*", - "https://login-api.intelligrow.cn/*", - "https://talent-search.intelligrow.cn/*", - "http://192.168.31.21:8083/*", - "https://*/*" - ] -} diff --git a/dist-release/popup/index.html b/dist-release/popup/index.html deleted file mode 100644 index 6e17f31..0000000 --- a/dist-release/popup/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Star Chart Search Enhancer - - -
- - - diff --git a/dist-release/popup/index.js b/dist-release/popup/index.js deleted file mode 100644 index 49af92e..0000000 --- a/dist-release/popup/index.js +++ /dev/null @@ -1,422 +0,0 @@ -"use strict"; -(() => { - // src/popup/view.ts - function renderLoggedOut(root, error) { - root.innerHTML = ` -
-

Star Chart Search Enhancer

-

\u767B\u5F55\u540E\u624D\u80FD\u4F7F\u7528\u661F\u56FE\u589E\u5F3A\u529F\u80FD

- ${error ? `

${error}

` : ""} - -
- `; - } - function renderLoggedIn(root, authState) { - const userInfo = authState.userInfo; - root.innerHTML = ` -
-

Star Chart Search Enhancer

-

\u5DF2\u767B\u5F55

-

${userInfo?.name ?? userInfo?.username ?? "\u672A\u77E5\u7528\u6237"}

-

${userInfo?.email ?? ""}

-
-

\u7248\u672C\u66F4\u65B0

-

\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...

-
- -
- `; - } - function renderUpdateStatus(root, options) { - const container = root.querySelector('[data-popup-update="root"]'); - if (!container) { - return; - } - if (options.status === "checking") { - container.innerHTML = ` -

\u7248\u672C\u66F4\u65B0

-

\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}

-

\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...

- `; - return; - } - if (options.status === "error") { - container.innerHTML = ` -

\u7248\u672C\u66F4\u65B0

-

\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}

-

\u6682\u65F6\u65E0\u6CD5\u68C0\u67E5\u66F4\u65B0

-

\u5982\u679C\u9700\u8981\u65B0\u7248\uFF0C\u8BF7\u8054\u7CFB\u7EF4\u62A4\u540C\u4E8B\u83B7\u53D6\u66F4\u65B0\u5305\u3002

- `; - return; - } - if (options.status === "latest" || !options.manifest) { - container.innerHTML = ` -

\u7248\u672C\u66F4\u65B0

-

\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}

-

\u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C

- `; - return; - } - container.innerHTML = ` -

\u7248\u672C\u66F4\u65B0

-

\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}

-

\u53D1\u73B0\u65B0\u7248\u672C\uFF1A${options.manifest.latestVersion}

- ${renderReleaseNotes(options.manifest.releaseNotes)} - - -

\u4E0B\u8F7D\u540E\u8BF7\u89E3\u538B\u65B0\u7248 zip\uFF0C\u5E76\u5728 chrome://extensions \u91CC\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u3002

- `; - } - function setUpdateDownloadStatus(root, value) { - const output = root.querySelector('[data-popup-update-download-status="text"]'); - if (!output) { - return; - } - output.textContent = value; - } - function renderReleaseNotes(releaseNotes) { - if (releaseNotes.length === 0) { - return ""; - } - return ` - - `; - } - function escapeHtml(value) { - return value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); - } - function renderDevPanel(root, authState) { - const panel = root.ownerDocument.createElement("section"); - panel.dataset.popupDevPanel = "root"; - panel.innerHTML = ` -

dev auth panel

-

resource: ${authState.resource ?? ""}

-

scopes: ${(authState.scopes ?? []).join(", ")}

-

token: ${authState.tokenAvailable ? "available" : "missing"}

-

expires: ${authState.accessTokenExpiresAt ?? "unknown"}

-

error: ${authState.lastError ?? ""}

- -

-  `;
-    root.appendChild(panel);
-  }
-  function setProtectedApiResult(root, value) {
-    const output = root.querySelector(
-      '[data-popup-protected-api-result="output"]'
-    );
-    if (!output) {
-      return;
-    }
-    output.textContent = value;
-  }
-
-  // src/shared/auth-config.ts
-  var defaultAuthConfig = {
-    apiResource: "https://talent-search.intelligrow.cn",
-    appId: "i4jkllbvih0554r4n0fd3",
-    enableDevAuthPanel: false,
-    logtoEndpoint: "https://login-api.intelligrow.cn",
-    scopes: ["openid", "profile", "offline_access", "talent-search:read"]
-  };
-  function readAuthConfig(overrides = {}) {
-    const nextConfig = {
-      ...defaultAuthConfig,
-      ...overrides
-    };
-    if (!nextConfig.logtoEndpoint.trim()) {
-      throw new Error("auth config logtoEndpoint is required");
-    }
-    if (!nextConfig.appId.trim()) {
-      throw new Error("auth config appId is required");
-    }
-    if (!nextConfig.apiResource.trim()) {
-      throw new Error("auth config apiResource is required");
-    }
-    return nextConfig;
-  }
-
-  // src/shared/auth-messages.ts
-  function isAuthResponseMessage(value) {
-    if (!value || typeof value !== "object") {
-      return false;
-    }
-    const candidate = value;
-    if (candidate.ok === false) {
-      return candidate.type === "auth:error" && typeof candidate.error === "string";
-    }
-    if (candidate.ok !== true || typeof candidate.type !== "string") {
-      return false;
-    }
-    if (candidate.type === "auth:ack") {
-      return true;
-    }
-    if (candidate.type === "auth:token") {
-      return Boolean(
-        candidate.value && typeof candidate.value === "object" && typeof candidate.value.accessToken === "string"
-      );
-    }
-    if (candidate.type === "auth:state") {
-      return Boolean(
-        candidate.value && typeof candidate.value === "object" && typeof candidate.value.isAuthenticated === "boolean"
-      );
-    }
-    return false;
-  }
-
-  // src/shared/protected-api-client.ts
-  function createProtectedApiClient(options) {
-    const fetchImpl = options.fetchImpl ?? fetch;
-    return {
-      async loadProtectedMockData() {
-        const token = await readAccessToken(options.sendMessage);
-        const response = await fetchImpl(
-          new URL("/api/mock/protected", options.baseUrl).toString(),
-          {
-            headers: {
-              Authorization: `Bearer ${token}`
-            },
-            method: "GET"
-          }
-        );
-        if (response.status === 401 || response.status === 403) {
-          throw new Error("protected api unauthorized");
-        }
-        if (!response.ok) {
-          throw new Error(`protected api request failed: ${response.status}`);
-        }
-        return response.json();
-      }
-    };
-  }
-  async function readAccessToken(sendMessage) {
-    const response = await sendMessage({ type: "auth:get-access-token" });
-    if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:token" || !response.value.accessToken.trim()) {
-      throw new Error("protected api token unavailable");
-    }
-    return response.value.accessToken;
-  }
-
-  // src/shared/update-check.ts
-  function compareExtensionVersions(left, right) {
-    const leftParts = parseVersionParts(left);
-    const rightParts = parseVersionParts(right);
-    const maxLength = Math.max(leftParts.length, rightParts.length);
-    for (let index = 0; index < maxLength; index += 1) {
-      const leftValue = leftParts[index] ?? 0;
-      const rightValue = rightParts[index] ?? 0;
-      if (leftValue !== rightValue) {
-        return leftValue - rightValue;
-      }
-    }
-    return 0;
-  }
-  function parseUpdateManifest(value) {
-    if (!value || typeof value !== "object") {
-      return null;
-    }
-    const candidate = value;
-    if (!isVersionString(candidate.latestVersion) || !isVersionString(candidate.minSupportedVersion) || !isHttpsUrl(candidate.zipUrl) || !isHttpsUrl(candidate.guideUrl) || typeof candidate.publishedAt !== "string" || !Array.isArray(candidate.releaseNotes) || !candidate.releaseNotes.every((note) => typeof note === "string")) {
-      return null;
-    }
-    return {
-      guideUrl: candidate.guideUrl,
-      latestVersion: candidate.latestVersion,
-      minSupportedVersion: candidate.minSupportedVersion,
-      publishedAt: candidate.publishedAt,
-      releaseNotes: candidate.releaseNotes,
-      zipUrl: candidate.zipUrl
-    };
-  }
-  async function fetchUpdateManifest(manifestUrl, fetchImpl = fetch) {
-    const response = await fetchImpl(manifestUrl, {
-      cache: "no-store"
-    });
-    if (!response.ok) {
-      throw new Error(`update manifest request failed: ${response.status}`);
-    }
-    const manifest = parseUpdateManifest(await response.json());
-    if (!manifest) {
-      throw new Error("update manifest is invalid");
-    }
-    return manifest;
-  }
-  function parseVersionParts(value) {
-    return value.split(".").map((part) => {
-      const parsed = Number.parseInt(part, 10);
-      return Number.isFinite(parsed) ? parsed : 0;
-    });
-  }
-  function isVersionString(value) {
-    return typeof value === "string" && /^\d+(?:\.\d+)*$/.test(value);
-  }
-  function isHttpsUrl(value) {
-    if (typeof value !== "string") {
-      return false;
-    }
-    try {
-      return new URL(value).protocol === "https:";
-    } catch {
-      return false;
-    }
-  }
-
-  // src/shared/update-config.ts
-  var UPDATE_MANIFEST_URL = "https://wksgx-1343191620.cos.ap-nanjing.myqcloud.com/star-chart-search-enhancer/latest.json";
-
-  // src/popup/index.ts
-  async function bootPopup(options = {}) {
-    const currentDocument = options.document ?? document;
-    const popupConfig = readAuthConfig(options.config);
-    const currentVersion = options.currentVersion ?? readCurrentVersion();
-    const root = currentDocument.querySelector("#app");
-    const HTMLElementCtor = currentDocument.defaultView?.HTMLElement;
-    if (!root || HTMLElementCtor && !(root instanceof HTMLElementCtor)) {
-      throw new Error("popup root #app is required");
-    }
-    const sendMessage = options.sendMessage ?? ((message) => Promise.resolve(
-      globalThis.chrome?.runtime?.sendMessage?.(message)
-    ));
-    const fetchProtectedApi = options.fetchProtectedApi ?? createProtectedApiClient({
-      baseUrl: "http://127.0.0.1:4319",
-      sendMessage
-    }).loadProtectedMockData;
-    const fetchUpdateManifest2 = options.fetchUpdateManifest ?? (() => fetchUpdateManifest(
-      options.updateManifestUrl ?? UPDATE_MANIFEST_URL
-    ));
-    await renderCurrentAuthState(root, popupConfig, sendMessage, fetchProtectedApi, {
-      currentVersion,
-      fetchUpdateManifest: fetchUpdateManifest2
-    });
-  }
-  async function renderCurrentAuthState(root, popupConfig, sendMessage, fetchProtectedApi, updateOptions) {
-    const response = await sendMessage({ type: "auth:get-state" });
-    if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") {
-      renderLoggedOut(root, "\u8BA4\u8BC1\u72B6\u6001\u8BFB\u53D6\u5931\u8D25");
-      return;
-    }
-    if (!response.value.isAuthenticated) {
-      renderLoggedOut(root, response.value.lastError);
-      root.querySelector('[data-popup-sign-in="button"]')?.addEventListener("click", () => {
-        void runAuthAction(root, popupConfig, sendMessage, {
-          actionMessage: { type: "auth:sign-in" },
-          fetchProtectedApi,
-          updateOptions
-        });
-      });
-      return;
-    }
-    renderLoggedIn(root, response.value);
-    void runUpdateCheck(root, sendMessage, updateOptions);
-    root.querySelector('[data-popup-sign-out="button"]')?.addEventListener("click", () => {
-      void runAuthAction(root, popupConfig, sendMessage, {
-        actionMessage: { type: "auth:sign-out" },
-        fetchProtectedApi,
-        updateOptions
-      });
-    });
-    if (popupConfig.enableDevAuthPanel) {
-      renderDevPanel(root, response.value);
-      root.querySelector('[data-popup-test-protected-api="button"]')?.addEventListener("click", () => {
-        void runProtectedApiProbe(root, fetchProtectedApi);
-      });
-    }
-  }
-  async function runAuthAction(root, popupConfig, sendMessage, options) {
-    const response = await sendMessage(options.actionMessage);
-    if (isActionError(response)) {
-      renderLoggedOut(root, response.error);
-      root.querySelector('[data-popup-sign-in="button"]')?.addEventListener("click", () => {
-        void runAuthAction(root, popupConfig, sendMessage, options);
-      });
-      return;
-    }
-    await renderCurrentAuthState(
-      root,
-      popupConfig,
-      sendMessage,
-      options.fetchProtectedApi,
-      options.updateOptions
-    );
-  }
-  function isActionError(response) {
-    return isAuthResponseMessage(response) && !response.ok && response.type === "auth:error";
-  }
-  async function runUpdateCheck(root, sendMessage, options) {
-    renderUpdateStatus(root, {
-      currentVersion: options.currentVersion,
-      status: "checking"
-    });
-    try {
-      const manifest = await options.fetchUpdateManifest();
-      if (compareExtensionVersions(manifest.latestVersion, options.currentVersion) <= 0) {
-        renderUpdateStatus(root, {
-          currentVersion: options.currentVersion,
-          status: "latest"
-        });
-        return;
-      }
-      renderUpdateStatus(root, {
-        currentVersion: options.currentVersion,
-        manifest,
-        status: "available"
-      });
-      bindUpdateDownloadButtons(root, sendMessage, manifest);
-    } catch {
-      renderUpdateStatus(root, {
-        currentVersion: options.currentVersion,
-        status: "error"
-      });
-    }
-  }
-  function bindUpdateDownloadButtons(root, sendMessage, manifest) {
-    root.querySelector('[data-popup-download-update="button"]')?.addEventListener("click", () => {
-      void downloadUpdateAsset(root, sendMessage, {
-        filename: "star-chart-search-enhancer-internal.zip",
-        url: manifest.zipUrl
-      });
-    });
-    root.querySelector('[data-popup-download-guide="button"]')?.addEventListener("click", () => {
-      void downloadUpdateAsset(root, sendMessage, {
-        filename: "\u661F\u56FE\u589E\u5F3A\u63D2\u4EF6-\u8D85\u7B80\u5355\u5B89\u88C5\u4F7F\u7528\u6307\u5357.pdf",
-        url: manifest.guideUrl
-      });
-    });
-  }
-  async function downloadUpdateAsset(root, sendMessage, options) {
-    setUpdateDownloadStatus(root, "\u6B63\u5728\u4E0B\u8F7D...");
-    try {
-      await sendMessage({
-        filename: options.filename,
-        type: "update:download",
-        url: options.url
-      });
-      setUpdateDownloadStatus(root, "\u5DF2\u89E6\u53D1\u4E0B\u8F7D\u3002\u4E0B\u8F7D\u540E\u8BF7\u89E3\u538B\u65B0\u7248 zip\uFF0C\u5E76\u5728 chrome://extensions \u91CC\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u3002");
-    } catch (error) {
-      setUpdateDownloadStatus(
-        root,
-        error instanceof Error ? error.message : "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
-      );
-    }
-  }
-  function readCurrentVersion() {
-    const runtime = globalThis.chrome?.runtime;
-    return runtime?.getManifest?.().version ?? "0.0.0";
-  }
-  async function runProtectedApiProbe(root, fetchProtectedApi) {
-    setProtectedApiResult(root, "\u8BF7\u6C42\u4E2D...");
-    try {
-      const result = await fetchProtectedApi();
-      setProtectedApiResult(root, JSON.stringify(result, null, 2));
-    } catch (error) {
-      setProtectedApiResult(
-        root,
-        error instanceof Error ? error.message : String(error)
-      );
-    }
-  }
-  if (typeof document !== "undefined") {
-    void bootPopup();
-  }
-})();