@buoy-gg/license 7.0.58 → 7.0.60
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/lib/commonjs/nodeSession.js +1 -1
- package/lib/module/nodeSession.js +1 -1
- package/lib/typescript/commonjs/headless.d.ts +1 -1
- package/lib/typescript/commonjs/nodeSession.d.ts +16 -0
- package/lib/typescript/commonjs/sessionManager.d.ts +1 -0
- package/lib/typescript/module/headless.d.ts +1 -1
- package/lib/typescript/module/nodeSession.d.ts +16 -0
- package/lib/typescript/module/sessionManager.d.ts +1 -0
- package/package.json +1 -1
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
|
|
5
5
|
* (https://buoy.gg/terms), not an OSS license.
|
|
6
|
-
*/const DESKTOP_AUDIENCE=exports.DESKTOP_AUDIENCE="app:gg.buoy.desktop";const CLI_AUDIENCE=exports.CLI_AUDIENCE="app:gg.buoy.cli";const LOGIN_TIMEOUT_MS=5*60*1e3;const MAX_BODY_BYTES=4096;const LOCK_STALE_MS=6e4;const LOCK_WAIT_MS=3e4;function defaultSessionFile(){return process.env.BUOY_SESSION_FILE||(0,_nodePath.join)((0,_nodeOs.homedir)(),".buoy","session.json")}function fileSessionStorage(e){const
|
|
6
|
+
*/const DESKTOP_AUDIENCE=exports.DESKTOP_AUDIENCE="app:gg.buoy.desktop";const CLI_AUDIENCE=exports.CLI_AUDIENCE="app:gg.buoy.cli";const LOGIN_TIMEOUT_MS=5*60*1e3;const MAX_BODY_BYTES=4096;const LOCK_STALE_MS=6e4;const LOCK_WAIT_MS=3e4;const STATUS_TIMEOUT_MS=8e3;const DEV_TOKEN_TIMEOUT_MS=15e3;const TLS_INTERCEPT_CODES=new Set(["UNABLE_TO_GET_ISSUER_CERT_LOCALLY","SELF_SIGNED_CERT_IN_CHAIN","DEPTH_ZERO_SELF_SIGNED_CERT","UNABLE_TO_VERIFY_LEAF_SIGNATURE","CERT_UNTRUSTED"]);function describeFetchError(e){const r=e?.cause;if(r&&typeof r.code==="string")return r.code;if(e?.name==="TimeoutError")return"timed out";if(r&&typeof r.message==="string")return r.message;return e instanceof Error?e.message:String(e)}function defaultSessionFile(){return process.env.BUOY_SESSION_FILE||(0,_nodePath.join)((0,_nodeOs.homedir)(),".buoy","session.json")}function fileSessionStorage(e){const r=async()=>{try{const n=JSON.parse(await _nodeFs.promises.readFile(e,"utf8"));return n&&typeof n==="object"?n:{}}catch{return{}}};const u=async n=>{await _nodeFs.promises.mkdir((0,_nodePath.dirname)(e),{recursive:true,mode:448});if(Object.keys(n).length===0){await _nodeFs.promises.rm(e,{force:true});return}const i=`${e}.${process.pid}.${(0,_nodeCrypto.randomBytes)(4).toString("hex")}.tmp`;await _nodeFs.promises.writeFile(i,JSON.stringify(n),{mode:384});await _nodeFs.promises.rename(i,e)};return{async getItem(n){return(await r())[n]??null},async setItem(n,i){const d=await r();d[n]=i;await u(d)},async removeItem(n){const i=await r();if(!(n in i))return;delete i[n];await u(i)}}}function fileLock(e,r={}){const u=r.staleMs??LOCK_STALE_MS;const n=r.waitMs??LOCK_WAIT_MS;return async(i,d)=>{const a=`${process.pid}:${(0,_nodeCrypto.randomBytes)(8).toString("hex")}`;const p=Date.now()+n;await _nodeFs.promises.mkdir((0,_nodePath.dirname)(e),{recursive:true,mode:448});for(;;){try{await _nodeFs.promises.writeFile(e,a,{flag:"wx",mode:384});break}catch(t){if(t.code!=="EEXIST")throw t;const s=await _nodeFs.promises.stat(e).catch(()=>null);if(s&&Date.now()-s.mtimeMs>u){await _nodeFs.promises.rm(e,{force:true});continue}if(Date.now()>p)throw new Error("Another Buoy process holds the sign-in lock.");await new Promise(o=>setTimeout(o,50))}}try{return await d()}finally{const t=await _nodeFs.promises.readFile(e,"utf8").catch(()=>null);if(t===a)await _nodeFs.promises.rm(e,{force:true})}}}function safeEqual(e,r){const u=Buffer.from(e);const n=Buffer.from(r);return u.length===n.length&&(0,_nodeCrypto.timingSafeEqual)(u,n)}async function receiveCodeOnLoopback(e,r,u,n={}){let i;let d;const a=new Promise((c,l)=>{i=c;d=l});a.catch(()=>{});const p=_nodeHttp.default.createServer((c,l)=>{if(c.headers.origin===u){l.setHeader("Access-Control-Allow-Origin",u);l.setHeader("Access-Control-Allow-Headers","Content-Type");l.setHeader("Access-Control-Allow-Methods","POST, OPTIONS");l.setHeader("Access-Control-Allow-Private-Network","true")}if(c.method==="OPTIONS")return void l.writeHead(204).end();if(c.method!=="POST")return void l.writeHead(405).end();let m="";c.on("data",g=>{m+=g;if(m.length>MAX_BODY_BYTES)c.destroy()});c.on("end",()=>{let g=null;try{g=JSON.parse(m)}catch{}if(!g||typeof g.state!=="string"||!safeEqual(g.state,r))return void l.writeHead(400).end();l.writeHead(200,{"Content-Type":"application/json"}).end('{"ok":true}');if(typeof g.code==="string"&&g.code)o(()=>i(g.code));else o(()=>d(new _sessionManager.SessionError("access_denied","Sign-in was cancelled.")))})});const t=setTimeout(()=>o(()=>d(new _sessionManager.SessionError("timeout","Sign-in took too long."))),n.timeoutMs??LOGIN_TIMEOUT_MS);t.unref?.();const s=()=>o(()=>d(new _sessionManager.SessionError("popup_closed","Sign-in was cancelled.")));n.signal?.addEventListener("abort",s);function o(c){clearTimeout(t);n.signal?.removeEventListener("abort",s);p.close();c()}await new Promise((c,l)=>{p.once("error",l);p.listen(0,"127.0.0.1",()=>c())});const f=p.address();if(!f||typeof f==="string"){o(()=>d(new Error("Could not open a local port")));throw new Error("Could not open a local port")}return{url:`${e}&port=${f.port}`,code:a}}function createNodeSession(e){const r=defaultSessionFile();const u=e.storage??fileSessionStorage(r);const n=(e.signinOrigin??process.env.BUOY_SITE_URL??"https://buoy.gg").replace(/\/$/,"");const i=e.fetch??((t,s)=>fetch(t,s));const d={fetch:i,storage:async()=>u,now:e.now??(()=>Date.now()),random:t=>new Uint8Array((0,_nodeCrypto.randomBytes)(t)),sha256:async t=>new Uint8Array((0,_nodeCrypto.createHash)("sha256").update(t).digest()),openPopup:()=>null,onMessage:()=>()=>{},withLock:fileLock(e.lockFile??`${r}.lock`),setTimer:(t,s)=>{const o=setTimeout(t,Math.min(s,2147483647));o.unref?.();return o},clearTimer:t=>clearTimeout(t),publicKeyHex:e.publicKeyHex};const a=new _sessionManager.SessionManagerImpl(d);a.configure({audience:e.audience,signinOrigin:n});const p=async()=>{let t;try{t=await i(`${n}/api/auth/status`,{method:"GET",headers:{},signal:AbortSignal.timeout(STATUS_TIMEOUT_MS)})}catch(o){const f=describeFetchError(o);return TLS_INTERCEPT_CODES.has(f)?{state:"unreachable",detail:f,hint:"Your network may re-sign secure traffic. Point NODE_EXTRA_CA_CERTS at your company's root certificate."}:{state:"unreachable",detail:f}}const s=await t.json().catch(()=>null);if(t.status===200&&s?.enabled===true)return{state:"enabled"};if(t.status===200&&s?.enabled===false)return{state:"disabled"};return{state:"unreachable",detail:s?`HTTP ${t.status}`:`HTTP ${t.status}, not a Buoy answer`}};return{manager:a,async load(){await a.load();return a.getStatus()},getStatus:()=>a.getStatus(),signIn:(t={})=>a.signInWith(async(s,o)=>{const{url:f,code:c}=await receiveCodeOnLoopback(s,o,n,t);await e.openBrowser(f);return c}),signOut:()=>a.signOut(),refresh:t=>a.refresh(t),sync:()=>a.sync(),async devToken(){const t=a.getAccessToken();if(!t)return{ok:false,detail:"This computer is not signed in. Run `npx buoy login`."};try{const s=await i(`${n}/api/auth/dev-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:t}),signal:AbortSignal.timeout(DEV_TOKEN_TIMEOUT_MS)});const o=await s.json().catch(()=>null);if(s.status===200&&typeof o?.dev_token==="string"){return{ok:true,token:o.dev_token,tier:typeof o.tier==="string"?o.tier:"free"}}return{ok:false,detail:typeof o?.error_description==="string"?o.error_description:"buoy.gg did not make a dev token."}}catch{return{ok:false,detail:"Buoy could not be reached. Check your connection and try again."}}},checkAvailability:p,async isAvailable(){return(await p()).state==="enabled"},subscribe:t=>a.subscribe(t)}}
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Buoy. All rights reserved. Licensed under the Buoy EULA
|
|
5
5
|
* (https://buoy.gg/terms), not an OSS license.
|
|
6
|
-
*/import
|
|
6
|
+
*/import E from"node:http";import{createHash as T,randomBytes as y,timingSafeEqual as _}from"node:crypto";import{promises as f}from"node:fs";import{dirname as S,join as b}from"node:path";import{homedir as k}from"node:os";import{SessionManagerImpl as O,SessionError as h}from"./sessionManager.js";export const DESKTOP_AUDIENCE="app:gg.buoy.desktop";export const CLI_AUDIENCE="app:gg.buoy.cli";const A=5*60*1e3;const C=4096;const I=6e4;const v=3e4;const N=8e3;const L=15e3;const H=new Set(["UNABLE_TO_GET_ISSUER_CERT_LOCALLY","SELF_SIGNED_CERT_IN_CHAIN","DEPTH_ZERO_SELF_SIGNED_CERT","UNABLE_TO_VERIFY_LEAF_SIGNATURE","CERT_UNTRUSTED"]);function P(t){const r=t?.cause;if(r&&typeof r.code==="string")return r.code;if(t?.name==="TimeoutError")return"timed out";if(r&&typeof r.message==="string")return r.message;return t instanceof Error?t.message:String(t)}export function defaultSessionFile(){return process.env.BUOY_SESSION_FILE||b(k(),".buoy","session.json")}export function fileSessionStorage(t){const r=async()=>{try{const n=JSON.parse(await f.readFile(t,"utf8"));return n&&typeof n==="object"?n:{}}catch{return{}}};const u=async n=>{await f.mkdir(S(t),{recursive:true,mode:448});if(Object.keys(n).length===0){await f.rm(t,{force:true});return}const i=`${t}.${process.pid}.${y(4).toString("hex")}.tmp`;await f.writeFile(i,JSON.stringify(n),{mode:384});await f.rename(i,t)};return{async getItem(n){return(await r())[n]??null},async setItem(n,i){const l=await r();l[n]=i;await u(l)},async removeItem(n){const i=await r();if(!(n in i))return;delete i[n];await u(i)}}}export function fileLock(t,r={}){const u=r.staleMs??I;const n=r.waitMs??v;return async(i,l)=>{const s=`${process.pid}:${y(8).toString("hex")}`;const p=Date.now()+n;await f.mkdir(S(t),{recursive:true,mode:448});for(;;){try{await f.writeFile(t,s,{flag:"wx",mode:384});break}catch(e){if(e.code!=="EEXIST")throw e;const a=await f.stat(t).catch(()=>null);if(a&&Date.now()-a.mtimeMs>u){await f.rm(t,{force:true});continue}if(Date.now()>p)throw new Error("Another Buoy process holds the sign-in lock.");await new Promise(o=>setTimeout(o,50))}}try{return await l()}finally{const e=await f.readFile(t,"utf8").catch(()=>null);if(e===s)await f.rm(t,{force:true})}}}function U(t,r){const u=Buffer.from(t);const n=Buffer.from(r);return u.length===n.length&&_(u,n)}export async function receiveCodeOnLoopback(t,r,u,n={}){let i;let l;const s=new Promise((c,d)=>{i=c;l=d});s.catch(()=>{});const p=E.createServer((c,d)=>{if(c.headers.origin===u){d.setHeader("Access-Control-Allow-Origin",u);d.setHeader("Access-Control-Allow-Headers","Content-Type");d.setHeader("Access-Control-Allow-Methods","POST, OPTIONS");d.setHeader("Access-Control-Allow-Private-Network","true")}if(c.method==="OPTIONS")return void d.writeHead(204).end();if(c.method!=="POST")return void d.writeHead(405).end();let g="";c.on("data",w=>{g+=w;if(g.length>C)c.destroy()});c.on("end",()=>{let w=null;try{w=JSON.parse(g)}catch{}if(!w||typeof w.state!=="string"||!U(w.state,r))return void d.writeHead(400).end();d.writeHead(200,{"Content-Type":"application/json"}).end('{"ok":true}');if(typeof w.code==="string"&&w.code)o(()=>i(w.code));else o(()=>l(new h("access_denied","Sign-in was cancelled.")))})});const e=setTimeout(()=>o(()=>l(new h("timeout","Sign-in took too long."))),n.timeoutMs??A);e.unref?.();const a=()=>o(()=>l(new h("popup_closed","Sign-in was cancelled.")));n.signal?.addEventListener("abort",a);function o(c){clearTimeout(e);n.signal?.removeEventListener("abort",a);p.close();c()}await new Promise((c,d)=>{p.once("error",d);p.listen(0,"127.0.0.1",()=>c())});const m=p.address();if(!m||typeof m==="string"){o(()=>l(new Error("Could not open a local port")));throw new Error("Could not open a local port")}return{url:`${t}&port=${m.port}`,code:s}}export function createNodeSession(t){const r=defaultSessionFile();const u=t.storage??fileSessionStorage(r);const n=(t.signinOrigin??process.env.BUOY_SITE_URL??"https://buoy.gg").replace(/\/$/,"");const i=t.fetch??((e,a)=>fetch(e,a));const l={fetch:i,storage:async()=>u,now:t.now??(()=>Date.now()),random:e=>new Uint8Array(y(e)),sha256:async e=>new Uint8Array(T("sha256").update(e).digest()),openPopup:()=>null,onMessage:()=>()=>{},withLock:fileLock(t.lockFile??`${r}.lock`),setTimer:(e,a)=>{const o=setTimeout(e,Math.min(a,2147483647));o.unref?.();return o},clearTimer:e=>clearTimeout(e),publicKeyHex:t.publicKeyHex};const s=new O(l);s.configure({audience:t.audience,signinOrigin:n});const p=async()=>{let e;try{e=await i(`${n}/api/auth/status`,{method:"GET",headers:{},signal:AbortSignal.timeout(N)})}catch(o){const m=P(o);return H.has(m)?{state:"unreachable",detail:m,hint:"Your network may re-sign secure traffic. Point NODE_EXTRA_CA_CERTS at your company's root certificate."}:{state:"unreachable",detail:m}}const a=await e.json().catch(()=>null);if(e.status===200&&a?.enabled===true)return{state:"enabled"};if(e.status===200&&a?.enabled===false)return{state:"disabled"};return{state:"unreachable",detail:a?`HTTP ${e.status}`:`HTTP ${e.status}, not a Buoy answer`}};return{manager:s,async load(){await s.load();return s.getStatus()},getStatus:()=>s.getStatus(),signIn:(e={})=>s.signInWith(async(a,o)=>{const{url:m,code:c}=await receiveCodeOnLoopback(a,o,n,e);await t.openBrowser(m);return c}),signOut:()=>s.signOut(),refresh:e=>s.refresh(e),sync:()=>s.sync(),async devToken(){const e=s.getAccessToken();if(!e)return{ok:false,detail:"This computer is not signed in. Run `npx buoy login`."};try{const a=await i(`${n}/api/auth/dev-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:e}),signal:AbortSignal.timeout(L)});const o=await a.json().catch(()=>null);if(a.status===200&&typeof o?.dev_token==="string"){return{ok:true,token:o.dev_token,tier:typeof o.tier==="string"?o.tier:"free"}}return{ok:false,detail:typeof o?.error_description==="string"?o.error_description:"buoy.gg did not make a dev token."}}catch{return{ok:false,detail:"Buoy could not be reached. Check your connection and try again."}}},checkAvailability:p,async isAvailable(){return(await p()).state==="enabled"},subscribe:e=>s.subscribe(e)}}
|
|
@@ -8,7 +8,7 @@ export { signCache, verifyCacheSignature } from "./cacheSignature";
|
|
|
8
8
|
export { LICENSE_CACHE_DURATION, LICENSE_REVALIDATION_INTERVAL, LICENSE_LAUNCH_VALIDATION_TTL, LICENSE_TRANSIENT_RETRY_DELAYS, KEYGEN_CONFIG } from "./config";
|
|
9
9
|
export type { CachedLicense, BuoyTier, KeygenValidationResponse } from "./types";
|
|
10
10
|
export { createNodeSession, fileSessionStorage, fileLock, receiveCodeOnLoopback, defaultSessionFile, DESKTOP_AUDIENCE, CLI_AUDIENCE, } from "./nodeSession";
|
|
11
|
-
export type { NodeSession, NodeSessionOptions, NodeSessionStorage } from "./nodeSession";
|
|
11
|
+
export type { NodeSession, NodeSessionOptions, NodeSessionStorage, SignInAvailability } from "./nodeSession";
|
|
12
12
|
export { SessionError, SessionManagerImpl, SESSION_OFFLINE_GRACE_MS } from "./sessionManager";
|
|
13
13
|
export type { SessionStatus, SessionErrorCode, CodePrompt } from "./sessionManager";
|
|
14
14
|
export { verifySessionToken, mintSessionToken, __setSessionTokenPublicKeyForTests } from "./sessionToken";
|
|
@@ -8,6 +8,20 @@ import { SessionManagerImpl, type SessionDeps, type SessionStatus } from "./sess
|
|
|
8
8
|
/** First-party app ids buoy.gg signs in without a site list. Keep in step with apps/website signin.server.ts. */
|
|
9
9
|
export declare const DESKTOP_AUDIENCE = "app:gg.buoy.desktop";
|
|
10
10
|
export declare const CLI_AUDIENCE = "app:gg.buoy.cli";
|
|
11
|
+
/**
|
|
12
|
+
* Whether buoy.gg has sign-in on. "unreachable" is kept apart from
|
|
13
|
+
* "disabled": a VPN or proxy that lets the browser through but not Node lands
|
|
14
|
+
* here, and the CLI has to say so instead of quietly taking another path.
|
|
15
|
+
*/
|
|
16
|
+
export type SignInAvailability = {
|
|
17
|
+
state: "enabled";
|
|
18
|
+
} | {
|
|
19
|
+
state: "disabled";
|
|
20
|
+
} | {
|
|
21
|
+
state: "unreachable";
|
|
22
|
+
detail: string;
|
|
23
|
+
hint?: string;
|
|
24
|
+
};
|
|
11
25
|
export interface NodeSessionStorage {
|
|
12
26
|
getItem(key: string): Promise<string | null>;
|
|
13
27
|
setItem(key: string, value: string): Promise<void>;
|
|
@@ -80,6 +94,8 @@ export interface NodeSession {
|
|
|
80
94
|
sync(): Promise<void>;
|
|
81
95
|
/** Whether buoy.gg has sign-in turned on. False when it can't be reached. */
|
|
82
96
|
isAvailable(): Promise<boolean>;
|
|
97
|
+
/** Like isAvailable, but says why: off, or not reachable from this process. */
|
|
98
|
+
checkAvailability(): Promise<SignInAvailability>;
|
|
83
99
|
subscribe(listener: () => void): () => void;
|
|
84
100
|
}
|
|
85
101
|
export declare function createNodeSession(options: NodeSessionOptions): NodeSession;
|
|
@@ -8,7 +8,7 @@ export { signCache, verifyCacheSignature } from "./cacheSignature";
|
|
|
8
8
|
export { LICENSE_CACHE_DURATION, LICENSE_REVALIDATION_INTERVAL, LICENSE_LAUNCH_VALIDATION_TTL, LICENSE_TRANSIENT_RETRY_DELAYS, KEYGEN_CONFIG } from "./config";
|
|
9
9
|
export type { CachedLicense, BuoyTier, KeygenValidationResponse } from "./types";
|
|
10
10
|
export { createNodeSession, fileSessionStorage, fileLock, receiveCodeOnLoopback, defaultSessionFile, DESKTOP_AUDIENCE, CLI_AUDIENCE, } from "./nodeSession";
|
|
11
|
-
export type { NodeSession, NodeSessionOptions, NodeSessionStorage } from "./nodeSession";
|
|
11
|
+
export type { NodeSession, NodeSessionOptions, NodeSessionStorage, SignInAvailability } from "./nodeSession";
|
|
12
12
|
export { SessionError, SessionManagerImpl, SESSION_OFFLINE_GRACE_MS } from "./sessionManager";
|
|
13
13
|
export type { SessionStatus, SessionErrorCode, CodePrompt } from "./sessionManager";
|
|
14
14
|
export { verifySessionToken, mintSessionToken, __setSessionTokenPublicKeyForTests } from "./sessionToken";
|
|
@@ -8,6 +8,20 @@ import { SessionManagerImpl, type SessionDeps, type SessionStatus } from "./sess
|
|
|
8
8
|
/** First-party app ids buoy.gg signs in without a site list. Keep in step with apps/website signin.server.ts. */
|
|
9
9
|
export declare const DESKTOP_AUDIENCE = "app:gg.buoy.desktop";
|
|
10
10
|
export declare const CLI_AUDIENCE = "app:gg.buoy.cli";
|
|
11
|
+
/**
|
|
12
|
+
* Whether buoy.gg has sign-in on. "unreachable" is kept apart from
|
|
13
|
+
* "disabled": a VPN or proxy that lets the browser through but not Node lands
|
|
14
|
+
* here, and the CLI has to say so instead of quietly taking another path.
|
|
15
|
+
*/
|
|
16
|
+
export type SignInAvailability = {
|
|
17
|
+
state: "enabled";
|
|
18
|
+
} | {
|
|
19
|
+
state: "disabled";
|
|
20
|
+
} | {
|
|
21
|
+
state: "unreachable";
|
|
22
|
+
detail: string;
|
|
23
|
+
hint?: string;
|
|
24
|
+
};
|
|
11
25
|
export interface NodeSessionStorage {
|
|
12
26
|
getItem(key: string): Promise<string | null>;
|
|
13
27
|
setItem(key: string, value: string): Promise<void>;
|
|
@@ -80,6 +94,8 @@ export interface NodeSession {
|
|
|
80
94
|
sync(): Promise<void>;
|
|
81
95
|
/** Whether buoy.gg has sign-in turned on. False when it can't be reached. */
|
|
82
96
|
isAvailable(): Promise<boolean>;
|
|
97
|
+
/** Like isAvailable, but says why: off, or not reachable from this process. */
|
|
98
|
+
checkAvailability(): Promise<SignInAvailability>;
|
|
83
99
|
subscribe(listener: () => void): () => void;
|
|
84
100
|
}
|
|
85
101
|
export declare function createNodeSession(options: NodeSessionOptions): NodeSession;
|