@hexclave/js 1.0.24 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/dev-tool/dev-tool-core.js +4 -68
- package/dist/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/esm/dev-tool/dev-tool-core.js +4 -68
- package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +6 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +31 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +18 -11
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js +2 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js +11 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts +2 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/esm/lib/hexclave-app/index.d.ts +2 -1
- package/dist/esm/lib/hexclave-app/plan-usage/index.d.ts +27 -0
- package/dist/esm/lib/hexclave-app/plan-usage/index.d.ts.map +1 -0
- package/dist/esm/lib/hexclave-app/plan-usage/index.js +1 -0
- package/dist/esm/lib/hexclave-app/projects/index.d.ts +7 -0
- package/dist/esm/lib/hexclave-app/projects/index.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/projects/index.js.map +1 -1
- package/dist/esm/pushed-config-error-overlay/index.d.ts +7 -0
- package/dist/esm/pushed-config-error-overlay/index.d.ts.map +1 -0
- package/dist/esm/pushed-config-error-overlay/index.js +464 -0
- package/dist/esm/pushed-config-error-overlay/index.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +6 -0
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +31 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +18 -11
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
- package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/event-tracker.js +1 -0
- package/dist/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
- package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
- package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/session-replay.js +11 -0
- package/dist/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
- package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +2 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/lib/hexclave-app/index.d.ts +2 -1
- package/dist/lib/hexclave-app/plan-usage/index.d.ts +27 -0
- package/dist/lib/hexclave-app/plan-usage/index.d.ts.map +1 -0
- package/dist/lib/hexclave-app/plan-usage/index.js +0 -0
- package/dist/lib/hexclave-app/projects/index.d.ts +7 -0
- package/dist/lib/hexclave-app/projects/index.d.ts.map +1 -1
- package/dist/lib/hexclave-app/projects/index.js.map +1 -1
- package/dist/pushed-config-error-overlay/index.d.ts +7 -0
- package/dist/pushed-config-error-overlay/index.d.ts.map +1 -0
- package/dist/pushed-config-error-overlay/index.js +466 -0
- package/dist/pushed-config-error-overlay/index.js.map +1 -0
- package/package.json +2 -2
- package/src/dev-tool/dev-tool-core.ts +5 -59
- package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +40 -1
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +20 -11
- package/src/lib/hexclave-app/apps/implementations/event-tracker.test.ts +33 -0
- package/src/lib/hexclave-app/apps/implementations/event-tracker.ts +6 -1
- package/src/lib/hexclave-app/apps/implementations/session-replay.test.ts +52 -0
- package/src/lib/hexclave-app/apps/implementations/session-replay.ts +20 -0
- package/src/lib/hexclave-app/apps/interfaces/admin-app.ts +2 -0
- package/src/lib/hexclave-app/apps/interfaces/client-app.ts +1 -0
- package/src/lib/hexclave-app/index.ts +8 -0
- package/src/lib/hexclave-app/plan-usage/index.ts +29 -0
- package/src/lib/hexclave-app/projects/index.ts +3 -0
- package/src/pushed-config-error-overlay/index.ts +548 -0
|
@@ -23,6 +23,7 @@ import { EmailConfig, hexclaveAppInternalsSymbol } from "../../common";
|
|
|
23
23
|
import { AdminEmailTemplate } from "../../email-templates";
|
|
24
24
|
import { InternalApiKey, InternalApiKeyBase, InternalApiKeyBaseCrudRead, InternalApiKeyCreateOptions, InternalApiKeyFirstView, internalApiKeyCreateOptionsToCrud } from "../../internal-api-keys";
|
|
25
25
|
import { AdminProjectPermission, AdminProjectPermissionDefinition, AdminProjectPermissionDefinitionCreateOptions, AdminProjectPermissionDefinitionUpdateOptions, AdminTeamPermission, AdminTeamPermissionDefinition, AdminTeamPermissionDefinitionCreateOptions, AdminTeamPermissionDefinitionUpdateOptions, adminProjectPermissionDefinitionCreateOptionsToCrud, adminProjectPermissionDefinitionUpdateOptionsToCrud, adminTeamPermissionDefinitionCreateOptionsToCrud, adminTeamPermissionDefinitionUpdateOptionsToCrud } from "../../permissions";
|
|
26
|
+
import type { PlanUsage } from "../../plan-usage";
|
|
26
27
|
import { AdminOwnedProject, AdminProject, AdminProjectUpdateOptions, PushConfigOptions, adminProjectUpdateOptionsToCrud } from "../../projects";
|
|
27
28
|
import type { AdminSessionReplay, AdminSessionReplayChunk, ListSessionReplayChunksOptions, ListSessionReplayChunksResult, ListSessionReplaysOptions, ListSessionReplaysResult, SessionReplayAllEventsResult } from "../../session-replays";
|
|
28
29
|
import { ManagedEmailProviderListItem, ManagedEmailProviderSetupResult, ManagedEmailProviderStatus, EmailOutboxUpdateOptions, StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/admin-app";
|
|
@@ -35,6 +36,7 @@ import * as yup from "yup";
|
|
|
35
36
|
import { PushedConfigSource } from "../../projects";
|
|
36
37
|
|
|
37
38
|
type BranchConfigSourceApi = yup.InferType<typeof branchConfigSourceSchema>;
|
|
39
|
+
type PlanUsageResponse = Awaited<ReturnType<HexclaveAdminInterface["getPlanUsage"]>>;
|
|
38
40
|
/**
|
|
39
41
|
* Converts a PushedConfigSource (SDK camelCase) to BranchConfigSourceApi (API snake_case).
|
|
40
42
|
*/
|
|
@@ -77,6 +79,9 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
|
|
|
77
79
|
private readonly _adminProjectCache = createCache(async () => {
|
|
78
80
|
return await this._interface.getProject();
|
|
79
81
|
});
|
|
82
|
+
private readonly _planUsageCache = createCache(async () => {
|
|
83
|
+
return await this._interface.getPlanUsage();
|
|
84
|
+
});
|
|
80
85
|
private readonly _internalApiKeysCache = createCache(async () => {
|
|
81
86
|
const res = await this._interface.listInternalApiKeys();
|
|
82
87
|
return res;
|
|
@@ -194,10 +199,17 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
|
|
|
194
199
|
isDevelopmentEnvironment: data.is_development_environment,
|
|
195
200
|
ownerTeamId: data.owner_team_id,
|
|
196
201
|
onboardingStatus: data.onboarding_status,
|
|
202
|
+
onboardingState: data.onboarding_state ?? null,
|
|
197
203
|
logoUrl: data.logo_url,
|
|
198
204
|
logoFullUrl: data.logo_full_url,
|
|
199
205
|
logoDarkModeUrl: data.logo_dark_mode_url,
|
|
200
206
|
logoFullDarkModeUrl: data.logo_full_dark_mode_url,
|
|
207
|
+
pushedConfigError: data.pushed_config_error == null ? null : {
|
|
208
|
+
message: data.pushed_config_error.message,
|
|
209
|
+
},
|
|
210
|
+
configWarnings: data.config_warnings.map((warning) => ({
|
|
211
|
+
message: warning.message,
|
|
212
|
+
})),
|
|
201
213
|
config: {
|
|
202
214
|
signUpEnabled: data.config.sign_up_enabled,
|
|
203
215
|
credentialEnabled: data.config.credential_enabled,
|
|
@@ -322,6 +334,28 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
|
|
|
322
334
|
};
|
|
323
335
|
}
|
|
324
336
|
|
|
337
|
+
_planUsageFromCrud(data: PlanUsageResponse): PlanUsage {
|
|
338
|
+
return {
|
|
339
|
+
ownerTeamId: data.owner_team_id,
|
|
340
|
+
ownerTeamDisplayName: data.owner_team_display_name,
|
|
341
|
+
planId: data.plan_id,
|
|
342
|
+
planDisplayName: data.plan_display_name,
|
|
343
|
+
periodStart: new Date(data.period_start_millis),
|
|
344
|
+
periodEnd: new Date(data.period_end_millis),
|
|
345
|
+
nextPlanId: data.next_plan_id,
|
|
346
|
+
rows: data.rows.map((row) => ({
|
|
347
|
+
itemId: row.item_id,
|
|
348
|
+
displayName: row.display_name,
|
|
349
|
+
kind: row.kind,
|
|
350
|
+
used: row.used,
|
|
351
|
+
limit: row.limit,
|
|
352
|
+
remaining: row.remaining,
|
|
353
|
+
overage: row.overage,
|
|
354
|
+
isUnlimited: row.is_unlimited,
|
|
355
|
+
})),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
325
359
|
override async getProject(): Promise<AdminProject> {
|
|
326
360
|
return this._adminProjectFromCrud(
|
|
327
361
|
Result.orThrow(await this._adminProjectCache.getOrWait([], "write-only")),
|
|
@@ -330,6 +364,11 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
|
|
|
330
364
|
}
|
|
331
365
|
|
|
332
366
|
|
|
367
|
+
async getPlanUsage(): Promise<PlanUsage> {
|
|
368
|
+
return this._planUsageFromCrud(Result.orThrow(await this._planUsageCache.getOrWait([], "write-only")));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
|
|
333
372
|
protected _createInternalApiKeyBaseFromCrud(data: InternalApiKeyBaseCrudRead): InternalApiKeyBase {
|
|
334
373
|
const app = this;
|
|
335
374
|
return {
|
|
@@ -1030,7 +1069,7 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
|
|
|
1030
1069
|
|
|
1031
1070
|
|
|
1032
1071
|
async getStripeAccountInfo(): Promise<null | { account_id: string, charges_enabled: boolean, details_submitted: boolean, payouts_enabled: boolean }> {
|
|
1033
|
-
return await this.
|
|
1072
|
+
return Result.orThrow(await this._stripeAccountInfoCache.getOrWait([], "write-only"));
|
|
1034
1073
|
}
|
|
1035
1074
|
|
|
1036
1075
|
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
//===========================================
|
|
3
3
|
// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
|
|
4
4
|
//===========================================
|
|
5
|
-
import {
|
|
6
|
-
import { KnownError, KnownErrors, HexclaveClientInterface } from "@hexclave/shared";
|
|
5
|
+
import { HexclaveClientInterface, KnownError, KnownErrors } from "@hexclave/shared";
|
|
7
6
|
import type { RequestListener } from "@hexclave/shared/dist/interface/client-interface";
|
|
8
7
|
import { ContactChannelsCrud } from "@hexclave/shared/dist/interface/crud/contact-channels";
|
|
9
8
|
import { CurrentUserCrud } from "@hexclave/shared/dist/interface/crud/current-user";
|
|
@@ -41,12 +40,13 @@ import type { TurnstileAction } from "@hexclave/shared/dist/utils/turnstile";
|
|
|
41
40
|
import { BotChallengeExecutionFailedError, BotChallengeUserCancelledError, withBotChallengeFlow } from "@hexclave/shared/dist/utils/turnstile-flow";
|
|
42
41
|
import { createUrlIfValid, getRelativePart, isRelative } from "@hexclave/shared/dist/utils/urls";
|
|
43
42
|
import { generateUuid } from "@hexclave/shared/dist/utils/uuids";
|
|
43
|
+
import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
|
|
44
44
|
import * as cookie from "cookie";
|
|
45
45
|
import type * as yup from "yup";
|
|
46
|
+
import { envVars } from "../../../../generated/env";
|
|
46
47
|
import { constructRedirectUrl } from "../../../../utils/url";
|
|
47
48
|
import { callOAuthCallback, getNewOAuthProviderOrScopeUrl } from "../../../auth";
|
|
48
49
|
import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, getCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
|
|
49
|
-
import { envVars } from "../../../../generated/env";
|
|
50
50
|
import { ApiKey, ApiKeyCreationOptions, ApiKeyUpdateOptions, apiKeyCreationOptionsToCrud } from "../../api-keys";
|
|
51
51
|
import { ConvexCtx, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, RequestLike, ResolvedHandlerUrls, TokenStoreInit, hexclaveAppInternalsSymbol } from "../../common";
|
|
52
52
|
import { DeprecatedOAuthConnection, OAuthConnection } from "../../connected-accounts";
|
|
@@ -69,6 +69,7 @@ import { AnalyticsOptions, SessionRecorder, analyticsOptionsFromJson, analyticsO
|
|
|
69
69
|
|
|
70
70
|
import { mountClickmapOverlay } from "../../../../clickmap";
|
|
71
71
|
import { mountDevTool } from "../../../../dev-tool";
|
|
72
|
+
import { mountPushedConfigErrorOverlay } from "../../../../pushed-config-error-overlay";
|
|
72
73
|
|
|
73
74
|
let isReactServer = false;
|
|
74
75
|
|
|
@@ -344,7 +345,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
344
345
|
{
|
|
345
346
|
provider,
|
|
346
347
|
redirectUrl: this._getOAuthCallbackRedirectUri(),
|
|
347
|
-
errorRedirectUrl: this.
|
|
348
|
+
errorRedirectUrl: this._getUrls().error,
|
|
348
349
|
providerScope: mergeScopeStrings(scopeString, (this._oauthScopesOnSignIn[provider as ProviderType] ?? []).join(" ")),
|
|
349
350
|
},
|
|
350
351
|
session,
|
|
@@ -546,7 +547,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
546
547
|
{
|
|
547
548
|
provider: options.providerId,
|
|
548
549
|
redirectUrl: this._getOAuthCallbackRedirectUri(),
|
|
549
|
-
errorRedirectUrl: this.
|
|
550
|
+
errorRedirectUrl: this._getUrls().error,
|
|
550
551
|
providerScope: mergeScopeStrings(options.scope || "", (this._oauthScopesOnSignIn[options.providerId] ?? []).join(" ")),
|
|
551
552
|
},
|
|
552
553
|
options.session,
|
|
@@ -712,6 +713,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
712
713
|
// when a dashboard-minted token is handed over, so the listener is
|
|
713
714
|
// mounted unconditionally (the heavy UI is lazy-loaded on demand).
|
|
714
715
|
mountClickmapOverlay(this as any);
|
|
716
|
+
mountPushedConfigErrorOverlay(this as any);
|
|
715
717
|
}
|
|
716
718
|
}
|
|
717
719
|
|
|
@@ -823,7 +825,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
823
825
|
|
|
824
826
|
protected _getOAuthCallbackRedirectUri(): string {
|
|
825
827
|
if (!this._isOAuthCallbackUrlHosted()) {
|
|
826
|
-
return this.
|
|
828
|
+
return this._getUrls().oauthCallback;
|
|
827
829
|
}
|
|
828
830
|
if (typeof window === "undefined") {
|
|
829
831
|
throw new HexclaveAssertionError("Hosted OAuth callback URLs require a browser environment to use the current URL as the redirect URI");
|
|
@@ -1571,6 +1573,12 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
1571
1573
|
return {
|
|
1572
1574
|
id: crud.id,
|
|
1573
1575
|
displayName: crud.display_name,
|
|
1576
|
+
pushedConfigError: crud.pushed_config_error == null ? null : {
|
|
1577
|
+
message: crud.pushed_config_error.message,
|
|
1578
|
+
},
|
|
1579
|
+
configWarnings: crud.config_warnings.map((warning) => ({
|
|
1580
|
+
message: warning.message,
|
|
1581
|
+
})),
|
|
1574
1582
|
config: {
|
|
1575
1583
|
signUpEnabled: crud.config.sign_up_enabled,
|
|
1576
1584
|
credentialEnabled: crud.config.credential_enabled,
|
|
@@ -2749,13 +2757,13 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
2749
2757
|
async redirectToMfa(options?: RedirectToOptions) { return await this._redirectToHandler("mfa", options); }
|
|
2750
2758
|
|
|
2751
2759
|
async sendForgotPasswordEmail(email: string, options?: { callbackUrl?: string }): Promise<Result<undefined, KnownErrors["UserNotFound"]>> {
|
|
2752
|
-
return await this._interface.sendForgotPasswordEmail(email, options?.callbackUrl ?? constructRedirectUrl(this.
|
|
2760
|
+
return await this._interface.sendForgotPasswordEmail(email, options?.callbackUrl ?? constructRedirectUrl(this._getUrls().passwordReset, "callbackUrl"));
|
|
2753
2761
|
}
|
|
2754
2762
|
|
|
2755
2763
|
async sendMagicLinkEmail(email: string, options?: {
|
|
2756
2764
|
callbackUrl?: string,
|
|
2757
2765
|
}): Promise<Result<{ nonce: string }, KnownErrors["RedirectUrlNotWhitelisted"] | KnownErrors["BotChallengeFailed"]>> {
|
|
2758
|
-
const callbackUrl = options?.callbackUrl ?? constructRedirectUrl(this.
|
|
2766
|
+
const callbackUrl = options?.callbackUrl ?? constructRedirectUrl(this._getUrls().magicLinkCallback, "callbackUrl");
|
|
2759
2767
|
return await this._executeResultWithBotChallengeFlow({
|
|
2760
2768
|
action: "send_magic_link_email",
|
|
2761
2769
|
execute: async (challenge) => {
|
|
@@ -2965,7 +2973,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
2965
2973
|
return await this._interface.authorizeOAuth({
|
|
2966
2974
|
provider,
|
|
2967
2975
|
redirectUrl: constructRedirectUrl(this._getOAuthCallbackRedirectUri(), "redirectUrl"),
|
|
2968
|
-
errorRedirectUrl: constructRedirectUrl(this.
|
|
2976
|
+
errorRedirectUrl: constructRedirectUrl(this._getUrls().error, "errorRedirectUrl"),
|
|
2969
2977
|
afterCallbackRedirectUrl,
|
|
2970
2978
|
type: "authenticate",
|
|
2971
2979
|
providerScope: this._oauthScopesOnSignIn[provider]?.join(" "),
|
|
@@ -3085,7 +3093,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
3085
3093
|
}
|
|
3086
3094
|
this._ensurePersistentTokenStore();
|
|
3087
3095
|
const session = await this._getSession();
|
|
3088
|
-
const emailVerificationRedirectUrl = options.noVerificationCallback ? undefined : options.verificationCallbackUrl ?? constructRedirectUrl(this.
|
|
3096
|
+
const emailVerificationRedirectUrl = options.noVerificationCallback ? undefined : options.verificationCallbackUrl ?? constructRedirectUrl(this._getUrls().emailVerification, "verificationCallbackUrl");
|
|
3089
3097
|
|
|
3090
3098
|
const executeSignUp = async (challenge: { token?: string, phase?: "invisible" | "visible", unavailable?: true }) => {
|
|
3091
3099
|
let result = await this._interface.signUpWithCredential(
|
|
@@ -3248,7 +3256,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
3248
3256
|
|
|
3249
3257
|
// Step 2: Open the browser for the user to authenticate and display the verification code
|
|
3250
3258
|
const url = buildCliAuthConfirmUrl({
|
|
3251
|
-
cliAuthConfirmUrl: this.
|
|
3259
|
+
cliAuthConfirmUrl: this._getUrls().cliAuthConfirm,
|
|
3252
3260
|
appUrl: options.appUrl,
|
|
3253
3261
|
loginCode,
|
|
3254
3262
|
});
|
|
@@ -3645,6 +3653,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
3645
3653
|
) => {
|
|
3646
3654
|
return await this._interface.sendClientRequest(path, requestOptions, await this._getSession(), requestType);
|
|
3647
3655
|
},
|
|
3656
|
+
getUrls: () => this._getUrls(),
|
|
3648
3657
|
getRedirectMethod: () => this._redirectMethod ?? throwErr("Redirect method should have been initialized in the Stack client app constructor"),
|
|
3649
3658
|
redirectToUrl: async (url: string | URL, options?: { replace?: boolean }) => {
|
|
3650
3659
|
await this._redirectTo({ url, ...options });
|
|
@@ -391,6 +391,39 @@ describe("EventTracker", () => {
|
|
|
391
391
|
}
|
|
392
392
|
});
|
|
393
393
|
|
|
394
|
+
it("silently ignores network errors caused by ad blockers", async () => {
|
|
395
|
+
vi.useFakeTimers();
|
|
396
|
+
document.body.innerHTML = "<button>Click me</button>";
|
|
397
|
+
|
|
398
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
399
|
+
const sentBodies: string[] = [];
|
|
400
|
+
const tracker = new EventTracker({
|
|
401
|
+
projectId: "internal",
|
|
402
|
+
sendBatch: async (body) => {
|
|
403
|
+
sentBodies.push(body);
|
|
404
|
+
return Result.error(new TypeError("Failed to fetch"));
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
tracker.start();
|
|
410
|
+
|
|
411
|
+
await advancePastFlush();
|
|
412
|
+
expect(sentBodies).toHaveLength(1);
|
|
413
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
414
|
+
|
|
415
|
+
// Unlike ANALYTICS_NOT_ENABLED, ad blocker errors do NOT disable the
|
|
416
|
+
// tracker — subsequent flushes continue attempting delivery.
|
|
417
|
+
document.querySelector("button")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
418
|
+
await advancePastFlush();
|
|
419
|
+
expect(sentBodies).toHaveLength(2);
|
|
420
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
421
|
+
} finally {
|
|
422
|
+
tracker.stop();
|
|
423
|
+
warnSpy.mockRestore();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
394
427
|
it("silently disables when client interface returns ANALYTICS_NOT_ENABLED as an error", async () => {
|
|
395
428
|
vi.useFakeTimers();
|
|
396
429
|
document.body.innerHTML = "<button>Click me</button>";
|
|
@@ -8,7 +8,7 @@ import { cssEscapeIdent } from "@hexclave/shared/dist/utils/dom";
|
|
|
8
8
|
import { buildElementsChain, ELEMENTS_CHAIN_MAX_DEPTH } from "@hexclave/shared/dist/utils/elements-chain";
|
|
9
9
|
import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
|
|
10
10
|
import { Result } from "@hexclave/shared/dist/utils/results";
|
|
11
|
-
import { generateUuid, isAnalyticsNotEnabledError } from "./session-replay";
|
|
11
|
+
import { generateUuid, isAdBlockerNetworkError, isAnalyticsNotEnabledError } from "./session-replay";
|
|
12
12
|
|
|
13
13
|
const FLUSH_INTERVAL_MS = 10_000;
|
|
14
14
|
const MAX_EVENTS_PER_BATCH = 50;
|
|
@@ -511,6 +511,11 @@ export class EventTracker {
|
|
|
511
511
|
this._disable();
|
|
512
512
|
return;
|
|
513
513
|
}
|
|
514
|
+
// Ad blockers commonly block analytics endpoints, causing network
|
|
515
|
+
// errors. These are expected and should not pollute the console.
|
|
516
|
+
if (isAdBlockerNetworkError(res.error)) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
514
519
|
console.warn("EventTracker flush failed:", res.error);
|
|
515
520
|
return;
|
|
516
521
|
}
|
|
@@ -49,6 +49,58 @@ describe("analytics option JSON conversion", () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
describe("SessionRecorder flush", () => {
|
|
52
|
+
it("silently ignores network errors caused by ad blockers", async () => {
|
|
53
|
+
vi.useFakeTimers();
|
|
54
|
+
|
|
55
|
+
const storageKey = `hexclave:session-replay:v1:test-project`;
|
|
56
|
+
localStorage.setItem(storageKey, JSON.stringify({
|
|
57
|
+
session_id: "test-session",
|
|
58
|
+
created_at_ms: Date.now(),
|
|
59
|
+
last_activity_ms: Date.now(),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
const sentBodies: string[] = [];
|
|
63
|
+
const recorder = new SessionRecorder(
|
|
64
|
+
{
|
|
65
|
+
projectId: "test-project",
|
|
66
|
+
sendBatch: async (body) => {
|
|
67
|
+
sentBodies.push(body);
|
|
68
|
+
return Result.error(new TypeError("Failed to fetch"));
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
78
|
+
(recorder as any)._events = [{ type: 2, timestamp: Date.now(), data: {} }];
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
81
|
+
(recorder as any)._tick();
|
|
82
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
83
|
+
|
|
84
|
+
expect(sentBodies).toHaveLength(1);
|
|
85
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
86
|
+
|
|
87
|
+
// Unlike ANALYTICS_NOT_ENABLED, ad blocker errors do NOT disable the
|
|
88
|
+
// recorder — subsequent flushes continue attempting delivery.
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
90
|
+
(recorder as any)._events = [{ type: 3, timestamp: Date.now(), data: {} }];
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
92
|
+
(recorder as any)._tick();
|
|
93
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
94
|
+
expect(sentBodies).toHaveLength(2);
|
|
95
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
96
|
+
} finally {
|
|
97
|
+
recorder.stop();
|
|
98
|
+
warnSpy.mockRestore();
|
|
99
|
+
localStorage.removeItem(storageKey);
|
|
100
|
+
vi.useRealTimers();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
52
104
|
it("silently disables when client interface returns ANALYTICS_NOT_ENABLED as an error", async () => {
|
|
53
105
|
vi.useFakeTimers();
|
|
54
106
|
|
|
@@ -170,6 +170,21 @@ export function isAnalyticsNotEnabledError(error: unknown): boolean {
|
|
|
170
170
|
return KnownErrors.AnalyticsNotEnabled.isInstance(error);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Whether the error looks like a network failure caused by an ad blocker or
|
|
175
|
+
* similar extension blocking analytics requests. These are expected in
|
|
176
|
+
* production and should be silently ignored rather than logged as warnings.
|
|
177
|
+
*/
|
|
178
|
+
export function isAdBlockerNetworkError(error: unknown): boolean {
|
|
179
|
+
if (error instanceof Error) {
|
|
180
|
+
return error.message.includes("Failed to fetch")
|
|
181
|
+
|| error.message.includes("NetworkError")
|
|
182
|
+
|| error.message.includes("Load failed")
|
|
183
|
+
|| error.message.includes("network connection");
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
173
188
|
export class SessionRecorder {
|
|
174
189
|
private _started = false;
|
|
175
190
|
private _cancelled = false;
|
|
@@ -278,6 +293,11 @@ export class SessionRecorder {
|
|
|
278
293
|
this._disable();
|
|
279
294
|
return;
|
|
280
295
|
}
|
|
296
|
+
// Ad blockers commonly block analytics endpoints, causing network
|
|
297
|
+
// errors. These are expected and should not pollute the console.
|
|
298
|
+
if (isAdBlockerNetworkError(res.error)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
281
301
|
captureWarning("SessionRecorder.flush", res.error);
|
|
282
302
|
return;
|
|
283
303
|
}
|
|
@@ -13,6 +13,7 @@ import { AsyncStoreProperty, EmailConfig } from "../../common";
|
|
|
13
13
|
import { AdminEmailOutbox, AdminSentEmail } from "../../email";
|
|
14
14
|
import { InternalApiKey, InternalApiKeyCreateOptions, InternalApiKeyFirstView } from "../../internal-api-keys";
|
|
15
15
|
import { AdminProjectPermission, AdminProjectPermissionDefinition, AdminProjectPermissionDefinitionCreateOptions, AdminProjectPermissionDefinitionUpdateOptions, AdminTeamPermission, AdminTeamPermissionDefinition, AdminTeamPermissionDefinitionCreateOptions, AdminTeamPermissionDefinitionUpdateOptions } from "../../permissions";
|
|
16
|
+
import type { PlanUsage } from "../../plan-usage";
|
|
16
17
|
import { AdminProject } from "../../projects";
|
|
17
18
|
import { _HexclaveAdminAppImpl } from "../implementations";
|
|
18
19
|
import { StackServerApp, StackServerAppConstructorOptions } from "./server-app";
|
|
@@ -75,6 +76,7 @@ export type StackAdminAppConstructorOptions<HasTokenStore extends boolean, Proje
|
|
|
75
76
|
/** @deprecated Use `HexclaveAdminApp` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */
|
|
76
77
|
export type StackAdminApp<HasTokenStore extends boolean = boolean, ProjectId extends string = string> = (
|
|
77
78
|
& AsyncStoreProperty<"project", [], AdminProject, false>
|
|
79
|
+
& AsyncStoreProperty<"planUsage", [], PlanUsage, false>
|
|
78
80
|
& AsyncStoreProperty<"internalApiKeys", [], InternalApiKey[], true>
|
|
79
81
|
& AsyncStoreProperty<"teamPermissionDefinitions", [], AdminTeamPermissionDefinition[], true>
|
|
80
82
|
& AsyncStoreProperty<"projectPermissionDefinitions", [], AdminProjectPermissionDefinition[], true>
|
|
@@ -123,6 +123,7 @@ export type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId ex
|
|
|
123
123
|
sendAnalyticsEventBatch(body: string, options: { keepalive: boolean }): Promise<Result<Response, Error>>,
|
|
124
124
|
addRequestListener(listener: RequestListener): () => void,
|
|
125
125
|
sendRequest(path: string, requestOptions: RequestInit, requestType?: "client" | "server" | "admin"): Promise<Response>,
|
|
126
|
+
getUrls(): Readonly<ResolvedHandlerUrls>,
|
|
126
127
|
getRedirectMethod(): RedirectMethod,
|
|
127
128
|
redirectToUrl(url: string | URL, options?: { replace?: boolean }): Promise<void>,
|
|
128
129
|
redirectToHandler(handlerName: keyof HandlerUrls, options?: RedirectToOptions): Promise<void>,
|
|
@@ -118,6 +118,14 @@ export type {
|
|
|
118
118
|
PushedConfigSource
|
|
119
119
|
} from "./projects";
|
|
120
120
|
|
|
121
|
+
export type {
|
|
122
|
+
PlanUsage,
|
|
123
|
+
PlanUsageKind,
|
|
124
|
+
PlanUsageNextPlanId,
|
|
125
|
+
PlanUsagePlanId,
|
|
126
|
+
PlanUsageRow,
|
|
127
|
+
} from "./plan-usage";
|
|
128
|
+
|
|
121
129
|
export type {
|
|
122
130
|
EditableTeamMemberProfile, ReceivedTeamInvitation,
|
|
123
131
|
SentTeamInvitation, ServerListUsersOptions,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
//===========================================
|
|
3
|
+
// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
|
|
4
|
+
//===========================================
|
|
5
|
+
export type PlanUsageKind = "current" | "metered" | "capability";
|
|
6
|
+
export type PlanUsagePlanId = "free" | "team" | "growth";
|
|
7
|
+
export type PlanUsageNextPlanId = "team" | "growth";
|
|
8
|
+
|
|
9
|
+
export type PlanUsageRow = {
|
|
10
|
+
itemId: string,
|
|
11
|
+
displayName: string,
|
|
12
|
+
kind: PlanUsageKind,
|
|
13
|
+
used: number | null,
|
|
14
|
+
limit: number | null,
|
|
15
|
+
remaining: number | null,
|
|
16
|
+
overage: number | null,
|
|
17
|
+
isUnlimited: boolean,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type PlanUsage = {
|
|
21
|
+
ownerTeamId: string,
|
|
22
|
+
ownerTeamDisplayName: string,
|
|
23
|
+
planId: PlanUsagePlanId,
|
|
24
|
+
planDisplayName: string,
|
|
25
|
+
periodStart: Date,
|
|
26
|
+
periodEnd: Date,
|
|
27
|
+
nextPlanId: PlanUsageNextPlanId | null,
|
|
28
|
+
rows: PlanUsageRow[],
|
|
29
|
+
};
|
|
@@ -30,6 +30,8 @@ export type PushConfigOptions = {
|
|
|
30
30
|
export type Project = {
|
|
31
31
|
readonly id: string,
|
|
32
32
|
readonly displayName: string,
|
|
33
|
+
readonly pushedConfigError: { message: string } | null,
|
|
34
|
+
readonly configWarnings: { message: string }[],
|
|
33
35
|
readonly config: ProjectConfig,
|
|
34
36
|
};
|
|
35
37
|
|
|
@@ -42,6 +44,7 @@ export type AdminProject = {
|
|
|
42
44
|
readonly isDevelopmentEnvironment: boolean,
|
|
43
45
|
readonly ownerTeamId: string | null,
|
|
44
46
|
readonly onboardingStatus: ProjectOnboardingStatus,
|
|
47
|
+
readonly onboardingState: NonNullable<ProjectsCrud["Admin"]["Read"]["onboarding_state"]> | null,
|
|
45
48
|
readonly logoUrl: string | null | undefined,
|
|
46
49
|
readonly logoFullUrl: string | null | undefined,
|
|
47
50
|
readonly logoDarkModeUrl: string | null | undefined,
|