@scalemule/nextjs 0.1.24 → 0.1.27
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/client.d.mts +14 -1
- package/dist/client.d.ts +14 -1
- package/dist/client.js +22 -1
- package/dist/client.mjs +22 -1
- package/dist/{index-DTWyUcyd.d.mts → index-zQloSkpW.d.mts} +5 -1
- package/dist/{index-DTWyUcyd.d.ts → index-zQloSkpW.d.ts} +5 -1
- package/dist/index.d.mts +150 -23
- package/dist/index.d.ts +150 -23
- package/dist/index.js +443 -493
- package/dist/index.mjs +442 -493
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +7 -9
- package/dist/server/index.mjs +7 -9
- package/dist/server/ledvery.js +1 -5
- package/dist/server/ledvery.mjs +1 -5
- package/dist/server/webhook-handler.d.mts +2 -2
- package/dist/server/webhook-handler.d.ts +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{webhook-handler-DsxJS_He.d.mts → webhook-handler-C_bKDevj.d.mts} +1 -1
- package/dist/{webhook-handler-CTx4Uh8u.d.ts → webhook-handler-D4uulN2T.d.ts} +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -580,6 +580,7 @@ var ScaleMuleClient = class {
|
|
|
580
580
|
this.anonymousId = null;
|
|
581
581
|
this.sessionPool = /* @__PURE__ */ new Map();
|
|
582
582
|
this.knownAccounts = /* @__PURE__ */ new Map();
|
|
583
|
+
this.refreshPromise = null;
|
|
583
584
|
this.apiKey = config.apiKey;
|
|
584
585
|
this.applicationId = config.applicationId || null;
|
|
585
586
|
this.baseUrl = config.baseUrl || GATEWAY_URLS[config.environment || "prod"];
|
|
@@ -598,6 +599,9 @@ var ScaleMuleClient = class {
|
|
|
598
599
|
this.multiSessionEnabled = config.enableMultiSession || false;
|
|
599
600
|
this.accountSwitcherEnabled = config.enableAccountSwitcher || false;
|
|
600
601
|
this.accountSwitcherPrivacy = config.accountSwitcherPrivacy || "full";
|
|
602
|
+
this.onRefreshStart = config.onRefreshStart;
|
|
603
|
+
this.onRefreshEnd = config.onRefreshEnd;
|
|
604
|
+
this.onAutoRefreshFailed = config.onAutoRefreshFailed;
|
|
601
605
|
}
|
|
602
606
|
// --------------------------------------------------------------------------
|
|
603
607
|
// Session Management
|
|
@@ -883,34 +887,28 @@ var ScaleMuleClient = class {
|
|
|
883
887
|
const method = (init.method || "GET").toUpperCase();
|
|
884
888
|
const timeout = init.timeout || this.defaultTimeout;
|
|
885
889
|
const maxRetries = init.skipRetry ? 0 : init.retries ?? this.maxRetries;
|
|
886
|
-
const
|
|
887
|
-
"x-api-key": this.apiKey,
|
|
888
|
-
"User-Agent": `ScaleMule-SDK-TypeScript/${SDK_VERSION}`,
|
|
889
|
-
...init.headers
|
|
890
|
-
};
|
|
891
|
-
if (!init.skipAuth && this.sessionToken) {
|
|
892
|
-
headers["Authorization"] = `Bearer ${this.sessionToken}`;
|
|
893
|
-
}
|
|
894
|
-
if (this.workspaceId) {
|
|
895
|
-
headers["x-sm-workspace-id"] = this.workspaceId;
|
|
896
|
-
}
|
|
897
|
-
if (!this.sessionToken && this.anonymousId) {
|
|
898
|
-
headers["x-anonymous-id"] = this.anonymousId;
|
|
899
|
-
}
|
|
900
|
-
let bodyStr;
|
|
901
|
-
if (init.body !== void 0 && init.body !== null) {
|
|
902
|
-
bodyStr = typeof init.body === "string" ? init.body : JSON.stringify(init.body);
|
|
903
|
-
if (!headers["Content-Type"]) {
|
|
904
|
-
headers["Content-Type"] = "application/json";
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
if (this.debug) {
|
|
908
|
-
console.log(`[ScaleMule] ${method} ${path}`);
|
|
909
|
-
}
|
|
910
|
-
const idempotencyKey = method === "POST" ? generateIdempotencyKey() : void 0;
|
|
890
|
+
const bodyStr = init.body ? JSON.stringify(init.body) : void 0;
|
|
911
891
|
let lastError = null;
|
|
892
|
+
let idempotencyKey = null;
|
|
912
893
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
913
|
-
|
|
894
|
+
const headers = {
|
|
895
|
+
"x-api-key": this.apiKey,
|
|
896
|
+
"User-Agent": `ScaleMule-SDK-TypeScript/${SDK_VERSION}`,
|
|
897
|
+
...init.headers
|
|
898
|
+
};
|
|
899
|
+
if (!init.skipAuth && this.sessionToken) {
|
|
900
|
+
headers["Authorization"] = `Bearer ${this.sessionToken}`;
|
|
901
|
+
}
|
|
902
|
+
if (this.workspaceId) {
|
|
903
|
+
headers["x-sm-workspace-id"] = this.workspaceId;
|
|
904
|
+
}
|
|
905
|
+
if (!this.sessionToken && this.anonymousId) {
|
|
906
|
+
headers["x-anonymous-id"] = this.anonymousId;
|
|
907
|
+
}
|
|
908
|
+
if (attempt > 0 && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
909
|
+
if (!idempotencyKey) {
|
|
910
|
+
idempotencyKey = generateIdempotencyKey();
|
|
911
|
+
}
|
|
914
912
|
headers["x-idempotency-key"] = idempotencyKey;
|
|
915
913
|
}
|
|
916
914
|
const controller = new AbortController();
|
|
@@ -930,6 +928,10 @@ var ScaleMuleClient = class {
|
|
|
930
928
|
signal: controller.signal
|
|
931
929
|
});
|
|
932
930
|
clearTimeout(timeoutId);
|
|
931
|
+
const rotatedToken = response.headers.get("x-rotated-session-token");
|
|
932
|
+
if (rotatedToken && this.sessionToken && this.userId) {
|
|
933
|
+
this.setSession(rotatedToken, this.userId);
|
|
934
|
+
}
|
|
933
935
|
if (this.rateLimitQueue) {
|
|
934
936
|
this.rateLimitQueue.updateFromHeaders(response.headers);
|
|
935
937
|
}
|
|
@@ -952,6 +954,45 @@ var ScaleMuleClient = class {
|
|
|
952
954
|
status: response.status,
|
|
953
955
|
details: responseData?.error?.details || responseData?.details
|
|
954
956
|
};
|
|
957
|
+
if (response.status === 401 && this.sessionToken && !init.isAutoRefresh) {
|
|
958
|
+
if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
|
|
959
|
+
let isPrimaryRefresh = false;
|
|
960
|
+
if (!this.refreshPromise) {
|
|
961
|
+
isPrimaryRefresh = true;
|
|
962
|
+
this.onRefreshStart?.();
|
|
963
|
+
this.refreshPromise = this.auth?.refreshSession ? this.auth.refreshSession() : this.post("/auth/refresh", {}, { isAutoRefresh: true });
|
|
964
|
+
}
|
|
965
|
+
const currentRefreshPromise = this.refreshPromise;
|
|
966
|
+
try {
|
|
967
|
+
const refreshResult = await currentRefreshPromise;
|
|
968
|
+
if (!refreshResult || refreshResult.error) {
|
|
969
|
+
if (this.debug) console.log("[ScaleMule] Auto-refresh failed:", refreshResult?.error);
|
|
970
|
+
const apiError = refreshResult?.error || {
|
|
971
|
+
code: "refresh_failed",
|
|
972
|
+
message: "Auto-refresh failed",
|
|
973
|
+
status: 400
|
|
974
|
+
};
|
|
975
|
+
init.onAutoRefreshFailed?.(apiError);
|
|
976
|
+
this.onAutoRefreshFailed?.(apiError);
|
|
977
|
+
return { data: null, error };
|
|
978
|
+
}
|
|
979
|
+
if (this.debug) console.log("[ScaleMule] Auto-refresh succeeded, retrying original request...");
|
|
980
|
+
const newToken = refreshResult.data?.session_token || refreshResult.data?.access_token;
|
|
981
|
+
if (newToken) {
|
|
982
|
+
this.sessionToken = newToken;
|
|
983
|
+
if (this.userId) {
|
|
984
|
+
await this.storage.setItem(SESSION_STORAGE_KEY, newToken);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
attempt--;
|
|
988
|
+
continue;
|
|
989
|
+
} finally {
|
|
990
|
+
if (isPrimaryRefresh && this.refreshPromise === currentRefreshPromise) {
|
|
991
|
+
this.refreshPromise = null;
|
|
992
|
+
this.onRefreshEnd?.();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
955
996
|
if (response.status === 429) {
|
|
956
997
|
const retryAfter = response.headers.get("Retry-After");
|
|
957
998
|
if (retryAfter) {
|
|
@@ -5735,7 +5776,7 @@ var AudioService = class extends ServiceModule {
|
|
|
5735
5776
|
constructor(client, storage) {
|
|
5736
5777
|
super(client);
|
|
5737
5778
|
this.storage = storage;
|
|
5738
|
-
this.basePath = "/v1/
|
|
5779
|
+
this.basePath = "/v1/audio";
|
|
5739
5780
|
}
|
|
5740
5781
|
/**
|
|
5741
5782
|
* Register a storage-uploaded audio asset with the audio service.
|
|
@@ -5792,6 +5833,32 @@ var AudioService = class extends ServiceModule {
|
|
|
5792
5833
|
};
|
|
5793
5834
|
}
|
|
5794
5835
|
};
|
|
5836
|
+
var TtsService = class extends ServiceModule {
|
|
5837
|
+
constructor() {
|
|
5838
|
+
super(...arguments);
|
|
5839
|
+
this.basePath = "/v1/tts";
|
|
5840
|
+
}
|
|
5841
|
+
async synthesize(params, options) {
|
|
5842
|
+
return this.post(
|
|
5843
|
+
"/synthesize",
|
|
5844
|
+
{
|
|
5845
|
+
text: params.text,
|
|
5846
|
+
voice: params.voice,
|
|
5847
|
+
model: params.model,
|
|
5848
|
+
provider: params.provider,
|
|
5849
|
+
async: params.async,
|
|
5850
|
+
access_mode: params.accessMode
|
|
5851
|
+
},
|
|
5852
|
+
options
|
|
5853
|
+
);
|
|
5854
|
+
}
|
|
5855
|
+
async getJob(id, options) {
|
|
5856
|
+
return this._get(`/jobs/${id}`, options);
|
|
5857
|
+
}
|
|
5858
|
+
async listVoices(params, options) {
|
|
5859
|
+
return this._get(this.withQuery("/voices", { provider: params?.provider }), options);
|
|
5860
|
+
}
|
|
5861
|
+
};
|
|
5795
5862
|
var DeadLetterApi = class extends ServiceModule {
|
|
5796
5863
|
constructor() {
|
|
5797
5864
|
super(...arguments);
|
|
@@ -6612,6 +6679,7 @@ var ScaleMule = class {
|
|
|
6612
6679
|
this.functions = new FunctionsService(this._client);
|
|
6613
6680
|
this.photo = new PhotoService(this._client, this.storage);
|
|
6614
6681
|
this.audio = new AudioService(this._client, this.storage);
|
|
6682
|
+
this.tts = new TtsService(this._client);
|
|
6615
6683
|
this.flagContent = new FlagContentService(this._client);
|
|
6616
6684
|
this.creatorMaker = new CreatorMakerService(this._client);
|
|
6617
6685
|
this.compliance = new ComplianceService(this._client);
|
|
@@ -7178,6 +7246,24 @@ var ScaleMuleClient2 = class {
|
|
|
7178
7246
|
console.log("[ScaleMule] Session set for user:", userId);
|
|
7179
7247
|
}
|
|
7180
7248
|
}
|
|
7249
|
+
/**
|
|
7250
|
+
* Token-only setter for member-auth surfaces.
|
|
7251
|
+
*
|
|
7252
|
+
* Unlike `setSession(token, userId)`, this does NOT persist a userId or
|
|
7253
|
+
* write to local storage — useful when the host platform owns identity
|
|
7254
|
+
* (e.g. ScaleMule's member dashboards use `MemberAuthProvider` and the
|
|
7255
|
+
* token comes from a `${env}_member_access_token` cookie). The token is
|
|
7256
|
+
* applied in-memory and used as a `Bearer` Authorization header on
|
|
7257
|
+
* subsequent requests.
|
|
7258
|
+
*
|
|
7259
|
+
* Pass `null` to clear without touching userId/storage.
|
|
7260
|
+
*/
|
|
7261
|
+
setSessionToken(token) {
|
|
7262
|
+
this.sessionToken = token;
|
|
7263
|
+
if (this.debug) {
|
|
7264
|
+
console.log("[ScaleMule] Session token", token ? "set (token-only)" : "cleared (token-only)");
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7181
7267
|
/**
|
|
7182
7268
|
* Clear session on logout
|
|
7183
7269
|
*/
|
|
@@ -7242,6 +7328,8 @@ var ScaleMuleClient2 = class {
|
|
|
7242
7328
|
console.log(`[ScaleMule] ${options.method || "GET"} ${path}`);
|
|
7243
7329
|
}
|
|
7244
7330
|
let lastError = null;
|
|
7331
|
+
const MAX_REFRESH_ATTEMPTS = 1;
|
|
7332
|
+
let refreshAttempts = 0;
|
|
7245
7333
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
7246
7334
|
const controller = new AbortController();
|
|
7247
7335
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -7265,7 +7353,8 @@ var ScaleMuleClient2 = class {
|
|
|
7265
7353
|
if (!response.ok) {
|
|
7266
7354
|
const rawError = responseData?.error;
|
|
7267
7355
|
const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
|
|
7268
|
-
if (response.status === 401 && this.sessionToken && !options.isAutoRefresh) {
|
|
7356
|
+
if (response.status === 401 && this.sessionToken && !options.isAutoRefresh && refreshAttempts < MAX_REFRESH_ATTEMPTS) {
|
|
7357
|
+
refreshAttempts++;
|
|
7269
7358
|
if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
|
|
7270
7359
|
if (!this.refreshPromise) {
|
|
7271
7360
|
this.onRefreshStart?.();
|
|
@@ -7634,8 +7723,12 @@ function ScaleMuleProvider({
|
|
|
7634
7723
|
onLogout,
|
|
7635
7724
|
onAuthError,
|
|
7636
7725
|
bootstrapFlags,
|
|
7637
|
-
mediaPolicy
|
|
7726
|
+
mediaPolicy,
|
|
7727
|
+
getToken,
|
|
7728
|
+
userResolver,
|
|
7729
|
+
memberTokenPollMs
|
|
7638
7730
|
}) {
|
|
7731
|
+
const memberMode = typeof getToken === "function";
|
|
7639
7732
|
const [user, setUser] = React.useState(null);
|
|
7640
7733
|
const [initializing, setInitializing] = React.useState(true);
|
|
7641
7734
|
const [error, setError] = React.useState(null);
|
|
@@ -7652,9 +7745,13 @@ function ScaleMuleProvider({
|
|
|
7652
7745
|
gatewayUrl: resolvedGatewayUrl,
|
|
7653
7746
|
debug,
|
|
7654
7747
|
storage,
|
|
7655
|
-
|
|
7748
|
+
// Make outbound API calls wait for the first token to land in any
|
|
7749
|
+
// mode where the provider is responsible for populating the
|
|
7750
|
+
// session asynchronously: auth-proxy fetch, or member-mode
|
|
7751
|
+
// getToken() callback. Resolved in the init effect below.
|
|
7752
|
+
pendingSessionInit: !!authProxyUrl || memberMode
|
|
7656
7753
|
}),
|
|
7657
|
-
[apiKey, applicationId, environment, resolvedGatewayUrl, debug, storage, authProxyUrl]
|
|
7754
|
+
[apiKey, applicationId, environment, resolvedGatewayUrl, debug, storage, authProxyUrl, memberMode]
|
|
7658
7755
|
);
|
|
7659
7756
|
const money$1 = React.useMemo(
|
|
7660
7757
|
() => money.createMoneyClient({
|
|
@@ -7676,8 +7773,10 @@ function ScaleMuleProvider({
|
|
|
7676
7773
|
});
|
|
7677
7774
|
}, [apiKey, applicationId, environment, resolvedGatewayUrl, debug]);
|
|
7678
7775
|
const [fetchedPolicy, setFetchedPolicy] = React.useState(void 0);
|
|
7776
|
+
const [tokenVersion, setTokenVersion] = React.useState(0);
|
|
7679
7777
|
React.useEffect(() => {
|
|
7680
7778
|
if (mediaPolicy) return;
|
|
7779
|
+
if (memberMode && tokenVersion === 0) return;
|
|
7681
7780
|
let mounted = true;
|
|
7682
7781
|
void (async () => {
|
|
7683
7782
|
try {
|
|
@@ -7695,7 +7794,7 @@ function ScaleMuleProvider({
|
|
|
7695
7794
|
return () => {
|
|
7696
7795
|
mounted = false;
|
|
7697
7796
|
};
|
|
7698
|
-
}, [baseClient, mediaPolicy]);
|
|
7797
|
+
}, [baseClient, mediaPolicy, memberMode, tokenVersion]);
|
|
7699
7798
|
const effectiveMediaPolicy = mediaPolicy ?? fetchedPolicy;
|
|
7700
7799
|
React.useEffect(() => {
|
|
7701
7800
|
const token = client.getSessionToken();
|
|
@@ -7713,6 +7812,43 @@ function ScaleMuleProvider({
|
|
|
7713
7812
|
try {
|
|
7714
7813
|
await client.initialize();
|
|
7715
7814
|
const cachedUser = getCachedUser();
|
|
7815
|
+
if (memberMode) {
|
|
7816
|
+
try {
|
|
7817
|
+
const token = await Promise.resolve(getToken()) ?? null;
|
|
7818
|
+
if (mounted) {
|
|
7819
|
+
if (token) {
|
|
7820
|
+
client.setSessionToken(token);
|
|
7821
|
+
baseClient.setAccessToken(token);
|
|
7822
|
+
money$1.setAccessToken(token);
|
|
7823
|
+
} else {
|
|
7824
|
+
client.setSessionToken(null);
|
|
7825
|
+
baseClient.clearAccessToken();
|
|
7826
|
+
money$1.setAccessToken(void 0);
|
|
7827
|
+
}
|
|
7828
|
+
setTokenVersion((v) => v + 1);
|
|
7829
|
+
}
|
|
7830
|
+
if (userResolver) {
|
|
7831
|
+
try {
|
|
7832
|
+
const resolvedUser = await userResolver();
|
|
7833
|
+
if (mounted) {
|
|
7834
|
+
setUser(resolvedUser);
|
|
7835
|
+
setCachedUser(resolvedUser);
|
|
7836
|
+
}
|
|
7837
|
+
} catch (resolveErr) {
|
|
7838
|
+
if (mounted && debug) {
|
|
7839
|
+
console.debug("[ScaleMule] userResolver() failed:", resolveErr);
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7842
|
+
}
|
|
7843
|
+
} catch (memberErr) {
|
|
7844
|
+
if (mounted && debug) {
|
|
7845
|
+
console.debug("[ScaleMule] getToken() failed:", memberErr);
|
|
7846
|
+
}
|
|
7847
|
+
} finally {
|
|
7848
|
+
client.resolveSessionPending();
|
|
7849
|
+
}
|
|
7850
|
+
return;
|
|
7851
|
+
}
|
|
7716
7852
|
if (authProxyUrl) {
|
|
7717
7853
|
if (cachedUser && mounted) {
|
|
7718
7854
|
setUser(cachedUser);
|
|
@@ -7784,7 +7920,43 @@ function ScaleMuleProvider({
|
|
|
7784
7920
|
return () => {
|
|
7785
7921
|
mounted = false;
|
|
7786
7922
|
};
|
|
7787
|
-
}, [client, debug, onAuthError, authProxyUrl]);
|
|
7923
|
+
}, [client, baseClient, money$1, debug, onAuthError, authProxyUrl, memberMode, getToken, userResolver]);
|
|
7924
|
+
React.useEffect(() => {
|
|
7925
|
+
if (!memberMode) return;
|
|
7926
|
+
if (memberTokenPollMs === null) return;
|
|
7927
|
+
const interval = memberTokenPollMs ?? 6e4;
|
|
7928
|
+
if (interval <= 0) return;
|
|
7929
|
+
let cancelled = false;
|
|
7930
|
+
const id = setInterval(async () => {
|
|
7931
|
+
try {
|
|
7932
|
+
const token = await Promise.resolve(getToken()) ?? null;
|
|
7933
|
+
if (cancelled) return;
|
|
7934
|
+
const current = client.getSessionToken();
|
|
7935
|
+
if (token === current) return;
|
|
7936
|
+
if (token) {
|
|
7937
|
+
client.setSessionToken(token);
|
|
7938
|
+
baseClient.setAccessToken(token);
|
|
7939
|
+
money$1.setAccessToken(token);
|
|
7940
|
+
} else {
|
|
7941
|
+
client.setSessionToken(null);
|
|
7942
|
+
baseClient.clearAccessToken();
|
|
7943
|
+
money$1.setAccessToken(void 0);
|
|
7944
|
+
if (debug) {
|
|
7945
|
+
console.debug("[ScaleMule] Member token cleared on refresh");
|
|
7946
|
+
}
|
|
7947
|
+
}
|
|
7948
|
+
setTokenVersion((v) => v + 1);
|
|
7949
|
+
} catch (err) {
|
|
7950
|
+
if (debug) {
|
|
7951
|
+
console.debug("[ScaleMule] Member token refresh failed:", err);
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
}, interval);
|
|
7955
|
+
return () => {
|
|
7956
|
+
cancelled = true;
|
|
7957
|
+
clearInterval(id);
|
|
7958
|
+
};
|
|
7959
|
+
}, [memberMode, memberTokenPollMs, getToken, client, baseClient, money$1, debug]);
|
|
7788
7960
|
const handleSetUser = React.useCallback(
|
|
7789
7961
|
(newUser) => {
|
|
7790
7962
|
setUser(newUser);
|
|
@@ -8266,8 +8438,8 @@ function useAuth() {
|
|
|
8266
8438
|
const getLinkedAccounts = React.useCallback(async () => {
|
|
8267
8439
|
setError(null);
|
|
8268
8440
|
try {
|
|
8269
|
-
const data = await client.get("/v1/auth/oauth/
|
|
8270
|
-
return data.
|
|
8441
|
+
const data = await client.get("/v1/auth/oauth/providers");
|
|
8442
|
+
return data.providers;
|
|
8271
8443
|
} catch (err) {
|
|
8272
8444
|
if (err instanceof ScaleMuleApiError) {
|
|
8273
8445
|
setError(err);
|
|
@@ -8276,41 +8448,30 @@ function useAuth() {
|
|
|
8276
8448
|
}
|
|
8277
8449
|
}, [client, setError]);
|
|
8278
8450
|
const linkAccount = React.useCallback(
|
|
8279
|
-
async (
|
|
8451
|
+
async (_config) => {
|
|
8280
8452
|
setError(null);
|
|
8281
8453
|
if (!user) {
|
|
8282
|
-
const
|
|
8454
|
+
const err2 = {
|
|
8283
8455
|
code: "NOT_AUTHENTICATED",
|
|
8284
8456
|
message: "Must be logged in to link accounts"
|
|
8285
8457
|
};
|
|
8286
|
-
setError(
|
|
8287
|
-
throw
|
|
8458
|
+
setError(err2);
|
|
8459
|
+
throw err2;
|
|
8288
8460
|
}
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
});
|
|
8296
|
-
} catch (err) {
|
|
8297
|
-
if (err instanceof ScaleMuleApiError) {
|
|
8298
|
-
setError(err);
|
|
8299
|
-
}
|
|
8300
|
-
throw err;
|
|
8301
|
-
}
|
|
8302
|
-
if (typeof sessionStorage !== "undefined") {
|
|
8303
|
-
sessionStorage.setItem("scalemule_oauth_state", linkData.state);
|
|
8304
|
-
}
|
|
8305
|
-
return linkData;
|
|
8461
|
+
const err = {
|
|
8462
|
+
code: "NOT_IMPLEMENTED",
|
|
8463
|
+
message: "OAuth account linking from a logged-in session is not yet supported. Use the standard OAuth sign-in flow instead."
|
|
8464
|
+
};
|
|
8465
|
+
setError(err);
|
|
8466
|
+
throw err;
|
|
8306
8467
|
},
|
|
8307
|
-
[
|
|
8468
|
+
[user, setError]
|
|
8308
8469
|
);
|
|
8309
8470
|
const unlinkAccount = React.useCallback(
|
|
8310
8471
|
async (provider) => {
|
|
8311
8472
|
setError(null);
|
|
8312
8473
|
try {
|
|
8313
|
-
await client.delete(`/v1/auth/oauth/
|
|
8474
|
+
await client.delete(`/v1/auth/oauth/providers/${encodeURIComponent(provider)}`);
|
|
8314
8475
|
} catch (err) {
|
|
8315
8476
|
if (err instanceof ScaleMuleApiError) {
|
|
8316
8477
|
setError(err);
|
|
@@ -9383,7 +9544,9 @@ function useMedia() {
|
|
|
9383
9544
|
async (file, options) => {
|
|
9384
9545
|
setUploading(true);
|
|
9385
9546
|
setError(null);
|
|
9386
|
-
const
|
|
9547
|
+
const visibility = options?.visibility ?? (options?.is_public === true ? "app_public" : options?.is_public === false ? "private" : "private");
|
|
9548
|
+
const isPublic = visibility !== "private";
|
|
9549
|
+
const isAnonymous = visibility === "anonymous_visible";
|
|
9387
9550
|
const mimeType = file.type || "application/octet-stream";
|
|
9388
9551
|
const policy = options?.policy ?? providerDefaultPolicy ?? "safe_visible";
|
|
9389
9552
|
const gateOnPipeline = policy === "safe_public" || policy === "moderated" || policy === "compliance";
|
|
@@ -9393,7 +9556,31 @@ function useMedia() {
|
|
|
9393
9556
|
onProgress: options?.onProgress,
|
|
9394
9557
|
signal: options?.signal
|
|
9395
9558
|
};
|
|
9559
|
+
const storageAnon = storage;
|
|
9396
9560
|
try {
|
|
9561
|
+
if (isAnonymous) {
|
|
9562
|
+
const r2 = await storageAnon.uploadAnonymous(file, sharedOpts);
|
|
9563
|
+
if (r2.error || !r2.data) {
|
|
9564
|
+
throw r2.error ?? { code: "upload_error", message: "Upload failed", status: 0 };
|
|
9565
|
+
}
|
|
9566
|
+
const f2 = r2.data;
|
|
9567
|
+
return {
|
|
9568
|
+
file_id: f2.id,
|
|
9569
|
+
photo_id: null,
|
|
9570
|
+
// For anon files, original_view_url is the same unsigned
|
|
9571
|
+
// CDN URL — no signed alternative exists. Callers should
|
|
9572
|
+
// prefer `cdn_url` (more specific) but `original_view_url`
|
|
9573
|
+
// works too for back-compat with code that already reads
|
|
9574
|
+
// that field.
|
|
9575
|
+
original_view_url: f2.cdn_url ?? null,
|
|
9576
|
+
optimized_url_promise: Promise.resolve(null),
|
|
9577
|
+
hls_url_promise: Promise.resolve(null),
|
|
9578
|
+
mime_type: mimeType,
|
|
9579
|
+
is_public: true,
|
|
9580
|
+
visibility: "anonymous_visible",
|
|
9581
|
+
cdn_url: f2.cdn_url ?? null
|
|
9582
|
+
};
|
|
9583
|
+
}
|
|
9397
9584
|
if (mimeType.startsWith("image/") && !options?.skipPhotoRegister && !isPublic) {
|
|
9398
9585
|
const r2 = await photo.uploadViaStorage(file, sharedOpts);
|
|
9399
9586
|
if (r2.error || !r2.data) {
|
|
@@ -9409,7 +9596,9 @@ function useMedia() {
|
|
|
9409
9596
|
optimized_url_promise: r2.data.optimized_url_promise,
|
|
9410
9597
|
hls_url_promise: Promise.resolve(null),
|
|
9411
9598
|
mime_type: mimeType,
|
|
9412
|
-
is_public: false
|
|
9599
|
+
is_public: false,
|
|
9600
|
+
visibility: "private",
|
|
9601
|
+
cdn_url: null
|
|
9413
9602
|
};
|
|
9414
9603
|
}
|
|
9415
9604
|
if (mimeType.startsWith("video/") && !isPublic) {
|
|
@@ -9427,7 +9616,9 @@ function useMedia() {
|
|
|
9427
9616
|
optimized_url_promise: Promise.resolve(null),
|
|
9428
9617
|
hls_url_promise: r2.data.hls_url_promise,
|
|
9429
9618
|
mime_type: mimeType,
|
|
9430
|
-
is_public: false
|
|
9619
|
+
is_public: false,
|
|
9620
|
+
visibility: "private",
|
|
9621
|
+
cdn_url: null
|
|
9431
9622
|
};
|
|
9432
9623
|
}
|
|
9433
9624
|
if (mimeType.startsWith("audio/") && !isPublic) {
|
|
@@ -9442,13 +9633,15 @@ function useMedia() {
|
|
|
9442
9633
|
optimized_url_promise: Promise.resolve(null),
|
|
9443
9634
|
hls_url_promise: Promise.resolve(null),
|
|
9444
9635
|
mime_type: mimeType,
|
|
9445
|
-
is_public: false
|
|
9636
|
+
is_public: false,
|
|
9637
|
+
visibility: "private",
|
|
9638
|
+
cdn_url: null
|
|
9446
9639
|
};
|
|
9447
9640
|
}
|
|
9448
9641
|
if (mimeType.startsWith("image/") && isPublic) {
|
|
9449
9642
|
const r2 = await storage.upload(file, {
|
|
9450
9643
|
...sharedOpts,
|
|
9451
|
-
|
|
9644
|
+
visibility: "app_public",
|
|
9452
9645
|
skipCompression: true
|
|
9453
9646
|
});
|
|
9454
9647
|
if (r2.error || !r2.data) {
|
|
@@ -9462,10 +9655,16 @@ function useMedia() {
|
|
|
9462
9655
|
optimized_url_promise: Promise.resolve(null),
|
|
9463
9656
|
hls_url_promise: Promise.resolve(null),
|
|
9464
9657
|
mime_type: mimeType,
|
|
9465
|
-
is_public: f2.is_public ?? true
|
|
9658
|
+
is_public: f2.is_public ?? true,
|
|
9659
|
+
visibility: f2.visibility ?? "app_public",
|
|
9660
|
+
cdn_url: f2.cdn_url ?? null
|
|
9466
9661
|
};
|
|
9467
9662
|
}
|
|
9468
|
-
const r = isPublic ? await storage.upload(file, {
|
|
9663
|
+
const r = isPublic ? await storage.upload(file, {
|
|
9664
|
+
...sharedOpts,
|
|
9665
|
+
visibility: "app_public",
|
|
9666
|
+
skipCompression: true
|
|
9667
|
+
}) : await storage.uploadPrivate(file, sharedOpts);
|
|
9469
9668
|
if (r.error || !r.data) {
|
|
9470
9669
|
throw r.error ?? { code: "upload_error", message: "Upload failed", status: 0 };
|
|
9471
9670
|
}
|
|
@@ -9477,7 +9676,9 @@ function useMedia() {
|
|
|
9477
9676
|
optimized_url_promise: Promise.resolve(null),
|
|
9478
9677
|
hls_url_promise: Promise.resolve(null),
|
|
9479
9678
|
mime_type: mimeType,
|
|
9480
|
-
is_public: f.is_public ?? isPublic
|
|
9679
|
+
is_public: f.is_public ?? isPublic,
|
|
9680
|
+
visibility: f.visibility ?? (isPublic ? "app_public" : "private"),
|
|
9681
|
+
cdn_url: f.cdn_url ?? null
|
|
9481
9682
|
};
|
|
9482
9683
|
} catch (err) {
|
|
9483
9684
|
const e = err;
|
|
@@ -10920,6 +11121,75 @@ function useFeedback(options = {}) {
|
|
|
10920
11121
|
);
|
|
10921
11122
|
return { items, loading, error, submit, refresh };
|
|
10922
11123
|
}
|
|
11124
|
+
var ZERO = { value: 0, up_count: 0, down_count: 0, score: 0 };
|
|
11125
|
+
function useVote({
|
|
11126
|
+
targetType,
|
|
11127
|
+
targetId,
|
|
11128
|
+
initialState,
|
|
11129
|
+
refetchOnMount = false,
|
|
11130
|
+
enabled = true
|
|
11131
|
+
}) {
|
|
11132
|
+
const { client } = useScaleMule();
|
|
11133
|
+
const [state, setState] = React.useState(initialState ?? ZERO);
|
|
11134
|
+
const [isLoading, setIsLoading] = React.useState(!initialState && enabled);
|
|
11135
|
+
const [error, setError] = React.useState(null);
|
|
11136
|
+
const inFlight = React.useRef(null);
|
|
11137
|
+
const path = `/v1/social/${encodeURIComponent(targetType)}/${encodeURIComponent(targetId)}/vote`;
|
|
11138
|
+
const refetch = React.useCallback(async () => {
|
|
11139
|
+
if (!enabled) return;
|
|
11140
|
+
setIsLoading(true);
|
|
11141
|
+
setError(null);
|
|
11142
|
+
try {
|
|
11143
|
+
const data = await client.get(path);
|
|
11144
|
+
setState(data);
|
|
11145
|
+
} catch (e) {
|
|
11146
|
+
setError(e);
|
|
11147
|
+
} finally {
|
|
11148
|
+
setIsLoading(false);
|
|
11149
|
+
}
|
|
11150
|
+
}, [client, path, enabled]);
|
|
11151
|
+
React.useEffect(() => {
|
|
11152
|
+
if (!enabled) return;
|
|
11153
|
+
if (initialState && !refetchOnMount) return;
|
|
11154
|
+
void refetch();
|
|
11155
|
+
}, [enabled, refetch, initialState, refetchOnMount]);
|
|
11156
|
+
const cast = React.useCallback(
|
|
11157
|
+
async (value) => {
|
|
11158
|
+
const prev = state;
|
|
11159
|
+
const optimistic = applyVote(prev, value);
|
|
11160
|
+
setState(optimistic);
|
|
11161
|
+
setError(null);
|
|
11162
|
+
const send = async () => {
|
|
11163
|
+
try {
|
|
11164
|
+
const data = await client.put(path, { value });
|
|
11165
|
+
setState(data);
|
|
11166
|
+
} catch (e) {
|
|
11167
|
+
setState(prev);
|
|
11168
|
+
setError(e);
|
|
11169
|
+
}
|
|
11170
|
+
};
|
|
11171
|
+
const p = inFlight.current ? inFlight.current.then(send) : send();
|
|
11172
|
+
inFlight.current = p;
|
|
11173
|
+
await p;
|
|
11174
|
+
if (inFlight.current === p) inFlight.current = null;
|
|
11175
|
+
},
|
|
11176
|
+
[client, path, state]
|
|
11177
|
+
);
|
|
11178
|
+
return { state, isLoading, error, cast, refetch };
|
|
11179
|
+
}
|
|
11180
|
+
function applyVote(prev, next) {
|
|
11181
|
+
let { up_count, down_count } = prev;
|
|
11182
|
+
if (prev.value === 1) up_count = Math.max(0, up_count - 1);
|
|
11183
|
+
if (prev.value === -1) down_count = Math.max(0, down_count - 1);
|
|
11184
|
+
if (next === 1) up_count += 1;
|
|
11185
|
+
if (next === -1) down_count += 1;
|
|
11186
|
+
return {
|
|
11187
|
+
value: next,
|
|
11188
|
+
up_count,
|
|
11189
|
+
down_count,
|
|
11190
|
+
score: up_count - down_count
|
|
11191
|
+
};
|
|
11192
|
+
}
|
|
10923
11193
|
var TYPE_LABELS = {
|
|
10924
11194
|
bug_report: "Bug",
|
|
10925
11195
|
feature_request: "Feature request",
|
|
@@ -11288,449 +11558,128 @@ function FeedbackWidget(props) {
|
|
|
11288
11558
|
) })
|
|
11289
11559
|
] });
|
|
11290
11560
|
}
|
|
11291
|
-
var
|
|
11292
|
-
var
|
|
11293
|
-
var
|
|
11561
|
+
var ARROW_UP = "\u25B2";
|
|
11562
|
+
var ARROW_DOWN = "\u25BC";
|
|
11563
|
+
var STYLES2 = {
|
|
11294
11564
|
root: {
|
|
11295
|
-
|
|
11296
|
-
border: "1px solid rgba(226, 232, 240, 0.8)",
|
|
11297
|
-
background: "linear-gradient(180deg, #f8fbff 0%, #edf4ff 100%)",
|
|
11298
|
-
padding: "1rem",
|
|
11299
|
-
color: "#0f172b",
|
|
11300
|
-
boxShadow: "0 22px 70px rgba(4, 12, 24, 0.28)"
|
|
11301
|
-
},
|
|
11302
|
-
layout: {
|
|
11303
|
-
display: "flex",
|
|
11304
|
-
flexWrap: "wrap",
|
|
11565
|
+
display: "inline-flex",
|
|
11305
11566
|
alignItems: "center",
|
|
11306
|
-
gap:
|
|
11567
|
+
gap: 6
|
|
11307
11568
|
},
|
|
11308
|
-
|
|
11309
|
-
display: "flex",
|
|
11310
|
-
|
|
11311
|
-
width: "4.5rem",
|
|
11312
|
-
flexShrink: 0,
|
|
11313
|
-
alignItems: "center",
|
|
11314
|
-
justifyContent: "center",
|
|
11315
|
-
borderRadius: "9999px",
|
|
11316
|
-
border: "none",
|
|
11317
|
-
backgroundColor: "#020618",
|
|
11318
|
-
color: "#ffffff",
|
|
11319
|
-
boxShadow: "0 10px 28px rgba(15, 23, 42, 0.25)",
|
|
11320
|
-
cursor: "pointer"
|
|
11321
|
-
},
|
|
11322
|
-
playIcon: {
|
|
11323
|
-
display: "block",
|
|
11324
|
-
width: "1.75rem",
|
|
11325
|
-
height: "1.75rem",
|
|
11326
|
-
fill: "currentColor"
|
|
11327
|
-
},
|
|
11328
|
-
playIconOffset: {
|
|
11329
|
-
marginLeft: "0.25rem"
|
|
11330
|
-
},
|
|
11331
|
-
content: {
|
|
11332
|
-
minWidth: "16rem",
|
|
11333
|
-
flex: "1 1 18rem"
|
|
11334
|
-
},
|
|
11335
|
-
metaRow: {
|
|
11336
|
-
display: "flex",
|
|
11337
|
-
alignItems: "center",
|
|
11338
|
-
justifyContent: "space-between",
|
|
11339
|
-
gap: "0.75rem",
|
|
11340
|
-
fontSize: "0.8rem",
|
|
11341
|
-
fontWeight: 500,
|
|
11342
|
-
color: "#62748e"
|
|
11343
|
-
},
|
|
11344
|
-
metaRight: {
|
|
11345
|
-
display: "flex",
|
|
11569
|
+
rootStacked: {
|
|
11570
|
+
display: "inline-flex",
|
|
11571
|
+
flexDirection: "column",
|
|
11346
11572
|
alignItems: "center",
|
|
11347
|
-
gap:
|
|
11348
|
-
},
|
|
11349
|
-
badge: {
|
|
11350
|
-
borderRadius: "9999px",
|
|
11351
|
-
backgroundColor: "#dbeafe",
|
|
11352
|
-
padding: "0.25rem 0.75rem",
|
|
11353
|
-
fontSize: "0.65rem",
|
|
11354
|
-
fontWeight: 600,
|
|
11355
|
-
letterSpacing: "0.24em",
|
|
11356
|
-
textTransform: "uppercase",
|
|
11357
|
-
color: "#1447e6"
|
|
11573
|
+
gap: 2
|
|
11358
11574
|
},
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11575
|
+
button: {
|
|
11576
|
+
border: "1px solid rgba(255,255,255,0.12)",
|
|
11577
|
+
background: "rgba(255,255,255,0.04)",
|
|
11578
|
+
color: "inherit",
|
|
11363
11579
|
cursor: "pointer",
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11580
|
+
borderRadius: 9999,
|
|
11581
|
+
padding: "6px 10px",
|
|
11582
|
+
lineHeight: 1,
|
|
11583
|
+
fontSize: 14
|
|
11368
11584
|
},
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
inset: 0,
|
|
11373
|
-
height: "100%",
|
|
11374
|
-
width: "100%"
|
|
11585
|
+
buttonCompact: {
|
|
11586
|
+
padding: "3px 7px",
|
|
11587
|
+
fontSize: 12
|
|
11375
11588
|
},
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
},
|
|
11381
|
-
fallbackProgress: {
|
|
11382
|
-
position: "absolute",
|
|
11383
|
-
insetBlock: 0,
|
|
11384
|
-
left: 0,
|
|
11385
|
-
backgroundColor: "#155dfc"
|
|
11386
|
-
},
|
|
11387
|
-
chipsRow: {
|
|
11388
|
-
marginTop: "1rem",
|
|
11389
|
-
display: "flex",
|
|
11390
|
-
flexWrap: "wrap",
|
|
11391
|
-
alignItems: "center",
|
|
11392
|
-
gap: "0.5rem",
|
|
11393
|
-
fontSize: "0.72rem",
|
|
11394
|
-
fontWeight: 500,
|
|
11395
|
-
color: "#62748e"
|
|
11396
|
-
},
|
|
11397
|
-
chipStrong: {
|
|
11398
|
-
borderRadius: "9999px",
|
|
11399
|
-
border: "1px solid #e2e8f0",
|
|
11400
|
-
backgroundColor: "rgba(255, 255, 255, 0.85)",
|
|
11401
|
-
padding: "0.25rem 0.75rem",
|
|
11402
|
-
color: "#334155"
|
|
11403
|
-
},
|
|
11404
|
-
chipMuted: {
|
|
11405
|
-
borderRadius: "9999px",
|
|
11406
|
-
border: "1px solid #e2e8f0",
|
|
11407
|
-
backgroundColor: "rgba(255, 255, 255, 0.65)",
|
|
11408
|
-
padding: "0.25rem 0.75rem"
|
|
11409
|
-
},
|
|
11410
|
-
controls: {
|
|
11411
|
-
display: "flex",
|
|
11412
|
-
flexShrink: 0,
|
|
11413
|
-
alignItems: "center",
|
|
11414
|
-
gap: "0.5rem",
|
|
11415
|
-
alignSelf: "flex-start"
|
|
11416
|
-
},
|
|
11417
|
-
secondaryButton: {
|
|
11418
|
-
borderRadius: "9999px",
|
|
11419
|
-
border: "1px solid #cad5e2",
|
|
11420
|
-
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
11421
|
-
padding: "0.5rem 1rem",
|
|
11422
|
-
fontSize: "1rem",
|
|
11423
|
-
fontWeight: 600,
|
|
11424
|
-
color: "#334155",
|
|
11425
|
-
boxShadow: "0 1px 3px rgba(15, 23, 42, 0.08)",
|
|
11426
|
-
cursor: "pointer"
|
|
11589
|
+
buttonActiveUp: {
|
|
11590
|
+
background: "rgba(34,197,94,0.18)",
|
|
11591
|
+
borderColor: "rgba(34,197,94,0.45)",
|
|
11592
|
+
color: "#86efac"
|
|
11427
11593
|
},
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
padding: "0.5rem 1rem",
|
|
11433
|
-
fontSize: "0.875rem",
|
|
11434
|
-
fontWeight: 600,
|
|
11435
|
-
color: "#334155",
|
|
11436
|
-
cursor: "pointer"
|
|
11594
|
+
buttonActiveDown: {
|
|
11595
|
+
background: "rgba(239,68,68,0.18)",
|
|
11596
|
+
borderColor: "rgba(239,68,68,0.45)",
|
|
11597
|
+
color: "#fca5a5"
|
|
11437
11598
|
},
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11599
|
+
score: {
|
|
11600
|
+
minWidth: 24,
|
|
11601
|
+
textAlign: "center",
|
|
11602
|
+
fontVariantNumeric: "tabular-nums",
|
|
11603
|
+
fontWeight: 600
|
|
11441
11604
|
}
|
|
11442
11605
|
};
|
|
11443
|
-
function
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
if (currentIndex === -1) {
|
|
11455
|
-
return SPEEDS[0];
|
|
11456
|
-
}
|
|
11457
|
-
return SPEEDS[(currentIndex + 1) % SPEEDS.length];
|
|
11458
|
-
}
|
|
11459
|
-
function NarrationPlayer({
|
|
11460
|
-
audio,
|
|
11461
|
-
className,
|
|
11462
|
-
providerLabel = "ScaleMule",
|
|
11463
|
-
narrationLabel = "AI-Narrated",
|
|
11464
|
-
refreshing = false,
|
|
11465
|
-
onRefresh,
|
|
11466
|
-
showRefreshButton = false,
|
|
11467
|
-
onPlaybackError
|
|
11606
|
+
function VoteButton({
|
|
11607
|
+
targetType,
|
|
11608
|
+
targetId,
|
|
11609
|
+
initialState,
|
|
11610
|
+
layout = "horizontal",
|
|
11611
|
+
size = "regular",
|
|
11612
|
+
onSignInRequired,
|
|
11613
|
+
onError,
|
|
11614
|
+
classNames,
|
|
11615
|
+
upLabel = "Upvote",
|
|
11616
|
+
downLabel = "Downvote"
|
|
11468
11617
|
}) {
|
|
11469
|
-
const
|
|
11470
|
-
const
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
const
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
return [];
|
|
11481
|
-
}
|
|
11482
|
-
const sampled = audio.waveform_peaks.filter((_, index) => index % 3 === 0).slice(0, 220);
|
|
11483
|
-
return sampled.length > 0 ? sampled : audio.waveform_peaks.slice(0, 220);
|
|
11484
|
-
}, [audio.waveform_peaks]);
|
|
11485
|
-
React.useEffect(() => {
|
|
11486
|
-
const element = audioRef.current;
|
|
11487
|
-
if (!element) return;
|
|
11488
|
-
recoveredUrlRef.current = null;
|
|
11489
|
-
const resumeTime = pendingResumeTimeRef.current;
|
|
11490
|
-
setCurrentTime(resumeTime ?? 0);
|
|
11491
|
-
setPlaying(false);
|
|
11492
|
-
element.pause();
|
|
11493
|
-
try {
|
|
11494
|
-
element.currentTime = resumeTime ?? 0;
|
|
11495
|
-
} catch {
|
|
11496
|
-
element.currentTime = 0;
|
|
11497
|
-
}
|
|
11498
|
-
if (pendingResumeRef.current) {
|
|
11499
|
-
pendingResumeRef.current = false;
|
|
11500
|
-
pendingResumeTimeRef.current = null;
|
|
11501
|
-
element.play().catch(() => {
|
|
11502
|
-
setPlaying(false);
|
|
11503
|
-
onPlaybackError?.();
|
|
11504
|
-
});
|
|
11505
|
-
return;
|
|
11506
|
-
}
|
|
11507
|
-
pendingResumeTimeRef.current = null;
|
|
11508
|
-
}, [audio.url]);
|
|
11509
|
-
React.useEffect(() => {
|
|
11510
|
-
const element = audioRef.current;
|
|
11511
|
-
if (!element) return;
|
|
11512
|
-
element.playbackRate = speed;
|
|
11513
|
-
element.preservesPitch = true;
|
|
11514
|
-
}, [speed]);
|
|
11515
|
-
function isUrlExpiringSoon() {
|
|
11516
|
-
if (!audio.expires_at) {
|
|
11517
|
-
return false;
|
|
11518
|
-
}
|
|
11519
|
-
const expiresAtMs = Date.parse(audio.expires_at);
|
|
11520
|
-
if (!Number.isFinite(expiresAtMs)) {
|
|
11521
|
-
return false;
|
|
11522
|
-
}
|
|
11523
|
-
return expiresAtMs - Date.now() <= EXPIRY_REFRESH_WINDOW_MS;
|
|
11524
|
-
}
|
|
11525
|
-
async function refreshAndResume() {
|
|
11526
|
-
if (!onRefresh || refreshing) {
|
|
11527
|
-
return false;
|
|
11528
|
-
}
|
|
11529
|
-
if (recoveredUrlRef.current === (audio.url ?? null)) {
|
|
11530
|
-
return false;
|
|
11531
|
-
}
|
|
11532
|
-
recoveredUrlRef.current = audio.url ?? null;
|
|
11533
|
-
pendingResumeRef.current = true;
|
|
11534
|
-
pendingResumeTimeRef.current = currentTime;
|
|
11535
|
-
try {
|
|
11536
|
-
await Promise.resolve(onRefresh());
|
|
11537
|
-
return true;
|
|
11538
|
-
} catch {
|
|
11539
|
-
pendingResumeRef.current = false;
|
|
11540
|
-
pendingResumeTimeRef.current = null;
|
|
11541
|
-
onPlaybackError?.();
|
|
11542
|
-
return false;
|
|
11543
|
-
}
|
|
11544
|
-
}
|
|
11545
|
-
function togglePlayback() {
|
|
11546
|
-
const element = audioRef.current;
|
|
11547
|
-
if (!element) return;
|
|
11548
|
-
if (element.paused) {
|
|
11549
|
-
if (refreshing) {
|
|
11550
|
-
pendingResumeRef.current = true;
|
|
11551
|
-
pendingResumeTimeRef.current = currentTime;
|
|
11618
|
+
const { isAuthenticated } = useAuth();
|
|
11619
|
+
const { state, cast, error } = useVote({
|
|
11620
|
+
targetType,
|
|
11621
|
+
targetId,
|
|
11622
|
+
initialState: initialState ?? null,
|
|
11623
|
+
enabled: isAuthenticated
|
|
11624
|
+
});
|
|
11625
|
+
const handleVote = React.useCallback(
|
|
11626
|
+
async (next) => {
|
|
11627
|
+
if (!isAuthenticated) {
|
|
11628
|
+
onSignInRequired?.();
|
|
11552
11629
|
return;
|
|
11553
11630
|
}
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11631
|
+
const desired = state.value === next ? 0 : next;
|
|
11632
|
+
try {
|
|
11633
|
+
await cast(desired);
|
|
11634
|
+
} catch (e) {
|
|
11635
|
+
onError?.(e);
|
|
11557
11636
|
}
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11637
|
+
},
|
|
11638
|
+
[cast, isAuthenticated, onSignInRequired, onError, state.value]
|
|
11639
|
+
);
|
|
11640
|
+
React.useEffect(() => {
|
|
11641
|
+
if (error) onError?.(error);
|
|
11642
|
+
}, [error, onError]);
|
|
11643
|
+
const compactStyle = size === "compact" ? STYLES2.buttonCompact : {};
|
|
11644
|
+
const upActive = state.value === 1;
|
|
11645
|
+
const downActive = state.value === -1;
|
|
11646
|
+
const upBtn = /* @__PURE__ */ jsxRuntime.jsx(
|
|
11647
|
+
"button",
|
|
11648
|
+
{
|
|
11649
|
+
type: "button",
|
|
11650
|
+
"aria-label": upLabel,
|
|
11651
|
+
"aria-pressed": upActive,
|
|
11652
|
+
title: isAuthenticated ? upLabel : "Sign in to vote",
|
|
11653
|
+
className: [classNames?.upButton, upActive ? classNames?.upButtonActive : void 0].filter(Boolean).join(" ") || void 0,
|
|
11654
|
+
onClick: () => void handleVote(1),
|
|
11655
|
+
style: classNames?.upButton ? void 0 : { ...STYLES2.button, ...compactStyle, ...upActive ? STYLES2.buttonActiveUp : {} },
|
|
11656
|
+
children: ARROW_UP
|
|
11563
11657
|
}
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
}
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
src: audio.url ?? void 0,
|
|
11590
|
-
preload: "metadata",
|
|
11591
|
-
onLoadedMetadata: (event) => {
|
|
11592
|
-
setDuration(event.currentTarget.duration || 0);
|
|
11593
|
-
},
|
|
11594
|
-
onTimeUpdate: (event) => {
|
|
11595
|
-
setCurrentTime(event.currentTarget.currentTime);
|
|
11596
|
-
},
|
|
11597
|
-
onPlay: () => setPlaying(true),
|
|
11598
|
-
onPause: () => setPlaying(false),
|
|
11599
|
-
onEnded: () => setPlaying(false),
|
|
11600
|
-
onError: () => {
|
|
11601
|
-
if (onRefresh) {
|
|
11602
|
-
void refreshAndResume().then((didRefresh) => {
|
|
11603
|
-
if (!didRefresh) {
|
|
11604
|
-
onPlaybackError?.();
|
|
11605
|
-
}
|
|
11606
|
-
});
|
|
11607
|
-
return;
|
|
11608
|
-
}
|
|
11609
|
-
onPlaybackError?.();
|
|
11610
|
-
}
|
|
11611
|
-
}
|
|
11612
|
-
),
|
|
11613
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.layout, children: [
|
|
11614
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11615
|
-
"button",
|
|
11616
|
-
{
|
|
11617
|
-
type: "button",
|
|
11618
|
-
onClick: togglePlayback,
|
|
11619
|
-
"aria-label": playing ? "Pause narration" : "Play narration",
|
|
11620
|
-
style: styles.playButton,
|
|
11621
|
-
children: playing ? /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", style: styles.playIcon, "aria-hidden": "true", children: [
|
|
11622
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1.2" }),
|
|
11623
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1.2" })
|
|
11624
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
11625
|
-
"svg",
|
|
11626
|
-
{
|
|
11627
|
-
viewBox: "0 0 24 24",
|
|
11628
|
-
style: { ...styles.playIcon, ...styles.playIconOffset },
|
|
11629
|
-
"aria-hidden": "true",
|
|
11630
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 5.2c0-1.02 1.12-1.65 2-.98l10.2 7.78a1.24 1.24 0 0 1 0 1.98L9 21.76c-.88.67-2-.04-2-1.08V5.2Z" })
|
|
11631
|
-
}
|
|
11632
|
-
)
|
|
11633
|
-
}
|
|
11634
|
-
),
|
|
11635
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.content, children: [
|
|
11636
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaRow, children: [
|
|
11637
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(currentTime / speed) }),
|
|
11638
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaRight, children: [
|
|
11639
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.badge, children: narrationLabel }),
|
|
11640
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration / speed) })
|
|
11641
|
-
] })
|
|
11642
|
-
] }),
|
|
11643
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11644
|
-
"div",
|
|
11645
|
-
{
|
|
11646
|
-
ref: scrubRef,
|
|
11647
|
-
role: "slider",
|
|
11648
|
-
"aria-label": "Narration progress",
|
|
11649
|
-
"aria-valuemin": 0,
|
|
11650
|
-
"aria-valuemax": Math.max(1, Math.floor(duration)),
|
|
11651
|
-
"aria-valuenow": Math.floor(currentTime),
|
|
11652
|
-
tabIndex: 0,
|
|
11653
|
-
onClick: (event) => {
|
|
11654
|
-
seekTo(event.clientX);
|
|
11655
|
-
},
|
|
11656
|
-
style: styles.scrubber,
|
|
11657
|
-
children: peaks.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
11658
|
-
"svg",
|
|
11659
|
-
{
|
|
11660
|
-
viewBox: `0 0 ${peaks.length} 40`,
|
|
11661
|
-
preserveAspectRatio: "none",
|
|
11662
|
-
style: styles.waveform,
|
|
11663
|
-
children: peaks.map((peak, index) => {
|
|
11664
|
-
const height = Math.max(2, Math.round(Math.abs(peak) * 34));
|
|
11665
|
-
const y = (40 - height) / 2;
|
|
11666
|
-
const filled = index / peaks.length < progress;
|
|
11667
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11668
|
-
"rect",
|
|
11669
|
-
{
|
|
11670
|
-
x: index,
|
|
11671
|
-
y,
|
|
11672
|
-
width: 0.82,
|
|
11673
|
-
height,
|
|
11674
|
-
rx: 0.4,
|
|
11675
|
-
fill: filled ? "#155dfc" : "#90c5ff"
|
|
11676
|
-
},
|
|
11677
|
-
`${audio.id}-${index}`
|
|
11678
|
-
);
|
|
11679
|
-
})
|
|
11680
|
-
}
|
|
11681
|
-
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11682
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.fallbackTrack }),
|
|
11683
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11684
|
-
"div",
|
|
11685
|
-
{
|
|
11686
|
-
style: { ...styles.fallbackProgress, width: `${progress * 100}%` }
|
|
11687
|
-
}
|
|
11688
|
-
)
|
|
11689
|
-
] })
|
|
11690
|
-
}
|
|
11691
|
-
),
|
|
11692
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.chipsRow, children: [
|
|
11693
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipStrong, children: [
|
|
11694
|
-
"Provider: ",
|
|
11695
|
-
providerLabel
|
|
11696
|
-
] }),
|
|
11697
|
-
duration > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipMuted, children: [
|
|
11698
|
-
"Length ",
|
|
11699
|
-
formatTime(duration / speed)
|
|
11700
|
-
] }),
|
|
11701
|
-
expiresLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.chipMuted, children: [
|
|
11702
|
-
"Expires ",
|
|
11703
|
-
expiresLabel
|
|
11704
|
-
] })
|
|
11705
|
-
] })
|
|
11706
|
-
] }),
|
|
11707
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.controls, children: [
|
|
11708
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11709
|
-
"button",
|
|
11710
|
-
{
|
|
11711
|
-
type: "button",
|
|
11712
|
-
onClick: () => {
|
|
11713
|
-
setSpeed((currentSpeed) => getNextSpeed(currentSpeed));
|
|
11714
|
-
},
|
|
11715
|
-
style: styles.secondaryButton,
|
|
11716
|
-
children: [
|
|
11717
|
-
speed,
|
|
11718
|
-
"\xD7"
|
|
11719
|
-
]
|
|
11720
|
-
}
|
|
11721
|
-
),
|
|
11722
|
-
onRefresh && showRefreshButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
11723
|
-
"button",
|
|
11724
|
-
{
|
|
11725
|
-
type: "button",
|
|
11726
|
-
onClick: onRefresh,
|
|
11727
|
-
disabled: refreshing,
|
|
11728
|
-
style: refreshing ? { ...styles.refreshButton, ...styles.disabled } : styles.refreshButton,
|
|
11729
|
-
children: refreshing ? "Refreshing\u2026" : "Refresh"
|
|
11730
|
-
}
|
|
11731
|
-
)
|
|
11732
|
-
] })
|
|
11733
|
-
] })
|
|
11658
|
+
);
|
|
11659
|
+
const downBtn = /* @__PURE__ */ jsxRuntime.jsx(
|
|
11660
|
+
"button",
|
|
11661
|
+
{
|
|
11662
|
+
type: "button",
|
|
11663
|
+
"aria-label": downLabel,
|
|
11664
|
+
"aria-pressed": downActive,
|
|
11665
|
+
title: isAuthenticated ? downLabel : "Sign in to vote",
|
|
11666
|
+
className: [classNames?.downButton, downActive ? classNames?.downButtonActive : void 0].filter(Boolean).join(" ") || void 0,
|
|
11667
|
+
onClick: () => void handleVote(-1),
|
|
11668
|
+
style: classNames?.downButton ? void 0 : { ...STYLES2.button, ...compactStyle, ...downActive ? STYLES2.buttonActiveDown : {} },
|
|
11669
|
+
children: ARROW_DOWN
|
|
11670
|
+
}
|
|
11671
|
+
);
|
|
11672
|
+
const scoreClass = [
|
|
11673
|
+
classNames?.score,
|
|
11674
|
+
state.score > 0 ? classNames?.scorePositive : void 0,
|
|
11675
|
+
state.score < 0 ? classNames?.scoreNegative : void 0
|
|
11676
|
+
].filter(Boolean).join(" ") || void 0;
|
|
11677
|
+
const scoreEl = /* @__PURE__ */ jsxRuntime.jsx("span", { className: scoreClass, style: classNames?.score ? void 0 : STYLES2.score, children: state.score });
|
|
11678
|
+
const rootStyle = layout === "stacked" ? STYLES2.rootStacked : STYLES2.root;
|
|
11679
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, style: classNames?.root ? void 0 : rootStyle, children: [
|
|
11680
|
+
upBtn,
|
|
11681
|
+
scoreEl,
|
|
11682
|
+
downBtn
|
|
11734
11683
|
] });
|
|
11735
11684
|
}
|
|
11736
11685
|
|
|
@@ -12034,11 +11983,11 @@ Object.defineProperty(exports, "createMoneyClient", {
|
|
|
12034
11983
|
get: function () { return money.createMoneyClient; }
|
|
12035
11984
|
});
|
|
12036
11985
|
exports.FeedbackWidget = FeedbackWidget;
|
|
12037
|
-
exports.NarrationPlayer = NarrationPlayer;
|
|
12038
11986
|
exports.ScaleMuleApiError = ScaleMuleApiError;
|
|
12039
11987
|
exports.ScaleMuleClient = ScaleMuleClient2;
|
|
12040
11988
|
exports.ScaleMuleMedia = ScaleMuleMedia;
|
|
12041
11989
|
exports.ScaleMuleProvider = ScaleMuleProvider;
|
|
11990
|
+
exports.VoteButton = VoteButton;
|
|
12042
11991
|
exports.composePhone = composePhone;
|
|
12043
11992
|
exports.createClient = createClient;
|
|
12044
11993
|
exports.createSafeLogger = createSafeLogger;
|
|
@@ -12063,5 +12012,6 @@ exports.useScaleMuleClient = useScaleMuleClient;
|
|
|
12063
12012
|
exports.useShare = useShare;
|
|
12064
12013
|
exports.useTtsJob = useTtsJob;
|
|
12065
12014
|
exports.useUser = useUser;
|
|
12015
|
+
exports.useVote = useVote;
|
|
12066
12016
|
exports.validateForm = validateForm;
|
|
12067
12017
|
exports.validators = validators;
|