Clean up legacy 0.3.5 scaffolding and standalone-mode artifacts
- Removes start9/0.4/ — the StartOS 0.3.5-style YAML manifest folder, superseded by the TypeScript-based startos/ package. - Removes pre-StartOS standalone-mode artifacts: setup.sh, create-app.sh, Start Summarizer.command, build-guide-pdf.py, GET-STARTED.md/.pdf. - Removes conversion-era design docs (START9_PACKAGING_GAMEPLAN.md, UPGRADE-DESIGN.md, KEYSAT_INTEGRATION.md). Recoverable from history if needed. - Tightens .gitignore: untracks node_modules, javascript/ build output, *.s9pk, history/ user data, cookies.txt, library export, .env, .DS_Store, and .claude/ worktree state. Files remain on disk; just removed from the index.
This commit is contained in:
+24
@@ -5,3 +5,27 @@
|
||||
# prompt. Written by Claude after code changes, consumed + deleted on the
|
||||
# next `make deploy` / `make bump`.
|
||||
.release-notes-pending.txt
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Build artifacts (regenerated by `make` / `npm run build`)
|
||||
javascript/
|
||||
*.s9pk
|
||||
image.tar
|
||||
|
||||
# Runtime / user data — must never be committed
|
||||
history/
|
||||
cookies.txt
|
||||
*.txt.bak
|
||||
youtube-summarizer-library-export*.json
|
||||
ytdlp-cache/
|
||||
|
||||
# Local dev secrets
|
||||
.env
|
||||
|
||||
# Claude Code state (worktrees, plans, etc.)
|
||||
.claude/
|
||||
|
||||
-198
@@ -1,198 +0,0 @@
|
||||
# YouTube Transcript Summarizer — Mac Setup Guide
|
||||
|
||||
This is a step-by-step guide to get the app running on your Mac. No prior experience with servers or Terminal needed.
|
||||
|
||||
---
|
||||
|
||||
## What you're setting up
|
||||
|
||||
This app has two parts:
|
||||
|
||||
1. **A small server** that runs locally on your Mac (not on the internet — only you can access it). It downloads YouTube audio and talks to Google's Gemini AI.
|
||||
2. **A webpage** you open in your browser that connects to that local server.
|
||||
|
||||
Nothing is uploaded anywhere. Everything runs on your machine.
|
||||
|
||||
---
|
||||
|
||||
## Where to save the project
|
||||
|
||||
The `youtube-summarizer` folder you downloaded from Cowork can live anywhere, but the conventional place for projects like this on a Mac is:
|
||||
|
||||
```
|
||||
~/Projects/youtube-summarizer
|
||||
```
|
||||
|
||||
`~` means your home folder (e.g., `/Users/grant`). If you don't have a `Projects` folder yet, you'll create one below.
|
||||
|
||||
**To move the files there:**
|
||||
|
||||
1. Open **Finder**
|
||||
2. Go to your home folder (press `Cmd + Shift + H`)
|
||||
3. Create a new folder called `Projects` if you don't have one (right-click → New Folder)
|
||||
4. Drag the `youtube-summarizer` folder from your Cowork downloads into `Projects`
|
||||
|
||||
You should end up with this structure:
|
||||
|
||||
```
|
||||
~/Projects/youtube-summarizer/
|
||||
├── public/
|
||||
│ └── index.html ← the app you open in your browser
|
||||
├── server/
|
||||
│ ├── index.js ← the backend server code
|
||||
│ └── package.json ← lists the server's dependencies
|
||||
├── setup.sh ← automatic setup script
|
||||
└── GET-STARTED.md ← this file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install Node.js (if you don't have it)
|
||||
|
||||
Node.js is what runs the server. Check if you already have it:
|
||||
|
||||
1. Open **Terminal** (press `Cmd + Space`, type "Terminal", hit Enter)
|
||||
2. Type this and press Enter:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
- If you see a version number like `v20.11.0` → you're good, skip to Step 2
|
||||
- If you see "command not found" → install it:
|
||||
1. Go to **https://nodejs.org**
|
||||
2. Download the **LTS** version (the green button)
|
||||
3. Open the downloaded `.pkg` file and follow the installer
|
||||
4. Close and reopen Terminal, then try `node --version` again
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Run the setup script
|
||||
|
||||
This installs yt-dlp (the YouTube audio downloader) and the server's dependencies. In Terminal:
|
||||
|
||||
```bash
|
||||
cd ~/Projects/youtube-summarizer
|
||||
bash setup.sh
|
||||
```
|
||||
|
||||
You should see checkmarks for Node.js, yt-dlp, and the server dependencies. If yt-dlp isn't found and can't auto-install, run:
|
||||
|
||||
```bash
|
||||
brew install yt-dlp
|
||||
```
|
||||
|
||||
(If you don't have Homebrew either, install it first from **https://brew.sh** — it's a one-line Terminal command shown on their homepage.)
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Start the server
|
||||
|
||||
```bash
|
||||
cd ~/Projects/youtube-summarizer/server
|
||||
npm start
|
||||
```
|
||||
|
||||
You should see:
|
||||
|
||||
```
|
||||
YouTube Summarizer API running on http://localhost:3001
|
||||
✓ yt-dlp 2025.x.x (up to date)
|
||||
```
|
||||
|
||||
**Leave this Terminal window open.** The server runs as long as this window is open. To stop it later, press `Ctrl + C`.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Open the app
|
||||
|
||||
Open the webpage in your browser. The easiest way — open a **new** Terminal tab (`Cmd + T`) and run:
|
||||
|
||||
```bash
|
||||
open ~/Projects/youtube-summarizer/public/index.html
|
||||
```
|
||||
|
||||
Or just double-click `index.html` in Finder. It opens like any webpage.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Add your Gemini API key
|
||||
|
||||
1. In the app, click **"Gemini API Settings"** to expand it
|
||||
2. Paste your Gemini API key into the API Key field
|
||||
3. Pick a model (the default `gemini-2.0-flash` is fast and cheap)
|
||||
|
||||
**Don't have a key yet?** Get one free:
|
||||
1. Go to **https://aistudio.google.com/apikey**
|
||||
2. Sign in with your Google account
|
||||
3. Click "Create API Key"
|
||||
4. Copy the key and paste it in the app
|
||||
|
||||
Your key is saved in your browser's localStorage so you don't have to re-enter it each time.
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Summarize a video
|
||||
|
||||
1. Paste a YouTube URL
|
||||
2. Click **Summarize**
|
||||
3. Watch the 3-step pipeline: Download audio → Transcribe → Analyze topics
|
||||
4. Click any topic section to expand the full transcript with clickable timestamps
|
||||
|
||||
---
|
||||
|
||||
## Day-to-day usage
|
||||
|
||||
Each time you want to use the app:
|
||||
|
||||
1. Open Terminal
|
||||
2. Run:
|
||||
```bash
|
||||
cd ~/Projects/youtube-summarizer/server && npm start
|
||||
```
|
||||
3. Open `index.html` in your browser (bookmark it for easy access)
|
||||
4. When done, press `Ctrl + C` in Terminal to stop the server
|
||||
|
||||
That's it. Two commands.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Cannot connect to backend at localhost:3001"**
|
||||
→ The server isn't running. Go back to Step 3.
|
||||
|
||||
**"yt-dlp not installed"**
|
||||
→ Run `brew install yt-dlp` in Terminal, then restart the server.
|
||||
|
||||
**Download fails or hangs**
|
||||
→ yt-dlp might be outdated. The app will try to auto-update it, but you can also manually run:
|
||||
```bash
|
||||
yt-dlp -U
|
||||
```
|
||||
|
||||
**"Gemini API error: 403" or "401"**
|
||||
→ Your API key is invalid or expired. Get a new one from https://aistudio.google.com/apikey
|
||||
|
||||
**Long videos take a while**
|
||||
→ Normal. A 1-hour video takes ~30 seconds to download audio, then 30-60 seconds for Gemini to transcribe, then another 10-20 seconds for topic analysis. The app shows live progress.
|
||||
|
||||
---
|
||||
|
||||
## Optional: Make it even easier to start
|
||||
|
||||
You can create a shortcut so you just double-click to launch everything. In Terminal:
|
||||
|
||||
```bash
|
||||
cat > ~/Projects/youtube-summarizer/start.command << 'EOF'
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")/server"
|
||||
echo "Starting YouTube Summarizer..."
|
||||
echo "Press Ctrl+C to stop."
|
||||
echo ""
|
||||
npm start
|
||||
EOF
|
||||
chmod +x ~/Projects/youtube-summarizer/start.command
|
||||
```
|
||||
|
||||
Now you have a `start.command` file in the project folder. Double-click it to launch the server — it opens Terminal automatically. Then just open `index.html` in your browser.
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
%PDF-1.4
|
||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 16 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 17 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Contents 18 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 19 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Contents 20 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Contents 21 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 14 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Author (\(anonymous\)) /CreationDate (D:20260213152439+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260213152439+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Count 7 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1395
|
||||
>>
|
||||
stream
|
||||
Gau0CgN)%,&:N/3lq>Ak@m^Ain`%%-9jDnjeu>&$FN,sF$&Q#\Jd"H@mUYnU?1=RjU1?<)CQBbP"Cf]KF*8@o@,WbU?DALuLCe(d-r:-,$E-<3#iEPSE:VV6Furuj0Q7,KgF"'Il`N5P=,Ag/IHCO7!fI78&F)<JjapZ30Eg3$FWns`+p6trLc.'ZCl\^jMk>_Y9`L?"=>'i2MAK9gdSduWc?$/:ItW+Oi@T`?_qF'?&FP;UctpILAd=:?4Wr&*#qQ9km`_fK+$c=)d#\[J+i?7cW:,q\:HNC-n-Bdrf-O\QBe6]+G'4mWK;UVH-uADAk6^[,Q[*Wp!J7S<\\OmQ;u6H5;D\pHi-6:t3G,KTFQ@fjHn5#Yaj<TJ*i=E4>Q_9\U4c*5Vo9Q<Q@3Y+Z^KRL$]TP>N`Wjg3>N?TV%'V7'/`J3$@#]?_CNtP#piXks"e%@;VcniCS;E8cC[RXTj&e'@NlQ22GqdO@TgqJdFF^=YXuS5EY(9*.=jESjZL_Z''d4&g_]n;(8Imf$8NLX^'B`'4NL]Wn]/n]OP6cYGXr2o<$=)gS`(4L`,-(U!dGE9We+4-]HVcUQ9dOEdLaSrO\$Tqckcefj&?(5@aI6L6^f9*9"E6c8HkG):Q#qD>`fVqgu&`BNBGPJL)Fke@Ti903)#6%\toiK=eVL&8'OrR%=S;eVgHk[T1lUEcY'S=9_6WAD-:u\psJ0d_$9!\:1mO)>`NH3_t).cK\=lKNk1MC](K.('2o_2ft'#gL`"ZefUtG4J[_$76bB%s7;msO8%9s:T\I:ApX%_O7P)Or$[s`I*Nk[?'p0Pom^N@#=C(apY+SS&,/dT1RN?)\VFZ\9:S1Y!?%"O<XCN*AK1lUE'nS3.C<BFJ\_#jUQ??h.N>9?2D!J<=@2d8I?5,FhO]G@Gfoh#rSDG.;:uSc"krCY;9_I5L&pQgi[2/9i=8\I5UFnO.KLhH[%c0[lca-BPC3G9+*mu)Pk-P(R`9HGNNTU@uEbk)2k+\4'Ah(ljS.6$rIDh;PAnrCJHN,O4HuQLYe1=?O%K[m7i/F7j!LDRP6D&/7`R@SdECWW?/[,]g/4CmE,8J;hG46S1M,6Mk]@pJcR5!(urA\75Ae2<(Dp'BZiQ1kt'Lbf'%r;"o/`JqR%)Z$?ddT42/u@ep#CKAu1ZnC^?0!`QL..9.1qruH[)UR^l/A'124/4^(Tg7g'OoMn2WGd(b!+[W*_ZeC7aI'EJ&W<H!UgTp;=Sd=[/G"_mB53\=XfcJP$Q/k`qAHIJ_(\51^V4OCMRXomT;-T1#cIEW^PE@$j&(KGR<9'3b_fAV,\Q#HF2=0p;4h/hflfGF3h;^kn@A*bBfSQdA&Q!G`D$d<rXM'cEj)/m:C$VqkriTgKc[Enh78$HGf~>endstream
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 294
|
||||
>>
|
||||
stream
|
||||
Gaqcqb>,u0$q9pdMT]HS*bqY..\-_^&;:ROJ`[2(RSGrn(MUHHHos#8#].VgI-ku$'4O`5WjW0#1p.o+PX*in9P2XI.("44oWtN;R;c$9/iGf[lI#>Q"BXuL2aS/=.a&u,`VDEE9N"LS'Rh\PI:A:)VP&;08jW!VfBRJW!T5Gb0H#3oHRB?q3*\]jTJ'GY@<Q(IjP9YPQGN?HEp7r2gNVhW*P!1?L>B(=NQZ,#,29O!LbPPZb@$gG?c6^S=Ql^5[kG>&QW$oE:TTB!Znq!UF/L8^ag6Nm";G`N`W~>endstream
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1376
|
||||
>>
|
||||
stream
|
||||
Gau0C;/bG`&:Vs/(njtRD6U]3(nA&lGF(sLD,Z!8<Q`Ko0<g;Hle>^#qB#Lf41iKX#@&7*>`_:(mFcElq.AcA5_(NII]!@b0/N)^&0N.?(-mW2&8%QmrTrV:$/+)OO1c+E6&`;5Vd;'G9Yqrt5n]8)#*6\iHVl@j0GN20O'u'sj:K2D7LioI[m)aO"EFN%65uBVn;#5TGHZRT$u.T>o(!3$pFuKd6MLpq4##YRgqX33$V/)[_"YqKllcY0"p-,A>]O<Zi?)3R?n;DdQ=*`u/2co$%a-K5M!k,>&fP[rTUC+:lL+!V9"^\9[E'Z=1gn7)2.o1TN6AghY>(]T_hT,[S7r(@>(_t)8MHKC"^Js6j82>LYdn4YY9WjV]Le]V0sY]-&&o9a72V/#][+C%#%%s]Z[`rSaJ:dQYbI1pHTos'>]a?rRfY]6#h@j.C>qGiI]u;g-Q`1g1'-g9-Zo;);[!sohgF9Mkp+fV@Tr4`E)gH<1JCH1ZJB0=_:0c`?5p4p%P)GT]eHmV0;gd%a=B.!-GHC_d">Q&%C>nG`8+t^cL5.cKS@A)J%Sct+]Ugf@G*54oT`E(0ao!p&WiZ"C59p'F5bHE?GRb<h@Fi/Zr/mm(4,f`<lUC#_Y4dNHV7=K26TjKWC485Gc<7l<[.N4_#DCKQg`XX4MA<fl[>&LDGnBoZ(+9)I#rV+f<-Jo-&^4(2T;s;fNV<>!:j]tXl+Dl6:nRk99jXc4*$O%s)X@97CV4a&DOAF+*S`7(ae;3+pQZd/Ll=M%Sbhd/<eIIU5&2RCE%Yr[!smN(U^A\IB(r+C"u!L'HTgUR2fbX&Mb3]d+;N[m5X!^rk/[EaU6$XP%+0M?HF\CDuh]GS-bo>k4kk'\m6L$i&oW^g^;r%V\mZnGLa-Y@/h.\5u`$/Q=puGfm41$f)*s6:3afuhIfNX\-Y69K^k23c0WBV6PlP+"2bMtjH#\$h$3%m]=gR:@1mr0EMXQNkMIb7cVgNdXI;HD0[Nfs:rU1'jYq?mX#a)nD+D5a\FA#^p7'@K;>4U:XF>VpO*$g??5go\>TlH-g_2[mV*4-p:9iSTV]$?T?rCQi_Fp0$?Dkf7JMdqSD,N@#(L1pm@a-SpTs(l1,nsjl2rf<:C@j!W6bt#c^6sSJ$b4D9S!AS@L6CM7H$]Y.Z+:$)Hu.89a%4CZi>&o5Q?&D"k9(h5o8uL>Jb3tu4-;abWij+pPi*FC\r3T9iB^55a]iSoCM".R6D55-Ce's,'"rjg]U"kI*Im:8i@%5DGVA>*)M+6.A/$EZRF^k(Y)RSkhU3#'X:?iX6W^+Jf7"0SaWaLUdTV:c4+QoSRrZt7Q'F$V6Ppp4;UJ?IjZIsAhA'o;FK6@CpN@%$PQ(ZcR@.7~>endstream
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 197
|
||||
>>
|
||||
stream
|
||||
GapXOaUZ0A$q0R[MRfu_KK>p2\:WYW"j&t?<TsQj6:h^dHdVm8a^a#!-PMXcMS85('4ZM<O91ak/N*<X$]C@E?S]g$6;D6NLM`ET9(anFIY`]_&qTV7.uatmIAmp[q8-BPM=]Y;ptF_ABC'f/LM>;IE9?O>)NUbV=5t8ol0X;X9RTbsIC0)?^t[tWHP"+#1C>h2~>endstream
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1482
|
||||
>>
|
||||
stream
|
||||
Gatm:?#SIU'Rf_Z\198_/Rhl4Vr`[7m^a/_!]4]B%0cUF89DdN+uP78k<81@muip+/9[b(8K?KA6sZC2Gj`,/J<)?m5Doa(9b-c="@rOg)G&pXOIs?Bq;dSC)-/)`(I775\/_\uI_8bc&U8ceP:Q`q,a#X:XsP!Cm%Yqi2]+Q*^c.C.`5J3QB\#MWKJV>?U&nUT=LINHaM1%?g2-X:^@#g.UtMtp!+$4,R?-7!rOHiJb_Ohu;S-^NnIG'?Cq&g9ZrC`M^;q?FHHe]n,])Z[mn9T2YiQ.b,W1AfY6X<BP`QSn1$&,o!m(d9&uLG'iuL]:[NtYam`/@L.:BQ:\$EcMfrV,tnK#>='e<hD0N#.Hl`1VR8<S[I8<5"WL8:TO_Xnib-.krSn%rK^Sf^i8(Oab/`W'Di1OuDPLNU4j;r@_cZ<,C&q2H+-f!6or!aH)omA.8c*_lih\O6^Jfcgb0ZC(kA:,9_aJ-]`pLm#tT+<dEIJ7%%R\+3"*LR)YlnrUBsV&-$VEKe,//PslU"d=@PUr$-061]i%5XUHj(\kOp%c:;X]Oju:nP[7N"5,HG9)P`_IE+%DQQ</mCbTA_"YZ:jI*HY"af\d+X-EpflWrdSD%p1$(8GtE;#I?A"$sQ$:8"P]`6\V:_2nc!Ko$cFh6dpfQiIr]0b9ZC1-:bkf(-u6L(s:r"@LC8(Sq7eE=Z0/JrQ=kT0Bi(KCER5XrqAWiI]Xb5rm'sIathPgSP5s;qEEJHaPZ`4L#O*d^rJ!e*V&$?H\(<?I0Us1'('L(h0*LP4(\C;0-b_s6suF;+?R,5STDpFFZ<nIJtI1K&hb(H+Go0@C`[$;J)jJ.N<+TfSCW(RHZ#]+4'IkWK:`:b&aTH,?Z,qOG-%:%Y>t7%8D`nrgo`D$gHdZ;"p;mf'Vj;m`p<3L8&OT%/ttT0%qS"U,D)1\?(F&C<N_,O`QZsHR*p_NDm[#d^i`0kI!\a<jL2td.bX&hn3U:]pfLp*\"bK=m!Qn+H)koLW@T-/cZT76\gj9UC*1'!UY^%^[P"F1S%<MZ(;rfRqk/?hD;5M%]f2"\6rK]ERh`^kYF\$/aYB9`1"[&fleWnkcolNbNkhF!Y<TDY1-QiNbX<pT?cqRSJbjg_5$.emI^aL?,?Pj1.)S$5"ac4>Xoq3nPe+s4SYK4@1E]YSInoG>@oGlLS'R),q[NV9.jZH'BG%.Y#$*@_uEbNMd'/QQ7Xc$LVXT[T-o5MGC38mFaITl.BH>*iNYUO,:PQ[@_^:me5g)Zd?W01`OX%7'Bi:f3sI]>N`IlkT/mI3\YXO,(3__@QZkRs3b^ZB.F!P+k%'>?eI3fC]ZW/dC/,8F)6kD!-+aJ$KC9<aDm@)Lfsi8QV/PM,aRff6oH*0Uo3_YtRc6Tq6OajP6>"&T(ti-)SS:+%0DO>MWtE#mO-TTj['/&,BWY1:R3<%DV#(qkV7ahDY%!:Im"#7r-2uKV[:f>R+,7m%V8%>t~>endstream
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 410
|
||||
>>
|
||||
stream
|
||||
GatUn;,>(P'SYH=/'aWTEEHCF[TB4fR2@^mA$Qp%ieD5W!d2mt0C?1bkE8!L9n6Ko]mM1mOiF'm>W0BW_"Bhobog=Y).='e\.aMh!So76:,6cLNq/QsqSPV=:d?k1]W:;/d&p$+PAJI?BjC7Y^b([FJm6t^C,IJV?*5o8G`,St^+,NSklPI6dn$COPU!#a>>t2UES3W@`AdoLVJHK\o%$aL5G1+-,U7Q=Xi&$/X]H!UUPstEK5G/GV6&uYN(2Gj*u&7`(%">]en0o_LS[.daRfEUjoj!\APcO)Y#*^#.tj1>:p/28f%=_9fknsTd.=:Fo-CEELC\RgHZA]OU"k$?L:6;QqJVs"p+@cT"lg`48GRp-90(8mZ'Ft>,l?ss7)B<B*=*^GMM?S/"??P$i#BfnWc\~>endstream
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1025
|
||||
>>
|
||||
stream
|
||||
Gatm:bAu>q']&X:mV#P,A'k:mLZD_^D5K+W]Z`">KLhIu$j%s[>#?(6p0L"KSMZ5WNZuc))N_Jli1,]-;':,2pg*UocRZO%N<2hJ\cnNK)-!fufBVA!$Tbt(PXu3..I_I"9h<!)V\paj6g1CtKGI^8B\NnG73ANG;"[pNKp785BbBV_.r3/,'!=%19ATRUeOQQe$$bq/qA3a@DC/%L910(gUg,-fas\dnpG"+VX!o9*cg/&!YVZ,'%qZ@kDEPBLDT9ER<dkfBW=AjVbmYOl*nYi@M)qF`J0(AXO:`Dg:XVA[&A5@E6*d`m+PY2FC*i1-n=>)6WPg+4W.krsi?-`+h]gZdlr\FV)5_+-<;8*[5jF0d6p4`<pWuI69Ue;t8I%_c4`l(eM=.nMjaieA*k^i.18@G[<V^g.OrrZ=iW[rfGc6VQU..TV[_0<f\cM4bR@:2%3.K0YTo5h:k3Mr0J&VGlYDP[#HXT4Ocq\c6)V[6&Qa*Yo?hqR!*^&+o?[B[q0(k1]H't9=4O;VD7.@ls2/7Xa]%##HV*m,;/BW%2Z6)R7NGMfLG"3$]TQR9H.q*e(\XKs]'oJ'`mUFdXB@"\fZI<hXjP<sZ^,.pR$ub'i9838fibm5IH^[l6A7m:7A7H\_#IX]XkSrqYPJE*3oND)HKJE+F[t*l0^!Xm15iF<t>PgQj`hBfQ&J(Qin'?q)=e;0@,!3g4R=5$++:AM%hYdr$>%=`Z,A[tC\B&9g,K&R3bRX[%N&IEfM*7^@.W`q$C]XY-Bok5To1F8cTM]$j0nZUm&)nrnbK4@Up$h0V3@()Yk/A[umCXOrp!jeDcB!rA8S_##7*(`G5"J)2=W0k-lHYE0H(OH-NGd]]2SqHCr!kdrFrd0#:Qo8@PUIou4Lm*d%@E77(u&QP_)%ab/r1TP6kU"/AgA@Q7Q2ri.F#4o07f6cnbD;`h3f2%S&YWZEp[Tl[pmK"8lk3.PIqcD%34,On"^>hD;8'?IscDLPe7ed=OUI4gs!K?C^MT#p]i[?EFJ~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 22
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000112 00000 n
|
||||
0000000219 00000 n
|
||||
0000000331 00000 n
|
||||
0000000436 00000 n
|
||||
0000000631 00000 n
|
||||
0000000826 00000 n
|
||||
0000001021 00000 n
|
||||
0000001216 00000 n
|
||||
0000001411 00000 n
|
||||
0000001607 00000 n
|
||||
0000001803 00000 n
|
||||
0000001873 00000 n
|
||||
0000002154 00000 n
|
||||
0000002252 00000 n
|
||||
0000003739 00000 n
|
||||
0000004124 00000 n
|
||||
0000005592 00000 n
|
||||
0000005880 00000 n
|
||||
0000007454 00000 n
|
||||
0000007955 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<f489779f8c1c153720fb5c785d0c5179><f489779f8c1c153720fb5c785d0c5179>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 13 0 R
|
||||
/Root 12 0 R
|
||||
/Size 22
|
||||
>>
|
||||
startxref
|
||||
9072
|
||||
%%EOF
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,842 +0,0 @@
|
||||
# YouTube Summarizer: StartOS 0.4.0 Packaging Game Plan
|
||||
|
||||
**Date:** April 9, 2026
|
||||
**Status:** Feasibility Assessment & Implementation Roadmap
|
||||
**Target:** StartOS 0.4.0 Beta (s9pk package format)
|
||||
**Reference:** Workout Log packaging for StartOS 0.3.5
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Packaging the YouTube Summarizer for StartOS 0.4.0 is **feasible** but presents unique challenges that a typical web app does not. The app is more than a simple CRUD service -- it downloads audio from YouTube (requiring yt-dlp to always be current), authenticates against YouTube to bypass bot detection, transcribes audio via the Gemini API, and manages podcast RSS feeds. Running this on a headless remote server introduces three core challenges that require thoughtful solutions:
|
||||
|
||||
1. **yt-dlp must stay current** -- YouTube changes its anti-bot signatures regularly, so a stale yt-dlp binary means broken downloads within days or weeks.
|
||||
2. **YouTube authentication without a local browser** -- The current app relies on cookies from your local browser or a cookies.txt file. A remote server has no browser session.
|
||||
3. **Server/datacenter IP reputation** -- YouTube is more aggressive about blocking downloads from non-residential IPs.
|
||||
|
||||
All three are solvable. This document lays out the approach for each, plus the full packaging plan.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Current App Architecture](#1-current-app-architecture)
|
||||
2. [What Changes for StartOS](#2-what-changes-for-startos)
|
||||
3. [Challenge 1: Keeping yt-dlp Updated](#3-challenge-1-keeping-yt-dlp-updated)
|
||||
4. [Challenge 2: YouTube Authentication on a Headless Server](#4-challenge-2-youtube-authentication-on-a-headless-server)
|
||||
5. [Challenge 3: Server IP Bot Detection](#5-challenge-3-server-ip-bot-detection)
|
||||
6. [StartOS 0.4.0 Packaging Structure](#6-startos-040-packaging-structure)
|
||||
7. [Differences from v0.3.5 Packaging (Workout Log)](#7-differences-from-v035-packaging-workout-log)
|
||||
8. [Implementation Phases](#8-implementation-phases)
|
||||
9. [Persistent Data Contract](#9-persistent-data-contract)
|
||||
10. [Ongoing Maintenance Plan](#10-ongoing-maintenance-plan)
|
||||
11. [Risk Assessment](#11-risk-assessment)
|
||||
12. [Multi-User Distribution: API Keys and Clean Installs](#12-multi-user-distribution-api-keys-and-clean-installs)
|
||||
13. [Appendix A: File-by-File Packaging Checklist](#appendix-a-file-by-file-packaging-checklist)
|
||||
14. [Appendix B: Reusable Packaging Roadmap for Future Apps](#appendix-b-reusable-packaging-roadmap-for-future-apps)
|
||||
|
||||
---
|
||||
|
||||
## 1. Current App Architecture
|
||||
|
||||
The YouTube Summarizer is a Node.js application with these components:
|
||||
|
||||
**Backend (server/index.js -- ~1800 lines):**
|
||||
- Express.js server on port 3001
|
||||
- Calls yt-dlp as a child process to download audio from YouTube
|
||||
- Downloads podcast episodes directly via HTTP
|
||||
- Uploads audio to Google Gemini File API for transcription
|
||||
- Runs topic analysis via Gemini (multiple model fallback chain)
|
||||
- Manages subscriptions (YouTube channels + podcast RSS feeds)
|
||||
- Auto-queue system: checks subscriptions for new content, queues for approval
|
||||
- Cookie management: supports cookies.txt file and --cookies-from-browser flag
|
||||
- yt-dlp auto-update: checks GitHub releases every 24h, updates via self-update / brew / pip
|
||||
- History storage: JSON files on disk (not a database)
|
||||
- Health check endpoint at /api/health
|
||||
- Heartbeat/auto-sleep system (shuts down when no browser connected for 30s)
|
||||
|
||||
**Frontend (public/index.html):**
|
||||
- Single-page app, served as static files by Express
|
||||
- Split-screen layout: video embed + topic summaries
|
||||
- Subscription management UI
|
||||
- Cookie upload/test UI
|
||||
- Settings panel for API key and model selection
|
||||
|
||||
**External Dependencies:**
|
||||
- yt-dlp (system binary, called via child_process)
|
||||
- ffmpeg (required by yt-dlp for audio extraction)
|
||||
- Node.js 20+
|
||||
- Google Gemini API (requires API key)
|
||||
|
||||
**Data Storage (all in /history/ directory):**
|
||||
- Individual summary JSON files (one per processed video/podcast)
|
||||
- subscriptions.json (channel/feed list)
|
||||
- _meta.json (folder structure for organizing summaries)
|
||||
- seen-list.json, skip-list.json (dedup tracking)
|
||||
- auto-queue.json (pending items from subscription checks)
|
||||
- cookies.txt (YouTube authentication)
|
||||
- .env (Gemini API key, cookie browser preference)
|
||||
|
||||
---
|
||||
|
||||
## 2. What Changes for StartOS
|
||||
|
||||
### Things That Work As-Is
|
||||
- The Express server, Gemini API integration, and frontend are platform-agnostic
|
||||
- Podcast RSS feed parsing (direct HTTP, no auth needed)
|
||||
- History/subscription JSON storage
|
||||
- Health check endpoint
|
||||
|
||||
### Things That Need Adaptation
|
||||
|
||||
| Current Behavior | StartOS Adaptation |
|
||||
|---|---|
|
||||
| Runs on macOS with Homebrew | Docker container on Alpine Linux (ARM64) |
|
||||
| yt-dlp installed via brew | yt-dlp installed via pip in container, self-updates at runtime |
|
||||
| Cookies from local Firefox/Chrome | cookies.txt uploaded via web UI (already supported) + OAuth2 |
|
||||
| Auto-sleep when no browser connected | Remove sleep logic; service runs continuously |
|
||||
| Hardcoded port 3001 | Configurable, default 3001, mapped via StartOS interfaces |
|
||||
| .env file for config | StartOS config UI (manifest config spec) |
|
||||
| macOS .app bundle / launcher | Not needed; StartOS manages service lifecycle |
|
||||
| LAN mode dialog | Not needed; StartOS handles network exposure (Tor + LAN) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Challenge 1: Keeping yt-dlp Updated
|
||||
|
||||
### The Problem
|
||||
YouTube frequently changes its download mechanisms and anti-bot measures. A yt-dlp version that works today may stop working within 1-2 weeks. The app already handles this with a multi-strategy auto-update (self-update, brew, pip), but inside a Docker container on StartOS, brew is not available and we need a reliable update path.
|
||||
|
||||
### The Solution: Runtime Self-Update with Persistent Storage
|
||||
|
||||
**Strategy:**
|
||||
1. Install yt-dlp via pip at Docker **build time** (gives a known-good starting point)
|
||||
2. Store the yt-dlp binary on the **persistent volume** (`/data/bin/yt-dlp`) so updates survive container restarts
|
||||
3. On each container start, check if the persistent binary exists and is newer than the built-in one; use whichever is newer
|
||||
4. The existing auto-update logic runs `yt-dlp -U` (self-update) which writes to the pip site-packages, but we can also add a dedicated update mechanism that downloads the latest binary directly from GitHub releases to `/data/bin/yt-dlp`
|
||||
|
||||
**Implementation in docker_entrypoint.sh:**
|
||||
```bash
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
DATA_DIR="/data"
|
||||
BIN_DIR="$DATA_DIR/bin"
|
||||
YTDLP_LOCAL="$BIN_DIR/yt-dlp"
|
||||
|
||||
mkdir -p "$BIN_DIR" "$DATA_DIR/history" "$DATA_DIR/config"
|
||||
|
||||
# Use persistent yt-dlp if available, otherwise fall back to system
|
||||
if [ -x "$YTDLP_LOCAL" ]; then
|
||||
export PATH="$BIN_DIR:$PATH"
|
||||
echo "Using persistent yt-dlp: $($YTDLP_LOCAL --version)"
|
||||
else
|
||||
echo "Using system yt-dlp: $(yt-dlp --version)"
|
||||
fi
|
||||
|
||||
# Start the Node.js server
|
||||
exec node /app/server/index.js
|
||||
```
|
||||
|
||||
**Auto-update enhancement in server code:**
|
||||
- Modify `autoUpdateYtdlp()` to: (1) try `yt-dlp -U`, (2) try `pip install -U yt-dlp`, (3) as a last resort, download the latest binary directly from `https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp` and save to `/data/bin/yt-dlp`
|
||||
- Add a scheduled check: on server startup and every 12 hours, check for updates
|
||||
- Expose update status in the health check endpoint so StartOS can surface it
|
||||
|
||||
**Why this works on StartOS:**
|
||||
- `/data` is the persistent StartOS volume; it survives container restarts and package upgrades
|
||||
- The container image provides a baseline yt-dlp that works at build time
|
||||
- Runtime updates keep it fresh without rebuilding the entire package
|
||||
- If a runtime update breaks something, removing `/data/bin/yt-dlp` reverts to the built-in version
|
||||
|
||||
### Also: ffmpeg
|
||||
ffmpeg is a build-time dependency (Alpine package `ffmpeg`). It rarely needs updates for this use case, so installing it in the Dockerfile is sufficient. It is updated whenever you rebuild and push a new package version.
|
||||
|
||||
---
|
||||
|
||||
## 4. Challenge 2: YouTube Authentication on a Headless Server
|
||||
|
||||
### The Problem
|
||||
YouTube increasingly requires authentication to download content. The current app supports two methods:
|
||||
1. **cookies.txt file** -- a Netscape-format cookie export from a browser
|
||||
2. **--cookies-from-browser** -- reads cookies directly from a local browser's cookie store
|
||||
|
||||
On a remote StartOS server, there is no local browser, so method 2 is unavailable.
|
||||
|
||||
### The Solution: Three-Tier Authentication
|
||||
|
||||
**Tier 1: OAuth2 Device Flow (Primary -- Best Option)**
|
||||
|
||||
yt-dlp now supports OAuth2 authentication with a device code flow, which is perfect for headless servers:
|
||||
|
||||
```bash
|
||||
yt-dlp --username oauth --password '' <URL>
|
||||
```
|
||||
|
||||
How it works:
|
||||
- On first use, yt-dlp prints a code and a URL (https://www.google.com/device)
|
||||
- The user opens that URL on any device (phone, laptop) and enters the code
|
||||
- A refresh token is cached in yt-dlp's cache directory
|
||||
- All subsequent downloads use the cached token -- no further interaction needed
|
||||
|
||||
For StartOS integration:
|
||||
- Mount yt-dlp's cache directory on the persistent volume (`/data/ytdlp-cache/`)
|
||||
- Add a "Setup YouTube Auth" action in the StartOS manifest that triggers the OAuth flow and displays the device code in the service logs or a dedicated endpoint
|
||||
- Build a simple UI page in the web frontend that shows the device code and instructions
|
||||
- The token persists across restarts because it lives on `/data`
|
||||
|
||||
**Tier 2: cookies.txt Upload (Fallback)**
|
||||
|
||||
The app already has a full cookies.txt management system via the web UI:
|
||||
- Upload endpoint: POST /api/cookies/upload
|
||||
- Test endpoint: POST /api/cookies/test
|
||||
- Status endpoint: GET /api/cookies/status
|
||||
- Delete endpoint: POST /api/cookies/delete
|
||||
|
||||
This works on StartOS as-is. The cookies.txt file should be stored on the persistent volume (`/data/cookies.txt`) instead of the project root.
|
||||
|
||||
**Important caveat:** YouTube cookies expire in approximately 3-5 days. This means the user would need to re-export and re-upload cookies regularly. This is why OAuth2 is the recommended primary method.
|
||||
|
||||
**Tier 3: No Authentication (Limited)**
|
||||
|
||||
Some YouTube content can be downloaded without any authentication. The app already handles this gracefully -- it attempts download without cookies if cookie-based download fails. This will work for some videos but not all, especially age-restricted or bot-flagged content.
|
||||
|
||||
### Implementation Priority
|
||||
1. Add OAuth2 support to the server code (new endpoint to initiate flow, display code, check status)
|
||||
2. Move cookies.txt storage to persistent volume
|
||||
3. Remove --cookies-from-browser logic (not applicable on remote server)
|
||||
4. Add a clear "Authentication Setup" section in the web UI that guides through OAuth2 first, cookies.txt as backup
|
||||
|
||||
---
|
||||
|
||||
## 5. Challenge 3: Server IP Bot Detection
|
||||
|
||||
### The Problem
|
||||
YouTube maintains IP reputation databases. Residential ISP IPs (like your home connection) are generally trusted, but datacenter IPs are frequently flagged. A Start9 server running at home on your residential IP should actually be fine -- this is a significant advantage of self-hosted infrastructure.
|
||||
|
||||
### Assessment: Low Risk for Start9 Home Servers
|
||||
|
||||
**Start9 servers typically run at home on a residential IP.** This means:
|
||||
- The server's IP is a normal residential ISP address
|
||||
- YouTube treats these the same as any home computer
|
||||
- Bot detection is primarily triggered by datacenter/cloud IPs (AWS, GCP, Azure, etc.)
|
||||
- Rate limiting may still occur with heavy usage, but this is the same as running the app on your Mac
|
||||
|
||||
**When it could be a problem:**
|
||||
- If you access the Start9 server through a VPN and route yt-dlp traffic through the VPN
|
||||
- If your ISP uses CGNAT (carrier-grade NAT) which shares IPs with many users
|
||||
- If you process a very high volume of downloads in a short period
|
||||
|
||||
### Mitigation Strategies (Built Into the App)
|
||||
|
||||
The app already has several mitigations that carry over directly:
|
||||
|
||||
1. **Retry with exponential backoff** -- the download logic already retries with 30s, 60s, 120s delays when rate-limited
|
||||
2. **Cookie/OAuth authentication** -- authenticated requests are less likely to trigger bot detection
|
||||
3. **yt-dlp auto-update** -- newer yt-dlp versions include workarounds for the latest YouTube anti-bot measures
|
||||
|
||||
**Additional measures to add for StartOS:**
|
||||
1. **Rate limiting between downloads** -- when processing auto-queue items from subscriptions, add configurable delays between downloads (e.g., 30-60 seconds between videos)
|
||||
2. **Download scheduling** -- spread subscription checks and downloads across the day rather than bursting
|
||||
3. **Proxy support (optional)** -- add a config option for users to specify a SOCKS5 or HTTP proxy if they want to route yt-dlp traffic through a specific connection. This is an advanced option, not required for most users.
|
||||
4. **Browser impersonation** -- yt-dlp supports `--impersonate chrome` which mimics Chrome's TLS fingerprint. Add this as a default argument.
|
||||
|
||||
### Summary
|
||||
For a home Start9 server, YouTube bot detection is **not a significant concern**. The residential IP is the same one you'd be using from your Mac. Combined with OAuth2 authentication and yt-dlp staying current, this should work reliably.
|
||||
|
||||
---
|
||||
|
||||
## 6. StartOS 0.4.0 Packaging Structure
|
||||
|
||||
### Key Changes from 0.3.5
|
||||
|
||||
StartOS 0.4.0 is a complete rewrite. The major packaging-relevant changes:
|
||||
|
||||
| Aspect | 0.3.5 | 0.4.0 |
|
||||
|---|---|---|
|
||||
| Container runtime | Podman | LXC (Linux Containers) |
|
||||
| Package format | s9pk (same name, different internals) | s9pk with signatures, partial downloads, multi-arch |
|
||||
| Dev tooling | Shell/make-based | TypeScript SDK available |
|
||||
| Networking | Tor + LAN | Same, with improved LAN port forwarding |
|
||||
| Backups | Not compatible across versions | Fresh backups required after migration |
|
||||
|
||||
### Package Files Needed
|
||||
|
||||
Based on the workout-log template and v0.4.0 documentation:
|
||||
|
||||
```
|
||||
start9/
|
||||
0.4/
|
||||
manifest.yaml # Service metadata, config spec, interfaces
|
||||
Dockerfile # Multi-stage build for ARM64
|
||||
docker_entrypoint.sh # Service startup script
|
||||
healthcheck.sh # Health check script
|
||||
Makefile # Build automation
|
||||
instructions.md # User-facing documentation
|
||||
LICENSE # License file
|
||||
icon.png # Service icon
|
||||
DEPLOY.md # Deploy/sideload instructions
|
||||
```
|
||||
|
||||
### Draft manifest.yaml
|
||||
|
||||
```yaml
|
||||
id: youtube-summarizer
|
||||
title: YouTube Summarizer
|
||||
version: 0.1.0.1
|
||||
release-notes: >-
|
||||
Initial StartOS package. YouTube/podcast audio download,
|
||||
transcription via Gemini, and topic analysis.
|
||||
license: Proprietary
|
||||
wrapper-repo: https://github.com/user/youtube-summarizer-startos
|
||||
upstream-repo: https://github.com/user/youtube-summarizer
|
||||
support-site: https://github.com/user/youtube-summarizer/issues
|
||||
marketing-site: https://github.com/user/youtube-summarizer
|
||||
build: ["make image-arm"]
|
||||
|
||||
description:
|
||||
short: Download, transcribe, and summarize YouTube videos and podcasts.
|
||||
long: >-
|
||||
YouTube Summarizer downloads audio from YouTube videos and podcast feeds,
|
||||
transcribes them using Google Gemini, and produces structured topic
|
||||
summaries. Supports subscriptions with automatic new episode detection,
|
||||
organized history with folders, and a responsive web interface.
|
||||
|
||||
assets:
|
||||
license: LICENSE
|
||||
icon: icon.png
|
||||
instructions: instructions.md
|
||||
docker-images: image.tar
|
||||
|
||||
main:
|
||||
type: docker
|
||||
image: main
|
||||
entrypoint: docker_entrypoint.sh
|
||||
args: []
|
||||
mounts:
|
||||
main: /data
|
||||
|
||||
health-checks:
|
||||
main:
|
||||
name: API health
|
||||
success-message: YouTube Summarizer is responding.
|
||||
type: docker
|
||||
image: main
|
||||
entrypoint: healthcheck.sh
|
||||
args: []
|
||||
inject: true
|
||||
|
||||
config:
|
||||
get:
|
||||
type: docker
|
||||
image: main
|
||||
system: false
|
||||
entrypoint: sh
|
||||
args:
|
||||
- -c
|
||||
- cat /data/config/startos-config.json 2>/dev/null || echo '{}'
|
||||
set:
|
||||
type: docker
|
||||
image: main
|
||||
system: false
|
||||
entrypoint: sh
|
||||
args:
|
||||
- -c
|
||||
- cat > /data/config/startos-config.json
|
||||
|
||||
dependencies: {}
|
||||
|
||||
volumes:
|
||||
main:
|
||||
type: data
|
||||
|
||||
interfaces:
|
||||
main:
|
||||
name: Web Interface
|
||||
description: Browser UI for YouTube Summarizer.
|
||||
tor-config:
|
||||
port-mapping:
|
||||
80: "3001"
|
||||
lan-config:
|
||||
443:
|
||||
ssl: true
|
||||
internal: 3001
|
||||
ui: true
|
||||
protocols: [tcp, http, https]
|
||||
|
||||
backup:
|
||||
create:
|
||||
type: docker
|
||||
image: main
|
||||
system: false
|
||||
entrypoint: sh
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
rm -rf /backup/*
|
||||
cp -a /data/. /backup/
|
||||
mounts:
|
||||
main: /data
|
||||
BACKUP: /backup
|
||||
restore:
|
||||
type: docker
|
||||
image: main
|
||||
system: false
|
||||
entrypoint: sh
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
cp -a /backup/. /data/
|
||||
mounts:
|
||||
main: /data
|
||||
BACKUP: /backup
|
||||
|
||||
actions:
|
||||
update-ytdlp:
|
||||
name: Update yt-dlp
|
||||
description: Downloads the latest version of yt-dlp for YouTube compatibility.
|
||||
warning: This may take a minute. Service will continue running.
|
||||
implementation:
|
||||
type: docker
|
||||
image: main
|
||||
system: false
|
||||
entrypoint: sh
|
||||
args:
|
||||
- -c
|
||||
- pip install --upgrade yt-dlp && yt-dlp --version
|
||||
inject: true
|
||||
```
|
||||
|
||||
### Draft Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy server package files and install deps
|
||||
COPY server/package.json server/package-lock.json ./server/
|
||||
RUN cd server && npm ci --production
|
||||
|
||||
# Copy application files
|
||||
COPY server/index.js ./server/
|
||||
COPY public/ ./public/
|
||||
COPY assets/ ./assets/
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache \
|
||||
dumb-init \
|
||||
curl \
|
||||
python3 \
|
||||
py3-pip \
|
||||
ffmpeg \
|
||||
&& pip3 install --break-system-packages yt-dlp \
|
||||
&& addgroup -S appgroup -g 1001 \
|
||||
&& adduser -S appuser -u 1001 -G appgroup
|
||||
|
||||
# Copy app from builder
|
||||
COPY --from=builder --chown=appuser:appgroup /app ./
|
||||
|
||||
# Copy StartOS scripts
|
||||
COPY start9/0.4/docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh
|
||||
COPY start9/0.4/healthcheck.sh /usr/local/bin/healthcheck.sh
|
||||
RUN chmod +x /usr/local/bin/docker_entrypoint.sh /usr/local/bin/healthcheck.sh
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /data && chown -R appuser:appgroup /data
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3001
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
ENTRYPOINT ["dumb-init", "--", "/usr/local/bin/docker_entrypoint.sh"]
|
||||
```
|
||||
|
||||
### Draft docker_entrypoint.sh
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
DATA_DIR="/data"
|
||||
HISTORY_DIR="$DATA_DIR/history"
|
||||
CONFIG_DIR="$DATA_DIR/config"
|
||||
BIN_DIR="$DATA_DIR/bin"
|
||||
CACHE_DIR="$DATA_DIR/ytdlp-cache"
|
||||
COOKIES_PATH="$DATA_DIR/cookies.txt"
|
||||
|
||||
# Create directory structure
|
||||
mkdir -p "$HISTORY_DIR" "$CONFIG_DIR" "$BIN_DIR" "$CACHE_DIR"
|
||||
|
||||
# Use persistent yt-dlp binary if it exists and is executable
|
||||
if [ -x "$BIN_DIR/yt-dlp" ]; then
|
||||
export PATH="$BIN_DIR:$PATH"
|
||||
fi
|
||||
|
||||
# Point yt-dlp cache to persistent storage (for OAuth tokens, etc.)
|
||||
export XDG_CACHE_HOME="$CACHE_DIR"
|
||||
|
||||
# Load config from StartOS config file if it exists
|
||||
if [ -f "$CONFIG_DIR/startos-config.json" ]; then
|
||||
# Extract Gemini API key from config
|
||||
GEMINI_KEY=$(cat "$CONFIG_DIR/startos-config.json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('gemini_api_key',''))" 2>/dev/null || echo "")
|
||||
if [ -n "$GEMINI_KEY" ]; then
|
||||
export GEMINI_API_KEY="$GEMINI_KEY"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Also check for .env in data dir
|
||||
if [ -f "$DATA_DIR/.env" ]; then
|
||||
export $(grep -v '^#' "$DATA_DIR/.env" | xargs)
|
||||
fi
|
||||
|
||||
export PORT="${PORT:-3001}"
|
||||
export HOSTNAME="0.0.0.0"
|
||||
|
||||
echo "Starting YouTube Summarizer..."
|
||||
echo " yt-dlp version: $(yt-dlp --version 2>/dev/null || echo 'not found')"
|
||||
echo " ffmpeg: $(ffmpeg -version 2>/dev/null | head -1 || echo 'not found')"
|
||||
echo " Data dir: $DATA_DIR"
|
||||
|
||||
exec node /app/server/index.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Differences from v0.3.5 Packaging (Workout Log)
|
||||
|
||||
The workout-log package was a relatively straightforward Next.js app with a SQLite database. YouTube Summarizer is significantly more complex:
|
||||
|
||||
| Aspect | Workout Log (0.3.5) | YouTube Summarizer (0.4.0) |
|
||||
|---|---|---|
|
||||
| App framework | Next.js + Prisma + SQLite | Express.js + JSON files |
|
||||
| External binaries | None | yt-dlp, ffmpeg |
|
||||
| External APIs | None | Google Gemini API |
|
||||
| Authentication | App-level (user/pass) | YouTube OAuth + cookies |
|
||||
| Data storage | Single SQLite DB | Multiple JSON files + temp audio |
|
||||
| Network access | Inbound only | Inbound + outbound (YouTube, Gemini, RSS) |
|
||||
| Binary updates | None needed | yt-dlp must stay current |
|
||||
| Temp file management | None | Large audio files downloaded and cleaned up |
|
||||
| Container size | Small (~100MB) | Larger (~300-500MB with ffmpeg + yt-dlp + Python) |
|
||||
|
||||
### Key Differences in Packaging Approach
|
||||
|
||||
1. **Outbound network access:** The workout-log only needed to accept inbound connections. YouTube Summarizer needs outbound access to YouTube, Google Gemini API, and podcast RSS feeds. StartOS containers have outbound network access by default, so this works, but it's worth verifying in the 0.4.0 LXC environment.
|
||||
|
||||
2. **Larger image size:** Adding Python, pip, yt-dlp, and ffmpeg significantly increases the Docker image. Expect 300-500MB vs. ~100MB for workout-log. This is acceptable but worth minimizing where possible.
|
||||
|
||||
3. **Runtime binary management:** The entrypoint must handle a mutable binary (yt-dlp) that updates at runtime. The workout-log had no such requirement.
|
||||
|
||||
4. **Temporary file cleanup:** Audio files are downloaded to /tmp during processing and should be cleaned up. Need to ensure the container's /tmp has sufficient space or use the persistent volume with cleanup logic.
|
||||
|
||||
5. **Config complexity:** Workout-log had no configuration. YouTube Summarizer needs at minimum a Gemini API key, and optionally proxy settings, download rate limits, and authentication preferences. This maps to the StartOS config spec in the manifest.
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation Phases
|
||||
|
||||
### Phase 1: Prepare the App for Headless Operation (Estimated: 1-2 days)
|
||||
|
||||
Code changes to server/index.js before any packaging work:
|
||||
|
||||
1. **Remove auto-sleep logic** -- the heartbeat/sleep system is designed for a desktop app that shuts down when you close the browser. On StartOS, the service should run continuously.
|
||||
|
||||
2. **Move all data paths to a configurable base directory** -- currently paths are relative to the project root. Change to use an environment variable (e.g., `DATA_DIR=/data`) so the persistent volume can be targeted.
|
||||
|
||||
3. **Add OAuth2 authentication flow** -- new endpoints:
|
||||
- POST /api/auth/oauth/start -- initiates OAuth device flow, returns device code
|
||||
- GET /api/auth/oauth/status -- checks if OAuth token has been cached
|
||||
- The web UI gets a new "Authentication" section in Settings
|
||||
|
||||
4. **Enhance yt-dlp update logic for Linux/container** -- remove Homebrew strategy, add direct binary download from GitHub releases as a fallback.
|
||||
|
||||
5. **Add download rate limiting** -- configurable delay between auto-queue downloads (default 30s).
|
||||
|
||||
6. **Add browser impersonation flag** -- pass `--impersonate chrome` to yt-dlp by default when available.
|
||||
|
||||
7. **Remove macOS-specific code** -- the .app bundle creation, osascript dialogs, LAN mode prompt, etc. are not needed.
|
||||
|
||||
### Phase 2: Create the StartOS Package Scaffold (Estimated: 1 day)
|
||||
|
||||
1. Create `start9/0.4/` directory with all packaging files (see Section 6)
|
||||
2. Write Dockerfile with multi-stage build
|
||||
3. Write docker_entrypoint.sh
|
||||
4. Write healthcheck.sh
|
||||
5. Write manifest.yaml with full config spec
|
||||
6. Write instructions.md
|
||||
7. Copy icon.png
|
||||
8. Create Makefile
|
||||
|
||||
### Phase 3: Build, Test, and Iterate (Estimated: 2-3 days)
|
||||
|
||||
1. Build ARM64 Docker image: `make -C start9/0.4 image-arm`
|
||||
2. Smoke test locally:
|
||||
- `docker load -i start9/0.4/image.tar`
|
||||
- Run container with a volume mount and verify /api/health responds
|
||||
- Test yt-dlp download of a known video
|
||||
- Test Gemini API transcription
|
||||
- Test OAuth flow
|
||||
- Test subscription check
|
||||
3. Package with start-sdk: `make -C start9/0.4 package`
|
||||
4. Sideload on StartOS and test end-to-end:
|
||||
- Install service
|
||||
- Set up Gemini API key via config
|
||||
- Run OAuth authentication
|
||||
- Process a YouTube video
|
||||
- Process a podcast episode
|
||||
- Test subscription auto-discovery
|
||||
- Test backup/restore
|
||||
5. Iterate on issues found
|
||||
|
||||
### Phase 4: Documentation and Polish (Estimated: 1 day)
|
||||
|
||||
1. Write comprehensive instructions.md for StartOS users
|
||||
2. Update DEPLOY.md with StartOS 0.4.0 specific steps
|
||||
3. Update START9_PACKAGING_LOG.md with the complete process
|
||||
4. Verify backup/restore works correctly
|
||||
5. Test yt-dlp auto-update from within the running service
|
||||
6. Document the config spec clearly
|
||||
|
||||
---
|
||||
|
||||
## 9. Persistent Data Contract
|
||||
|
||||
Everything under `/data` persists across container restarts and package upgrades:
|
||||
|
||||
```
|
||||
/data/
|
||||
history/ # All summary JSON files
|
||||
subscriptions.json # Channel/feed subscriptions
|
||||
_meta.json # Folder organization
|
||||
seen-list.json # Dedup tracking
|
||||
skip-list.json # Deleted item tracking
|
||||
auto-queue.json # Pending queue items
|
||||
*.json # Individual summary records
|
||||
config/
|
||||
startos-config.json # StartOS-managed configuration
|
||||
cookies.txt # YouTube cookie file (if uploaded)
|
||||
.env # Environment overrides (Gemini key, etc.)
|
||||
bin/
|
||||
yt-dlp # Updated yt-dlp binary (runtime-managed)
|
||||
ytdlp-cache/ # yt-dlp cache (OAuth tokens, etc.)
|
||||
```
|
||||
|
||||
**Migration contract:** Any future package version must preserve this layout. If the schema of JSON files changes, handle migration in the entrypoint script or a dedicated migration step.
|
||||
|
||||
---
|
||||
|
||||
## 10. Ongoing Maintenance Plan
|
||||
|
||||
### Regular Maintenance (Monthly)
|
||||
|
||||
- **Rebuild and push package** -- even if the app code hasn't changed, rebuilding picks up the latest Alpine packages, Node.js patches, and yt-dlp version at build time
|
||||
- **Check yt-dlp compatibility** -- verify that the runtime auto-update is working by checking the version via the health endpoint
|
||||
|
||||
### When YouTube Breaks Things
|
||||
|
||||
YouTube periodically makes changes that break yt-dlp. When this happens:
|
||||
|
||||
1. Users can trigger "Update yt-dlp" from StartOS Actions menu
|
||||
2. If the action doesn't fix it, rebuild and push a new package with the latest yt-dlp
|
||||
3. If yt-dlp itself hasn't released a fix yet, the nightly build channel may have it -- consider switching the auto-update to use `yt-dlp --update-to nightly` temporarily
|
||||
|
||||
### When StartOS Updates
|
||||
|
||||
- Test the package on new StartOS versions before they're widely deployed
|
||||
- The 0.4.0 beta may have breaking changes as it stabilizes; pin to specific beta versions during testing
|
||||
- Keep the wrapper repo and packaging log updated
|
||||
|
||||
### Gemini API Changes
|
||||
|
||||
- The app already handles model fallbacks (tries multiple models)
|
||||
- Google periodically deprecates Gemini model versions; update the model list in server/index.js when this happens
|
||||
- API key management is handled via StartOS config, so no package rebuild needed for key rotation
|
||||
|
||||
---
|
||||
|
||||
## 11. Risk Assessment
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|---|---|---|---|
|
||||
| YouTube breaks yt-dlp | High (happens regularly) | Downloads fail until yt-dlp updates | Runtime auto-update + manual action button |
|
||||
| OAuth tokens expire | Medium | Need to re-authenticate | Clear UI instructions; cookies.txt as backup |
|
||||
| Gemini API changes/deprecates models | Low-Medium | Transcription fails | Model fallback chain already implemented |
|
||||
| StartOS 0.4.0 beta has breaking changes | Medium | Package may need rework | Stay on documented APIs; test frequently |
|
||||
| Docker image too large | Low | Slow install/update | Multi-stage build; Alpine base; minimize layers |
|
||||
| Container can't reach YouTube (network) | Low (home network) | Downloads fail | StartOS allows outbound; verify in LXC |
|
||||
| Temp audio files fill disk | Low | Processing fails | Cleanup in /tmp; use TMPDIR on volume if needed |
|
||||
| cookies.txt expires quickly | High (3-5 days) | Auth fails | OAuth2 as primary; clear messaging about cookie limits |
|
||||
|
||||
---
|
||||
|
||||
## 12. Multi-User Distribution: API Keys and Clean Installs
|
||||
|
||||
### Gemini API Key Management
|
||||
|
||||
The Gemini API key must never be baked into the Docker image or package. Each user provides their own key after installation.
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. The `.env` file (which contains your personal API key) is excluded from the Docker image via `.dockerignore`
|
||||
2. On fresh install, the service starts with no API key configured
|
||||
3. The user enters their key via the StartOS config UI, which writes it to `/data/config/startos-config.json` on the persistent volume
|
||||
4. The entrypoint script reads this file and sets `GEMINI_API_KEY` as an environment variable before starting the server
|
||||
5. The web UI settings panel also lets users enter/change the key at any time (this writes to the server's in-memory state and persists to the config file)
|
||||
|
||||
The app already supports this pattern: it reads `GEMINI_API_KEY` from the environment first, then falls back to the `.env` file. On StartOS the environment variable takes precedence, and the `.env` file simply doesn't exist in a fresh install.
|
||||
|
||||
### Clean Installs: No Inherited Data
|
||||
|
||||
The Docker image contains only application code -- no user data. Everything personal lives on the `/data` volume, which starts empty for every new install. This means:
|
||||
|
||||
- No inherited history (processed videos/podcasts)
|
||||
- No inherited subscriptions
|
||||
- No inherited cookies or authentication tokens
|
||||
- No inherited API keys
|
||||
- No inherited folder organization or skip/seen lists
|
||||
|
||||
**The `.dockerignore` ensures nothing personal leaks into the image:**
|
||||
|
||||
```
|
||||
history/
|
||||
cookies.txt
|
||||
.env
|
||||
*.s9pk
|
||||
image.tar
|
||||
node_modules/
|
||||
.DS_Store
|
||||
GET-STARTED.*
|
||||
build-guide-pdf.py
|
||||
create-app.sh
|
||||
setup.sh
|
||||
Start Summarizer.command
|
||||
start9/
|
||||
```
|
||||
|
||||
**First-run flow for a new user:**
|
||||
1. Install the package from StartOS
|
||||
2. Open service config, enter Gemini API key
|
||||
3. Open the web UI
|
||||
4. Set up YouTube OAuth authentication (one-time device code flow)
|
||||
5. Add subscriptions to channels/feeds they want to follow
|
||||
6. Start summarizing -- their library builds from scratch
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: File-by-File Packaging Checklist
|
||||
|
||||
When creating the StartOS package, create/modify these files:
|
||||
|
||||
**New files to create (in start9/0.4/):**
|
||||
|
||||
- [ ] `manifest.yaml` -- set id, title, version, interfaces, config spec, actions, backup
|
||||
- [ ] `Dockerfile` -- multi-stage build, Alpine, Node 20, Python, yt-dlp, ffmpeg
|
||||
- [ ] `docker_entrypoint.sh` -- data dir setup, yt-dlp path, env loading, server start
|
||||
- [ ] `healthcheck.sh` -- curl to /api/health
|
||||
- [ ] `Makefile` -- image-arm, package, verify, clean targets
|
||||
- [ ] `instructions.md` -- setup guide, OAuth instructions, cookie upload, troubleshooting
|
||||
- [ ] `DEPLOY.md` -- build + sideload steps for StartOS 0.4.0
|
||||
- [ ] `LICENSE` -- appropriate license file
|
||||
- [ ] `icon.png` -- app icon (already exists in assets/)
|
||||
|
||||
**App code modifications (in server/index.js):**
|
||||
|
||||
- [ ] Add DATA_DIR environment variable support for all file paths
|
||||
- [ ] Remove auto-sleep/heartbeat shutdown logic
|
||||
- [ ] Remove macOS-specific code (osascript, LAN mode, brew update strategy)
|
||||
- [ ] Add OAuth2 device flow endpoints
|
||||
- [ ] Add download rate limiting for auto-queue
|
||||
- [ ] Add --impersonate chrome flag to yt-dlp calls
|
||||
- [ ] Move cookies.txt path to DATA_DIR
|
||||
- [ ] Update yt-dlp update strategies for Linux container
|
||||
- [ ] Add proxy support (optional config)
|
||||
- [ ] Bind to 0.0.0.0 (currently does this via HOSTNAME env)
|
||||
|
||||
**Documentation updates:**
|
||||
|
||||
- [ ] START9_PACKAGING_LOG.md -- full process documentation (like workout-log)
|
||||
- [ ] VERSIONING.md -- update with youtube-summarizer version policy
|
||||
- [ ] 0.4/README.md -- migration notes and packaging intent
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: Reusable Packaging Roadmap for Future Apps
|
||||
|
||||
This section generalizes the process so it can be reused for packaging any app for StartOS.
|
||||
|
||||
### Step 1: Assess the App
|
||||
|
||||
Before packaging, answer these questions:
|
||||
1. What language/runtime does the app use? (Determines base Docker image)
|
||||
2. Does it need external binaries? (yt-dlp, ffmpeg, etc. -- add to Dockerfile)
|
||||
3. Does it need outbound network access? (API calls, downloads -- verify in StartOS)
|
||||
4. What is the persistent data? (Database, files, config -- maps to /data volume)
|
||||
5. Does it have a health endpoint? (Required for StartOS health checks)
|
||||
6. Does it need configuration? (Maps to StartOS config spec)
|
||||
7. Does it have any platform-specific code? (macOS, Windows -- must be removed/adapted)
|
||||
|
||||
### Step 2: Create the Wrapper
|
||||
|
||||
1. Copy the template from an existing wrapper (this project or workout-log)
|
||||
2. Edit manifest.yaml first -- it defines everything
|
||||
3. Write the Dockerfile -- multi-stage build, Alpine, minimal layers
|
||||
4. Write docker_entrypoint.sh -- data init, env setup, exec app
|
||||
5. Write healthcheck.sh -- simple curl to health endpoint
|
||||
6. Write instructions.md -- user-facing setup guide
|
||||
|
||||
### Step 3: Build and Test Locally
|
||||
|
||||
```bash
|
||||
# Build ARM64 image
|
||||
make -C start9/0.4 image-arm
|
||||
|
||||
# Load and test
|
||||
docker load -i start9/0.4/image.tar
|
||||
docker run -it --rm -v ./test-data:/data -p 3001:3001 <image-name>
|
||||
|
||||
# Verify health
|
||||
curl http://localhost:3001/api/health
|
||||
```
|
||||
|
||||
### Step 4: Package and Sideload
|
||||
|
||||
```bash
|
||||
# Must be in a git repo
|
||||
git init && git add . && git commit -m "Initial packaging"
|
||||
|
||||
# Build s9pk
|
||||
make -C start9/0.4 package
|
||||
|
||||
# Verify
|
||||
make -C start9/0.4 verify
|
||||
|
||||
# Sideload via StartOS web UI
|
||||
```
|
||||
|
||||
### Step 5: Document Everything
|
||||
|
||||
- Update START9_PACKAGING_LOG.md with the full process
|
||||
- Record all issues encountered and how they were resolved
|
||||
- Note any StartOS version-specific quirks
|
||||
- Keep the manifest and Dockerfile well-commented
|
||||
|
||||
### Template Variables for New Projects
|
||||
|
||||
| Variable | Description | Example |
|
||||
|---|---|---|
|
||||
| `<PKG_ID>` | StartOS package identifier | `youtube-summarizer` |
|
||||
| `<APP_PORT>` | Internal container port | `3001` |
|
||||
| `<DATA_PATH>` | Persistent volume mount | `/data` |
|
||||
| `<HEALTH_PATH>` | Health endpoint path | `/api/health` |
|
||||
| `<PROJECT_ROOT>` | Absolute path to repo | `/Users/macpro/Projects/youtube-summarizer` |
|
||||
|
||||
---
|
||||
|
||||
*This document should be updated as the implementation progresses. Each phase completed should be annotated with actual outcomes, issues discovered, and any deviations from the plan.*
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# YouTube Summarizer — Double-click this file to launch!
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
# Move to the folder where this script lives (the project folder)
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
clear
|
||||
echo "============================================"
|
||||
echo " YouTube Summarizer — Starting up..."
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# ── Check for Node.js ────────────────────────────────────────
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "ERROR: Node.js is not installed."
|
||||
echo ""
|
||||
echo "To install it, open Terminal and run:"
|
||||
echo " brew install node"
|
||||
echo ""
|
||||
echo "(If you don't have Homebrew, install it first:"
|
||||
echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
||||
echo ")"
|
||||
echo ""
|
||||
echo "Press any key to close..."
|
||||
read -n 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Check for yt-dlp ─────────────────────────────────────────
|
||||
if ! command -v yt-dlp &> /dev/null; then
|
||||
echo "yt-dlp not found. Installing via Homebrew..."
|
||||
if command -v brew &> /dev/null; then
|
||||
brew install yt-dlp
|
||||
else
|
||||
echo "ERROR: yt-dlp is not installed and Homebrew is not available."
|
||||
echo "Install yt-dlp manually: https://github.com/yt-dlp/yt-dlp"
|
||||
echo ""
|
||||
echo "Press any key to close..."
|
||||
read -n 1
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Install npm dependencies if needed ───────────────────────
|
||||
if [ ! -d "server/node_modules" ]; then
|
||||
echo "First run — installing dependencies..."
|
||||
cd server
|
||||
npm install
|
||||
cd ..
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ── Start the server ─────────────────────────────────────────
|
||||
echo "Starting server..."
|
||||
echo ""
|
||||
|
||||
# Start server in the background
|
||||
cd server
|
||||
node index.js &
|
||||
SERVER_PID=$!
|
||||
cd ..
|
||||
|
||||
# Wait for server to be ready
|
||||
echo "Waiting for server to start..."
|
||||
for i in {1..20}; do
|
||||
if curl -s http://localhost:3001/api/health > /dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
# ── Open browser ─────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "Opening browser..."
|
||||
open http://localhost:3001
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " App is running at http://localhost:3001"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Leave this window open while using the app."
|
||||
echo "To stop the server, close this window or press Ctrl+C."
|
||||
echo ""
|
||||
|
||||
# Keep the script running so the server stays alive
|
||||
# When user closes the terminal window or presses Ctrl+C, clean up
|
||||
trap "echo ''; echo 'Shutting down server...'; kill $SERVER_PID 2>/dev/null; exit 0" INT TERM
|
||||
|
||||
wait $SERVER_PID
|
||||
@@ -1,467 +0,0 @@
|
||||
# YouTube Summarizer — Upgrade design notes
|
||||
|
||||
Design sketches for three follow-up features:
|
||||
|
||||
1. **Bundled-LLM relay** — let buyers use the operator's API credentials without exposing them
|
||||
2. **Multi-provider LLM support** — OpenAI, Anthropic Claude, VeniceAI, etc. as Pro alternatives to Gemini
|
||||
3. **OpenWebUI integration** — connect to a buyer's local LLM running on the same Start9 box
|
||||
|
||||
These are not mutually exclusive; the relay design is the architectural foundation that the other two slot into.
|
||||
|
||||
---
|
||||
|
||||
## 1. Bundled-LLM relay
|
||||
|
||||
### The user's first idea — "use my running youtube-summarizer instance"
|
||||
|
||||
> *"If I already have my own youtube-summarizer instance running on clearnet, maybe other users can somehow hit that instance and my instance will send them the json files?"*
|
||||
|
||||
This is coherent and would work, but the tradeoffs aren't great:
|
||||
|
||||
- **Privacy.** The relay sees every YouTube URL every customer processes — channel, video, timing. This makes you a de facto traffic-analysis honeypot whether you want one or not. Customers who care about Start9-style sovereignty will balk.
|
||||
- **Bandwidth.** youtube-summarizer downloads the audio with `yt-dlp`, splits it with `ffmpeg`, then sends *audio* to Gemini for transcription. If your instance does that work, every customer's video transits your bandwidth twice (down from YouTube, up to Google). A typical hour-long podcast is 30–80 MB. 100 active customers running 5 videos/day each = 15–40 GB/day.
|
||||
- **Self-host violation.** Customers chose Start9 *specifically* to keep media-access local. Routing it through your box silently undoes that choice.
|
||||
- **Operational scope creep.** Your instance now needs hardened uptime, capacity planning, abuse handling, ban-evasion when YouTube rate-limits you, etc. — turning a side product into infrastructure.
|
||||
|
||||
So while it'd technically work, I'd retire that variant in favor of a narrower relay design that keeps user-data and self-host benefits intact.
|
||||
|
||||
### The architecture: a thin LLM proxy
|
||||
|
||||
Keep `yt-dlp`, `ffmpeg`, and the orchestration on the customer's box. Move *only* the LLM API call through your relay. Your Gemini key never leaves your server.
|
||||
|
||||
```
|
||||
[customer's Start9 box] [your relay] [Google]
|
||||
youtube-summarizer ──signed─────► relay.keysat.xyz ──────► generativelanguage.googleapis.com
|
||||
yt-dlp + ffmpeg verifies LIC1 (your API key)
|
||||
builds Gemini prompt enforces tier limits
|
||||
POSTs to relay forwards to Gemini
|
||||
◄─stream─ streams response back ◄────────
|
||||
receives chunks JSON
|
||||
saves to history
|
||||
```
|
||||
|
||||
The customer's app does all the heavy lifting (download, audio split, prompt assembly, history). The only thing that crosses the wire to your relay is the actual Gemini API call body — which would have gone to Google directly anyway.
|
||||
|
||||
### Relay API contract (v1)
|
||||
|
||||
Single endpoint, mirrors Gemini's REST shape so the customer's existing Gemini client code barely changes:
|
||||
|
||||
```
|
||||
POST https://relay.keysat.xyz/v1/proxy/gemini/{model}:generateContent
|
||||
|
||||
Headers:
|
||||
X-Keysat-License: LIC1-... # the customer's license key
|
||||
X-Keysat-Product: youtube-summarizer
|
||||
Content-Type: application/json
|
||||
|
||||
Body:
|
||||
{ ...exactly what they would have sent to Google... }
|
||||
|
||||
Response:
|
||||
Either Gemini's response verbatim, or:
|
||||
402 { error: "license_required", message: "..." }
|
||||
402 { error: "feature_not_in_tier", feature: "bundled_api", message: "..." }
|
||||
402 { error: "rate_limit_exceeded", reset_at: "...", message: "..." }
|
||||
401 { error: "license_invalid", reason: "revoked|expired|product_mismatch" }
|
||||
```
|
||||
|
||||
A streaming variant for `:streamGenerateContent` does the same thing with chunked transfer encoding so first-token latency stays low.
|
||||
|
||||
The relay does four things, in order, per request:
|
||||
|
||||
1. **Verify the license** offline using the embedded Keysat public key (same `Verifier` the customer's app uses). Reject with 401 if signature fails or product slug doesn't match.
|
||||
2. **Check entitlements**. The license needs whatever entitlement gates this feature. For example, Pro tier might have `bundled_api`; Core wouldn't.
|
||||
3. **Enforce rate limits** per license per day, persisted in a small KV (Redis, SQLite, even a flat JSON file for v0). Default: Core = 0, Pro = N requests/day. Configurable at deploy time.
|
||||
4. **Forward to Gemini** with your real API key. Stream the response back. Log the `license_id`, model, token count, and rough cost for billing visibility — never log the prompt content (privacy).
|
||||
|
||||
### Implementation outline
|
||||
|
||||
A v1 relay is small — ~200 lines of Node, deployable on Cloudflare Workers, Fly.io, or your own Start9 box.
|
||||
|
||||
```typescript
|
||||
// pseudocode for the core forward handler
|
||||
import { Verifier, PublicKey } from '@keysat/licensing-client'
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const ISSUER_PEM = process.env.ISSUER_PEM! // your Keysat issuer pubkey
|
||||
const GEMINI_KEY = process.env.GEMINI_API_KEY!
|
||||
const verifier = new Verifier(PublicKey.fromPem(ISSUER_PEM))
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
app.post('/v1/proxy/gemini/:model{.+}', async (c) => {
|
||||
const license = c.req.header('X-Keysat-License')
|
||||
if (!license) return c.json({ error: 'license_required' }, 402)
|
||||
|
||||
// 1. Verify
|
||||
let payload
|
||||
try { payload = verifier.verify(license).payload }
|
||||
catch (e) { return c.json({ error: 'license_invalid', reason: e.message }, 401) }
|
||||
|
||||
if (payload.productSlug !== 'youtube-summarizer') {
|
||||
return c.json({ error: 'product_mismatch' }, 401)
|
||||
}
|
||||
|
||||
// 2. Entitlement
|
||||
if (!payload.entitlements.includes('bundled_api')) {
|
||||
return c.json({ error: 'feature_not_in_tier', feature: 'bundled_api' }, 402)
|
||||
}
|
||||
|
||||
// 3. Rate limit (per license per UTC day)
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
const used = await usageStore.incr(`${payload.licenseId}:${today}`)
|
||||
const cap = entitlementsToDailyCap(payload.entitlements) // e.g. Pro = 50
|
||||
if (used > cap) {
|
||||
return c.json({ error: 'rate_limit_exceeded', reset_at: tomorrowMidnightUtc() }, 402)
|
||||
}
|
||||
|
||||
// 4. Forward
|
||||
const upstream = `https://generativelanguage.googleapis.com/v1/models/${c.req.param('model')}?key=${GEMINI_KEY}`
|
||||
const r = await fetch(upstream, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: c.req.raw.body,
|
||||
})
|
||||
|
||||
// log usage (no body content)
|
||||
await metrics.record({
|
||||
license_id: payload.licenseId,
|
||||
model: c.req.param('model'),
|
||||
tokens_in: r.headers.get('x-goog-prompt-tokens'),
|
||||
tokens_out: r.headers.get('x-goog-completion-tokens'),
|
||||
status: r.status,
|
||||
})
|
||||
|
||||
return new Response(r.body, { status: r.status, headers: r.headers })
|
||||
})
|
||||
```
|
||||
|
||||
### Customer-side change
|
||||
|
||||
In the youtube-summarizer's `server/index.js`, the existing `GoogleGenAI` instantiation gets a base URL override when the user has the bundled-API option enabled:
|
||||
|
||||
```js
|
||||
// today
|
||||
const ai = new GoogleGenAI({ apiKey, httpOptions: { ... } })
|
||||
|
||||
// with relay
|
||||
const useBundled = LIC.entitlements.has('bundled_api') && !clientKey
|
||||
const ai = new GoogleGenAI({
|
||||
apiKey: useBundled ? LIC.licenseKey : apiKey, // license key as bearer
|
||||
baseUrl: useBundled ? RELAY_BASE_URL : undefined,
|
||||
defaultHeaders: useBundled ? { 'X-Keysat-License': LIC.licenseKey } : undefined,
|
||||
})
|
||||
```
|
||||
|
||||
The user never sees a Gemini API key field if their license includes `bundled_api`. They install, activate, and it works.
|
||||
|
||||
### Operational considerations
|
||||
|
||||
- **Cost monitoring.** Wire the metrics output into a billing alert (e.g. Cloudflare Workers Analytics, or a Slack webhook on threshold). A misconfigured policy that gives Core users `bundled_api` could blow through your budget overnight.
|
||||
- **Provider hot-swap.** Because the relay sits between customer and Gemini, you can swap to a different LLM provider without any client update. Useful for cost optimization or if Google ever revokes your key.
|
||||
- **Self-hostable relay.** Ship the relay code as its own s9pk so customers who *want* to bring their own API key can run their own relay. Same code, their key. This keeps the "Start9 sovereignty" pitch intact for the subset of users who care.
|
||||
- **Resilience.** Add an `X-Keysat-Bundled-Off` header (or a license entitlement flag) the customer's app can use to bypass the relay if it's down — falls back to "you need to enter a key in settings."
|
||||
|
||||
### Variants worth considering
|
||||
|
||||
- **Two-tier rate limits inside Pro.** Pro_lite (10/day), Pro_unlimited (no cap, costs more). Just two policies in Keysat admin with different `bundled_api` rate annotations.
|
||||
- **Token-budget instead of request-count.** Daily Gemini token cap rather than request cap. More accurate cost control but harder to communicate to buyers.
|
||||
- **Stripe-style metered billing later.** If usage ever justifies it, the relay's per-license metrics are exactly what you'd hand to a metered-billing system.
|
||||
|
||||
### Top-up credits — pay-as-you-go beyond the daily cap
|
||||
|
||||
A natural extension of the daily-rate-limit model: when a buyer hits their cap and wants more right now (instead of waiting for the UTC reset), let them buy a credit pack. Each pack adds N requests to their license's available pool, drawn down as they go.
|
||||
|
||||
**Why this works for your business model.** It captures the high-engagement long tail: the buyer who occasionally has a "need to summarize 30 podcasts today for research" day. Without credits, they either upgrade to a higher tier they don't actually need (overcharge), or they hit the wall and leave annoyed (lost engagement). Credits convert those moments into incremental Bitcoin revenue with no commitment.
|
||||
|
||||
**Why it works architecturally.** Keysat already has BTCPay integration and a buy-flow for products. Credit packs are just another product slug in your Keysat admin (`youtube-summarizer-credits-100`, `-500`, etc.) priced in sats. The buy flow returns a *credit token* — a short signed blob, similar shape to a `LIC1-...` license but smaller — that the customer's app posts to your relay to redeem. The relay verifies the signature, increments that license's credit balance in its KV store, marks the token as used.
|
||||
|
||||
**Token format and verification.** Same Ed25519 trust root as licenses, different prefix:
|
||||
|
||||
```
|
||||
CRED1-AIBAH5T...4LXMZW2A
|
||||
```
|
||||
|
||||
Payload includes: `credit_pack_id` (UUID), `license_id` (the license being topped up), `units` (e.g. 100), `issued_at`, `expires_at` (or 0 for never), signature. The relay's redeem endpoint:
|
||||
|
||||
```
|
||||
POST https://relay.keysat.xyz/v1/credits/redeem
|
||||
Headers: X-Keysat-License: LIC1-...
|
||||
Body: { "credit_token": "CRED1-..." }
|
||||
|
||||
200 → { "ok": true, "credits_added": 100, "balance": 134 }
|
||||
400 → { "error": "already_redeemed" | "license_mismatch" | "expired" | "bad_signature" }
|
||||
```
|
||||
|
||||
**Relay accounting.** Modify the rate-limit step in §"Implementation outline" to consult both the daily cap and the credit pool:
|
||||
|
||||
```
|
||||
1. Today's daily allotment used N of M (Pro = 50/day, say)
|
||||
2. If N < M → allow, increment daily counter
|
||||
3. If N >= M → check credit balance for this license
|
||||
4. If credits > 0 → allow, decrement credit balance
|
||||
5. Else → 402 rate_limit_exceeded with "buy more" hint
|
||||
```
|
||||
|
||||
The 402 response includes a buy-credits URL the app can deep-link to, same pattern as the existing `/buy/youtube-summarizer` link:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "rate_limit_exceeded",
|
||||
"reset_at": "2026-05-08T00:00:00Z",
|
||||
"buy_credits_url": "https://licensing.keysat.xyz/buy/youtube-summarizer-credits-100",
|
||||
"message": "You've used today's allotment. Buy a credit pack or wait until midnight UTC."
|
||||
}
|
||||
```
|
||||
|
||||
**Customer-side UX.** In settings, the License block now shows usage:
|
||||
|
||||
```
|
||||
Pro license — Active
|
||||
Today: 50 / 50 used. Credits: 23 available.
|
||||
[Buy 100 more credits — 5,000 sats]
|
||||
```
|
||||
|
||||
When they tap "Buy", an `await client.startPurchase('youtube-summarizer-credits-100')`-style flow opens BTCPay, settles, returns a `CRED1-...` token, the app POSTs it to `/v1/credits/redeem`, balance updates. Same friction profile as the original license activation — paste-from-clipboard or open-URL — except now it's mid-flow rather than first-launch.
|
||||
|
||||
**Operator pricing knobs.** Each credit pack you list in Keysat admin has its own price + units. Common shapes:
|
||||
|
||||
- 100 credits @ 5K sats ($3) — impulse-buy size
|
||||
- 500 credits @ 20K sats ($12) — moderate top-up
|
||||
- 2000 credits @ 60K sats ($35) — power user (effective ~30% discount per credit)
|
||||
|
||||
Set the underlying cost-per-credit so 1 credit ≈ 1 Gemini call ≈ your cost × margin. The pack-discount structure encourages bigger top-ups, which matches your incentive (you want fewer, larger top-ups to amortize the BTCPay fee per transaction).
|
||||
|
||||
**Edge cases worth thinking about.**
|
||||
|
||||
- **Refunds.** Credits are non-refundable by default (they're prepaid API time, not durable goods). State this in the buy-page copy.
|
||||
- **Expiration.** Decide whether credits expire. Pros of expiration: predictable cost liability on your books. Cons: customer-hostile. A 12-month rolling expiration is a fair compromise.
|
||||
- **Per-product slug or universal.** Credit packs could be product-specific (`youtube-summarizer-credits-100`) or operator-wide (`keysat-credits-100`, redeemable across any of your products). Universal is more flexible but harder to price coherently if the underlying API costs differ across products.
|
||||
- **Multi-machine.** Credits attach to a license, and licenses can move between machines if the operator allows. Make sure the credit balance follows the license, not the fingerprint. (Mostly a non-issue if your relay state is keyed by `license_id`.)
|
||||
|
||||
**Effort estimate.** ~2 days on top of the base relay:
|
||||
|
||||
- Credit-token format + Ed25519 signing helper in the licensing service: 0.5 day (mirrors the existing license format).
|
||||
- BTCPay product slug for credit packs in Keysat admin: 0.25 day (reuse existing buy-flow).
|
||||
- Relay redeem endpoint + balance accounting: 0.5 day.
|
||||
- Customer app: balance display + buy-flow integration: 0.5 day.
|
||||
- Test end-to-end (buy → redeem → relay deduct → exhaust → buy again): 0.25 day.
|
||||
|
||||
**Worth shipping with the v1 relay or after?** I'd ship credits a release after the base relay. Validate the daily-cap model is the right primitive first; if buyers regularly hit caps, *that's* the demand signal that justifies adding credits. Shipping both at once risks over-investing in a monetization layer no one needs.
|
||||
|
||||
---
|
||||
|
||||
## 2. Multi-provider LLM support
|
||||
|
||||
### Why this is a real lift
|
||||
|
||||
Each provider's API is similar in spirit but different in detail, especially around:
|
||||
|
||||
- **Audio handling.** Today's youtube-summarizer ships audio chunks directly to Gemini, which natively transcribes them. Most other providers don't accept audio at all — you have to transcribe first, then send text. So adding non-Gemini providers means adding a *transcription* layer, which is a separate API surface.
|
||||
- **Structured output.** youtube-summarizer's prompts ask Gemini for JSON-formatted topic chunks. Different providers express "give me JSON" differently (function-calling, response-format JSON mode, schema-guided decoding, or just "trust the prompt"). Inconsistent reliability.
|
||||
- **Streaming.** Each provider's stream protocol is different (SSE shape, delta encoding). Either standardize on a unified stream type internally, or accept that streaming UX differs per provider.
|
||||
- **Token limits and context windows.** A 3-hour podcast that fits in Gemini 2.5 Pro's 2M-token window won't fit in Claude Opus's 200K. Forces chunking strategy decisions per provider.
|
||||
|
||||
### Provider taxonomy
|
||||
|
||||
| Provider | Native audio | Long context | OpenAI-compat API | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Google Gemini 2.5 | yes | 2M tokens | no | What you have today. |
|
||||
| OpenAI GPT-4o | yes (audio modality) | 128K | yes (it's the reference) | Separate `audio.transcriptions` endpoint for Whisper. |
|
||||
| Anthropic Claude | no | 200K | partial (via wrappers) | Need external transcription. |
|
||||
| VeniceAI | no (most models) | varies | yes (OpenAI-compatible) | Privacy-focused; uses open models. |
|
||||
| OpenAI-compatible local (Ollama, OpenWebUI, vLLM) | usually no | varies | yes | See section 3. |
|
||||
|
||||
The realistic picture: there are **two** LLM steps in your pipeline (transcription + topic analysis), and providers split into "can do both," "can do only the second," and "can't do either with quality." Multi-provider support means designing both steps as pluggable.
|
||||
|
||||
### Provider abstraction
|
||||
|
||||
Define a small interface in `server/`:
|
||||
|
||||
```typescript
|
||||
// server/providers/types.ts
|
||||
export interface TranscriptionProvider {
|
||||
name: string
|
||||
transcribe(audioChunk: Buffer, opts: { language?: string }): Promise<TranscriptResult>
|
||||
}
|
||||
|
||||
export interface AnalysisProvider {
|
||||
name: string
|
||||
analyze(transcript: string, prompt: PromptSpec): AsyncIterable<AnalysisChunk>
|
||||
}
|
||||
|
||||
export interface ProviderBundle {
|
||||
transcribe: TranscriptionProvider
|
||||
analyze: AnalysisProvider
|
||||
}
|
||||
```
|
||||
|
||||
Then concrete implementations:
|
||||
|
||||
```
|
||||
server/providers/
|
||||
gemini.ts # both transcribe + analyze, native audio
|
||||
openai.ts # both transcribe (Whisper) + analyze
|
||||
claude.ts # analyze only — pairs with whisper or deepgram
|
||||
venice.ts # analyze only (OpenAI-compatible) — pairs with whisper
|
||||
openwebui.ts # analyze only, OpenAI-compatible at custom base URL
|
||||
whisper-cpp.ts # local transcribe via whisper.cpp binary
|
||||
deepgram.ts # remote transcribe, very cheap
|
||||
```
|
||||
|
||||
Then a small registry that picks the right combo:
|
||||
|
||||
```typescript
|
||||
const BUNDLES: Record<string, ProviderBundle> = {
|
||||
'gemini': { transcribe: gemini, analyze: gemini },
|
||||
'openai-gpt4o': { transcribe: openai, analyze: openai },
|
||||
'claude+whisper': { transcribe: openai, analyze: claude },
|
||||
'venice+whisper': { transcribe: openai, analyze: venice },
|
||||
'local': { transcribe: whisper, analyze: openwebui },
|
||||
}
|
||||
```
|
||||
|
||||
The user picks a bundle in settings; `/api/process` reads it and dispatches accordingly.
|
||||
|
||||
### What this looks like for the user
|
||||
|
||||
In the settings panel, the existing "Analysis Model" section becomes "Provider":
|
||||
|
||||
```
|
||||
Provider:
|
||||
◉ Gemini (default, fast, includes audio transcription)
|
||||
○ OpenAI GPT-4o (Pro feature)
|
||||
○ Anthropic Claude + Whisper (Pro feature)
|
||||
○ VeniceAI + Whisper (Pro feature, privacy-focused)
|
||||
○ Local LLM via OpenWebUI (Pro feature, see "Connect OpenWebUI")
|
||||
|
||||
Model: [model dropdown — provider-specific]
|
||||
API Key: [______________] (per-provider, stored locally)
|
||||
```
|
||||
|
||||
Each provider has its own API key field. Pro tier unlocks non-Gemini providers via a `multi_provider` entitlement.
|
||||
|
||||
### Pricing/tier tie-in
|
||||
|
||||
This pairs cleanly with the relay design from §1: your bundled relay can support multiple providers behind the same `bundled_api` entitlement. Customer's tier determines which providers are reachable.
|
||||
|
||||
```
|
||||
Core tier: ["core", "history", "library"]
|
||||
→ Gemini only, BYO key
|
||||
|
||||
Pro tier: ["core", "history", "library", "subscriptions", "clips", "multi_provider", "bundled_api"]
|
||||
→ all providers; bundled relay covers Gemini + OpenAI; BYO available for any
|
||||
```
|
||||
|
||||
### Effort estimate
|
||||
|
||||
- **Provider interface + Gemini refactor**: 1 day. Move existing logic into `gemini.ts` matching the new interface.
|
||||
- **OpenAI provider**: 1 day. The OpenAI Node SDK is straightforward; transcription via `audio.transcriptions.create({ model: 'whisper-1' })`.
|
||||
- **Claude provider**: 1 day. Pair with OpenAI's Whisper (or Deepgram) for audio. Prompt-engineer JSON output (Claude prefers `<json>...</json>` tags or function calling).
|
||||
- **VeniceAI**: 0.5 day if it's truly OpenAI-compatible — basically the OpenAI provider with a different base URL.
|
||||
- **Frontend provider switcher + per-provider key fields**: 1 day.
|
||||
- **Testing across providers**: 2 days. Different audio quality, different JSON adherence, different latencies.
|
||||
|
||||
About a week of focused work for v1. Worth doing only if you have buyers actively asking, since maintenance scales with provider count (each one breaks differently when the vendor changes pricing/APIs).
|
||||
|
||||
---
|
||||
|
||||
## 3. OpenWebUI / local LLM integration
|
||||
|
||||
### Why this is special
|
||||
|
||||
OpenWebUI on a Start9 box gives the user a self-hosted local LLM (Llama 3, Mistral, whatever they've pulled). It exposes an OpenAI-compatible API. From youtube-summarizer's perspective, it's "OpenAI provider but pointed at `http://openwebui-internal.local:3000/v1`" instead of `https://api.openai.com/v1`.
|
||||
|
||||
The Start9-specific superpower: **service mesh dependencies**. youtube-summarizer's manifest can declare a soft dependency on OpenWebUI, and StartOS will inject a hostname/port the customer's box can reach internally. No customer-typed URL required.
|
||||
|
||||
### Architectural shape
|
||||
|
||||
```
|
||||
[customer's Start9 box]
|
||||
+--------------------------+ +-------------------------+
|
||||
| youtube-summarizer | | OpenWebUI |
|
||||
| (this app) |◄────►| (local LLM via Ollama) |
|
||||
+--------------------------+ +-------------------------+
|
||||
uses openwebui's hosts the actual model
|
||||
OpenAI-compatible API (Llama 3 70B, etc.)
|
||||
```
|
||||
|
||||
In `manifest/index.ts`:
|
||||
|
||||
```typescript
|
||||
dependencies: {
|
||||
'openwebui': {
|
||||
type: 'opt-in',
|
||||
description: 'Optional: use a local LLM running on this server.',
|
||||
versionRange: '>=0.4.0',
|
||||
requirement: 'optional',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the dependency is present and started, StartOS gives youtube-summarizer the OpenWebUI hostname (e.g. `http://openwebui.embassy:8080` on the internal mesh). The app's "Local LLM" provider option becomes auto-configured.
|
||||
|
||||
### Limitations to call out clearly
|
||||
|
||||
- **No native audio.** Most local models won't accept audio; they're text-only. Need a separate transcription path: ship `whisper.cpp` in the youtube-summarizer container (~30 MB) and run it locally. The customer's CPU does the transcription. Slow on Pi-class hardware (a 1-hour podcast might take 10+ minutes); fine on desktop-class.
|
||||
- **JSON adherence varies.** Local models are less reliable at structured output than Gemini/Claude. Need defensive parsing + retries. Consider using a JSON-schema-guided decoder (xgrammar, llguidance) if available in OpenWebUI's runner.
|
||||
- **Context windows are smaller.** Llama 3.1 70B = 128K. Long podcasts may need chunked summarization-of-summarizations strategy. Existing chunking logic adapts but needs tuning.
|
||||
- **Compute cost.** Running 70B inference on a Start9 box with no GPU is ~5–30 sec/token. Fine for a topic-summary of a transcript chunk; rough for a long-form summary. Consider Llama 3.1 8B as default.
|
||||
|
||||
### Pricing tie-in
|
||||
|
||||
This one's interesting — it's a Pro feature but has *zero* operator cost (the customer's hardware does the work). So it's pure margin once Pro is bought. Could even be marketed as: "Pro gives you Bundled Gemini OR your own local LLM — no API bills either way."
|
||||
|
||||
### Effort estimate
|
||||
|
||||
- **Manifest dependency declaration**: 1 hour.
|
||||
- **OpenWebUI provider** (subset of the OpenAI provider work): 1 day. Just a different base URL + auto-config from the StartOS-injected hostname.
|
||||
- **Local Whisper integration**: 1 day. Ship `whisper.cpp` binary, expose a transcription endpoint, fall back to Gemini-based transcription if the binary errors.
|
||||
- **Tuning prompts for smaller local models**: 2 days. Llama 3.1 needs different prompting than Gemini. Iterative.
|
||||
- **Frontend "OpenWebUI detected" affordance**: 0.5 day. When the dependency is present, show a green badge and one-click switch.
|
||||
|
||||
About 4–5 days. Cleanest if done after the multi-provider abstraction (§2) lands, since OpenWebUI is just another provider in that taxonomy.
|
||||
|
||||
---
|
||||
|
||||
## How they all stack
|
||||
|
||||
There's a natural ordering of work:
|
||||
|
||||
1. **Land the relay first** (§1). It's the foundation — once Gemini-via-relay works, every later provider plugs into the same relay pipeline.
|
||||
2. **Multi-provider abstraction** (§2). The provider interface is what makes the relay support multiple upstreams without growing fragile if-statements.
|
||||
3. **OpenWebUI as a provider** (§3). Just another bundle in the registry once §2 is done.
|
||||
|
||||
If you ship in that order, the work compounds. If you ship them out of order, each one needs partial rework when the next lands.
|
||||
|
||||
A reasonable cadence: relay v1 (~1 week) → multi-provider Pro feature (~1 week) → OpenWebUI integration (~3 days). Roughly 3 weeks of focused work for the full vision, parallelizable in places.
|
||||
|
||||
---
|
||||
|
||||
## Pricing scenarios for reference
|
||||
|
||||
Sketches only — actual numbers depend on volume. Goal is to show how the architecture supports different pricing strategies.
|
||||
|
||||
**Scenario A: bundled-only, simple**
|
||||
- Core (one-time) → BYO Gemini key, all single-video features
|
||||
- Pro (one-time) → Bundled Gemini (50 videos/day), all features
|
||||
- Operator margin per Pro license = (Pro price) − (50 × cost-per-video × expected-license-lifetime)
|
||||
|
||||
**Scenario A+: bundled with credit top-ups**
|
||||
- Same as A, plus: when a Pro license hits its daily cap, buyer can purchase credit packs (100/500/2000) via BTCPay to extend within the same license. Captures heavy-use moments without forcing tier upgrades.
|
||||
- Margin per credit pack = (pack price) − (units × cost-per-call). Set so each pack discount tier still nets ≥40% margin even at full burn-down.
|
||||
- See section 1 for the architecture; effort is ~2 days on top of base relay.
|
||||
|
||||
**Scenario B: bundled subscription**
|
||||
- Core (one-time) → BYO key, all single-video features
|
||||
- Pro Lite (subscription) → Bundled Gemini (10/day), all features
|
||||
- Pro Unlimited (subscription) → Bundled Gemini (no cap), multi-provider, all features
|
||||
- Per-month subscription billing → recurring cost coverage
|
||||
|
||||
**Scenario C: provider-tiered**
|
||||
- Core (one-time) → BYO Gemini key only
|
||||
- Pro Standard (one-time) → Bundled Gemini, OpenAI, multi-provider
|
||||
- Pro Local (one-time) → Multi-provider including OpenWebUI/local LLM, no bundled
|
||||
- Splits the customer base by what they actually want
|
||||
|
||||
The current tier setup (Core/Pro split) is structurally compatible with all three scenarios. You'd just adjust the entitlement-to-feature mapping in Keysat admin policies.
|
||||
@@ -1,326 +0,0 @@
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.colors import HexColor
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib.enums import TA_LEFT, TA_CENTER
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
|
||||
PageBreak, KeepTogether, HRFlowable, Preformatted
|
||||
)
|
||||
from reportlab.lib import colors
|
||||
import os
|
||||
|
||||
OUTPUT = os.path.join(os.path.dirname(__file__), "GET-STARTED.pdf")
|
||||
|
||||
doc = SimpleDocTemplate(
|
||||
OUTPUT,
|
||||
pagesize=letter,
|
||||
topMargin=0.75 * inch,
|
||||
bottomMargin=0.75 * inch,
|
||||
leftMargin=0.85 * inch,
|
||||
rightMargin=0.85 * inch,
|
||||
)
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Custom styles
|
||||
styles.add(ParagraphStyle(
|
||||
"DocTitle", parent=styles["Title"],
|
||||
fontSize=26, leading=32, textColor=HexColor("#0f172a"),
|
||||
spaceAfter=4, fontName="Helvetica-Bold",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"DocSubtitle", parent=styles["Normal"],
|
||||
fontSize=13, leading=18, textColor=HexColor("#64748b"),
|
||||
spaceAfter=24, alignment=TA_CENTER,
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"H1", parent=styles["Heading1"],
|
||||
fontSize=20, leading=26, textColor=HexColor("#0f172a"),
|
||||
spaceBefore=28, spaceAfter=10, fontName="Helvetica-Bold",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"H2", parent=styles["Heading2"],
|
||||
fontSize=15, leading=20, textColor=HexColor("#1e293b"),
|
||||
spaceBefore=20, spaceAfter=8, fontName="Helvetica-Bold",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"Body", parent=styles["Normal"],
|
||||
fontSize=11, leading=17, textColor=HexColor("#334155"),
|
||||
spaceAfter=8, fontName="Helvetica",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"BodyBold", parent=styles["Normal"],
|
||||
fontSize=11, leading=17, textColor=HexColor("#1e293b"),
|
||||
spaceAfter=8, fontName="Helvetica-Bold",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"CodeBlock", parent=styles["Code"],
|
||||
fontSize=10, leading=14, textColor=HexColor("#1e293b"),
|
||||
backColor=HexColor("#f1f5f9"),
|
||||
fontName="Courier", leftIndent=16, rightIndent=16,
|
||||
spaceBefore=4, spaceAfter=8,
|
||||
borderPadding=(8, 8, 8, 8),
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"BulletItem", parent=styles["Normal"],
|
||||
fontSize=11, leading=17, textColor=HexColor("#334155"),
|
||||
leftIndent=24, bulletIndent=10, spaceAfter=4,
|
||||
fontName="Helvetica",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"StepNum", parent=styles["Normal"],
|
||||
fontSize=11, leading=17, textColor=HexColor("#334155"),
|
||||
leftIndent=24, bulletIndent=4, spaceAfter=4,
|
||||
fontName="Helvetica",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"Note", parent=styles["Normal"],
|
||||
fontSize=10, leading=15, textColor=HexColor("#92400e"),
|
||||
backColor=HexColor("#fffbeb"),
|
||||
leftIndent=12, rightIndent=12,
|
||||
spaceBefore=8, spaceAfter=8,
|
||||
borderPadding=(8, 8, 8, 8),
|
||||
fontName="Helvetica",
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
"Link", parent=styles["Normal"],
|
||||
fontSize=11, leading=17, textColor=HexColor("#2563eb"),
|
||||
spaceAfter=4, fontName="Helvetica",
|
||||
))
|
||||
|
||||
def code_block(text):
|
||||
"""Create a code block with background using Preformatted to preserve newlines."""
|
||||
return Preformatted(text, styles["CodeBlock"])
|
||||
|
||||
def hr():
|
||||
return HRFlowable(width="100%", thickness=1, color=HexColor("#e2e8f0"), spaceBefore=12, spaceAfter=12)
|
||||
|
||||
story = []
|
||||
|
||||
# -- Title --
|
||||
story.append(Spacer(1, 40))
|
||||
story.append(Paragraph("YouTube Transcript Summarizer", styles["DocTitle"]))
|
||||
story.append(Paragraph("Mac Setup Guide", styles["DocSubtitle"]))
|
||||
story.append(hr())
|
||||
|
||||
# -- What you're setting up --
|
||||
story.append(Paragraph("What You're Setting Up", styles["H1"]))
|
||||
story.append(Paragraph(
|
||||
"This app has two parts that work together:",
|
||||
styles["Body"]
|
||||
))
|
||||
story.append(Paragraph(
|
||||
"<b>1. A small server</b> that runs locally on your Mac (not on the internet -- only you can access it). "
|
||||
"It downloads YouTube audio and talks to Google's Gemini AI.",
|
||||
styles["BulletItem"]
|
||||
))
|
||||
story.append(Paragraph(
|
||||
"<b>2. A webpage</b> that the server hosts in your browser at <font face='Courier'>http://localhost:3001</font>.",
|
||||
styles["BulletItem"]
|
||||
))
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(Paragraph(
|
||||
"Nothing is uploaded anywhere. Everything runs on your machine.",
|
||||
styles["BodyBold"]
|
||||
))
|
||||
|
||||
# -- Where to save --
|
||||
story.append(Paragraph("Where to Save the Project", styles["H1"]))
|
||||
story.append(Paragraph(
|
||||
"The <font face='Courier' color='#1e293b'>youtube-summarizer</font> folder you downloaded can live anywhere, "
|
||||
"but the standard Mac convention for projects like this is:",
|
||||
styles["Body"]
|
||||
))
|
||||
story.append(code_block("~/Projects/youtube-summarizer"))
|
||||
story.append(Paragraph(
|
||||
"The <font face='Courier'>~</font> means your home folder (e.g., <font face='Courier'>/Users/grant</font>). "
|
||||
"If you don't have a <font face='Courier'>Projects</font> folder yet, you'll create one below.",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
story.append(Paragraph("To move the files there:", styles["BodyBold"]))
|
||||
story.append(Paragraph("1. Open <b>Finder</b>", styles["StepNum"]))
|
||||
story.append(Paragraph("2. Go to your home folder (press <b>Cmd + Shift + H</b>)", styles["StepNum"]))
|
||||
story.append(Paragraph("3. Create a new folder called <b>Projects</b> if you don't have one", styles["StepNum"]))
|
||||
story.append(Paragraph("4. Drag the <font face='Courier'>youtube-summarizer</font> folder from your Cowork downloads into <b>Projects</b>", styles["StepNum"]))
|
||||
|
||||
story.append(Spacer(1, 8))
|
||||
story.append(Paragraph("You should end up with this structure:", styles["Body"]))
|
||||
story.append(code_block(
|
||||
"~/Projects/youtube-summarizer/\n"
|
||||
"|-- public/\n"
|
||||
"| +-- index.html\n"
|
||||
"|-- server/\n"
|
||||
"| |-- index.js\n"
|
||||
"| +-- package.json\n"
|
||||
"|-- setup.sh\n"
|
||||
"|-- Start Summarizer.command <-- double-click to launch!\n"
|
||||
"+-- GET-STARTED.pdf <-- this file"
|
||||
))
|
||||
|
||||
# -- Step 1 --
|
||||
story.append(PageBreak())
|
||||
story.append(Paragraph("Step 1: Install Node.js and yt-dlp", styles["H1"]))
|
||||
story.append(Paragraph(
|
||||
"You need two things installed: Node.js (runs the server) and yt-dlp (downloads YouTube audio). "
|
||||
"The easiest way to install both is with Homebrew.",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
story.append(Paragraph("If you don't have Homebrew yet:", styles["BodyBold"]))
|
||||
story.append(Paragraph("1. Open <b>Terminal</b> (press <b>Cmd + Space</b>, type \"Terminal\", hit Enter)", styles["StepNum"]))
|
||||
story.append(Paragraph("2. Paste this and press Enter:", styles["StepNum"]))
|
||||
story.append(code_block('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'))
|
||||
story.append(Paragraph("3. Follow the prompts (it may ask for your Mac password)", styles["StepNum"]))
|
||||
|
||||
story.append(Spacer(1, 8))
|
||||
story.append(Paragraph("Then install Node.js and yt-dlp:", styles["BodyBold"]))
|
||||
story.append(code_block("brew install node yt-dlp"))
|
||||
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(Paragraph("Verify they're installed:", styles["Body"]))
|
||||
story.append(code_block("node --version\nyt-dlp --version"))
|
||||
story.append(Paragraph(
|
||||
"You should see version numbers for both. If so, you're ready for the next step.",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
# -- Step 2 --
|
||||
story.append(Paragraph("Step 2: Install Server Dependencies", styles["H1"]))
|
||||
story.append(Paragraph(
|
||||
"This is a one-time step. In Terminal, run:",
|
||||
styles["Body"]
|
||||
))
|
||||
story.append(code_block("cd ~/Projects/youtube-summarizer/server\nnpm install"))
|
||||
story.append(Paragraph(
|
||||
"This downloads the JavaScript libraries the server needs. You'll see some output scroll by -- "
|
||||
"when it finishes and shows you a new prompt, you're done.",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
# -- Step 3 --
|
||||
story.append(Paragraph("Step 3: Add Your Gemini API Key", styles["H1"]))
|
||||
story.append(Paragraph("The app needs a Google Gemini API key to transcribe and analyze videos.", styles["Body"]))
|
||||
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(Paragraph("Get a free key:", styles["BodyBold"]))
|
||||
story.append(Paragraph("1. Go to <b><font color='#2563eb'>https://aistudio.google.com/apikey</font></b>", styles["StepNum"]))
|
||||
story.append(Paragraph("2. Sign in with your Google account", styles["StepNum"]))
|
||||
story.append(Paragraph("3. Click \"Create API Key\"", styles["StepNum"]))
|
||||
story.append(Paragraph("4. Copy the key -- you'll paste it in the app after launching", styles["StepNum"]))
|
||||
|
||||
# -- Step 4 --
|
||||
story.append(PageBreak())
|
||||
story.append(Paragraph("Step 4: Launch the App", styles["H1"]))
|
||||
story.append(Paragraph(
|
||||
"This is the fun part. You have two ways to launch:",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(Paragraph("Option A: Double-click (recommended)", styles["H2"]))
|
||||
story.append(Paragraph(
|
||||
"Find <font face='Courier'>Start Summarizer.command</font> in your project folder and double-click it.",
|
||||
styles["Body"]
|
||||
))
|
||||
story.append(Paragraph(
|
||||
"The first time, macOS may say the file can't be opened because it's from an unidentified developer. "
|
||||
"If this happens: right-click the file, choose <b>Open</b>, then click <b>Open</b> in the dialog. "
|
||||
"You only need to do this once.",
|
||||
styles["Note"]
|
||||
))
|
||||
story.append(Paragraph(
|
||||
"A Terminal window will open, the server will start, and your browser will automatically open to the app. "
|
||||
"That's it!",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
story.append(Spacer(1, 8))
|
||||
story.append(Paragraph("Option B: Terminal", styles["H2"]))
|
||||
story.append(Paragraph("If you prefer the command line:", styles["Body"]))
|
||||
story.append(code_block("cd ~/Projects/youtube-summarizer/server\nnpm start"))
|
||||
story.append(Paragraph(
|
||||
"Then open <b><font color='#2563eb'>http://localhost:3001</font></b> in your browser.",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
# -- Step 5 --
|
||||
story.append(Spacer(1, 8))
|
||||
story.append(Paragraph("Step 5: Summarize a Video", styles["H1"]))
|
||||
story.append(Paragraph("1. In the app, click <b>\"Gemini API Settings\"</b> and paste your API key", styles["StepNum"]))
|
||||
story.append(Paragraph("2. Paste a YouTube URL into the main input", styles["StepNum"]))
|
||||
story.append(Paragraph("3. Click <b>Summarize</b>", styles["StepNum"]))
|
||||
story.append(Paragraph("4. Watch the 3-step pipeline: Download audio -> Transcribe -> Analyze topics", styles["StepNum"]))
|
||||
story.append(Paragraph("5. Click any topic section to expand the full transcript with clickable timestamps", styles["StepNum"]))
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(Paragraph(
|
||||
"Your API key is saved in your browser so you don't have to re-enter it each time.",
|
||||
styles["Body"]
|
||||
))
|
||||
|
||||
# -- Day-to-day --
|
||||
story.append(hr())
|
||||
story.append(Paragraph("Day-to-Day Usage", styles["H1"]))
|
||||
story.append(Paragraph(
|
||||
"After the one-time setup above, here's all you need to do each time:",
|
||||
styles["Body"]
|
||||
))
|
||||
story.append(Paragraph(
|
||||
"1. Double-click <font face='Courier'>Start Summarizer.command</font>",
|
||||
styles["StepNum"]
|
||||
))
|
||||
story.append(Paragraph(
|
||||
"2. Your browser opens automatically -- paste a YouTube link and go",
|
||||
styles["StepNum"]
|
||||
))
|
||||
story.append(Paragraph(
|
||||
"3. When done, close the Terminal window that opened (this stops the server)",
|
||||
styles["StepNum"]
|
||||
))
|
||||
story.append(Spacer(1, 4))
|
||||
story.append(Paragraph("That's it. One double-click.", styles["BodyBold"]))
|
||||
|
||||
# -- Troubleshooting --
|
||||
story.append(PageBreak())
|
||||
story.append(Paragraph("Troubleshooting", styles["H1"]))
|
||||
|
||||
troubles = [
|
||||
(
|
||||
"macOS won't open Start Summarizer.command",
|
||||
"Right-click the file, choose <b>Open</b>, then click <b>Open</b> in the security dialog. "
|
||||
"This only happens the first time."
|
||||
),
|
||||
(
|
||||
"\"Cannot connect to backend at localhost:3001\"",
|
||||
"The server isn't running. Double-click <font face='Courier'>Start Summarizer.command</font> again."
|
||||
),
|
||||
(
|
||||
"\"yt-dlp not installed\"",
|
||||
"Run <font face='Courier'>brew install yt-dlp</font> in Terminal, then relaunch the app."
|
||||
),
|
||||
(
|
||||
"Download fails or hangs",
|
||||
"yt-dlp might be outdated. Click the \"Update now\" button in the app, or run "
|
||||
"<font face='Courier'>brew upgrade yt-dlp</font> in Terminal."
|
||||
),
|
||||
(
|
||||
"\"Gemini API error: 403\" or \"401\"",
|
||||
"Your API key is invalid or expired. Get a new one from "
|
||||
"<font color='#2563eb'>https://aistudio.google.com/apikey</font>"
|
||||
),
|
||||
(
|
||||
"Long videos take a while",
|
||||
"Normal. A 1-hour video takes ~30 seconds to download, 30-60 seconds for Gemini "
|
||||
"to transcribe, then 10-20 seconds for topic analysis. The app shows live progress."
|
||||
),
|
||||
]
|
||||
|
||||
for problem, solution in troubles:
|
||||
story.append(Paragraph(f"<b>{problem}</b>", styles["BodyBold"]))
|
||||
story.append(Paragraph(solution, styles["BulletItem"]))
|
||||
story.append(Spacer(1, 6))
|
||||
|
||||
# Build
|
||||
doc.build(story)
|
||||
print(f"PDF created: {OUTPUT}")
|
||||
-838
@@ -1,838 +0,0 @@
|
||||
# Netscape HTTP Cookie File
|
||||
# This file is generated by yt-dlp. Do not edit.
|
||||
|
||||
www.recaptcha.net FALSE /recaptcha TRUE 1780180930 _GRECAPTCHA 09ADiQh0dtDEiRMi5WBdQDNRHouJD94ppMc5_pTy3zyVunQwBbEcKtw1XqCAXn_gkExDi4ugUhCOoddAB10vH5POM
|
||||
www.google.com FALSE /recaptcha TRUE 1781725220 _GRECAPTCHA 09ABCLyOG6cxagI3Cieygmf8CmIePx8g730YQnA8OgOrFiMW6on8sgc4c6cl3ZekVdq6dZNEKV3slW9ta33n5Z9iM
|
||||
secure.insightexpressai.com FALSE /adserver TRUE 1786504594 IgniteCookieSync true
|
||||
.google.com TRUE /verify TRUE 1789498633 SNID ALbZf37S4_CrH7J5UP4bphJFKXefW7V_AFkhVBncLQOAFISyal6NWxMB4che_ZQac_ZeJDJHUECJdI-UcqzC0PO5rYfTy9oILug
|
||||
.google.com TRUE / TRUE 1789570228 __Secure-ENID 28.SE=R2eukpRLgAoi_g94FwISFIyz_hrGkQy70QU-Qlld5sS9lrIQtgunjeL-JNU-z7HQl2yQJ0F6yqKfFzXlMoBWJicTsjN8GFNiyELINOi5vU9jtD7EwlOIxOxv71AkEzMFVbBaNq1zhDy7rfIDW9BtANnRn-pXkGN9CfeRQn4gdq1xBRNvuBWYoNqcnTYp5LhJwRdP65bdRw
|
||||
.google.com TRUE / TRUE 1786833884 __Secure-BUCKET COID
|
||||
.google.com TRUE / TRUE 1786833884 AEC AaJma5vPc7N0Rl8mb8fOV-a7JjrmXzK3VfwniJq_dBv23b_DaBVcU7x8zA
|
||||
.google.com TRUE / FALSE 1808253661 SID g.a0007whSQ2FTtN3oaH2z5zAUg7rUNnJXuBC-FtNcmf_q4VZxCH6I5yCVYxA-LmPOHRXLgQktwAACgYKASYSARMSFQHGX2MitoydJ2nBzhSIkt8ceGNDPRoVAUF8yKpvGQGXHR2ixssAhIZ7Tlhg0076
|
||||
.google.com TRUE / TRUE 1808253661 __Secure-1PSID g.a0007whSQ2FTtN3oaH2z5zAUg7rUNnJXuBC-FtNcmf_q4VZxCH6IIWYz6SgNFiNwCFav4fBlxgACgYKAZgSARMSFQHGX2MiUhFl0FoKqeIalNYwK9r2kRoVAUF8yKquv1J4_ggvLRhNHGom3rco0076
|
||||
.google.com TRUE / TRUE 1808253661 __Secure-3PSID g.a0007whSQ2FTtN3oaH2z5zAUg7rUNnJXuBC-FtNcmf_q4VZxCH6Ip1KVQpXe0-3wXj_r0kABhgACgYKAVESARMSFQHGX2MiaYklqIL3lhBMrkEcEBXS5RoVAUF8yKpcc98ulcMv04cCcRloUdNL0076
|
||||
.google.com TRUE / FALSE 1808253661 HSID AKxOhbOXvqjOJatr5
|
||||
.google.com TRUE / TRUE 1808253661 SSID A4lGYkDwWuxlWMvHl
|
||||
.google.com TRUE / FALSE 1808253661 APISID MdMQfFFo_4b0Cw2d/Au_xxzGsUXCUZnFia
|
||||
.google.com TRUE / TRUE 1808253661 SAPISID gAA5ERfoktTnNYkF/A7x17szT_7WqXM9A1
|
||||
.google.com TRUE / TRUE 1808253661 __Secure-1PAPISID gAA5ERfoktTnNYkF/A7x17szT_7WqXM9A1
|
||||
.google.com TRUE / TRUE 1808253661 __Secure-3PAPISID gAA5ERfoktTnNYkF/A7x17szT_7WqXM9A1
|
||||
.google.com TRUE / TRUE 1790880901 NID 530=VZck2xuPHmmWPJE5RehuzIMISpa_iDox0Zh3zpvPk65oDM9Ux4odkGSz1xvlgMD-hc02ehFEliXyGMl1od4-zm3pIl29SHANM8ScGoGvuhUIlkqdgXi2cjC7lpiFf0oMdulG9947fGvqPBUG4WvTqVhqvww7AZ2DGFlK77FUt6AOkfj2l6CtJowDJGcafmu6jE7pw6VACKnOg-X_W2WhjIcSh30wN6HtZCeF_pnwDrtuRjWkDjuhZU9sYSZeXDyPPa6lekXjzwgBegdVTb7aznOCXViTxJdWG8TOwfDhGAcB_RXi0fGeVzRfqQkS-o2UjLkZtWhy1ZPDb699vhn8rGEtYcO-5OhQaf9g8qUnRkJQrtSDZMMtGSdCLey4SQxe1q4uF5Sl9hdMWCk3cAk1pKtm3zjql140Jzb7MOQpgCvgz392qwqwZp05Zi2ZyrdDDol7UIppKQQgEGeT2Gh2d7XJlzU_zS21W1YWF8GvEc801uU_QJ_MGORyghk__TwLfpRsiMRzOCGmxULHQMZqKZum0CrIaW_JOJbBcLeXG3xvb-Sma8Ja7l20dQZN_Sq2J0wx-aZxdNr4mAsYV_a5FnvoosEL3XIGbFRk_eEskDKUDQTnX86ZWpTBpW2wMPQ2mcmRZtRQkeHNyLpM71g-62pB3TBR_XqYuDABI3_xdkJbWgu1TVk9t1-CRpUbxETp2UIes7xTJ7oiusmMOoxX1igZvRnmbQCwhnvOGUZBZ1Ha8c9Uo9orPmMV6N3TFodn9EZu9sqr2yN-2BYUeC3wIOcLNpkygQ
|
||||
.google.com TRUE / FALSE 1806605701 SIDCC AKEyXzXC7yJXWDKmUNJMRQiH3LhIdsJivlmcAdK81cOTLXy5Z8Pd_WSnHvMeeSK6a0o8yzop
|
||||
.google.com TRUE / TRUE 1806605701 __Secure-1PSIDCC AKEyXzVfeKdFV0eLw1OkaCBWbw09BtzNgRtQSbrpcfN41eZEdt5ELWZRWXhaFdXl3U9Jxlgl
|
||||
.google.com TRUE / TRUE 1806605701 __Secure-3PSIDCC AKEyXzXdf2ecDiT04OPnOxH3dHiwXB2bfOSCeHXlFcqz5hKZFHwPGkaeie0FfwqxzqwvwlaG
|
||||
.docsend.com TRUE /view TRUE 1787106781 dbx_js_analytics_id AABO4ekPMjJgWc5twV879ddvZiBCkhtzHwGoowWv5363VQ
|
||||
.docsend.com TRUE / TRUE 1787106780 _us_ eyJfcmFpbHMiOnsibWVzc2FnZSI6IkluWnBaWGRsWkNCa2IyTWkiLCJleHAiOm51bGwsInB1ciI6ImNvb2tpZS5fdXNfIn19--d99e89135b29409ec95f7b01021ec543a463b2ba
|
||||
.docsend.com TRUE / FALSE 1787106781 _ga GA1.2.1299589222.1752546781
|
||||
.docsend.com TRUE / FALSE 1787106782 _ga_JPP8SP2PRX GS2.2.s1752546781$o1$g0$t1752546781$j60$l0$h0
|
||||
.docsend.com TRUE / FALSE 1775876784 intercom-id-lv6lji7h e901f578-bb42-4566-8776-a2dcc3939b1b
|
||||
.docsend.com TRUE / FALSE 1775876784 intercom-device-id-lv6lji7h 87e6f252-90d0-47b2-ab31-55527932921e
|
||||
.docsend.com TRUE / TRUE 1784082794 _v_ k%2BzmVODjr1ktn34Oy5Rj6FwijPhjhBGpWkI33sJ7Bj%2FtpHLVf6p0Taeaxid5jg2zt2mSMii9qw3DrLaqZsBZh1%2Bp8yO%2Fr4j%2Bjm5MHWpwCTM2RlbJ6aj8Sac%3D--l%2FPezHhegs%2Fdy9SM--lGHFKSAFr8z2NZdmHX6FoQ%3D%3D
|
||||
.liadm.com TRUE /j TRUE 1786540591 lidid b18b6947-1c7c-4b46-a344-36c6d4477e91
|
||||
.liadm.com TRUE / TRUE 1802646478 lidid b18b6947-1c7c-4b46-a344-36c6d4477e91
|
||||
.linkedin.com TRUE / TRUE 1799622470 bcookie "v=2&78faccf5-3ecd-4d84-8ab6-ef64a3bf747f"
|
||||
.linkedin.com TRUE / TRUE 1781649297 dfpfpt c83f1ecbd1d74f289955365b5826d8af
|
||||
.linkedin.com TRUE / FALSE 1783050085 sdui_ver sdui-flagship:0.1.7528+sdui-flagship.production
|
||||
.linkedin.com TRUE / TRUE 1790346598 liap true
|
||||
.linkedin.com TRUE / TRUE 1775862470 li_sugr 862c6d25-b5fa-45b5-91de-e013e23d12d0
|
||||
m.stripe.com FALSE / TRUE 1808408907 m 0d856586-3227-461d-a492-2fcbb6769537f33d44
|
||||
.bing.com TRUE / TRUE 1799780686 MUID 10188FE1CFA1634236249C51CE0F62C0
|
||||
.bing.com TRUE / TRUE 1785793502 MSPTC fytp5YpA8Dgp5mtmxchMYfBi46F4ajq3Odc1dbWzvvo
|
||||
.amazon-adsystem.com TRUE / TRUE 1802646478 ad-privacy 0
|
||||
.amazon-adsystem.com TRUE / TRUE 1790896078 ad-id A-mXDgT1iEQ0urAU5Uzkr5k
|
||||
.adsrvr.org TRUE / TRUE 1799622478 TDID 248c815d-b156-4db9-8a92-46c25764dd54
|
||||
.adsrvr.org TRUE / TRUE 1799622478 TDCPM CAESFwoIYXBwbmV4dXMSCwi0sdS11svYPhAFEhkKCnJpZ2h0bWVkaWESCwj6qYye-4rOPBAFEhUKBmdvb2dsZRILCKCmnrXWy9g-EAUSFgoHcnViaWNvbhILCNqij57jqeg-EAUSFQoGY2FzYWxlEgsIoIP2xoaAuz4QBRIXCghwdWJtYXRpYxILCIrZ9saGgLs-EAUSGAoJYmlkc3dpdGNoEgsIlvvKhOSp6D4QBRIUCgV0YXBhZBILCMrOs6Djqeg-EAUSGAoJbW9va2llLXBzEgsI1PONyPeu3j0QBRIbCgxzaGFyZXRocm91Z2gSCwiKgMj2hcDePRAFEhYKB3N2eDl0NTASCwjq7-aI5KnoPhAFEhcKCGxpdmVyYW1wEgsIkoa1_eOp6D4QBRIWCgdibHVla2FpEgsI-Ja-hefv7DwQBRIYCglhZGFkdmlzb3ISCwj2r_mG-a7ePRAFEhkKCmxpdmVpbnRlbnQSCwiamKPt0JzfPhAFEhYKB3lqbjBndXASCwjykoq_1svYPhAFEhYKB3NlbWFzaW8SCwjc-Lq-46noPhAFEhYKB2QwdHJvMWoSCwj6htye35HmPRAFEhIKA2FhbRILCNbqvIfAw5M-EAUSFgoHbGh3Yms1ORILCMqbh8f89Z4-EAUSFgoHYWRkdGhpcxILCKzKgoqB9p4-EAUYASABKAIyCwjA5um1-qnoPhAFOAFaB3N2eDl0NTBgAg..
|
||||
.tiktok.com TRUE / TRUE 1799780762 _ttp 2X2TZqZx4A6Bvfb8BIaqfx9Z0a3
|
||||
.everesttech.net TRUE / TRUE 1799622471 everest_g_v2 g_surferid~ZTLFuQAAMEMNAAAm
|
||||
.casalemedia.com TRUE / TRUE 1799622478 CMID ZTLFuNNR.cMSZsiLqmTKvgAA
|
||||
.casalemedia.com TRUE / TRUE 1775862478 CMPRO 1585
|
||||
.casalemedia.com TRUE / TRUE 1799622514 receive-cookie-deprecation 1
|
||||
.bidswitch.net TRUE / TRUE 1797620686 tuuid 7b3a0d56-9687-4b59-b959-b7bc82b68d2a
|
||||
.bidswitch.net TRUE / TRUE 1797620686 tuuid_lu 1766084685
|
||||
.mookie1.com TRUE / TRUE 1802214470 id 10595069975717265745
|
||||
.mookie1.com TRUE / TRUE 1802214470 mdata 1|10595069975717265745|1697826233608
|
||||
.mookie1.com TRUE / TRUE 1802214470 ov e128077993a710de39333b74744f58cb
|
||||
.scorecardresearch.com TRUE / TRUE 1801782478 UID 10E8f6bc100d2f5646935961697826233
|
||||
.scorecardresearch.com TRUE / TRUE 1801782478 XID 10E8f6bc100d2f5646935961697826233
|
||||
.openx.net TRUE / TRUE 1799622478 i 3ceaaa71-5ebd-457b-b49c-3cae42bce526|1697826233
|
||||
.openx.net TRUE / TRUE 1783481370 receive-cookie-deprecation 1
|
||||
.kargo.com TRUE / TRUE 1799622478 ktcid 6e219f4f-0df5-0bff-52c2-a571328f2277
|
||||
.pinterest.com TRUE / TRUE 1797620838 ar_debug 1
|
||||
.semasio.net TRUE / TRUE 1799622471 SEUNCY CAFD648EEF8CF9CB
|
||||
.taboola.com TRUE / TRUE 1799622477 t_gid f2df2eab-3a6c-44ab-a01a-6a5636142726-tuctc2c4b3a
|
||||
.taboola.com TRUE / TRUE 1799622477 t_pt_gid f2df2eab-3a6c-44ab-a01a-6a5636142726-tuctc2c4b3a
|
||||
.pubmatic.com TRUE / TRUE 1799622478 KADUSERCOOKIE 7EA55DAD-5ED1-42DD-AFF7-96B0187E2FE8
|
||||
.pubmatic.com TRUE / TRUE 1775862467 KRTBCOOKIE_153 19420-e4Ku6SuEpe9gja67eIW67HrTpLtgja64eNO-SLLc&KRTB&22979-e4Ku6SuEpe9gja67eIW67HrTpLtgja64eNO-SLLc&KRTB&23462-e4Ku6SuEpe9gja67eIW67HrTpLtgja64eNO-SLLc&KRTB&23661-e4Ku6SuEpe9gja67eIW67HrTpLtgja64eNO-SLLc
|
||||
.pubmatic.com TRUE / TRUE 1775862467 KRTBCOOKIE_80 16514-CAESEIBxL5SQt0wXrkALugcFNF8&KRTB&22987-CAESEIBxL5SQt0wXrkALugcFNF8&KRTB&23025-CAESEIBxL5SQt0wXrkALugcFNF8&KRTB&23386-CAESEIBxL5SQt0wXrkALugcFNF8
|
||||
.pubmatic.com TRUE / TRUE 1775862467 KRTBCOOKIE_740 23053-654168E218FD9BEFFD4A78C3BLIS&KRTB&23460-654168E218FD9BEFFD4A78C3BLIS&KRTB&23518-654168E218FD9BEFFD4A78C3BLIS&KRTB&23654-654168E218FD9BEFFD4A78C3BLIS
|
||||
.pubmatic.com TRUE / TRUE 1775862467 KRTBCOOKIE_188 3189-bad985e7-8794-4157-b22d-37333612913c-654168d8-5553&KRTB&23418-bad985e7-8794-4157-b22d-37333612913c-654168d8-5553&KRTB&23634-bad985e7-8794-4157-b22d-37333612913c-654168d8-5553
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_57 22776-1446664633062455738&KRTB&23339-1446664633062455738
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_466 16530-7b3a0d56-9687-4b59-b959-b7bc82b68d2a&KRTB&23280-7b3a0d56-9687-4b59-b959-b7bc82b68d2a
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_32 11175-AQALN3bf5gd9IQFydVqNAQEBAQEBAQEBAQEBAQEBAJqrK3NY&KRTB&22715-AQALN3bf5gd9IQFydVqNAQEBAQEBAQEBAQEBAQEBAJqrK3NY&KRTB&23519-AQALN3bf5gd9IQFydVqNAQEBAQEBAQEBAQEBAQEBAJqrK3NY&KRTB&23632-AQALN3bf5gd9IQFydVqNAQEBAQEBAQEBAQEBAQEBAJqrK3NY
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_377 6810-248c815d-b156-4db9-8a92-46c25764dd54&KRTB&22918-248c815d-b156-4db9-8a92-46c25764dd54&KRTB&22926-248c815d-b156-4db9-8a92-46c25764dd54&KRTB&23031-248c815d-b156-4db9-8a92-46c25764dd54
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_860 16335-Rinil22qUYFi4TknhSNdb2vCVQw&KRTB&23334-Rinil22qUYFi4TknhSNdb2vCVQw&KRTB&23417-Rinil22qUYFi4TknhSNdb2vCVQw&KRTB&23426-Rinil22qUYFi4TknhSNdb2vCVQw
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_1278 23329-0925f90d-eabf-4ced-b2b9-cadb9694d5e0&KRTB&23498-0925f90d-eabf-4ced-b2b9-cadb9694d5e0
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_1251 23269-di_deeb83f6ca8243feb00ca&KRTB&23571-di_deeb83f6ca8243feb00ca&KRTB&23677-di_deeb83f6ca8243feb00ca
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_1003 22761-4e497f88-782f-11ee-81f8-32482e403c1e&KRTB&23275-4e497f88-782f-11ee-81f8-32482e403c1e
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_964 20918-cuid_bb00f223-da8d-11ef-a301-12328e819285&KRTB&23354-cuid_bb00f223-da8d-11ef-a301-12328e819285&KRTB&23415-cuid_bb00f223-da8d-11ef-a301-12328e819285&KRTB&23422-cuid_bb00f223-da8d-11ef-a301-12328e819285
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_18 22947-970314644980085599&KRTB&23796-970314644980085599
|
||||
.pubmatic.com TRUE / TRUE 1775862468 KRTBCOOKIE_218 22978-ZTLFuQAAMEMNAAAm&KRTB&23194-ZTLFuQAAMEMNAAAm&KRTB&23209-ZTLFuQAAMEMNAAAm&KRTB&23244-ZTLFuQAAMEMNAAAm
|
||||
.pubmatic.com TRUE / TRUE 1775862469 SyncRTB4 1769212800%3A81_201_46_234_283_176_238_48_266_7_13_278_166_220_165_231_71_8_196_286_203_56_275_178_240_272_279_22_233_281_250_96_249_214_99_161_282_3_104_55_271_285_21_267_264_54%7C1768608000%3A2_15_223%7C1769299200%3A268%7C1770595200%3A224_273%7C1768435200%3A216%7C1768867200%3A63%7C1773187200%3A69
|
||||
.pubmatic.com TRUE / TRUE 1775862469 KRTBCOOKIE_1469 23583-7fafe40b-12f1-414e-b944-2666315f73d5
|
||||
.pubmatic.com TRUE / TRUE 1775862469 KRTBCOOKIE_945 19558-83bc6354-0805-4310-befc-80a97066c1a2
|
||||
.pubmatic.com TRUE / TRUE 1775862469 KRTBCOOKIE_1513 23789-o08rOa33HNvb&KRTB&23793-o08rOa33HNvb
|
||||
.pubmatic.com TRUE / TRUE 1775862469 KRTBCOOKIE_469 8273-847260793351&KRTB&23428-847260793351
|
||||
.pubmatic.com TRUE / TRUE 1775862469 KRTBCOOKIE_1199 23168-000011CF9CE959F2&KRTB&23175-000011CF9CE959F2
|
||||
.pubmatic.com TRUE / TRUE 1775862469 KRTBCOOKIE_1465 23572-A6781448135364746810
|
||||
.pubmatic.com TRUE / TRUE 1775862469 KRTBCOOKIE_27 16735-uid:66516793-f126-4800-87d3-3aaaac9ea049
|
||||
.pubmatic.com TRUE / TRUE 1775862470 KRTBCOOKIE_1515 23794-6962dbc575e323aa0d79326e&KRTB&23804-6962dbc575e323aa0d79326e
|
||||
.pubmatic.com TRUE / TRUE 1775862470 KRTBCOOKIE_594 17105-RX-f60793b1-0eef-4ca4-82c5-ee8a3b5cdab9-005&KRTB&17107-RX-f60793b1-0eef-4ca4-82c5-ee8a3b5cdab9-005
|
||||
.pubmatic.com TRUE / TRUE 1775862470 DPSync4 1769212800%3A228_245_197_219_226%7C1768089600%3A248%7C1770595200%3A102%7C1768608000%3A164_252
|
||||
.pubmatic.com TRUE / TRUE 1775862470 KRTBCOOKIE_699 22727-AADBck7KgtEAABjN509uAw&KRTB&22744-AADBck7KgtEAABjN509uAw&KRTB&23649-AADBck7KgtEAABjN509uAw
|
||||
.pubmatic.com TRUE / TRUE 1775862478 chkChromeAb67Sec 23
|
||||
.intentiq.com TRUE / TRUE 1802646477 IQver 1.9
|
||||
.intentiq.com TRUE / TRUE 1802646477 intentIQ 8YNjKdno5A
|
||||
.intentiq.com TRUE / TRUE 1802646477 IQadv 1704742284384
|
||||
.intentiq.com TRUE / TRUE 1802646477 IQPData 1807897868#1768086477519#0#1698785399818
|
||||
.zemanta.com TRUE / TRUE 1775862477 zuid B_w-bBKJ2SQ1pcXG69r3
|
||||
.bidr.io TRUE / TRUE 1802196478 bito AADBck7KgtEAABjN509uAw
|
||||
.targeting.unrulymedia.com TRUE / TRUE 1799622478 _rxuuid %7B%22rx_uuid%22%3A%22RX-f60793b1-0eef-4ca4-82c5-ee8a3b5cdab9-005%22%7D
|
||||
.lijit.com TRUE / TRUE 1799622493 ljt_reader HlHCURZHmnGY_htwRy2qdS7X
|
||||
.lijit.com TRUE / TRUE 1783480663 _ljtrtb_80 M6CF4TG8-W-J70S
|
||||
.lijit.com TRUE / TRUE 1783480566 _ljtrtb_58 7EA55DAD-5ED1-42DD-AFF7-96B0187E2FE8
|
||||
.lijit.com TRUE / TRUE 1783480583 _ljtrtb_5039 2YGMLNgKe4pU8mA41f8wMksvXS_5pk97WTqA5qysrKa8
|
||||
.lijit.com TRUE / TRUE 1783480589 _ljtrtb_27 248c815d-b156-4db9-8a92-46c25764dd54
|
||||
.lijit.com TRUE / TRUE 1783480589 _ljtrtb_85 AADBck7KgtEAABjN509uAw
|
||||
.lijit.com TRUE / TRUE 1783480606 _ljtrtb_1 7289271102826593249
|
||||
.lijit.com TRUE / TRUE 1796162270 ljtrtb eJyNVFtTGzsM%2Fi95rmYsW7LlM9OHzYXQBtIzhRLgpWN7vdxDSqAUzpz%2FfuTt%2BQHdh2TXkqzvIvufifeTvyZCwXoTonOMkw8TQbS6ymy8N9ETYrToMZLG2BjUmCtDrBoQiSI5RptqRUbjkHS14pjpg2ZqpQ1IwRkbnTGRnMZs6xqyS6ZnD9FLAMocIcf2E3IRm730NrXccReSIsg9ZNQC6nMESdEC%2BWI5eOp7bugoau7mltNhmbndLxcbG8NGVy%2BX1M02m%2FI8XXT6fJqevG27148fR6TY8Ix%2FHyaxcQ%2FE1vtoHGEQZ4P%2Fn7xrDezF8vhofbWqtPsmDx3hIK%2FHd%2Fuf5yffeXcXw%2Bb0R8c%2F3vZPqyRaFZpgYdExz7s58GKOQHY%2Bh%2B7gICj3qUEJC3uwaLmxkf16DsNoR0YwtQ5AJRGILQy1SnKZS59UAWO4bT866JJQrgLYSwUygpDioErZariqO1FGLXB0Ty7Wt6t%2B%2B8jdb%2FY4%2BkRRLTPkg3ocxQYjzSpGbPJRdOquc955MjEGltFj%2BXNqo8Y59VG4BpAQCQg5QLa2Bxfa3mgjugKeCdV9AWZuEEb9rESdIzRWrOfoLDU%2BzSpjvevMYopmbohNEKSDmXGexbGfhSaRVjXWo6iMMNjsgCUx5BQiBBfLEEqp3Jcx2Wnyl7%2B%2F6UBKJQrDQJn0EUquclZfSuirDpXy10yUvL9%2BogcfZP80zmtkRxp4SeD70LRM2tJYcM4OkFK1kLLPeSi2IOax5Th%2FFENg36qtGPF%2BPCrS1D%2F2swM6XQps4HMwJ2NJk55yYe6HjEPLbLiP1hfnm0%2FdDHAJdnU6HwGpGOMh%2Bv2imQ3e5eWv9c0Nzfbrp%2FXh9UDDz9OyvH68a%2FHGq%2Bvm03IXVlfPemCmt2s28aV7bdEGtrdfFg9nu0MXrzbd%2BxscpbCVsz3Oytft7eH6Md7wLJ%2FCanvWvKem%2F2D7wdaUwSVfgEjfkkEVKbFOFZLVSwieX8qz6kJ6N7RWDTZXCsYNBEjVALHLENV0iD3FYr3UbBopavTD%2B02fjwTuH56MIoYwh8%2Bp8u7%2BPbTvGJb3ac56P%2F37HxcjMs8%3D
|
||||
.lijit.com TRUE / TRUE 1783480662 _ljtrtb_76 83a84be8-1d8e-4081-a9f6-42e05e2ae989
|
||||
.lijit.com TRUE / TRUE 1796162270 _ljtrtb_5 18bshr4m678sr
|
||||
.lijit.com TRUE / TRUE 1799622478 ljtrtbexp eJxlkDkSQzEIQ%2B%2Fi2gWLWZSrZXL3b3tcfJMS9ECCb3NvHw5NWGpEb%2FIqtbfgVTsETkuOg6uprRpAbCI5JE8nz440JfSWdFtwsUy9PXIU3YpeI9f5nYiQYkEydT8JJ7D5KDfIvQ%2FbnxgugTmPKPmpPIlJK%2BF3pPEHZGlwefRAAURuwPJ91e8B2LtbPQ%3D%3D
|
||||
.lijit.com TRUE / TRUE 1799622493 3pids "8014:a7bfe61c1f5205b22e34465d03fefacb,,7d5b04e46ff8eaf91bec603bdbc20bba1fa3b4fa,,139c2033b0bb853ae6140cbd2ca1f01d680097a946f9a07139d46987737158b8,,|8100:b734715e576a5b620291c43f3b5a280f,,d97ab4c28d7a42897d04af42610829b0567be6ea,,1b042a61a1412bc36bc3d3631335628355f71e35c3a1ab246d663baa06c947d3,,|8110:a7bfe61c1f5205b22e34465d03fefacb,,7d5b04e46ff8eaf91bec603bdbc20bba1fa3b4fa,,139c2033b0bb853ae6140cbd2ca1f01d680097a946f9a07139d46987737158b8,,|8111:a7bfe61c1f5205b22e34465d03fefacb,,7d5b04e46ff8eaf91bec603bdbc20bba1fa3b4fa,,139c2033b0bb853ae6140cbd2ca1f01d680097a946f9a07139d46987737158b8,,|8105:a7bfe61c1f5205b22e34465d03fefacb,,7d5b04e46ff8eaf91bec603bdbc20bba1fa3b4fa,,139c2033b0bb853ae6140cbd2ca1f01d680097a946f9a07139d46987737158b8,,|8109:a7bfe61c1f5205b22e34465d03fefacb,,7d5b04e46ff8eaf91bec603bdbc20bba1fa3b4fa,,139c2033b0bb853ae6140cbd2ca1f01d680097a946f9a07139d46987737158b8,,"
|
||||
.ad.gt TRUE / TRUE 1783480661 au_id AU1D-0100-001698785414-LEW7MJH6-AH72
|
||||
.colossusssp.com TRUE / TRUE 1799622477 gtm_usr e0e12f89-8f68-4849-ae67-396e3605f5b6
|
||||
.colossusssp.com TRUE / TRUE 1799622477 lmg_r 66|15|3|10|12|76|11|67|77|101|97
|
||||
.sitescout.com TRUE / TRUE 1799622478 ssi bad985e7-8794-4157-b22d-37333612913c#1698785496290
|
||||
.blismedia.com TRUE / TRUE 1799626467 b 654168E218FD9BEFFD4A78C3BLIS
|
||||
.deepintent.com TRUE / TRUE 1802646478 CDIUSER di_deeb83f6ca8243feb00ca
|
||||
.deepintent.com TRUE / TRUE 1802646478 CDIPARTNERS %7B%221%22%3A%2220231031%22%2C%22129%22%3A%2220250124%22%2C%22131%22%3A%2220240410%22%2C%22132%22%3A%2220260110%22%2C%22136%22%3A%2220240222%22%2C%22108%22%3A%2220260110%22%2C%22140%22%3A%2220250708%22%2C%22141%22%3A%2220260110%22%2C%22142%22%3A%2220260110%22%2C%22143%22%3A%2220250708%22%2C%22112%22%3A%2220240222%22%2C%22113%22%3A%2220240109%22%2C%22145%22%3A%2220250125%22%2C%22114%22%3A%2220240222%22%2C%22146%22%3A%2220250124%22%2C%22147%22%3A%2220250708%22%2C%22149%22%3A%2220260110%22%2C%22150%22%3A%2220250708%22%2C%22152%22%3A%2220240410%22%2C%22121%22%3A%2220240410%22%2C%22122%22%3A%2220250124%22%2C%22126%22%3A%2220250124%22%7D
|
||||
.adgrx.com TRUE / TRUE 1802214468 ADGRX_UID 4e497f88-782f-11ee-81f8-32482e403c1e
|
||||
.rfihub.com TRUE / TRUE 1801782477 rud H4sIAAAAAAAA_-MSsjQ3MDY0MTMxsbQwMLAwNbW0FOIz1HX1yLTwC_dy9c33NwAABPBq-iQAAAA
|
||||
.rfihub.com TRUE / TRUE 1801782477 eud H4sIAAAAAAAA_1WPMU4DQQxFhUSFRIEikYYrDPKM7W-bDlBEA4KARL8hm46CFMA5KCkpU1JyhByBknKPQMWuCNkguXn297O8c5BPj0c3o5P7i3R1dn45f7ydz54g-vA89fH1bjYOpizMQrTY6thMQi1c6b1jODkELmIfHWsOEYUq5-U6D80iaNZzoCjR92a-sC22e1-Q0GfPZiz42lv53CIs42Vg9bTUDK7SrJ5YEvBdClIkj1p9UlVtx45WS07lUAMwxevg12Stpa3y9p-52e8vE5Eshxufumgz_PvMPILLDxiDv-1HAQAA
|
||||
.w55c.net TRUE / TRUE 1802300871 wfivefivec mVLEl9il1QXWCd5
|
||||
beacon.lynx.cognitivlabs.com FALSE / TRUE 1799622468 UID 0925f90d-eabf-4ced-b2b9-cadb9694d5e0
|
||||
beacon.lynx.cognitivlabs.com FALSE / TRUE 1799622468 ss 9ASfGcUg1Z%2FWeHvjiO8JiUrpDlWrXPsyV9c83nESnysaEoqoq3htALWD05NYmBxwcGQ8dvArMuP%2FrUwUteqA4ou6MJfDvjloGeeNrp1hxgU%3D
|
||||
.acuityplatform.com TRUE / TRUE 1799622477 auid 847260793351
|
||||
.acuityplatform.com TRUE / TRUE 1799622477 aum OikKAfqbdXNlck1hdGNoQnlVc2VyTWF0Y2hpbmdJZE1hcPqANvqNdXNlck1hdGNoaW5nSWTMkWxhc3REcm9wVGltZU1pbGxpcyUBTWpFJ1W8mGxhc3RTdWNjZXNzZnVsTWF0Y2hNaWxsaXMlAU1qRSdVvI90aGlyZFBhcnR5VXNlcklkIfuANPpCyEMlAUlDDkRIuEQlAUlDDkRIuEVaQ0FFU0VPbzJuT1ZTbG1wUGY4blFXTHk1VlFB+4Ay+kLEQyUBSiY+EUG6RCUBSiY+EUG6RSH7gTIy+kIkrEMlAU1qRSgehkQlAU1qRSgehkVjOTFhNmQ4OTUtNWJiZC00Yzk2LWI0ODQtMWVkYjk2M2ZlZWRj+4EyN/pCJLZDJQFKJjxgXoREJQFKJjxgXoRFV0hsSENVUlpIbW5HWV9odHdSeTJxZFM3WPuBMjP6QiSuQyUBSiY8Zg2IRCUBSiY8Zg2IRVUzNjk4MjUzNjI1MDM0Njc2OTQ2MDQ2+4A4+kLQQyUBSiY+MRS+RCUBSiY+MRS+RSH7gTEw+kLUQyUBSiY9GGySRCUBSiY9GGySRVI3NDUyNjY5MDM0MTc4MzI3Njk0+4EyMfpCJKpDJQFKJj4jaYZEJQFKJj4jaYZFIfuBMzT6QiQBhEMlAUomPlcfrkQlAUomPlcfrkVqUlgtZjYwNzkzYjEtMGVlZi00Y2E0LTgyYzUtZWU4YTNiNWNkYWI5LTAwNfuBNDj6QiQBoEMlAUomQQoxvkQlAUomQQoxvkUh+4E3MvpCJAKQQyUBTWpFKVOWRCUBTWpFKVOWRSH7gTQx+kIkAZJDJQFNTgN/RqhEJQFNTgN/RqhFZTY0NjUxMDE2NDMwMjQ0NTIyMTc0MDIzMDQ4MTg0NjIwMzQ4MDc0+/uGdmVyc2lvbsL7
|
||||
.owneriq.net TRUE / TRUE 1786504662 si Q7520753811633417448P
|
||||
.adx.opera.com TRUE / TRUE 1783480663 UID OPUb598e447ff4b444484a3e5b793c7de50
|
||||
.c.appier.net TRUE / TRUE 1799622470 _auid uRFPn0q_B3iJ-ONGdnZBZQ
|
||||
.clickagy.com TRUE / TRUE 1802646470 cb ZZxNii4CsNrNHhf4fvTcGhok
|
||||
.sportradarserving.com TRUE / TRUE 1799536067 zuuid 0c453c2e-5e55-4fe3-9049-740eec090ceb
|
||||
.sportradarserving.com TRUE / TRUE 1799536067 zuuid_k 1
|
||||
.sportradarserving.com TRUE / TRUE 1799536067 zuuid_lu 1768086466
|
||||
.sportradarserving.com TRUE / TRUE 1799536067 zuuid_k_lu 1768086466
|
||||
.mfadsrvr.com TRUE / TRUE 1799622466 tuuid 5e4703f4-14e0-453b-9026-9d49c268eb07
|
||||
.mfadsrvr.com TRUE / TRUE 1799622466 tuuid_lu 1768086466
|
||||
.mfadsrvr.com TRUE / TRUE 1799622470 ssh !minutemedia=1768086470!bidswitch=1768086467!rise=1768086466!smaato=1737765833!google=1737749553!yieldmo=1737749319!intentiq=1737749112!openweb=1737748827!triplelift=1737748799!onetag=1712791426!taboola=1712791378!sovrn=1708616102!mgid=1704742287
|
||||
.creative-serving.com TRUE / TRUE 1799190470 tuuid d76dec14-1b37-413d-9634-f86f69927815
|
||||
.creative-serving.com TRUE / TRUE 1799190470 tuuid_lu 1768086470
|
||||
.company-target.com TRUE / TRUE 1802300867 tuuid 50772b55-2539-48f4-9792-491248fd76f9
|
||||
.company-target.com TRUE / TRUE 1802300867 tuuid_lu 1768086466|eqx:0|ix:-186|rp:-349|tlx:-349
|
||||
.mgid.com TRUE / TRUE 1799622477 muidn o08rOa33HNvb
|
||||
.a-mx.com TRUE / TRUE 1799622477 amdt_t p::1704742288432
|
||||
.a-mx.com TRUE / TRUE 1799622477 amuid2 7a9a0eaa-8763-494f-a19c-ff6c08c88f76
|
||||
.a-mx.com TRUE / TRUE 1799622477 pamdt_t p::1704742288432
|
||||
.a-mx.com TRUE / TRUE 1799622477 pamuid2 7a9a0eaa-8763-494f-a19c-ff6c08c88f76
|
||||
.adhaven.com TRUE / TRUE 1786504662 uid 4c_c553ec4a-2731-4d1a-882a-843e046d4c06
|
||||
.media.net TRUE / TRUE 1799536071 data-o 276afcb7-5c3a-406c-8d05-8a2894744407~~3
|
||||
.media.net TRUE / TRUE 1799622475 data-pbs setstatuscode~~1
|
||||
.ctnsnet.com TRUE / TRUE 1799622469 cid 39a120f51a3e40d588e976fbafe13e6f
|
||||
.betweendigital.com TRUE / TRUE 1799622470 dc was1
|
||||
.betweendigital.com TRUE / TRUE 1799622470 tuuid b16f2b86-1f09-5309-b32b-76b7d6b7e147
|
||||
.betweendigital.com TRUE / TRUE 1799622470 ut aWLbxgAGJjjwjhvb2mDqv4a1-1UE-S_2NDFWRQ==
|
||||
.traversedlp.com TRUE / TRUE 1797620838 v1.cookieId s%3A3423a7ae-bcd9-4c3a-8e04-72bd2cbd9b0c.pqvYvjrrzijfWRft1Xi2w9q4NyfAW5Lgf3CLcgU1jjk
|
||||
.alocdn.com TRUE / TRUE 1783516591 uuid bc8a2178-c61f-4230-b4b4-9c1c65d13081
|
||||
.socdm.com TRUE / TRUE 1802646471 SOC ZZ9oQsCo5ugAADzpoEYAAAAA
|
||||
.sundaysky.com TRUE / TRUE 1802646478 sskyu d6.680b1a7f159a4c48921f356b642f47a9
|
||||
.sundaysky.com TRUE / TRUE 1802646478 sskyCreationTime 1704949195932
|
||||
.bfmio.com TRUE / TRUE 1799622466 __io_cid 248c815d-b156-4db9-8a92-46c25764dd54
|
||||
.bfmio.com TRUE / TRUE 1799622466 __103_cid 7b3a0d56-9687-4b59-b959-b7bc82b68d2a
|
||||
.bfmio.com TRUE / TRUE 1799622466 __106_cid 248c815d-b156-4db9-8a92-46c25764dd54
|
||||
.bfmio.com TRUE / TRUE 1799622466 __168_cid AADBck7KgtEAABjN509uAw
|
||||
.bfmio.com TRUE / TRUE 1799622466 __147_cid d6.680b1a7f159a4c48921f356b642f47a9
|
||||
.undertone.com TRUE / TRUE 1799643429 UID_EXT_46 248c815d-b156-4db9-8a92-46c25764dd54
|
||||
.rtb.mx TRUE / TRUE 1799622477 amdt_t p::1708616096070
|
||||
.rtb.mx TRUE / TRUE 1799622477 amuid2 7a9a0eaa-8763-494f-a19c-ff6c08c88f76
|
||||
.rtb.mx TRUE / TRUE 1799622477 pamdt_t p::1708616096070
|
||||
.rtb.mx TRUE / TRUE 1799622477 pamuid2 7a9a0eaa-8763-494f-a19c-ff6c08c88f76
|
||||
.onaudience.com TRUE / TRUE 1799622469 cookie 64096b5cf64dd62a
|
||||
.ml314.com TRUE / TRUE 1783480600 pi 3642252982602432536
|
||||
.go.affec.tv TRUE / TRUE 1799622470 ck 65d769bed154af0001cede82
|
||||
.go.affec.tv TRUE / TRUE 1775862470 pt eyJhbiI6eyJkdCI6MTc2ODA4NjQ3MCwiaWQiOiIxNDQ2NjY0NjMzMDYyNDU1NzM4IiwibHMiOjE3NjgwODY0NzB9LCJ0dCI6eyJkdCI6MTc2ODA4NjQ3MCwiaWQiOiJKbGplYm1lVDhTWFpQWlFWVHFQdUJ3PT0iLCJscyI6MTc2ODA4NjQ3MH0sInRkIjp7ImR0IjoxNzY4MDg2NDcwLCJpZCI6IjI0OGM4MTVkLWIxNTYtNGRiOS04YTkyLTQ2YzI1NzY0ZGQ1NCIsImxzIjoxNzY4MDg2NDcwfSwidiI6MH0=|1768086470|6b354ac39182a0f3e7e3fa9f9ed0005b01ab7646
|
||||
.c.bing.com TRUE / TRUE 1786249799 SRM_B 10188FE1CFA1634236249C51CE0F62C0
|
||||
.clarity.ms TRUE / TRUE 1786249800 MUID 10188FE1CFA1634236249C51CE0F62C0
|
||||
.alcmpn.com TRUE / TRUE 1799622493 _3ci 4898dc00-f791-11ee-87da-35dab27f2b66
|
||||
.onetag-sys.com TRUE / TRUE 1802273212 OTP HTJCyV8vRH5apyIbS4jgTGmh02TGaXE7yVXssIgnxhE
|
||||
.riverside.fm TRUE / FALSE 1787692546 ajs_anonymous_id 21dd7898-1bfc-402f-b77a-7b431fcb8478
|
||||
.riverside.fm TRUE / FALSE 1787243493 cookiehub eyJhbnN3ZXJlZCI6ZmFsc2UsInJldmlzaW9uIjoxLCJkbnQiOmZhbHNlLCJhbGxvd1NhbGUiOnRydWUsImltcGxpY3QiOnRydWUsInJlZ2lvbiI6IkcwIiwidG9rZW4iOiJwanhpdlJGdGpCeWw5bE1UTUxpWHNac3hWTzREdnNXb2c0eWRlYUhZNENlcmgxcmdhWkhaWVFGa3BWN0JTd1JJIiwidGltZXN0YW1wIjoiMjAyNC0wNC0xNlQxNzo1OTo1NC4wNTdaIiwiYWxsQWxsb3dlZCI6dHJ1ZSwiY2F0ZWdvcmllcyI6W10sInZlbmRvcnMiOltdLCJzZXJ2aWNlcyI6W119
|
||||
.riverside.fm TRUE / TRUE 1787243493 _zitok e8af6ff1d02a6d1909bc1713290389
|
||||
.riverside.fm TRUE / TRUE 1784140212 __stripe_mid 84ab569e-8d4f-4816-a632-04e6d62c32405c3e59
|
||||
.riverside.fm TRUE / FALSE 1790267493 _ga GA1.1.1848369474.1713290394
|
||||
.riverside.fm TRUE / FALSE 1789403493 _uetvid 21509ba0fc1b11ee99478f310d5b7258
|
||||
.riverside.fm TRUE / FALSE 1790716547 _ga_PF9PK8DC9Z GS2.1.s1756156546$o8$g0$t1756156546$j60$l0$h1160639619
|
||||
.d.adroll.com TRUE / TRUE 1786072565 __adroll 6cc6d395376a1bb9f56de1a8c78c955f-a_1713364488
|
||||
.d.adroll.com TRUE / TRUE 1786072565 receive-cookie-deprecation 1
|
||||
.adroll.com TRUE / TRUE 1786072565 __adroll_shared 6cc6d395376a1bb9f56de1a8c78c955f-a_1713364488
|
||||
.adroll.com TRUE / TRUE 1786072565 receive-cookie-deprecation 1
|
||||
.demdex.net TRUE / TRUE 1783638478 demdex 64651016430244522174023048184620348074
|
||||
.demdex.net TRUE / TRUE 1781725180 dextp 21-1-1766173172009|477-1-1766173172826|481-1-1766173173827|843-1-1766173174827|771-1-1766173175829|1957-1-1766173176827|12105-1-1766173177830|575-1-1766173178828|53196-1-1766173179826|121998-1-1766173180825
|
||||
.dpm.demdex.net TRUE / TRUE 1783638478 dpm 64651016430244522174023048184620348074
|
||||
.go.sonobi.com TRUE / TRUE 1799622514 __uis fee5909e-22fd-42a0-a1fb-e28d64e05182
|
||||
.synthesis.com TRUE / FALSE 1775770716 _ga GA1.1.2026762825.1731628556
|
||||
.synthesis.com TRUE / FALSE 1775770733 _ga_KQHY76450D GS1.1.1741210716.45.1.1741210732.44.0.0
|
||||
.synthesis.com TRUE / FALSE 1775770733 _ga_MJL598MDNX GS1.1.1741210716.45.1.1741210732.44.0.0
|
||||
.a-mo.net TRUE / TRUE 1799622514 amuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.a-mo.net TRUE / TRUE 1799622514 pamuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.prebid.a-mo.net TRUE / TRUE 1799622514 sd_amuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.prebid.a-mo.net TRUE / TRUE 1799622514 __amc 2888_1712791451_1768086514
|
||||
.prebid.a-mo.net TRUE / TRUE 1799622514 psd_amuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.crwdcntrl.net TRUE / TRUE 1791414601 _cc_id 3cf9e41188988b992aee1510314411e1
|
||||
.crwdcntrl.net TRUE / TRUE 1791414601 _cc_dc 0
|
||||
.crwdcntrl.net TRUE / TRUE 1791414479 _cc_cc "ACZ4nGNQME5Os0w1MTS0sLC0sEiytDRKTE01NDU0MDY0AYqmGjIAQWbS7XNLHi%2F7wc8AAwJTJr1QZWwrZ%2FjPyMgwEYndhcReMvsAExOE%2FYERLHJh8RwWVJFzRw8xo4rM7tZCFbi39ik3qshhDGMa%2FmuiCsz8cMEcVeTsvR5VVJFLpx6xoYps%2BlOIKvD2oyWqwO%2BNU9CsBgAgHmmA"
|
||||
.crwdcntrl.net TRUE / TRUE 1791414479 _cc_aud "ABR4nGNgYGDITLp9jgEGmBgYuGaAGFxNn4EkAE3%2BBJg%3D"
|
||||
.outbrain.com TRUE / TRUE 1775862477 obuid 83bc6354-0805-4310-befc-80a97066c1a2
|
||||
.inmobi.com TRUE / TRUE 1799622477 iid ID5-5-1342167a-ef28-4e21-bab5-fd43f14a737a
|
||||
.inmobi.com TRUE / TRUE 1799622469 gob_cookie YES
|
||||
.inmobi.com TRUE / TRUE 1775862469 idsp_c d8fbeb2e-e64d-4ded-8362-c043cf92d3c3
|
||||
.creativecdn.com TRUE / TRUE 1797620685 g 88s83VQB5MgVwZvDd6Z8_1737748773872
|
||||
.creativecdn.com TRUE / TRUE 1797620685 c 88s83VQB5MgVwZvDd6Z8_rvpPdB9Ykz6pEuCHxRxe_1766084684882
|
||||
.creativecdn.com TRUE / TRUE 1797620685 ts 1766084684
|
||||
.sync.a-mo.net TRUE / TRUE 1799622478 sd_amuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.sync.a-mo.net TRUE / TRUE 1799622478 psd_amuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.smartadserver.com TRUE / TRUE 1800299087 TestIfCookieP ok
|
||||
.mediawallahscript.com TRUE / TRUE 1800644838 mCookie cc96bf10-da8d-11ef-9bbb-13760a7a1a50
|
||||
.mediawallahscript.com TRUE / TRUE 1800644838 mUserCookie %7B%226d7065a93a3c4b7fac2bbe5cf7d31996%22%3A%5B%22a7bfe61c1f5205b22e34465d03fefacb%22%2C%227d5b04e46ff8eaf91bec603bdbc20bba1fa3b4fa%22%2C%22139c2033b0bb853ae6140cbd2ca1f01d680097a946f9a07139d46987737158b8%22%5D%7D
|
||||
.udmserve.net TRUE / TRUE 1797620686 dt 778C4ACD-EEFB-3BB1-8558-9CE42AFF4D9C
|
||||
.udmserve.net TRUE / TRUE 1797620686 udmts 1766084685.0
|
||||
.ad-stir.com TRUE / TRUE 1797620787 uid 92c9d51d-b1f9-4ed5-aed2-5a64d6d9fcb9
|
||||
.a.usbrowserspeed.com TRUE / TRUE 1797620804 tuid e9c2099e-35ab-4932-afa1-aa1367b10f1e
|
||||
.insightexpressai.com TRUE / TRUE 1783480594 DW 00000000-0000-000b-a862-6a1737752050
|
||||
.agkn.com TRUE / TRUE 1799622482 ab 0001%3AE8XKtnaP2Z0ZuSwwKJa%2Fca70CcxR1DZIhCajAiTWekfdmnpivPSPKlV1KU2AScJ9zOSVGGcwbF8s%2FIv51kx8ouEYbisZ%2Bu18RoxCUZYFIqgUvyb6tl9OYN0MbljWX0KC88Xyo%2FN1vB5ndGqjMM95YT%2BDEtG4PxU05hCPTIgfyYxqL7oxpOdAk958wz%2BoN1VWt9qTn81D90opZR%2BdWINFNGAgmGMwaFdtbK%2FTiHgYdXrfSL%2B6UHcDR8bWcMNKyF9KdMJ4uFrtDZXCB3PwyO8u7wByYLoxIbyWxn4fIiiID3LBIL%2FtQyBHrd%2FOio2TxWzDruQ6o1NVPSJY7WzuoJ0oFQ%3D%3D
|
||||
.agkn.com TRUE / TRUE 1797709222 u C|0EAAAAAAAMNhmpQAA4ABYAQAHAAAAAAIAEU___x4AAAAAAGOFrgAAAAAZJD-mAAAAAAd2kfAAAAAAJKG5lQA
|
||||
.medialiance.com TRUE / TRUE 1800644702 mCookie 89440e60-dab1-11ef-8ec3-892a738b03da
|
||||
.medialiance.com TRUE / TRUE 1800644702 mUserCookie %7B%22e5cd834d8052bae2c694ddeaeca6f999%22%3A%5B%22b734715e576a5b620291c43f3b5a280f%22%2C%22d97ab4c28d7a42897d04af42610829b0567be6ea%22%2C%221b042a61a1412bc36bc3d3631335628355f71e35c3a1ab246d663baa06c947d3%22%5D%7D
|
||||
.contextweb.com TRUE / TRUE 1799190477 V Pv9rW4ongCVL
|
||||
.contextweb.com TRUE / TRUE 1799622471 pb_rtb_ev 3-1w32|8kt.0.1|7Eb.1.1|8ly.l.1|8px.5|8tI.5|7Nq.5|7Bj.4.CAESEObJmDpmPVBEKUyOQ9zVbuY|8sZ.5.78e498bf-f874-4fc4-90db-ec299869f88b|8dw.5.1|8iH.5|8hH.5|7TY.0|7TZ.0.1|7fJ.5|8fr.5|88e.5|7bq.2bW.1|7br.11C.1|8nM.8.1|7bs.9.1|8fP.5|6zB.1.7b3a0d56-9687-4b59-b959-b7bc82b68d2a|8gU.2bW.1a14e546-92a3-4ee7-a6ad-dc4e8f83d083|80p.2bW.1|8dQ.1.1|7dW.0.1|87G.5|8bO.5|2N.1.AQALN3bf5gd9IQFydVqNAQA8IwEBAQEBAQEBAQEBAQEBAQEB|7GB.8.1|7RY.5|8vg.5.1|8ue.5.78e498bf-f874-4fc4-90db-ec299869f88b|7dN.2bW.AADBck7KgtEAABjN509uAw|4is.1.CAESENz6rsahLafnUk4itvuDrpI|7Xz.5|8rx.1.LNYXWIAC-1G-2KTD|7I7.5|3oy.5.bad985e7-8794-4157-b22d-37333612913c-654168d8-5553|82A.5|8ro.5|8f1.0.A4879159953770555400|86L.5|8i8.2bW.1
|
||||
.contextweb.com TRUE / TRUE 1799190477 VP part_Pv9rW4ongCVL
|
||||
.contextweb.com TRUE / TRUE 1799622471 pb_rtb_ev_part 3-1w32|8kt.0.1|7Eb.1.1|8ly.l.1|8px.5|8tI.5|7Nq.5|7Bj.4.CAESEObJmDpmPVBEKUyOQ9zVbuY|8sZ.5.78e498bf-f874-4fc4-90db-ec299869f88b|8dw.5.1|8iH.5|8hH.5|7TY.0|7TZ.0.1|7fJ.5|8fr.5|88e.5|7bq.2bW.1|7br.11C.1|8nM.8.1|7bs.9.1|8fP.5|6zB.1.7b3a0d56-9687-4b59-b959-b7bc82b68d2a|8gU.2bW.1a14e546-92a3-4ee7-a6ad-dc4e8f83d083|80p.2bW.1|8dQ.1.1|7dW.0.1|87G.5|8bO.5|2N.1.AQALN3bf5gd9IQFydVqNAQA8IwEBAQEBAQEBAQEBAQEBAQEB|7GB.8.1|7RY.5|8vg.5.1|8ue.5.78e498bf-f874-4fc4-90db-ec299869f88b|7dN.2bW.AADBck7KgtEAABjN509uAw|4is.1.CAESENz6rsahLafnUk4itvuDrpI|7Xz.5|8rx.1.LNYXWIAC-1G-2KTD|7I7.5|3oy.5.bad985e7-8794-4157-b22d-37333612913c-654168d8-5553|82A.5|8ro.5|8f1.0.A4879159953770555400|86L.5|8i8.2bW.1
|
||||
.trkn.us TRUE / TRUE 1783004736 barometric[cuid] cuid_67946932-4589-41ab-98a0-895e3e2d97a8
|
||||
.rubiconproject.com TRUE / TRUE 1799622478 khaos M6CF4TG8-W-J70S
|
||||
.rubiconproject.com TRUE / TRUE 1799622478 audit 1|tcR/wBEzWcKBeiMwxwaIMJH/wrGgPvJ3EHTCxBIN+XkJG9/P5Pbd26X+G7tBI/pLK4KewJT0BBosa0K79A6XVh3ukALJD0TTOcV4pE4CozChJWL1SayQt/FdJMTEemORX1vKT1ONdRY=
|
||||
.rubiconproject.com TRUE / TRUE 1799622478 khaos_p M6CF4TG8-W-J70S
|
||||
.rubiconproject.com TRUE / TRUE 1799622478 audit_p 1|tcR/wBEzWcKBeiMwxwaIMJH/wrGgPvJ3EHTCxBIN+XkJG9/P5Pbd26X+G7tBI/pLK4KewJT0BBosa0K79A6XVh3ukALJD0TTOcV4pE4CozChJWL1SayQt/FdJMTEemORX1vKT1ONdRY=
|
||||
.rubiconproject.com TRUE / TRUE 1775862478 receive-cookie-deprecation 1
|
||||
.turn.com TRUE / TRUE 1783638477 uid 7289271102826593249
|
||||
.yieldmo.com TRUE / TRUE 1799622476 yieldmo_id 3eHPvHHbmPHxStMoIXOR%7C1737849600000%7C3724524149782542238%7C3488079986244395387
|
||||
.bcbst.com TRUE / FALSE 1776969422 WT_FPC id=23ab50c55541c3198981742409300028:lv=1742409421989:ss=1742409300028
|
||||
.unchained.com TRUE / TRUE 1789657964 osano_consentmanager_uuid 59c5c276-5ecc-493b-8730-858eaf3c0a5f
|
||||
.unchained.com TRUE / TRUE 1789657964 osano_consentmanager rPSs2F3XcQDGXcdQCAI3PxJj_1u57qW_iTlDLyjgVTeNX-uK8InVJC0TTXTwBZ_fQDe5D8H7eSSH-WBNLcwyGO7r7-VKutZLjTTahHSxQgotJZgsxPatac9bLWOQyeah774yEWXH_7wYU2HwtHpLepF3TEH89eLciFtN8TN-s0TOfe4RS0eOoU1eSBl7t3UcmOML_Utx-arUek685WuQRGnaFcEIk_Z8J8-dYEKGyw4Mpp8XqCODSWuEgZVikxUIg5hmrBYZviEdapwxILbVlQ2dL-IiUZM9Say0a6o2Q_1xIxvg-qif3EnY_jBsv7wa6-PGLRQTwwo=
|
||||
.unchained.com TRUE / FALSE 1792680928 ab.storage.deviceId.5ed20afa-515f-4b9a-a9f1-5cf8769574ef g%3Af2f6ef8d-1172-1d40-a491-ae8efac5929e%7Ce%3Aundefined%7Cc%3A1758117898590%7Cl%3A1758120460258
|
||||
.unchained.com TRUE / FALSE 1792680928 ab.storage.userId.5ed20afa-515f-4b9a-a9f1-5cf8769574ef g%3Aeddf5362-4840-44fa-9ea6-cca1c2bfc860%7Ce%3Aundefined%7Cc%3A1758117898588%7Cl%3A1758120460258
|
||||
.unchained.com TRUE / FALSE 1792680928 ab.storage.sessionId.5ed20afa-515f-4b9a-a9f1-5cf8769574ef g%3Ab553b88d-0448-ca46-18e3-355394901910%7Ce%3A1758122728118%7Cc%3A1758120460258%7Cl%3A1758120928118
|
||||
.my.unchained.com TRUE / TRUE 1789657965 __stripe_mid 7e35a7e6-2671-4130-8964-ccd5ac2894477168e1
|
||||
ten31.vc FALSE / FALSE 1777385004 ss_cvr 15a4855b-9c66-4647-a7f8-61aa8b6595be|1730144511490|1731602788023|1742825003097|3
|
||||
.www.linkedin.com TRUE / TRUE 1790346598 bscookie "v=1&202503241857592b5ead33-6f67-4547-8ead-51dcfe02910aAQH8ycCs5-tSCgNIGNLairKP-yifEUhF"
|
||||
.www.linkedin.com TRUE / TRUE 1784050017 li_rm AQG7XdvCSOs9BAAAAZd64ZXVLmo_dWkMhTknsO1hJmw1642__4KUBhOQk-IPq4F333CgLCTedSmyIWVHvkQrz_B0m3jw4uth0WO67p5suv8V2zWX4QuOvwaxukP7Q8EiAFb6y7uUwulv84mX-3OXQiIsENB_-OsLPgk1FgQDVkIDd_LGlSFaPANKLOAK2SKTCaBzkWddFr2ESOryCmjzEtioqeeV9L-rC07LBeuIDhFtB-maFTKp0pxdxqJoHSAo_wiDIZnO3NNTyWU4Qe7tSN6HvaOQ-D7Xepm2MZYdIpRAlkvYkMnKbNSBYyV4CylY0hkzpNvLQgkFnoQsvJUQ9Q
|
||||
.www.linkedin.com TRUE / TRUE 1790346598 JSESSIONID "ajax:4171029513032656218"
|
||||
.www.linkedin.com TRUE / TRUE 1784062922 li_ep_auth_context AHVhcHA9c2FsZXNOYXZpZ2F0b3IsYWlkPTI5NTM3MjkzMixpaWQ9Mzg1Mjk1NDQ0LHBpZD00OTE4NTMzNTMsZXhwPTE3NTUxMTg5MjE0NTIsY3VyPXRydWUsc2lkPTE1Mzc4NTAxMTYsY2lkPTIwMTMyMzk2ODMBresxvFmgTpwVa7MvInSzFcXh5Gk
|
||||
.www.linkedin.com TRUE / TRUE 1790346598 li_at AQEFAHQBAAAAABb-2ykAAAGYCfm4NwAAAZmlVHycTgAAF3VybjpsaTptZW1iZXI6MzI2MTc2NzUyNv3G6zHnnd48jDJY2beVMl5kbCvn2KO6Gdn6NeRn_G1-hCe3g8eT0xMvjx3ANoSIRzueX9tT1VWF6P0UL28EArA3bRzI3uRWKO0tc5JNeNSywAlkv12xcNnKL8C7yGiEw1E01ROKaeK7oSPUkaUVI62vV-0aI0QG2SYtvnIkbiSkn9VGbLBIJnnvxbwrG4S_7A99DQ
|
||||
.inflightinternet.com TRUE / FALSE 0 rxVisitor 1743863097950UC0EEQA5P7KC2DRQ7BM3GGT4OU23FTM9
|
||||
.inflightinternet.com TRUE / FALSE 0 FLIGHT_TRACKER_STORED {%22departureAirportCode%22:%22BNA%22%2C%22destinationAirportCode%22:%22ORD%22%2C%22flightNumber%22:%22SKW6259%22%2C%22departureTime%22:%222026-03-31T16:11:00.00Z%22%2C%22arrivalTime%22:%222026-03-31T17:27:00.00Z%22}
|
||||
.inflightinternet.com TRUE / FALSE 0 LOGGER_DATA {%22airlineCode%22:%22AAL%22%2C%22flightNumber%22:%22SKW6259%22%2C%22mac%22:%227A:6D:B0:B5:E8:ED%22}
|
||||
.inflightinternet.com TRUE / FALSE 0 DYNATRACE_THEME default
|
||||
.inflightinternet.com TRUE / FALSE 0 flightIdentifier {%22tailNumber%22:%22N759EV%22%2C%22departureAirportCode%22:%22KBNA%22%2C%22destinationAirportCode%22:%22KORD%22}
|
||||
.inflightinternet.com TRUE / FALSE 0 hasActiveInflightSession true
|
||||
.inflightinternet.com TRUE / FALSE 0 airlineCode aal
|
||||
.inflightinternet.com TRUE / FALSE 0 dtCookie v_4_srv_21_sn_MME8PSIQ380J5EFVLH66ARG76FJG56J9_perc_100000_ol_0_mul_1_app-3Aa102791a62dd9f00_1_app-3A4eca8c42196ba4a2_1_app-3Aea7c4b59f27d43eb_1_rcs-3Acss_0
|
||||
.inflightinternet.com TRUE / FALSE 0 env prodp
|
||||
.inflightinternet.com TRUE / FALSE 0 loggerData {%22airlineCode%22:%22AAL%22%2C%22flightNumber%22:%22SKW6259%22%2C%22mac%22:%227A:6D:B0:B5:E8:ED%22}
|
||||
.inflightinternet.com TRUE / FALSE 0 rxvt 1774976057213|1774974154984
|
||||
.inflightinternet.com TRUE / FALSE 0 dtSa false%7CC%7C4%7CWI-FI%20PACKAGES%7Cx%7C1774974257207%7C174240510_433%7Chttps%3A%2F%2Fwifi.inflightinternet.com%2Fapp%2Faal%2Fifc%2F%7C%7C%7Cgobrowse%7C
|
||||
.inflightinternet.com TRUE / FALSE 0 dtPC 21$174206057_810h1p21$174268351_454h1vMFQEHSHFARVKFMKVPWJURICDEAJLMUQW-0e0
|
||||
.airbornesecure.inflightinternet.com TRUE / TRUE 1805384898 __stripe_mid 414200e8-7823-41f0-afc8-3f42d2ef439691897d
|
||||
.paypal.com TRUE / TRUE 1796165169 enforce_policy ccpa
|
||||
.paypal.com TRUE / TRUE 1779902570 cookie_check yes
|
||||
.paypal.com TRUE / TRUE 1779902570 d_id 80fb76c2e17a49e1a765a042b42e17241745342570275
|
||||
.paypal.com TRUE / FALSE 1796165168 TLTDID 27040534859840361107447354011634
|
||||
.paypal.com TRUE / TRUE 1796165182 ts_c vr%3D0658813d1960a5515860f94cfc3d321b%26vt%3Ddc142d6019a0a55100281bbcfb685727
|
||||
.paypal.com TRUE / TRUE 1796164903 _iidt CH4Hh6drZWFH8o+y2QON+f528H0a7DM3JGqCL5uu1wpJsHz46R4JUJJOav8LNV5X3jmFJjsXrVbEApq3l5Q8MuB8Cux+nm2vd8NclJ0=
|
||||
.paypal.com TRUE / TRUE 1799188912 login_email grant.gilliam%40gmail.com
|
||||
.paypal.com TRUE / TRUE 1799188927 rmuc 9lroqoP1vjt1EudVvIA_HPq_C19q2vzSEp2JWJOfcaqyHnYBt5tu-KrW3yZ1Sy158fuVuI-DZkOS8iS0YI3FQPx-8aqHquZdiOgK0P7QA04z2BfeyxgIAknf0QAAut48NYUq7b2fx26EFIwu2to7DqKzmyw-bVv1zQCz90PXRAzB5iKcEhqY_R3y34GqPCZHXNJE7WX6n9zbwOB3Q2NLy1nqH8G
|
||||
.paypal.com TRUE / TRUE 1796164928 cookie_prefs T%3D0%2CP%3D1%2CF%3D1%2Ctype%3Dimplicit
|
||||
.paypal.com TRUE / TRUE 1799189167 ui_experience tokenType%3Dsoftware_token_authenticator%26tokenIdentifier%3DWIquIrjeoFYEfLoCrFjE_dUugfLYs0LhEVfAIg2-XuT4cB6jNZ0ONljYUtZx1kzBMjPbPavKH5ReKg5E
|
||||
.paypal.com TRUE / TRUE 1796165182 ts vreXpYrS%3D1796165181%26vteXpYrS%3D1764630981%26vr%3D0658813d1960a5515860f94cfc3d321b%26vt%3Ddc142d6019a0a55100281bbcfb685727%26vtyp%3Dreturn
|
||||
.paypal.com TRUE / TRUE 1799189168 KHcl0EuY7AKSMgfvHl7J5E7hPtK FIfTcfpEkB2i1XvmAN4PP4SeJq5aSSd9DNTO8-_lo5rhvENJIgcciW9fAf8jzsCnPpD2zH2SA-BfXQMh
|
||||
.paypal.com TRUE / TRUE 1799189168 sc_f gkBt9_7bEOc05CHYIB-CZVSf6hnGCUjMm4o0U1EEZQ5TVg9WJlcdAf0KGbXipy31piIid40JIe5XcYCDZCVIhl4V1qs4AYsE6yb0iG
|
||||
.paypal.com TRUE / TRUE 1799189168 ddi cQ1FNbgqR-YWLJ15JSTC4edfi7MpfuC5MlVWS4HsWm-ssPIuApHPwVILKObl3lofRerCE1-FEkv9d-OVEHyhVBQqH8cgn3xbgcJnpUkZ1O4wMYh8
|
||||
.chase.com TRUE / FALSE 1807901097 v1st 951B54D03A5ADC53
|
||||
.chase.com TRUE / TRUE 1776350059 chase_campaign_data SourceCode=OS0001&iq_id=
|
||||
.chase.com TRUE / FALSE 1807901097 s_vi [CS]v1|951B54D03A5ADC53-423DCF575550D14A[CE]
|
||||
.chase.com TRUE / TRUE 1807901097 adtoken.chase.com |DYN|S85Tt1LnADdBsXuTfTrS7%2Fbn8yqG9rDCjlio3e%2BMLkhqtNC7mBTfZZt09%2B7uuG%2BaeeedqbPa95IcTyVOkgx5i1DXGkdn9R3kBzVcvy4ErOjUvXQpxD5Cpgcfo8DuGJMc%7CZoQo7mE96ssrsP2JUB%2FjifrYecMgP2Pa%2B7tpIzl%2B6MT2vvJgkyFQgGf%2ByEIopgCi
|
||||
.chase.com TRUE / FALSE 1807901097 NyJB5Jgn A-IWXeOcAQAAzSp6FikrPh4IyzXygB0y02ND6fi5NRhDkX-8S8Zv9KkNwt9hAWvCVQyucmVTwH9eCOfvosJeCA|1|NO_BIT|c039b4f5bec6e784a132343dce8fcfe170fa321d
|
||||
.chase.com TRUE / TRUE 1804880699 BRAND_1_0 WEALTH
|
||||
.chase.com TRUE / TRUE 1804877098 PC_1_0 pfid%3D10383181%7Csegment%3DPAF%7Csegg%3DPAF%7Czip%3D37027-1532%7CECI%3D0346242280%7CAOC%3D6615%2C6653%2C%7Cpm%3DATM%2CCHK%2CCHK%2CSAV%2CBAC%2CBAC%2CALA%2CALS%7CRPC%3D0550%2C0404%2C%7Capc%3D100%2C067%2C080%2C064%2C010%2C%7Clocale%3Den_us%7ClastUpdate%3D2025-04-21%7ClastSent%3D2025-04-21%7Cpod%3D07EA%7Cusrtp%3DPR%7Cpftp%3DPER%7Csc%3DTN%7C
|
||||
.chase.com TRUE / FALSE 1807901099 AMCV_EA673DFC5A2F19060A495C9C@AdobeOrg 1914845758|MCIDTS|17564|MCMID|78587949278339082538316563911219460485|MCAID|NONE|MCOPTOUT|isoptedout-false|MCAAMLH||MCAAMB||MCCIDH|1019576346|MCSYNCSOP|411-17568|vVersion|2.3.0|TESTPROFILE|false|IsCustom|true
|
||||
.chase.com TRUE / TRUE 1804880699 _iscpo 07EA
|
||||
.chase.com TRUE / TRUE 1804880699 DEFAULT_PAGE_1_0 octagonOverviewDashboard
|
||||
.twitter.com TRUE / TRUE 1782324962 personalization_id "v1_BPqyvG9JD5AM/EiiDpIOTA=="
|
||||
.twitter.com TRUE / TRUE 1787107244 guest_id_marketing v1%3A175254724421271077
|
||||
.twitter.com TRUE / TRUE 1787107244 guest_id_ads v1%3A175254724421271077
|
||||
.twitter.com TRUE / TRUE 1787107244 guest_id v1%3A175254724421271077
|
||||
.t.co TRUE / TRUE 1782324962 muc_ads f0a4d615-2d82-4018-be9d-fa847c1b1cc5
|
||||
.goduke.com TRUE / TRUE 1780040693 visid_incap_3109055 12O3hjvaTjau1Wo/L9c77mHTOGgAAAAAQUIPAAAAAADgd0MG9tKPy5WEPkv2jsF9
|
||||
api.phantombuster.com FALSE / TRUE 1784673334 session vMmRJdC83mC7jcKh60wDuoeCT6EE1Q1JrHbQAZadXNE
|
||||
api.phantombuster.com FALSE / TRUE 1787859447 amplitude_device_id 1750113334053923537236069351
|
||||
.disqus.com TRUE / TRUE 1783888531 disqus_unique 1rp6lv2627db3
|
||||
.disqus.com TRUE / TRUE 1799622478 zeta-ssp-user-id 1rp6lv2627db3
|
||||
.chromewebstore.google.com TRUE / FALSE 1805841466 _ga GA1.1.1914643352.1750113338
|
||||
.chromewebstore.google.com TRUE / FALSE 1808253614 _ga_KHZNC1Q6K0 GS2.1.s1773693584$o7$g1$t1773693614$j30$l0$h0
|
||||
phantombuster.com FALSE / TRUE 1781650031 CookieConsent {stamp:%27HhuJjYqXCqaOyewhVggQ6Y15nrzwtSUvdW4WnuTUu8garuTH22muNg==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1750114031149%2Cregion:%27us-47%27}
|
||||
.linkedin-ei.com TRUE / TRUE 1782431038 bcookie "v=2&b2cebf06-4e82-405b-84a3-620f0d797b85"
|
||||
.nextlayer.capital TRUE / TRUE 1782160419 _reb2buid 752ba5c6-07cd-49b1-ac28-7508358b0f25-1751056419084
|
||||
www.ten31.xyz FALSE / FALSE 1785617475 ss_cvr 9e202335-e808-4d6e-9559-dcf4bdb0f6db|1751057469803|1751057469803|1751057469803|1
|
||||
.calendly.com TRUE / TRUE 1783859117 __stripe_mid a5c306c1-a69f-408c-8771-60c5fa5917dd4a27ad
|
||||
.calendly.com TRUE / TRUE 1783859083 OptanonAlertBoxClosed 2025-07-12T12:24:43.800Z
|
||||
.calendly.com TRUE / TRUE 1783859083 OptanonConsent isGpcEnabled=0&datestamp=Sat+Jul+12+2025+07%3A24%3A43+GMT-0500+(Central+Daylight+Time)&version=202501.1.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=83e3e526-39d4-462d-a3b9-f7fc0cdd26a0&interactionCount=2&isAnonUser=1&landingPath=NotLandingPage&groups=C0001%3A1%2CC0003%3A1%2CC0002%3A1%2CC0004%3A1&AwaitingReconsent=false&intType=1
|
||||
.calendly.com TRUE / FALSE 1783859115 ajs_anonymous_id f1bc62b0-b6b1-4650-9dec-737555e0c0a6
|
||||
.calendly.com TRUE / FALSE 1783859115 analytics_session_id 1752323115018
|
||||
.calendly.com TRUE / FALSE 1783859115 analytics_session_id.last_access 1752323115018
|
||||
.primal.net TRUE / TRUE 1802375175 __stripe_mid 992a6e6e-7e2d-4fc6-808c-34d093472745a9847d
|
||||
.squarespace.com TRUE / FALSE 1785164736 SS_MID 3055171d-2109-4415-8dc8-67e67ccc9f60
|
||||
.squarespace.com TRUE / TRUE 1785974242 IR_PI 20d8783e-56d7-11f0-8c1b-63794422bed6%7C1751414241665
|
||||
.squarespace.com TRUE / TRUE 1785248955 SS_ANALYTICS_ID a1a96488-e25b-4653-bd8b-e01cea208d5f
|
||||
.squarespace.com TRUE / FALSE 1785600940 _scid CACU7iWxruAeEvOr9q2G_PUVUG36W21R
|
||||
.squarespace.com TRUE / FALSE 1785600941 _sctr 1%7C1751346000000
|
||||
.squarespace.com TRUE / FALSE 1786028737 _ga GA1.1.277295834.1751414244
|
||||
.squarespace.com TRUE / FALSE 1785164737 _tt_enable_cookie 1
|
||||
.squarespace.com TRUE / FALSE 1785164737 _ttp 01JZ46S0AQBRTJZZSZ5J8XDKKJ_.tt.1
|
||||
.squarespace.com TRUE / FALSE 1785655435 _scid_r E4CU7iWxruAeEvOr9q2G_PUVUG36W21RsZWTPQ
|
||||
.squarespace.com TRUE / FALSE 1785164737 _uetvid 21f1067056d711f084c3b9ce4219e763
|
||||
.squarespace.com TRUE / FALSE 1785164737 ttcsid_C2ENC2QQV140ORDIO960 1751468737412::x48b425HPqqgdt634mr6.2.1751468737413
|
||||
.squarespace.com TRUE / FALSE 1785164737 ttcsid 1751468737412::I-XIdf8s62VM_nUhb1Kc.2.1751468737413
|
||||
.squarespace.com TRUE / FALSE 1786917443 _ga_1L8CXRNJCG GS2.1.s1752357442$o3$g0$t1752357442$j60$l0$h0
|
||||
.syuh.net TRUE / TRUE 1786028735 brwsr 20d8783e-56d7-11f0-8c1b-63794422bed6
|
||||
.support.squarespace.com TRUE / FALSE 1783004737 _pin_unauth dWlkPVptSTNNak5tWkRrdE1tUXpNQzAwWmpRMExUbGpOV1l0Tm1Zd1lXTTJOVGhqTW1ObQ
|
||||
.adnxs.com TRUE / TRUE 1775862514 uuid2 1446664633062455738
|
||||
.adnxs.com TRUE / TRUE 1775862477 anj dTM7k!M40<E:X*YF']wIg2C'$n4d`8!rdB[<ZuYAZgU6__Ny_!/'atsWVY`OVR$?hD?QBUrX@Nlqb_#VF.a3apf[[[Uk`4rHogB+W1vceWNTqbI0F*=?k!)X@g@@m?J]s3ky-i?fs`<MAh[?P+4h`k!C%Jr(g4AbKvTjjre++lQ./RyStDj$!rd^!tP7@(oqS$[pzxKrb=fHKZ1M1UoD+NNeB?]Tb>:t9upA#3Rt'X^Fru/+Xdh$X>//ECId2BWai$i]<!bOVf.pDfAPdQuVmhwwZ3d=1EibY:mP0@@@m6d7TI_bLOWU<!?8WtH9#pPT>m[A$UBcKbCxr^=6d[wc9RkhB2?Pkz/X#8!VFwEWPvED^aC?429-W18Pqx$sRr6QPL@dv9<Ly$$1F23<ExW>salD[=0K$O^$+=cHe`9zz0BHZE:Z:EZF0Id%*g0D(36I3l
|
||||
.adnxs.com TRUE / TRUE 1775862477 uids eyJ0ZW1wVUlEcyI6eyJhZG54cyI6eyJ1aWQiOiIxNDQ2NjY0NjMzMDYyNDU1NzM4IiwiZXhwaXJlcyI6IjIwMjYtMDEtMjRUMjM6MDc6NDkuMDk0MTEwNzE1WiJ9LCJhbXgiOnsidWlkIjoiNjY5NGRlM2YtYTRlNC00MTUzLWJlOTAtMzgwNzBmMzIyYzc2IiwiZXhwaXJlcyI6IjIwMjYtMDEtMjRUMjM6MDc6NTcuMzI4NDE3MDQ0WiJ9LCJydWJpY29uIjp7InVpZCI6IjEiLCJleHBpcmVzIjoiMjAyNi0wNC0xMFQyMzowNzo0N1oifSwiY29ubmVjdGFkIjp7InVpZCI6IjEiLCJleHBpcmVzIjoiMjAyNi0wNC0xMFQyMzowNzo1N1oifX19
|
||||
.adnxs.com TRUE / TRUE 1775862514 XANDR_PANID PCf2dRo2AFUKrIDqTJhluM3eM1XLRFlhDROSCoEjPO_Sw3m5nzC-ztQr0_2_onNy6e4tHgdHVNekMcT6peC1QxLqYdSVViNIymGcFz_JA7k.
|
||||
support.squarespace.com FALSE / FALSE 1786028735 SQSP_HarveyDent {%22aus_live_chat%22:{%22variant%22:%22variant%22%2C%22when%22:%22Wed%2C%2002%20Jul%202025%2015:05:35%20GMT%22}%2C%22autologin-qa%22:{%22variant%22:%22variant%22%2C%22when%22:%22Wed%2C%2002%20Jul%202025%2015:05:35%20GMT%22}%2C%22fullstory_2024%22:{%22variant%22:%22control%22%2C%22when%22:%22Tue%2C%2001%20Jul%202025%2023:57:21%20GMT%22}}
|
||||
.www.ten31timestamp.com TRUE / TRUE 1783259137 ab_experiment_sampled %22false%22
|
||||
.www.ten31timestamp.com TRUE / TRUE 1783259137 ab_testing_id %224951d750-8a99-4bec-be9f-70654cb5e528%22
|
||||
.lynalden.com TRUE / FALSE 1786460292 _ga GA1.2.1028841246.1751900292
|
||||
.lynalden.com TRUE / FALSE 1786460293 _ga_L92TPRGR2X GS2.2.s1751900292$o1$g0$t1751900292$j60$l0$h0
|
||||
.www.lynalden.com TRUE / TRUE 1783436299 __stripe_mid b632f803-daa5-47b6-ac31-677c6d2eaa3c9f9398
|
||||
www.gentexcorp.com FALSE / TRUE 1783461745 CookieConsent {stamp:%27K6r5zFH9mn5JntJlZZFzhGRJN4b9yBifDOZtJtW14s06elm6tuanLA==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1751925745079%2Cregion:%27us-47%27}
|
||||
.fliphtml5.com TRUE / FALSE 1786485759 first_visit_channel_source {"channel":"","referer":"https://www.gentexcorp.com/ops-core/","page":"https://online.fliphtml5.com/usalo/ngjl/","time":1751925758501,"uuid":"1321c34c-fe88-41b9-918d-bdc107547787"}
|
||||
.fliphtml5.com TRUE / FALSE 1786485759 _ga GA1.1.2038386891.1751925759
|
||||
.fliphtml5.com TRUE / FALSE 1786485760 _ga_DQQGBZ508R GS2.1.s1751925758$o1$g0$t1751925760$j58$l0$h0
|
||||
www.atomicdefense.com FALSE / FALSE 1783461903 localization BR
|
||||
www.atomicdefense.com FALSE / FALSE 1783461791 po_visitor m8eTuYuxWK1s
|
||||
www.atomicdefense.com FALSE / FALSE 1783461903 _pin_unauth dWlkPVptSTNNak5tWkRrdE1tUXpNQzAwWmpRMExUbGpOV1l0Tm1Zd1lXTTJOVGhqTW1ObQ
|
||||
www.atomicdefense.com FALSE / FALSE 1783461900 AMP_9bdc728a74 {"deviceId":"f06d75df-1f90-4871-83ca-407a640705fa","sessionId":1751925900780,"lastEventTime":1751925900785,"optOut":false}
|
||||
www.atomicdefense.com FALSE / FALSE 1783461903 mktz_client %7B%22is_returning%22%3A0%2C%22uid%22%3A%2219038728291293545260%22%2C%22session%22%3A%22sess.2.1065091378.1751925789881%22%2C%22views%22%3A4%2C%22referer_url%22%3A%22https%3A//www.google.com/%22%2C%22referer_domain%22%3A%22www.google.com%22%2C%22referer_type%22%3A%22organic%22%2C%22visits%22%3A1%2C%22landing%22%3A%22https%3A//www.atomicdefense.com/collections/ops-core%3Fsrsltid%3DAfmBOooj_64xdS_VznDSwAnnBIus-V5EU_sm13jPgwNFmBugUYJYlVPZ%22%2C%22enter_at%22%3A%222025-07-7%7C17%3A3%3A9%22%2C%22first_visit%22%3A%222025-07-7%7C17%3A3%3A9%22%2C%22last_visit%22%3A%222025-07-7%7C17%3A3%3A9%22%2C%22last_variation%22%3A%22%22%2C%22utm_source%22%3Afalse%2C%22utm_term%22%3Afalse%2C%22utm_campaign%22%3Afalse%2C%22utm_content%22%3Afalse%2C%22utm_medium%22%3Afalse%2C%22consent%22%3A%22%22%2C%22device_type%22%3A%22desktop%22%2C%22id_website%22%3A%2222272%22%7D
|
||||
www.atomicdefense.com FALSE / FALSE 1786485904 __kla_id eyJjaWQiOiJOVEUxTWpKbVpqQXRaV0kzTWkwMFltVmlMV0UzTXpBdFpqazFaamMzWW1Sa05tUmkiLCIkcmVmZXJyZXIiOnsidHMiOjE3NTE5MjU3OTIsInZhbHVlIjoiaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8iLCJmaXJzdF9wYWdlIjoiaHR0cHM6Ly93d3cuYXRvbWljZGVmZW5zZS5jb20vY29sbGVjdGlvbnMvb3BzLWNvcmU/c3JzbHRpZD1BZm1CT29val82NHhkU19Wem5EU3dBbm5CSXVzLVY1RVVfc20xM2pQZ3dORm1CdWdVWUpZbFZQWiJ9LCIkbGFzdF9yZWZlcnJlciI6eyJ0cyI6MTc1MTkyNTkwNCwidmFsdWUiOiJodHRwczovL3d3dy5nb29nbGUuY29tLyIsImZpcnN0X3BhZ2UiOiJodHRwczovL3d3dy5hdG9taWNkZWZlbnNlLmNvbS9jb2xsZWN0aW9ucy9vcHMtY29yZT9zcnNsdGlkPUFmbUJPb29qXzY0eGRTX1Z6bkRTd0FubkJJdXMtVjVFVV9zbTEzalBnd05GbUJ1Z1VZSllsVlBaIn19
|
||||
.atomicdefense.com TRUE / FALSE 1783461789 ig-id ig_5776dfedafb4fbfa5d8a0463ec428fdb482d
|
||||
.atomicdefense.com TRUE / FALSE 1783461789 ig-fv 1751925789909
|
||||
.atomicdefense.com TRUE / FALSE 1783461904 ig-vars {%22redirectedFrom%22:%22%22}
|
||||
.atomicdefense.com TRUE / FALSE 1783461905 _tracking_consent 3.AMPS_USTN_f_f_zab7AzwiQy-Q8yLtLVc7Sg
|
||||
.atomicdefense.com TRUE / FALSE 1783483509 _shopify_y 451263ce-612e-4c39-b0fe-443739bf2b16
|
||||
.atomicdefense.com TRUE / FALSE 1783461792 _clck ocpdsc%7C2%7Cfxe%7C0%7C2014
|
||||
.atomicdefense.com TRUE / FALSE 1786485904 _ga GA1.1.1985663682.1751925793
|
||||
.atomicdefense.com TRUE / FALSE 1783461904 ig-pv 4
|
||||
.atomicdefense.com TRUE / FALSE 1785621904 _uetvid 2d99def05b7e11f095a86b06a58774de
|
||||
.atomicdefense.com TRUE / FALSE 1786485971 _ga_DR9XYZYM0N GS2.1.s1751925793$o1$g1$t1751925970$j23$l0$h0
|
||||
.atomicdefense.com TRUE / FALSE 1786485971 _ga_RXXREL5ZDN GS2.1.s1751925793$o1$g1$t1751925970$j60$l0$h0
|
||||
.shop.app TRUE / TRUE 1783461790 _shop_app_essential :AZfm6hWDAAEA2lpbeXDvhGPE2CP5hEllSYSKvAUDHudOlhmgIiEVVnPR0SjK8iStsKARIED0vfI:
|
||||
shop.gentexcorp.com FALSE / TRUE 1786485991 fornax_anonymousId 43a7fe87-4464-4364-a9b3-d09c4d9bc2c1
|
||||
imgs.signifyd.com FALSE / TRUE 1786485792 thx_guid 02ee0705746ea94abf905302f905850a
|
||||
imgs.signifyd.com FALSE / TRUE 1786485989 tmx_guid AAyzkeJccLt_C_o4kB4ST4GaYVbg5NQoZFdThM_wef9bqxO5rVGOJT_lY3ZajVHU_V0vj2k-mbu90T98Bfdkh2oEoUFhdQ
|
||||
.www.atomicdefense.com TRUE / FALSE 1785621904 _ttp 01JZKEM7EB58RNXBEDDN22T99Y_.tt.0
|
||||
.www.atomicdefense.com TRUE / FALSE 1785621904 ttcsid_C5D3RI94DV8CDK843CH0 1751925792247::ghM8Tb_9JiqcUXeKz3dd.1.1751925904202
|
||||
.www.atomicdefense.com TRUE / FALSE 1785621904 ttcsid 1751925792260::RdbnOzqaRoJcPIMjCNiQ.1.1751925904263
|
||||
.imgflip.com TRUE / FALSE 1786505380 _ga GA1.1.1468374004.1751944562
|
||||
.imgflip.com TRUE / FALSE 1786504563 _lc2_fpi 9099df4a8854--01jzm0h28krqc9vshh3xhn8bkn
|
||||
.imgflip.com TRUE / FALSE 1786504563 _lc2_fpi_meta %7B%22w%22%3A1751944562963%7D
|
||||
.imgflip.com TRUE / TRUE 1783480563 connectId {"ttl":86400000,"lastUsed":1751944563314,"lastSynced":1751944563314}
|
||||
.imgflip.com TRUE / TRUE 1783480661 _au_1d AU1D-0100-001751944563-69KY1P7C-GVCX
|
||||
.imgflip.com TRUE / FALSE 1785622660 cto_bundle oUv1S19mSmowTTh0d2pqaWpkb1dmYTVobmdNdVd0aWFMNThOd0lzajdSTDBSOWRaTUI3ZWVrMzQxeCUyQnhvVUoxNkRXOGNvM3VDNDZySFBoeiUyQm5QQnFqT3duaXZhS0Q3WUR5eWp4bjczMWZXSVR4RUVDVjVaYWhyVUhGSzRCb1ozd1VpY2xRMGRkVGVtNGZOVDdIWm5vdUI0cUMlMkZjdDJWMGtMJTJGNlU0YlA2WFlmJTJCb1UySUR3cFZuZmF6JTJCeXhYYXhPeUY4d05OYXZjVE12V1JLV3NIeWVsNUVQQWt3JTNEJTNE
|
||||
.imgflip.com TRUE / FALSE 1785622660 cto_bidid Lw_CZl95T0FzZWhSd3oxRnNXa0FvcDU2TnZDY3l0S0FLVkJHZURTRiUyQnY4aWpWeiUyQm5CTkpUdXpMQ29lZTBXbk0zSjkzekJCbmtDb2Q0dE42YjE3WTdPRzByMDZHOEx6ZldnZzhSSUVhRlVpb21pc2E0Y2dLanNUVXhxc2R4dWI0M1g3VGRTRE90MjN1WUNwN3psaXpqdEU0dUZRJTNEJTNE
|
||||
.imgflip.com TRUE / TRUE 1785640563 __gads ID=be74fbe2c6af3547:T=1751944563:RT=1751945371:S=ALNI_Majida08QPOx0983ktIinyR_WTtvw
|
||||
.imgflip.com TRUE / TRUE 1785640563 __gpi UID=000010f862a6cf50:T=1751944563:RT=1751945371:S=ALNI_MZePD2Obp6lMmzFkvBjgc8EFVzWug
|
||||
.imgflip.com TRUE / FALSE 1786505372 _ga_DCRTFFEBRL GS2.1.s1751944562$o1$g1$t1751945372$j60$l0$h0
|
||||
.imgflip.com TRUE / FALSE 1786505380 _ga_X8YQQMPY4Q GS2.1.s1751944562$o1$g1$t1751945380$j60$l0$h0
|
||||
.imgflip.com TRUE / FALSE 1786505380 _ga_FVWZ0RM4DH GS2.1.s1751944594$o1$g1$t1751945380$j60$l0$h0
|
||||
imgflip.com FALSE / FALSE 1785640563 usprivacy 1---
|
||||
imgflip.com FALSE / FALSE 1786504661 ad_clicker false
|
||||
imgflip.com FALSE / FALSE 1783480593 mako_fpc_id 3738993d-cd36-4677-b646-6fc049347db8
|
||||
.yahoo.com TRUE / TRUE 1799644078 A3 d=AQABBLnFMmUCEMk1D1H8-MekCRhtMCBNsz4FEgEBCAGBL2dXZ9xR0iMA_eMDAAcIucUyZSBNsz4&S=AQAAAoKPGn6r5rWbaNAGTE52Z1o
|
||||
.seedtag.com TRUE / TRUE 1799622514 st_uid 0197e809-1d28-76fe-bd2e-2a8367a9b191
|
||||
.seedtag.com TRUE / TRUE 1799190473 st_cs l+EQxuppf4rbMmxYXoBW58T59pDkRx2pDuf/17SALU9Kr4IZJN0cUVL9qSFzxTAd6H7BITeXLEgmthJP6fQVva7CVfG+zgCuqsD8N5JSlleiKNVn8TIyfg5O8p0Ff5DWk18CTQyrN1LPhrZQsSU26fG2cTnsjPdRDuSZqm3jHyGtWResS77PKvj5IR9wv7KBD9lfFPw+rcj7MYK14NZNBH9zDwhlZ6CQiHVH/gB40SyhhlvVnxvcGGyD2zdLMjJM94XMjgx0JPFSyGm6M1LjRGLCBip0RzrA8awS9XfgBBXD4jkuHKmqnkIY7xibjo/Ef1EjhGjXZ87kZ3+vgJhzGpz+MqoPTBm7G83cnoW8vKyBKWMpPolYLzdcmCokXd0JaiY5HjG/MCVxt21HD6wxD5W8eplRV+Xnf0NNqyhjdw0GVCwnqsOCz+jA8a2vMQJb+1aV3ZRIeIrzFzdAmb9VF4OO3Sf9HsM+Gw/gGcK5cyRXNyNPCh7Bf5DLNA/rVVoHngxiAPkZM/0a5xpk1VxRNNrdFNAXlVHBGMQqQrw2AUvdAZlpdqd3GN+tqhVA5DZTTODKPtehdNzRfse84/OFrTIb77P5m0ZjEOMsRvUNZ/JHJH0dBNWPLTJZ2vQ2GjhW4zX5tIaAAbPjVavGlHCYzbQBbw2rAzVhmJklTv03qxGlunzkDQTPzcx3Tj+m4f2y9x/Kbk3bX8AVX84Se59+swq+7PyNEaqydy37toP4jxkuQoHzVofwsxjsLFA6rwwUN4eTcrg5acgNRAl5VpTv7fi8B3UBXUnAThL1TV+WBBqX25UXrvC5b7lVUIYrpmwlzBkC4t72H2bW7UHw7YcLxLfpcJnP91i+qE0j5Bh0R3A1GV5TXKYU6G83s5Kw7F8s6hx8uL0Xeqz9iOf+Gwk8sr+gk6Hx8SqdTIzbIYDBjYXUR/oIC5hQa7/h0Sg3YldN5JyPc1UWz6GGHtOJknxGmsgfsySZ2KZR9P1j8hF2pnmF1IBV0ijc48jSMVH18gbmB9j0Rqb+QCLFnDa0+eo7m8SMc6Go4Ir3o4DTS4d/WUx7o0+jnVlqZg7BWkQdb2DCuyxTzW+e607oasZCQYzaYpNwyOPZiBbDHu20PMIg2XedEn6dZCM0TKI11NcF2bdI
|
||||
.seedtag.com TRUE / TRUE 1799190473 st_csd 1768086473030:1768086473030
|
||||
.ymmobi.com TRUE / TRUE 1783048602 ym_user_cookie ym_user_5b605628-abd0-48fd-bd12-83c5c527fa56
|
||||
.imageresizer.com TRUE / TRUE 1783480605 _au_1d AU1D-0100-001751944605-941OR5IA-XZDN
|
||||
.imageresizer.com TRUE / TRUE 1785640604 __gads ID=1f9e426ea177e36f:T=1751944604:RT=1751944604:S=ALNI_MbPQ8YWwfAMnZDnSpZTK-1tzXkpSQ
|
||||
.imageresizer.com TRUE / TRUE 1785640604 __gpi UID=000010f8629bfb1f:T=1751944604:RT=1751944604:S=ALNI_MannLbLSR4mPPaI59X6gzoqBfXaPQ
|
||||
.imageresizer.com TRUE / FALSE 1783480605 FCNEC %5B%5B%22AKsRol_EONZyLR1QTUgle0ZVzM-n9ByfL6ADfltvoktmT5_GjgRq-XCG1s0xa-hgfjTd-uRi6s_LNCs0l7GI-7ZPGJMD_EwaEXhwaCHyS6nynf6A5dCwHjop7b5xXj7GoDFI1NQZ3SHBWtd-UXkk7eiMAC3kWCCzlA%3D%3D%22%5D%5D
|
||||
.imageresizer.com TRUE / FALSE 1786504653 _ga_HKYMVDPKL5 GS2.1.s1751944604$o1$g1$t1751944652$j12$l0$h0
|
||||
.imageresizer.com TRUE / FALSE 1786504653 _ga_FVWZ0RM4DH GS2.1.s1751944652$o1$g0$t1751944652$j60$l0$h0
|
||||
.imageresizer.com TRUE / FALSE 1786504653 _ga GA1.1.375673278.1751944604
|
||||
.dotomi.com TRUE / TRUE 1802214483 DotomiUser 735907388364061728$0$1937005452$0$1
|
||||
.dotomi.com TRUE / TRUE 1802214483 receive-cookie-deprecation 1
|
||||
.tremorhub.com TRUE / TRUE 1786505372 tv_UIGL CAESEASjkMgMQrOFLnFB39UE0C4
|
||||
.substack.com TRUE / TRUE 1783516203 ab_experiment_sampled %22false%22
|
||||
.substack.com TRUE / TRUE 1783893441 ab_testing_id %2244509386-1a6b-42f4-b509-ab1db0e27257%22
|
||||
.substack.com TRUE / FALSE 1783893441 ajs_anonymous_id %227bb0425f-0f94-4e6c-8063-0f20a54752e2%22
|
||||
.substack.com TRUE / TRUE 1783516203 cf_clearance aUh6OlZdUfcR60RwE6JMZek1WPAqCm6uDxKFKqPiXog-1751980203-1.2.1.1-kjJYlHkQzvgVBAGX3gDpDTsZ1fH4YGoYbW5gPEh9PL_0ZxL5XuPCe84MnpCCiOgM6PkhHjQwx19CErUhQFVdrbpTu2rhi_uCSi_90idaOy4otuMFWzb3EEao5KHcecVNPa2OK1j4pzW2mgsYrREEItKlOZYEHiWQX2H7KmI25k2au7PEVvLyHuhpETFJ8QXFkezJns.8Foz5YEC6adgVgoEcTN5Ey6qyZmlGaJdPcbQ
|
||||
bitvocation.substack.com FALSE / FALSE 1783516203 ajs_anonymous_id %227bb0425f-0f94-4e6c-8063-0f20a54752e2%22
|
||||
bitvocation.substack.com FALSE / FALSE 1786540205 intro_popup_last_hidden_at 2025-07-08T13:10:04.898Z
|
||||
.spotify.com TRUE / TRUE 1784809599 sp_t 9b0c39cb0a61c277ad5448d840cfd76f
|
||||
.onrampbitcoin.com TRUE / FALSE 1786540591 _lc2_fpi 596329408dff--01jzn2whhcz8c8ajjthcwmkdtc
|
||||
.onrampbitcoin.com TRUE / TRUE 1783084591 _reb2buid 8bd9bd84-1391-4e3d-846c-328b0c081882-1751980590774
|
||||
.onrampbitcoin.com TRUE / FALSE 1786540591 _li_ss CgA
|
||||
.airtable.com TRUE / TRUE 1783633552 brw brw4smjnDVd8Cinv4
|
||||
.airtable.com TRUE / FALSE 1785793501 _uetvid f78798d05d0d11f08a6705efe43f47fe
|
||||
.airtable.com TRUE / FALSE 1783633501 _clck iakk65%7C2%7Cfxg%7C0%7C2016
|
||||
.airtable.com TRUE / FALSE 1786657502 _ga GA1.1.882492552.1752097502
|
||||
.airtable.com TRUE / FALSE 1786657502 _ga_VJY8J9RFZM GS2.1.s1752097501$o1$g0$t1752097501$j60$l0$h0
|
||||
.airtable.com TRUE / FALSE 1786657502 __q_state_bhFVoN1cyFKKDaNW eyJ1dWlkIjoiMzkxNzJkMjUtZjk5OS00YjZhLTljYmUtNTYwNTdiNjQ2YjEyIiwiY29va2llRG9tYWluIjoiYWlydGFibGUuY29tIn0=
|
||||
.airtable.com TRUE / TRUE 1783719903 acq eyJhY3F1aXNpdGlvbiI6Ilt7XCJwbGF0Zm9ybVwiOlwiZGVza3RvcFwiLFwib3JpZ2luXCI6XCJscFwiLFwidG91Y2hUaW1lXCI6XCIyMDI1LTA3LTA5VDIxOjQ1OjAxLjQzMFpcIixcInJlZmVycmVyXCI6XCJodHRwczovL3d3dy5nb29nbGUuY29tL1wiLFwidXRtX3NvdXJjZVwiOlwiZ29vZ2xlXCIsXCJ1dG1fbWVkaXVtXCI6XCJwYWlkc2VhcmNoXCIsXCJ1dG1fdGVybVwiOlwiYWlydGFibGVcIixcInV0bV9jYW1wYWlnblwiOlwiZGVtYW5kX2JyX2JyYW5kX2FsbF91c19lblwiLFwidXRtX2V4dHJhMlwiOlwiOTM2NDA3NjkxXCIsXCJ1dG1fZXh0cmE1XCI6XCJrd2QtMzI1Mjg5MzIzMTk0XCIsXCJ1dG1fZXh0cmE4XCI6XCJjXCIsXCJ1dG1fZXh0cmExMFwiOlwiNDc3MzU2MDA1NThcIixcInV0bV9leHRyYTlcIjpcIkVBSWFJUW9iQ2hNSTctang1OS13amdNVnhrN19BUjBjQWpXWkVBQVlBU0FBRWdMUTRfRF9Cd0VcIn0se1wicGxhdGZvcm1cIjpcImRlc2t0b3BcIixcIm9yaWdpblwiOlwibG9naW5cIixcInRvdWNoVGltZVwiOlwiMjAyNS0wNy0wOVQyMTo0NTowMi43OTdaXCIsXCJyZWZlcnJlclwiOlwiaHR0cHM6Ly93d3cuYWlydGFibGUuY29tL2xwL2FpLXBzdS1wbHA/dXRtX3NvdXJjZT1nb29nbGUmdXRtX21lZGl1bT1wYWlkc2VhcmNoJnV0bV9leHRyYTU9a3dkLTMyNTI4OTMyMzE5NCZ1dG1fZXh0cmEyPTkzNjQwNzY5MSZ1dG1fZXh0cmExMD00NzczNTYwMDU1OCZjcmVhdGl2ZT03MTk0ODYyMDM5MDgmdXRtX2V4dHJhOD1jJnV0bV90ZXJtPWFpcnRhYmxlJnV0bV9jYW1wYWlnbj1kZW1hbmRfYnJfYnJhbmRfYWxsX3VzX2VuJmdhZF9zb3VyY2U9MSZnYWRfY2FtcGFpZ25pZD05MzY0MDc2OTEmZ2JyYWlkPTBBQUFBQUR6UnFuVXNPTWY4OFlXQ3hmNmlVMUNkcXZ6VXomZ2NsaWQ9RUFJYUlRb2JDaE1JNy1qeDU5LXdqZ01WeGs3X0FSMGNBaldaRUFBWUFTQUFFZ0xRNF9EX0J3RVwifV0ifQ==
|
||||
.airtable.com TRUE / TRUE 1783719903 acq.sig VbCJhMJQ8qvQpqGFONOWbN1OXlApNf5ntop0TkAw-80
|
||||
.airtable.com TRUE / FALSE 1783633503 OptanonConsent isGpcEnabled=0&datestamp=Wed+Jul+09+2025+16%3A45%3A03+GMT-0500+(Central+Daylight+Time)&version=202407.1.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=abc3b4d9-ab2a-43a3-b283-c4af636ff8e5&interactionCount=1&isAnonUser=1&landingPath=NotLandingPage&groups=C0001%3A1%2CSSPD_BG%3A1%2CC0004%3A1%2CC0002%3A1%2CC0007%3A1%2CC0003%3A1&AwaitingReconsent=false
|
||||
.airtable.com TRUE / FALSE 1783633503 _pxvid f8799d0c-5d0d-11f0-b579-e0c8bc5ee247
|
||||
airtable.com FALSE / TRUE 1783719901 __Host-airtable-session eyJzZXNzaW9uSWQiOiJzZXN0TU1qR0R2VmxIZm5BMyIsImNzcmZTZWNyZXQiOiIzVDJRVUhfQXJCd0ZiOVRHZ3E5aElsX0QifQ==
|
||||
airtable.com FALSE / TRUE 1783719901 __Host-airtable-session.sig Aidk0OW2vbUxWfZB4R4MhZaWcq78REvJPHO8MMRLxI0
|
||||
www.airtable.com FALSE / TRUE 1786657501 _gd_visitor cfe31e24-0577-4f24-8a7b-c5b5b543d807
|
||||
.www.o-big.com TRUE / TRUE 1786659845 svSession 0fd5e4906b7339cf9bde249633a18234cf868b613a67599fb1325cf6699014e5c326ac79c994a282767ff1780b559f631e60994d53964e647acf431e4f798bcdb7ea6cd9965668a1c08575c953936cf7a269ffc58bff5c078e08f5ac4e0c9f3523ed3cd3d08cd0d5bf5f8249d5cb460e97486947151a2d4b846e328f0b3a965bfc47f77fd29db05c561cd0b5dc7cb10e
|
||||
www.capitalcreek.com FALSE / FALSE 1786662356 ss_cvr 9a009276-bc2c-42f1-ae1f-bd774a7e367a|1752102341467|1752102341467|1752102341467|1
|
||||
.phantombuster.com TRUE / FALSE 1786912344 _ga GA1.1.952538975.1752352344
|
||||
.phantombuster.com TRUE / FALSE 1783888344 amp_e1d871 17515780722794205086972709266.b3JnXzU0MDY4OTQ0Njk5OTgwNzg=..1j005diff.1j005diff.1.0.1
|
||||
.phantombuster.com TRUE / FALSE 1786912356 _ga_1J42NVR091 GS2.1.s1752352344$o1$g1$t1752352355$j49$l0$h0
|
||||
.dropbox.com TRUE / TRUE 1787106780 locale en
|
||||
.dropbox.com TRUE / TRUE 1784082806 t Ro6TSx0EmgdOtX2d9pqOd4IT
|
||||
www.dropbox.com FALSE / TRUE 1787106780 gvc MzA4NDYxNDYwOTg2NDU2ODM3NTY5MjU1OTU4OTMzMzkyODY0MDM5
|
||||
www.dropbox.com FALSE / TRUE 1784082806 __Host-js_csrf Ro6TSx0EmgdOtX2d9pqOd4IT
|
||||
.x.com TRUE / TRUE 1787107244 guest_id_marketing v1%3A175254724429867078
|
||||
.x.com TRUE / TRUE 1787107244 guest_id_ads v1%3A175254724429867078
|
||||
.x.com TRUE / TRUE 1787107244 guest_id v1%3A175254724429867078
|
||||
.x.com TRUE / TRUE 1787107244 personalization_id "v1_6oYA/pddvCo/znTfM2ySMg=="
|
||||
announcekit.app FALSE / TRUE 1784087903 ak_eu_41U9JC eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyIkIjpbMSwiODEyODM0MzEzODYwMjEzMS1wcm9kIl0sImlhdCI6MTc1MjU1MTkwM30.zHj6WLSLJK5OEX4a5WYD2vG75uxC24iffpeLzr47_FI
|
||||
.arkm.com TRUE / FALSE 1784089797 mp_f32068aad7a42457f4470f3e023dd36f_mixpanel %7B%22distinct_id%22%3A%20%22%24device%3A1980c58b88316d-00fd236b85478a-17525636-1fa400-1980c58b88316d%22%2C%22%24device_id%22%3A%20%221980c58b88316d-00fd236b85478a-17525636-1fa400-1980c58b88316d%22%2C%22%24initial_referrer%22%3A%20%22https%3A%2F%2Fprimal.net%2F%22%2C%22%24initial_referring_domain%22%3A%20%22primal.net%22%2C%22__mps%22%3A%20%7B%7D%2C%22__mpso%22%3A%20%7B%22%24initial_referrer%22%3A%20%22https%3A%2F%2Fprimal.net%2F%22%2C%22%24initial_referring_domain%22%3A%20%22primal.net%22%7D%2C%22__mpus%22%3A%20%7B%7D%2C%22__mpa%22%3A%20%7B%7D%2C%22__mpu%22%3A%20%7B%7D%2C%22__mpr%22%3A%20%5B%5D%2C%22__mpap%22%3A%20%5B%5D%7D
|
||||
.arkm.com TRUE / FALSE 1787113799 _ga GA1.1.1405382360.1752553799
|
||||
.arkm.com TRUE / FALSE 1784089799 _clck yis5e9%7C2%7Cfxm%7C0%7C2022
|
||||
.arkm.com TRUE / FALSE 1787113809 _ga_K3BXC51SZE GS2.1.s1752553798$o1$g1$t1752553809$j49$l0$h0
|
||||
.arkm.com TRUE / FALSE 1787113809 _ga_P74N755GGG GS2.1.s1752553799$o1$g1$t1752553809$j50$l0$h0
|
||||
www.clarity.ms FALSE / TRUE 1784089799 CLID 3796f63d2e084a1099127441ee73559a.20240224.20260715
|
||||
riverside.fm FALSE / TRUE 1787164202 fullstoryDisabled false
|
||||
riverside.fm FALSE / FALSE 1784140202 language en
|
||||
.techcrunch.com TRUE / FALSE 1787668441 _lc2_fpi 97c377c895b4--01k0ppft76z9bxs29b449re4k1
|
||||
.techcrunch.com TRUE / FALSE 1787668441 _lc2_fpi_meta %7B%22w%22%3A1753108441318%7D
|
||||
.techcrunch.com TRUE / TRUE 1787236442 _awl 2.1753108442.5-4718d52d8d3ff7e48da0c74e99d3637d-6763652d75732d6561737431-1
|
||||
.royalcaribbean.com TRUE / FALSE 1784673342 rwd_id 863febde-c986-4dbf-bfaf-3013289b94f2
|
||||
.royalcaribbean.com TRUE / TRUE 1784673135 BVBRANDID ad90f986-2e00-4e20-b3dc-2f11159e449c
|
||||
.royalcaribbean.com TRUE / FALSE 1784673135 crl8.fpcuid 92329951-c9c1-4190-90a4-3598a26f3afe
|
||||
.royalcaribbean.com TRUE / TRUE 1784807731 _abck F108A0B9EC005650B661BF9BB01E7D19~0~YAAQxB82F8Uusw2YAQAAwIMjNw7zxV37FmFRxuYUmdPLtoXWoJcetrWoLyngJEXmdAvZRLJ3G8bUCK/9WC1A83/xsytYK/VjD0VDLgtX6WMJupvmzPjR/NKzWFWNvcdtWyHy/viOAR1lY+TUp4hJJOvvvaQg5YWJQhTJketokwwY5LujWHmkCS62dIPHsRKIXzSB3di8GjJUJT10IXUdLwQTKdtBNuRabiqewxVQ2luIxxBji809V9VIsr0PtK2OZt8gWAyiyOUDsusB0reCIQXFMFv1z7MfvJnp5WNCHgt2q7+gMVPwt8Kl2VSOckIcuclbQK110QY+XuD8xHy1Z9N7HcfAr4c7GvrUSaR6RUqHRFZ2RTZKeUUl+urKkXreeiOpWfuGOZJnh3GBblgbnPR7SCB17e4JBGBKPoZUBxbn/fUNDTfO51UmMIyU0bqgAku7TrogzHb7RW2L+/DtTjdkbNAHvS2Kjx4KaqTY3UgqGVpYfHXb+HW7Mq3qsCpNXm+gPGhPRuKWgrR5WEzfRZ30R/GPyfWG3RuHCe3DkaAawSt/Z2zeFaIsQIsJ6LmPgDdp0krG6sOWsELRj/09lhSF017IcDkrvRBkhpmQOOnEhHBv0E15VsnoBEWbjeZG54PxNW0=~-1~-1~1753275331
|
||||
.affirm.com TRUE / TRUE 1787697151 tracker_device 4292def8-15ab-4237-b3ca-9f5e52246878
|
||||
.affirm.com TRUE / TRUE 1787697151 t_v2_s IjQyOTJkZWY4LTE1YWItNDIzNy1iM2NhLTlmNWU1MjI0Njg3OCI.G2BRfw.kUXQa7Q6O69gRkS5dOf07g_tjjU
|
||||
.affirm.com TRUE / TRUE 1787697151 3060738.3440491 4292def8-15ab-4237-b3ca-9f5e52246878
|
||||
www.affirm.com FALSE / TRUE 1787697151 session eyJfcGVybWFuZW50Ijp0cnVlfQ.G2BRfw.2iT6h9nSbIrnYiDK8QzBXBNu9rg
|
||||
www.royalcaribbean.com FALSE / FALSE 1787697365 checkout_continuity_service 4292def8-15ab-4237-b3ca-9f5e52246878
|
||||
www.royalcaribbean.com FALSE / FALSE 1787697365 tracker_device 4292def8-15ab-4237-b3ca-9f5e52246878
|
||||
.www.royalcaribbean.com TRUE / FALSE 1784673365 OptanonConsent isGpcEnabled=0&datestamp=Mon+Jul+21+2025+17%3A36%3A05+GMT-0500+(Central+Daylight+Time)&version=202411.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=3f85f375-54ce-4137-8722-39c3b4977d19&interactionCount=1&isAnonUser=1&landingPath=NotLandingPage&groups=C0001%3A1%2CC0003%3A1%2CC0002%3A1%2CC0007%3A1%2CC0004%3A1&AwaitingReconsent=false&geolocation=%3B
|
||||
.www.royalcaribbean.com TRUE / FALSE 1784673365 OptanonAlertBoxClosed 2025-07-21T22:36:05.381Z
|
||||
panteracapital.com FALSE / FALSE 1784862486 cookielawinfo-checkbox-necessary yes
|
||||
panteracapital.com FALSE / FALSE 1784862486 cookielawinfo-checkbox-functional no
|
||||
panteracapital.com FALSE / FALSE 1784862486 cookielawinfo-checkbox-performance no
|
||||
panteracapital.com FALSE / FALSE 1784862486 cookielawinfo-checkbox-analytics no
|
||||
panteracapital.com FALSE / FALSE 1784862486 cookielawinfo-checkbox-advertisement no
|
||||
panteracapital.com FALSE / FALSE 1784862486 cookielawinfo-checkbox-others no
|
||||
.espn.com TRUE / FALSE 1788711177 SWID 1CF34069-B43A-481D-C820-B9858B640E40
|
||||
.espn.com TRUE / FALSE 1788711180 mbox session#b77414c80b6646a6a0cc0845b13a44bc#1754153040|PC#b77414c80b6646a6a0cc0845b13a44bc.34_0#1817395980
|
||||
.agi.cash TRUE / FALSE 1789382528 __gsas ID=6758cd34cb5426af:T=1755686528:RT=1755686528:S=ALNI_MZ-wFdtgNKLvbYZ4dowVyQMR64yFw
|
||||
agi.cash FALSE / FALSE 1787222528 pvisitor e5aff3ba-477f-40b9-b537-d738f686877a
|
||||
.archive.is TRUE / FALSE 1784477267 tmr_lvid 4d87e89d7858ef61d33e1fe99f47ac73
|
||||
.archive.is TRUE / FALSE 1784477267 tmr_lvidTS 1755706067933
|
||||
.mail.ru TRUE / TRUE 1787328588 VID 24Phk500X52Y0026Kk1Q01oY:::0-0-0-cae53fc-0-dc04c0c:CAASEKxMyii6AT78_-3NUPqD0UIaYHtsqvds-0kSklpezGsuCNzWPVpfXllbLL1vqX4f428ZW2vUxODue-givca9Uz03dLH8ZLOAeFiLjUWT2D7xIKjFSSw1mNF8GXEjOnpIeFEPmwLXqAyKewzaUgOzLS9SFg
|
||||
.riverside.com TRUE / TRUE 1787243494 ajs_anonymous_id 21dd7898-1bfc-402f-b77a-7b431fcb8478
|
||||
.riverside.com TRUE / TRUE 1787243494 cookiehub eyJhbnN3ZXJlZCI6ZmFsc2UsInJldmlzaW9uIjoxLCJkbnQiOmZhbHNlLCJhbGxvd1NhbGUiOnRydWUsImltcGxpY3QiOnRydWUsInJlZ2lvbiI6IkcwIiwidG9rZW4iOiJwanhpdlJGdGpCeWw5bE1UTUxpWHNac3hWTzREdnNXb2c0eWRlYUhZNENlcmgxcmdhWkhaWVFGa3BWN0JTd1JJIiwidGltZXN0YW1wIjoiMjAyNC0wNC0xNlQxNzo1OTo1NC4wNTdaIiwiYWxsQWxsb3dlZCI6dHJ1ZSwiY2F0ZWdvcmllcyI6W10sInZlbmRvcnMiOltdLCJzZXJ2aWNlcyI6W119
|
||||
.riverside.com TRUE / TRUE 1787243494 _zitok e8af6ff1d02a6d1909bc1713290389
|
||||
.riverside.com TRUE / TRUE 1787243494 __stripe_mid 84ab569e-8d4f-4816-a632-04e6d62c32405c3e59
|
||||
.riverside.com TRUE / TRUE 1787243494 hubspotutk d4c3d9ccc190dc8a9c6c7ffef14b0996
|
||||
.riverside.com TRUE / TRUE 1787243494 _fbp fb.1.1731079730566.829343683934799639
|
||||
.riverside.com TRUE / TRUE 1787243494 _ga GA1.1.1848369474.1713290394
|
||||
.riverside.com TRUE / TRUE 1787243494 _clck viwp88%7C2%7Cfst%7C0%7C1849
|
||||
.riverside.com TRUE / TRUE 1787243494 fullstoryDisabled false
|
||||
.riverside.com TRUE / TRUE 1787243494 _gcl_au 1.1.1385786570.1755707485
|
||||
.riverside.com TRUE / TRUE 1787243494 __hstc 163253411.d4c3d9ccc190dc8a9c6c7ffef14b0996.1731079729263.1752604204117.1755707484497.7
|
||||
.riverside.com TRUE / TRUE 1787243494 __hssrc 1
|
||||
.riverside.com TRUE / TRUE 1787243494 _ga_PF9PK8DC9Z GS2.1.s1755707484$o7$g1$t1755707493$j51$l0$h1388164421
|
||||
.riverside.com TRUE / TRUE 1787243494 _rdt_uuid 1747764962554.d2f3b35b-0d3d-44cb-994a-6b01b09c67d9
|
||||
.riverside.com TRUE / TRUE 1787243494 _uetsid 1d1a53907de311f0943c870e79b8d202
|
||||
.riverside.com TRUE / TRUE 1787243494 _uetvid 21509ba0fc1b11ee99478f310d5b7258
|
||||
.riverside.com TRUE / TRUE 1787243494 __hssc 163253411.2.1755707484497
|
||||
.riverside.com TRUE / TRUE 1787243494 _dd_s rum
|
||||
riverside.com FALSE / TRUE 1787243494 language en
|
||||
.bbc.co.uk TRUE / TRUE 1788311620 ckns_policy 000
|
||||
.bbc.co.uk TRUE / TRUE 1788225215 ckns_privacy july2019
|
||||
.bbc.co.uk TRUE / TRUE 1791249209 ckns_sylphid OmbZGfsd9Pz9Yo_rfUvosfWknKRGe5bjx2yy809wxzg
|
||||
.bbc.co.uk TRUE / TRUE 1791249209 ckns_id eyJhYiI6Im8xOCIsInBhIjoiV0MyUiIsImVwIjp0cnVlLCJmaSI6eyJnYW0iOnsiaWQiOiIyNkJFMzY1MkZDMEU1NUM5NUZBN0ExQTA5NUFGMjdDQzEwRDIzNjczNDU0RDVDQjExOTJCNDczMDYxNjlCQzc4NTQyOENGQ0ZDQjU2MThFQkNGRUFBMzNGNDc1MkY2NTciLCJ2ZXJzaW9uIjoiYjk3MDk4MzItM2RhYS00ZTkyLWJjMDctMzg0MjA1YzIyZTQzIn0sInBpYW5vIjp7ImlkIjoieVNUQktyVlpLOTRuVnJDNzBNSUhjdlMwRXUzdVR6MnByd3hJcWM3UlFhbjRsUkZsUVN2elJhNThibnRSZFJ2ciIsInZlcnNpb24iOiI0NzFhMjM5YS1hYTNkLTQ5ZTYtOWIxZC1kNTcyNzhiNzkxMDgifSwiYWRhcHRlbXkiOnsiaWQiOiJGekFqQlBoWG5ydFQ0MEtENXhwdFg1K2ZlNDkzbXlpMmYxMTVDN0taTlV3RW5tSG5kNUxYZ2NUUFFrcVZTeEdDIiwidmVyc2lvbiI6IjFlZDZkOTM4LTM1NmEtNDU3MC1iMjZlLTU2NDg0NjcwNjhjYiJ9LCJnb29nbGUiOnsiaWQiOiJKcjQyVXZ3T1ZjbGZwNkdnbGE4bnpCRFNObk5GVFZ5eEdTdEhNR0ZwdkhoVUtNL1B5MVlZNjgvcW96OUhVdlpYIiwidmVyc2lvbiI6IjdjZDQ0MDJjLWViZjQtNDE5OC05MjliLWEyOWQ1ZmUwZTQxNiJ9LCJzdHVkaW9zX296b25lIjp7ImlkIjoiN3pWQkJWeE5HbEpqWUowbTFxcWFtWFpJMW5rYXFuK09XNExoNDkrOFpKeklQUlJGZU9ZWUNqeGl1SjJ4ZkF4SyIsInZlcnNpb24iOiI0OTNhNWI3Zi1jNjI1LTQ2ODctOTRmMi1lNWE1ZGIwOGM4OTkifSwicGVybXV0YXRpdmUiOnsiaWQiOiJUcitvU1B6RTVRNVh4ZVlQakFtdzNXaDNnMjlWbkRieHZScVJpdDZMbWM3UllGNHpGaWtEL1A1enNDSEFBZHl3IiwidmVyc2lvbiI6ImRkYjQ1ZGE3LWExNDQtNGUxNC04YjBlLTUwYjkyOTVjNzBmOSJ9fSwiZXYiOmZhbHNlLCJwbCI6ZmFsc2UsInBzIjoiaW1tUzFDRnpKMXg5VlRMQUx5aFJBeFpxSnkzZ3NPN1JSQm5MWGprX0JUMCIsImNsIjpmYWxzZSwiY2MiOiJnYiIsInJlYWxtIjoiLyIsInNlcy1leHAiOjE3NTY2OTAxMDkwMDAsImp3dC1leHAiOjE4MTk3NjEyMDkwMDAsInRrbi1leHAiOjE3NTY2OTU4MDgwMDAsInJ0a24tZXhwIjoxODE5NzYxMjA5MDAwfQ
|
||||
.bbc.co.uk TRUE / TRUE 1790903615 ckns_policy_exp 1788225215721
|
||||
.bbc.co.uk TRUE / TRUE 1788225216 ckns_iplayer_experiments {}
|
||||
.bbc.co.uk TRUE / TRUE 1788311620 ckns_explicit 1
|
||||
.bbc.com TRUE / TRUE 1788225190 ckns_mvt dc2f56e0-df7d-4cce-9e4b-dcf788e66fe4
|
||||
.bbc.com TRUE / TRUE 1788311620 ckns_policy 000
|
||||
.bbc.com TRUE / TRUE 1788311620 ckns_explicit 1
|
||||
.account.bbc.com TRUE / TRUE 1791249208 ckns_jwt eyJ0eXAiOiJKV1QiLCJjdHkiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ZXlKMGVYQWlPaUpLVjFRaUxDSnJhV1FpT2lKTk1VcDFVMHhGVlRkb2IwaG9NMVUwVlN0MmFHSnNiR1ZyU1VrOUlpd2laVzVqSWpvaVFURXlPRU5DUXkxSVV6STFOaUlzSW1Gc1p5STZJbEpUUVRGZk5TSjkuRjZFTDBMZFBiRkJwb245WmxUaXd0ZmI5aXFFOVFLaTdsQ2JRSWt0NzZBa2FqeXNjVzFsYUl1TGtmcmN1VHFicWJMWldTVHJPX0t3VTFaZ2JFOXgxaEtLRjQ5ZllKZ1BidXdoZ0Fha1lZWUlpSllnXy12M3ZwQXdmOTVwUFRnclkzeVItNFZJTGJldW1pS3Zvcy04blRpc3VPdzZydTBlVm5XemhscjVaTHdacDI0SXhHdUhOV3pkNEp0T1F2WUhuWDBxR2JsMUNDUElFTzFMX1FMZFhSOHRDMW9BQzJjd0FHYy1CY1lfOElfcGNqaHBmZGFGRklJaUprempaZ1Jad3pzZzhUOEVNSVgxYktJdlo3blZ1eDR0WWNTMnlfUU9NeU95aE1yUnMwQ2w1VG50S1A1cWFlUUdBY25SMmJwd19OUl9VTElMczFxQklDcTdGMjN0ZVhnLjJWOUVQdHFxcDByQjdQNGdkeXFVc0Euc0s3eXByMld5ZWJkNER2WEtaZkNWaVZudXROQTRHeXpZcjVKZXlwNExkWkkzVE9fWUNJN01LbXNjcVZtaGtqNjB1UVZvaXh1eFlmRURjZU1oOTdKRnhmT2hEM0M5aFZ5NS1mTmxzcmpZUjVqRFc2a3ZUWFlZbWd2ZjZPQVBsMFhJRUM3SEc3R2U5ckxBX3Z3REJKZ3FId1EzUWVxZ3ZqaWctTUJMQjNCNTM1Nlo1SDR5djBBVVVpRHY1MU8xLWNYT2dBLTF0QWpIaEhfWDhDM1dtY3VzS2dkSDZ0WHVxVV9hdWZ3MkVFQ3FwTmFSa0pGMnU4clNRTTlkczFNdW1PVzhGRmZHanljZjZ5bU1kZ1A0bU44SGR3SmdvQnpGU0NhckJNb0VZV2xsWjJZSnZuN3pxdl9uZnMwLUVrcnZVR1pZeEU3YnM2OEVtV3dFMnVMR3lxQkhXdlpmaExkOFc1ZGJ6blc5ZEJkMjdDS2paUElnazZDNk1vYWVhZHNiVF8yYUx1RjJFTVZRMGJ3TGpWcXdGQ1dVZ2ZaLWhubEVMWG81ZWlKbXpvS0gta2pGUTR0c3B4X2Q1TmRNSzFXMzRsSEF0SmQ0WmotMlVyclRrNlBTZWhvN0ZxTnJDaGRkR0pJMlRtQVpFY2pGcGNFeENtdW1XUHRrNWNScWJYaThETjBxdmZXb05BRG9DYTA5MWtEVEpBZXI5SVJfRFEzRmlUUVlzRHRybVFmRk5NdkJLbEF3Tk5wa09CamlGT1V4dGgxVXR3TGE5U0NsRWRXMXViV2R4UGFGckZzNHlURFoyTzA4ckdpdVJ6Nk1kUS5QVHZMcGpYU0lJYXRlYi11dS1scnJ3.fjjZwBvJzKI0vgBvZO6blAwtY0YJFjaRs8mio7nmpOE
|
||||
.session.bbc.co.uk TRUE / TRUE 1791249209 ckns_rtkn eyJ0eXAiOiJKV1QiLCJraWQiOiJFZ1VVKzhOaGFBWUtjNnlsb0NCcm5LUFRjZTg9IiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJiYzE5MzU0Yy04YmM2LTRhODYtODYyOC0yZTg2YjYxNzdmMDciLCJjdHMiOiJPQVVUSDJfU1RBVEVMRVNTX0dSQU5UIiwiYXV0aF9sZXZlbCI6MiwiYXVkaXRUcmFja2luZ0lkIjoiYTcyYWY3NjItZjMyNi00MGJjLWEwOTMtMjg2ZTIwNWYyMmE5LTExMjUxOTU2MSIsImlzcyI6Imh0dHBzOi8vYWNjZXNzLmFwaS5iYmMuY29tL2JiY2lkdjUvb2F1dGgyIiwidG9rZW5OYW1lIjoicmVmcmVzaF90b2tlbiIsImF1dGhNb2R1bGVzIjoiQkJDSURfSkRCQ19FTUFJTCIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJhdXRoR3JhbnRJZCI6IktuYXJhUVdkQUtJM05LWWNJaE1BRU92QlNRSSIsImF1ZCI6IkFjY291bnQiLCJhY3IiOiIwIiwibmJmIjoxNzU2Njg5MjA5LCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwic2NvcGUiOlsiZXhwbGljaXQiLCJjb3JlIiwiaW1wbGljaXQiLCJwaWkiLCJ1aWQiLCJmaSIsIm9wZW5pZCIsImVkIl0sImF1dGhfdGltZSI6MTc1NjY4OTIwOCwicmVhbG0iOiIvIiwiZXhwIjoxODE5NzYxMjA5LCJpYXQiOjE3NTY2ODkyMDksImV4cGlyZXNfaW4iOjYzMDcyMDAwLCJqdGkiOiJBYm5zYml6aGt1UmRDbl96LVVvLUVRYUM5eDQifQ.eZRT1ndGRwxnsShBMfaE4EBO8k9mCxl2XGvadQcYlYLJZAojTbuOqeX8CXuU0Kr3FZjKmobzis570EwEVMRZmw
|
||||
www.benchmade.com FALSE / FALSE 1788736114 localization US
|
||||
www.benchmade.com FALSE / TRUE 1788736103 _shopify_essential :AZkhSEvUAAEA4RpevYPi8TQPeiP9QhKEUeiCqTaYc1dsi3rL20srm56ll2cYCcB6HjNfk9cIGFH0qm5_GPxfVNOjFN2nlK76b6vUQh3RDB3Qsc80ptryk1aj7UyjMo7UUNl869Rb64HXIbeaKeI:
|
||||
.benchmade.com TRUE / TRUE 1791760011 __eventn_id 05grsl4da0.1757200010
|
||||
.benchmade.com TRUE / TRUE 1788736011 ssUserId f6a10f03-956c-496d-a271-dd135d92dca4
|
||||
.benchmade.com TRUE / TRUE 1788736011 _isuid f6a10f03-956c-496d-a271-dd135d92dca4
|
||||
.benchmade.com TRUE / TRUE 1791760011 ssViewedProducts 4010BK-01
|
||||
.benchmade.com TRUE / TRUE 1791760016 __mmapiwsid 01992148-6176-790f-9637-3c682e38cf66:abbb1dd4308adda60f6fd5c6d973e3d10626ab72
|
||||
.benchmade.com TRUE / TRUE 1788736021 osano_consentmanager_uuid b29340f4-60f9-4647-8eb1-b32488944e24
|
||||
.benchmade.com TRUE / TRUE 1788736021 osano_consentmanager wQMoLX7PZtaJlfz-sjj5K9A9o6kqoGvVi-ZBoD8HiI0yezCa-BNqhG_Vl_ZuQGEyEq3ruGM6-8fmI9Bx5vqNE5gRdx0rDX91eN28lxyyepTJspkSeOMuuUcvLz-tLU_byezjAsVcJm-_2VK9j1tm4mMX0yTRmuO01qYqVar_h9ixOL_aBckoMZWtVeCQZ_4GL3by9HrOBxnBxPcBiOg4awYi_RzpNNtCQaktptOrdK0Sitqh1cXXKWb5M-OU-OCb30yGQcoVz2r0PdEk2O8laKVeeio04id-hbuhK18fv-BlKdTUgUtClp0ft0cD4dnE
|
||||
.benchmade.com TRUE / FALSE 1788757621 _tracking_consent 3pams._USTN_t_f_3D7Z7*ANTre7GLEfvycF*g_{"consent_id":"b29340f4-60f9-4647-8eb1-b32488944e24","consent_timestamp":"2025-09-06T23:07:00.879Z"}
|
||||
.yotpo.com TRUE / TRUE 1788736222 pixel 3c1ab4cd-50fa-40c2-42fe-997c086dedae
|
||||
d-ipv6.mmapiws.com FALSE / TRUE 1791760016 __mmapiwsid 01992148-6176-790f-9637-3c682e38cf66:abbb1dd4308adda60f6fd5c6d973e3d10626ab72
|
||||
.7ce243a1e1a2.cdn4.forter.com TRUE / TRUE 1788736016 forterSId 2436eb89683740e5a1e2611cea77050c_1757200015707
|
||||
d-ipv4.mmapiws.com FALSE / TRUE 1791760016 __mmapiwsid 01992148-6176-790f-9637-3c682e38cf66:abbb1dd4308adda60f6fd5c6d973e3d10626ab72
|
||||
.kys-tech.com TRUE / TRUE 1788978962 __stripe_mid 15f79900-7ee4-4525-84c9-04990e72a685d657ef
|
||||
kys-tech.com FALSE / FALSE 1788978797 info true
|
||||
support.ledger.com FALSE / TRUE 1789655123 CookieConsentPolicy 0:1
|
||||
support.ledger.com FALSE / TRUE 1789655123 LSKey-c$CookieConsentPolicy 0:1
|
||||
.ledger.com TRUE / FALSE 1792679513 _ga GA1.1.677620686.1758119129
|
||||
.ledger.com TRUE / FALSE 1792247129 _scid Rq0GQzyhVgelp7ZQXEj4p4eywsSR_5l2
|
||||
.ledger.com TRUE / FALSE 1792283129 _cs_c 1
|
||||
.ledger.com TRUE / FALSE 1791815180 _tt_enable_cookie 1
|
||||
.ledger.com TRUE / FALSE 1791815180 _ttp 01K5C11W3Y5FE9VY2YJHPS51BG_.tt.1
|
||||
.ledger.com TRUE / FALSE 1792247130 _sctr 1%7C1758085200000
|
||||
.ledger.com TRUE / FALSE 1792283129 _cs_id 04a79f3b-fb89-a1b2-ca66-13a74ef5139b.1758119129.1.1758119180.1758119129.1743519975.1792283129139.1.x
|
||||
.ledger.com TRUE / FALSE 1791815180 ttcsid 1758119129217::4ILjIjLzkvXvFxQSxUaM.1.1758119180327.0
|
||||
.ledger.com TRUE / FALSE 1791815180 ttcsid_CCM80A3C77U9QMO0Q980 1758119129217::DWn4ZxMwXa91S6tK3o9C.1.1758119180716.0
|
||||
.ledger.com TRUE / FALSE 1792247512 _scid_r Ui0GQzyhVgelp7ZQXEj4p4eywsSR_5l2FzAF6Q
|
||||
.ledger.com TRUE / FALSE 1791815512 _uetvid 2929bb5093d211f094dfef3879dafab7
|
||||
.ledger.com TRUE / FALSE 1792681770 _ga_Y38HP2KGC3 GS2.1.s1758121743$o2$g1$t1758121769$j34$l0$h0
|
||||
.waterbear.com TRUE / FALSE 1790346513 AMP_MKTG_e6b575e207 JTdCJTdE
|
||||
.waterbear.com TRUE / FALSE 1793370694 ab.storage.deviceId.48d270e0-354a-41c6-ad46-4879e5b4e0ed g%3A97529535-dcfe-5088-5a99-b222d5a6f6a7%7Ce%3Aundefined%7Cc%3A1758810694823%7Cl%3A1758810694837
|
||||
.waterbear.com TRUE / FALSE 1793370694 ab.storage.userId.48d270e0-354a-41c6-ad46-4879e5b4e0ed g%3ApWAIJMExR2ZkGPyKaCkIMPA9TJI3%7Ce%3Aundefined%7Cc%3A1758810694837%7Cl%3A1758810694838
|
||||
.waterbear.com TRUE / FALSE 1793370716 ab.storage.sessionId.48d270e0-354a-41c6-ad46-4879e5b4e0ed g%3A39d5136d-2c47-c980-7d73-a8d1edcb6b79%7Ce%3A1758812516440%7Cc%3A1758810694837%7Cl%3A1758810716440
|
||||
.waterbear.com TRUE / FALSE 1790347120 AMP_e6b575e207 JTdCJTIyZGV2aWNlSWQlMjIlM0ElMjI5NDE0YWNiMS02NGQ4LTRmZGUtYjk4NC0wMzA1NmQyMjRhMzclMjIlMkMlMjJ1c2VySWQlMjIlM0ElMjJwV0FJSk1FeFIyWmtHUHlLYUNrSU1QQTlUSkkzJTIyJTJDJTIyc2Vzc2lvbklkJTIyJTNBMTc1ODgxMDUxMzg3OSUyQyUyMm9wdE91dCUyMiUzQWZhbHNlJTJDJTIybGFzdEV2ZW50VGltZSUyMiUzQTE3NTg4MTExMjA2NzYlMkMlMjJsYXN0RXZlbnRJZCUyMiUzQTQ0JTJDJTIycGFnZUNvdW50ZXIlMjIlM0EzJTdE
|
||||
.getalby.com TRUE / TRUE 1777649101 cc_cookie {"categories":["necessary","analytics"],"level":["necessary","analytics"],"revision":0,"data":null,"rfc_cookie":false,"consent_date":"2025-07-01T18:52:45.856Z","consent_uuid":"4a542391-0497-4fe9-832d-98ec5c2be76f","last_consent_update":"2025-10-31T15:25:01.672Z"}
|
||||
.getalby.com TRUE / TRUE 1802374856 ph_phc_W6d0RRrgfXiYX0pcFBdQHp4mC8HWgUdKQpDZkJYEAiD_posthog %7B%22%24device_id%22%3A%220197c755-9c24-7cdf-b425-62896d0125c1%22%2C%22distinct_id%22%3A%22nnISDNGBJUIqDV3NGap8%22%2C%22%24sesid%22%3A%5B1770838856866%2C%22019c4e0a-dbc9-7160-a65c-001c2749059e%22%2C1770835860407%5D%2C%22%24epp%22%3Atrue%2C%22%24initial_person_info%22%3A%7B%22r%22%3A%22https%3A%2F%2Fgetalby.com%2Foauth%2Fsessions%2Fnew%3Fclient_id%3DNYvmxLuaWE%26code_challenge%3D2z1VRRvKkLENMiKqy5814MVCv9g6X9UFSIMQXTxFs40%26code_challenge_method%3DS256%26redirect_uri%3Dhttps%253A%252F%252Fgetalby.com%252Fextension%252Fconnect%26scope%3Daccount%253Aread%2Bbalance%253Aread%2Binvoices%253Acreate%2Binvoices%253Aread%2Bpayments%253Asend%2Btransactions%253Aread%22%2C%22u%22%3A%22https%3A%2F%2Fgetalby.com%2Fextension%2Fconnect%3Fcode%3DZTE1MDLLMMMTOTQ5ZC0ZODQ5LWI4OGITZTHJMZJLNJDLNGEY%22%7D%7D
|
||||
.getalby.com TRUE / TRUE 1802371855 cf_clearance HTj7XPrpro_FDlj9EzpTHpJczEspX9S7r9hz9DeMX7w-1770835855-1.2.1.1-DYGWiCTlTQ8QYXzW3_yjuVTzkgs6FWjMM2zaiFFFTA9T_6D8UrAEO1aLmmwWhnoHI4aeKD9GA8KTJgSpoTI3IuHAO_1slPX1BJhKT0cMw7NumWND7WkQj7oFSE6.PuGCuiWRiuT7m2Rq6eqbu3Wu6k0WgfLItvqVoS0CYl1lM8oV0EzHHO6kggSipw5Wbau5rQvUUMzWoQBFgbhll2Lx14jtSmb0H4OznJ9_Wepuyrk
|
||||
.github.com TRUE / TRUE 1795617904 _octo GH1.1.659434916.1764081903
|
||||
.github.com TRUE / TRUE 1795617904 logged_in no
|
||||
.apliiq.com TRUE / FALSE 1799190046 _ga GA1.1.225605822.1764615090
|
||||
.apliiq.com TRUE / FALSE 1799190046 ccid.86838876 347223090.3087116993
|
||||
.apliiq.com TRUE / TRUE 1799190046 apliiq-_zldp hpL8Ey9L33r%2FpBmy3ES%2BLnEa9z5a3Ru0sqYqVOmGsifDZb6QH3ZBTrS1TGiJ%2FcX2T3IUrdIYs4k%3D
|
||||
.apliiq.com TRUE / FALSE 1799190453 _ga_ZL932S8CL9 GS2.1.s1764623129$o4$g1$t1764630453$j59$l0$h0
|
||||
.thrivecap.com TRUE / FALSE 1805844086 _ga GA1.1.1359966912.1764617644
|
||||
.thrivecap.com TRUE / FALSE 1805844086 _ga_CC3XJMND3M GS2.1.s1771284086$o2$g0$t1771284086$j60$l0$h0
|
||||
www.apliiq.com FALSE / FALSE 1799190046 __kla_id eyJjaWQiOiJZMlptTWpFNE4ySXRORFl4T1MwME5Ea3pMVGs1TjJNdE1XTTJPVFU0TmpsaE5EWmwiLCIkZXhjaGFuZ2VfaWQiOiJaYU9CMEwta1FPZjM1M0kxZEpPSVBNM3dzRjZjZVNTbFl2VTQ5aW8tV18wLkxHdkNFWCJ9
|
||||
checkout.apliiq.com FALSE / FALSE 1799189298 __kla_id eyJjaWQiOiJNRFprT1RReVpETXROekppTlMwMFpHTmhMVGcxTW1VdE5tWXpZMlZrWlRRd05EaGoiLCIkZXhjaGFuZ2VfaWQiOiJaYU9CMEwta1FPZjM1M0kxZEpPSVBNM3dzRjZjZVNTbFl2VTQ5aW8tV18wLkxHdkNFWCJ9
|
||||
.checkout.apliiq.com TRUE / TRUE 1799189183 apliiq-_zldp hpL8Ey9L33r%2FpBmy3ES%2BLnEa9z5a3Ru0sqYqVOmGsifDZb6QH3ZBTrS1TGiJ%2FcX2T3IUrdIYs4k%3D
|
||||
.gfore.com TRUE / FALSE 1796108616 visid_incap_2081288 fR3D7pzZRNClUUXvZ0djUlMPLmkAAAAAQUIPAAAAAAAR7nhfh+ALBI09CWSIi1xz
|
||||
.gfore.com TRUE / FALSE 1780394277 _conv_v vi%3A1*sc%3A1*cs%3A1764626267*fs%3A1764626267*pv%3A1*exp%3A%7B100493634.%7Bv.1004224736-g.%7B%7D%7D%7D
|
||||
.gfore.com TRUE / FALSE 1780178277 _conv_r s%3Awww.google.com*m%3Aorganic*t%3A*c%3A
|
||||
.gfore.com TRUE / TRUE 1796162268 BVBRANDID 2713ee0a-f16b-4724-ae7f-64dd6f7d9358
|
||||
.gfore.com TRUE / FALSE 1796162268 crl8.fpcuid 9ef62d87-b733-4303-9d76-cc57c9954c3a
|
||||
.gfore.com TRUE / FALSE 1798581468 TCPID 12512115574812185174123
|
||||
.gfore.com TRUE / FALSE 1798322268 TC_PRIVACY_CPRA 0%40005%7C1%7C6985%401%2C2%403%2C4%401764626268380%40
|
||||
.gfore.com TRUE / FALSE 1798322268 TC_PRIVACY_CPRA_CENTER 1%2C2
|
||||
.gfore.com TRUE / FALSE 1798754268 __cq_uuid abc0mIAQzkjEcz7G7jKiIAQmA6
|
||||
.gfore.com TRUE / FALSE 1799186269 _ga_XXXXXXX1 GS2.1.s1764626268$o1$g0$t1764626268$j60$l0$h0
|
||||
.gfore.com TRUE / FALSE 1799186269 _ga GA1.1.2041936879.1764626268
|
||||
.gfore.com TRUE / FALSE 1799186269 _blkp_xps %7B%22HJSHP%22%3A13%7D
|
||||
.gfore.com TRUE / FALSE 1799186269 tatari-session-cookie 4d93019f-1130-10d7-88d7-ba789134d1f2
|
||||
.gfore.com TRUE / TRUE 1798322269 __attentive_id f80a08145ae84bb4a15e3522ddf6ec89
|
||||
.gfore.com TRUE / TRUE 1798322269 _attn_ eyJ1Ijoie1wiY29cIjoxNzY0NjI2MjY5MTAxLFwidW9cIjoxNzY0NjI2MjY5MTAxLFwibWFcIjoyMTkwMCxcImluXCI6ZmFsc2UsXCJ2YWxcIjpcImY4MGEwODE0NWFlODRiYjRhMTVlMzUyMmRkZjZlYzg5XCJ9In0=
|
||||
.gfore.com TRUE / TRUE 1798322269 __attentive_cco 1764626269102
|
||||
.gfore.com TRUE / FALSE 1798790269 _cs_c 0
|
||||
.gfore.com TRUE / FALSE 1798790269 _cs_id 67f94baa-0c07-aa0a-8730-2b163746ee77.1764626269.1.1764626269.1764626269.1732464474.1798790269114.1.x
|
||||
.gfore.com TRUE / FALSE 1798322269 _uetvid c6d49190cf0011f0aeea9b5244ce38a2
|
||||
.gfore.com TRUE / FALSE 1799186269 forterToken e336e2a1c101466cab21e3b0cd875de2_1764626267339__UDF43-m4_15ck_
|
||||
.gfore.com TRUE / FALSE 1796162270 _ju_dc c76c9bf6-cf00-11f0-b023-2f66809d83c9
|
||||
.gfore.com TRUE / FALSE 1799186271 _lc2_fpi 8dce54196be4--01kbdyr369gqe5hbzn8fmg8sjx
|
||||
.gfore.com TRUE / FALSE 1799186272 _li_ss CgA
|
||||
.gfore.com TRUE / FALSE 1799186586 _ga_B0REW2WZF3 GS2.1.s1764626268$o1$g1$t1764626585$j60$l0$h411637605
|
||||
www.gfore.com FALSE / TRUE 1780178266 dwanonymous_9075bc7ed73102844fcce4f0052fc396 abc0mIAQzkjEcz7G7jKiIAQmA6
|
||||
www.gfore.com FALSE / FALSE 1799186269 __kla_id eyJjaWQiOiJNRGRsWm1JNFpUVXRObUkzWmkwME9HUmhMV0ppT1dNdFl6RTVORGc0WXpReE5EaGoifQ==
|
||||
www.gfore.com FALSE / FALSE 1799186269 tatari-cookie-test 7751488
|
||||
www.gfore.com FALSE / FALSE 1796162271 mmuid 8bc762a20cf84eb3
|
||||
.cquotient.com TRUE / TRUE 1798754268 uuid abc0mIAQzkjEcz7G7jKiIAQmA6
|
||||
.pippio.com TRUE / TRUE 1796162270 did 2lV2IoCTOVt2NS-_
|
||||
.pippio.com TRUE / TRUE 1796162270 didts 1764626270
|
||||
.stats.paypal.com TRUE / FALSE 1799189168 c 98d9b886327596d2998e
|
||||
www.paypal.com FALSE / FALSE 1796165182 _dd_s aid=5e3e2398-f9b0-450b-b96d-81e19d3ab6eb&rum=1&id=9519a9a5-c971-467f-9f02-e159e9c6b04c&created=1764628902830&expire=1764630082507
|
||||
.zaprite.com TRUE / FALSE 1799190233 _ga GA1.1.1488010542.1764630150
|
||||
.zaprite.com TRUE / FALSE 1799190191 _ga_XFZVJST60H GS2.1.s1764630150$o1$g1$t1764630190$j20$l0$h0
|
||||
.zaprite.com TRUE / FALSE 1799190233 _ga_DKN4TFVCXF GS2.1.s1764630191$o1$g1$t1764630233$j18$l0$h0
|
||||
.zaprite.com TRUE / FALSE 1787960234 intercom-id-vvv6kprm 53fb38b9-0790-4323-ad0d-bf81666b8e17
|
||||
.zaprite.com TRUE / FALSE 1787960234 intercom-device-id-vvv6kprm 28349a77-ec40-4fa5-8b4d-40552ffac9bb
|
||||
.zaprite.com TRUE / FALSE 1799190340 _ga_7GLS7Z6F3N GS2.1.s1764630233$o1$g1$t1764630339$j60$l0$h0
|
||||
skims.com FALSE / TRUE 1797188683 _skid 65794e07-1d7d-4046-82d7-97b491f2207a
|
||||
skims.com FALSE / TRUE 1797188683 _visitingStatus visited
|
||||
skims.com FALSE / TRUE 1797620763 __rtbh.lid %7B%22eventType%22%3A%22lid%22%2C%22id%22%3A%22YlhgkQKyMucYj6OgV707%22%2C%22expiryDate%22%3A%222026-12-18T19%3A06%3A03.356Z%22%7D
|
||||
skims.com FALSE / FALSE 1800644764 __kla_id eyJjaWQiOiJNbVF4WW1JeFpXRXRORGcxTkMwME9ESmhMV0ppTkRFdFpERmhaamRrWldRNE56Z3kifQ==
|
||||
skims.com FALSE / TRUE 1797620837 __rtbh.uid %7B%22eventType%22%3A%22uid%22%2C%22id%22%3A%2265794e07-1d7d-4046-82d7-97b491f2207a%22%2C%22expiryDate%22%3A%222026-12-18T19%3A07%3A17.905Z%22%7D
|
||||
.skims.com TRUE / FALSE 1800644684 GLBE_SESS_ID %7B%22sid%22%3A%22467132306.141432646.1155%22%2C%22expiry%22%3A%222025-12-18T19%3A34%3A43.788Z%22%7D
|
||||
.skims.com TRUE / FALSE 1797620762 _pin_unauth dWlkPVptSTNNak5tWkRrdE1tUXpNQzAwWmpRMExUbGpOV1l0Tm1Zd1lXTTJOVGhqTW1ObQ
|
||||
.skims.com TRUE / FALSE 1799780762 _tt_enable_cookie 1
|
||||
.skims.com TRUE / FALSE 1799780762 _ttp 01KCSDKA6XDMKRKZPT7DCE4AP2_.tt.1
|
||||
.skims.com TRUE / FALSE 1797188837 _shopify_y 32d9af33-1480-4758-D61F-71903CC651A6
|
||||
.skims.com TRUE / FALSE 1800644763 _ga GA1.1.504031878.1766084685
|
||||
.skims.com TRUE / FALSE 1800644685 _lc2_fpi 21287e9a14d4--01kcsdkavhydvmffvv0jxfzemq
|
||||
.skims.com TRUE / TRUE 1799780764 __attentive_id 47bbcb9b962e4edca1e9cbef53e28d00
|
||||
.skims.com TRUE / TRUE 1799780763 _attn_ eyJ1Ijoie1wiY29cIjoxNzY2MDg0Njg0Njc3LFwidW9cIjoxNzY2MDg0Njg0Njc3LFwibWFcIjoyMTkwMCxcImluXCI6ZmFsc2UsXCJ2YWxcIjpcIjQ3YmJjYjliOTYyZTRlZGNhMWU5Y2JlZjUzZTI4ZDAwXCJ9In0=
|
||||
.skims.com TRUE / TRUE 1799780684 __attentive_cco 1766084684678
|
||||
.skims.com TRUE / TRUE 1797620684 __ps_r _
|
||||
.skims.com TRUE / TRUE 1797620684 __ps_lu https://skims.com/collections/nikeskims?direction=next&cursor=eyJsYXN0X3ZhbHVlIjoiNDciLCJsYXN0X2lkIjoxMDI0Njg2MDI0MzI5Nywib2Zmc2V0Ijo0N30%3D
|
||||
.skims.com TRUE / TRUE 1797620684 __ps_did pscrb_226743b1-9d44-4bb8-f968-04a31e8f8041
|
||||
.skims.com TRUE / TRUE 1797620684 __ps_fva 1766084684717
|
||||
.skims.com TRUE / TRUE 1800644685 IR_PI 6a48fb22-dc44-11f0-870a-6584898419f4%7C1766084684614
|
||||
.skims.com TRUE / FALSE 1797620684 _axwrt 8c9ab40d-c69d-4ee6-9183-e996d8a1c7b8
|
||||
.skims.com TRUE / FALSE 1800299084 _scid JRoE-ZvIBUJLSPCc1lIQKUMvwHza3_Nj
|
||||
.skims.com TRUE / FALSE 1799953485 __qca P1-9906de41-ac66-4d3d-9d15-0f1f3e39e286
|
||||
.skims.com TRUE / TRUE 1797620766 QuantumMetricUserID 98414e535c76313591f9ee355cff76c4
|
||||
.skims.com TRUE / FALSE 1800299085 _sctr 1%7C1766037600000
|
||||
.skims.com TRUE / FALSE 1800644838 __ta_device ubGuBKNdUrjeaI0JaQhs4JFAcl4tQMLC
|
||||
.skims.com TRUE / TRUE 1797620760 _dy_soct 1766084760!1159934.-77'1159935.0'1272376.0'2016990.0!adg54ijwhx2tu6ef4um4hp3eo8uhne4p~1159939.-77'1324063.-77'1327720.0
|
||||
.skims.com TRUE / FALSE 1797620760 OptanonConsent isGpcEnabled=0&datestamp=Thu+Dec+18+2025+13%3A06%3A00+GMT-0600+(Central+Standard+Time)&version=202306.1.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=afbd17d7-6dcd-40fa-98d8-cdc9ff46f400&interactionCount=1&landingPath=NotLandingPage&groups=BG34%3A1%2CC0001%3A1%2CC0002%3A1%2CC0003%3A1%2CC0004%3A1&geolocation=US%3BTN&AwaitingReconsent=false
|
||||
.skims.com TRUE / FALSE 1797620760 OptanonAlertBoxClosed 2025-12-18T19:06:00.956Z
|
||||
.skims.com TRUE / FALSE 1797620761 _tracking_consent 3AMPS._USTN_f_t_0zEbzYtoSAGJag49cJEAlg
|
||||
.skims.com TRUE / FALSE 1800299163 _scid_r PpoE-ZvIBUJLSPCc1lIQKUMvwHza3_NjkUotaA
|
||||
.skims.com TRUE / FALSE 1799780763 _uetvid 6a477db0dc4411f0b033537fd8e9476f
|
||||
.skims.com TRUE / FALSE 1800248763 cto_bundle lxezUl9Ya05KT0lxalRzMlpmQTZ1WnNGcGVTa2FaVEtiTWIlMkJKY29ZSDRVM0Z1VGlwU3pqMWpLRm9OdUs5MWZLJTJCd1ZxbiUyQnByYVJXWE16QlhOS0FlVSUyRkI3aUwlMkY0a1pQTjN3ZkJaSDR6TjZGTlNWWEl6NnRVbmFzeWc2aHhQeVREcUVNYmZRM2tiT0pwayUyQjAlMkJNUCUyQkYwUFBDMSUyRnNQbmglMkZUUElseHdZbTZ6NVFzenF1bHNJckVuTGtLNyUyRmgwbzFwclEzb3Y0
|
||||
.skims.com TRUE / FALSE 1800644765 forterToken 0dc56201e2d447c483dafc18afc4d80f_1766084760076__UDF43-m4_23ck_
|
||||
.skims.com TRUE / TRUE 1797620837 _Elevar-apex [1%2C%2265794e07-1d7d-4046-82d7-97b491f2207a%22%2C%221766084684%22%2C%221%22%2C%221766084837%22%2C{}%2C[]%2C%22{%5C%22_ttp%5C%22:%5C%2201KCSDKA6XDMKRKZPT7DCE4AP2_.tt.1%5C%22%2C%5C%22_fbp%5C%22:%5C%22fb.1.1766084684456.3724806368%5C%22%2C%5C%22_uetsid%5C%22:%5C%226a4786e0dc4411f0be072b25ff65504f%5C%22%2C%5C%22_uetvid%5C%22:%5C%226a477db0dc4411f0b033537fd8e9476f%5C%22%2C%5C%22_ga%5C%22:%5C%22GA1.1.504031878.1766084685%5C%22%2C%5C%22_scid%5C%22:%5C%22JRoE-ZvIBUJLSPCc1lIQKUMvwHza3_Nj%5C%22%2C%5C%22_ga_FB40M4FWKG%5C%22:%5C%22GS2.1.s1766084684$o1$g1$t0$j60$l0$h0%5C%22}%22%2C0]
|
||||
.skims.com TRUE / TRUE 1800644838 _sp_id.5e0d cffe6173-3fe8-42c0-ba5a-5752434cd290.1766084685.1.1766084838..343b2361-d02d-43ff-aef6-a93963ca8f87..69e731b2-03d7-4e90-be5a-612485fa927c.1766084684861.18
|
||||
.skims.com TRUE / FALSE 1799780852 ttcsid 1766084683999::SeCLjuB76f1lL4FbZ7aD.1.1766084852083.0
|
||||
.skims.com TRUE / FALSE 1799780852 ttcsid_C0VSMI3M56Q7UP188TTG 1766084683999::x8lq-MdBmPA7eS2m0G8k.1.1766084852083.1
|
||||
.skims.com TRUE / FALSE 1797620858 ax_visitor %7B%22firstVisitTs%22%3A1766084684723%2C%22lastVisitTs%22%3Anull%2C%22currentVisitStartTs%22%3A1766084684723%2C%22ts%22%3A1766084858302%2C%22visitCount%22%3A1%7D
|
||||
.skims.com TRUE / FALSE 1800644858 _ga_FB40M4FWKG GS2.1.s1766084684$o1$g1$t1766084858$j13$l0$h0
|
||||
.wsktbf.net TRUE / TRUE 1797620685 brwsr 6a48fb22-dc44-11f0-870a-6584898419f4
|
||||
skims.wsktbf.net FALSE / TRUE 1781636685 irld L0DYwUSVnS1txSuPWHEVUBzXyUHPUSrSv2T1XXKQ1CYx9uScp
|
||||
.applovin.com TRUE / TRUE 1797620764 axcrt AL.1.749430528.1766084684889.3
|
||||
.snapchat.com TRUE / TRUE 1799780686 sc_at v2|H4sIAAAAAAAAAE3GwRGAMAgEwIqY4RAPsJuYkCpSvF/3tT13LqKENi7xek1GISQStYC2nvvA9UGQms68z6/6AQMbeYBAAAAA
|
||||
.360yield.com TRUE / TRUE 1775862466 tuuid 7fafe40b-12f1-414e-b944-2666315f73d5
|
||||
.360yield.com TRUE / TRUE 1775862466 tuuid_lu 1768086465
|
||||
.360yield.com TRUE / TRUE 1775862471 umeh !38,0,1828292838,-1!313,0,1830294470,-1
|
||||
.360yield.com TRUE / TRUE 1775862471 um !38,BMXavnbDSbrtd4.oP9GgaGHhGMIsdH1-0lojSjX0SUYXrIy507xIiBaWovN6Gzrz7PILKsTxjEyEIbK8RGgFG9lKZIw,1773860838!313,VFhMj4Rogp5bPH3bortlS155AbhA1mgZO86CZ1CUUsJ0Xfzu4KyqVsRHFR4jb-gbyldYYBTzwDrUjjgSqyqqA468msGKMkHPsMMcw4etS9OxrT1l,1775862470
|
||||
.3lift.com TRUE / TRUE 1775862514 tluid 1095917909795168967170
|
||||
.3lift.com TRUE / TRUE 1775862514 receive-cookie-deprecation 1
|
||||
.3lift.com TRUE / TRUE 1775862479 tluidp 1095917909795168967170
|
||||
.toast.com TRUE / TRUE 1781636741 BID RSSB37CZ5JRWID3174XWCDKWI
|
||||
.toast.com TRUE / TRUE 1781636741 txpub_1570163526 d2OEmVpH39gWAzy-La7n8Vs1CcRnjHNo9i5CbT-KnV8_:_EXP_:_1781636740_:_EXP_:_1766084740
|
||||
.toast.com TRUE / TRUE 1781636741 txsync 1766084740
|
||||
.nhnace.com TRUE / TRUE 1781636742 BID RSSB37CZ5JRWID3174XWCDKWI
|
||||
.nhnace.com TRUE / TRUE 1781636742 txpub_1570163526 d2OEmVpH39gWAzy-La7n8Vs1CcRnjHNo9i5CbT-KnV8_:_EXP_:_1781636741_:_EXP_:_1766084741
|
||||
.nhnace.com TRUE / TRUE 1781636742 txsync 1766084741
|
||||
.bidence.net TRUE / TRUE 1781669204 177_dsp_uid d2OEmVpH39gWAzy-La7n8Vs1CcRnjHNo9i5CbT-KnV8
|
||||
.bidence.net TRUE / TRUE 1781669204 duid_update_time 1766084803
|
||||
.bidence.net TRUE / TRUE 1781669204 114_ssp_update_time 1766084803
|
||||
.fwmrm.net TRUE / TRUE 1783638470 _uid umwbe31_7585846274144387383
|
||||
.ct.pinterest.com TRUE / TRUE 1797620838 _pinterest_ct_ua TWc9PSZWOW5BTVRpai94Y2ZzVm1seFNIOTROcXhJRlg1VUlYbUh2bDdERE8yWGhKVVJQSTFuOGt4YXhQTS9iamdSakp4SDJtbzhudlNnamZvdE01YVRxa2Q2VTkxdnlOb0J5YUZ5RGJqMEVZbVNqOD0mUEI4L2tkbFJySmhRKzhQb01FbU5JTVd1cUNvPQ==
|
||||
.vour.io TRUE / TRUE 1797620838 624_jwt eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhaWQiOiI5ZGYzMTczNC00ZGU5LTQxZmEtOTE4Ny0xMzRkNzM1YTU2NmUiLCJpYXQiOjE3NjYwODQ4Mzh9.MbIIoNSNCU_wsXuJOi4ja0I7YU83U5d3bu7IF4_ejKBJHxyMfxg6Hrly89pXFYMvHidqEGIzm9flveSs-Gsgqazc4-3XCP0iT7IkmRcr0iujb3gv_PxYIoBcO7r3UPxniRSAB3cxakZaPArmSWXWWmPj2xpGb7beTPjC6abp827UG0ezAMruCrRJCYpaFBR_OMj9T3QIzV6syrVwbW1kshiZikQU8o2lEUt32tOv99cmPsMqfxuXpDgVUzp9ZMlSXSaor5_o8-M61B_MFXptlEtAb4BvX_DM8GJ77QJQl7Cghv0EdZMhAsPTpL7rk7MFfvuwckLXjmaTuADhIHow1g
|
||||
.bestbuy.com TRUE / FALSE 1797709170 vt 6f690ae0-dd12-11f0-ad0a-0ee7b207dce3
|
||||
.bestbuy.com TRUE / FALSE 1776541172 pst2 170|N
|
||||
.bestbuy.com TRUE / TRUE 1799869172 __gads ID=429e68df663c9775:T=1766173172:RT=1766173172:S=ALNI_MZxhbQ31SYi0pxCdtetDZIMIskvdg
|
||||
.bestbuy.com TRUE / TRUE 1799869172 __gpi UID=0000131de02f3466:T=1766173172:RT=1766173172:S=ALNI_MbY9XCpzTvLqqUz0Rh78RjXf_ofgQ
|
||||
.bestbuy.com TRUE / TRUE 1781725172 __eoi ID=ca0d26dfc81cc6e4:T=1766173172:RT=1766173172:S=AA-AfjZtPe0_KiUjvlJ2NBiOFOBD
|
||||
.bestbuy.com TRUE / TRUE 1797709172 connectId {"connectId":"h41BbgQlSIHyJId5O-APjmQCXBg7b6ibxGpJLV7EEg81n8_XB96ECUyAVHwglTQesGfGtFseQZoaAKJHFHAVUw","puid":"05d8092bdb6760329fb8a13189c0cec789c100d61d0b4412e73274ed53edd6fc","ttl":86400000,"lastUsed":1766173172442,"lastSynced":1766173172442}
|
||||
.bestbuy.com TRUE / FALSE 1800733182 s_ecid MCMID%7C58080136048519668114536865538030263176
|
||||
.bestbuy.com TRUE / FALSE 1800733182 AMCV_F6301253512D2BDB0A490D45%40AdobeOrg 1585540135%7CMCMID%7C58080136048519668114536865538030263176%7CMCAAMLH-1766777971%7C7%7CMCAAMB-1766777971%7CRKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y%7CMCOPTOUT-1766180382s%7CNONE%7CMCAID%7CNONE%7CvVersion%7C4.4.0
|
||||
.bestbuy.com TRUE / FALSE 1800337186 _cs_c 1
|
||||
.bestbuy.com TRUE / TRUE 1797709196 _abck 98CE9281062EB29755F518F4E2632889~0~YAAQEX0vF7RqhzabAQAA9z4gOA8ujjyFR4zYs1rTxYLHEURlVdJ4OtbhDy2zsBP4b4YveBwzZEbAo/jJGNgPComzkF8zGClPAFi44WsKxhS0LcWx6mvKZwMZtasW5nI+Z4FLt7A89ko3d2NfGaS4fasItIDYUP54kWyyLcB+cwDYnTmI+1Qwr/vWiC880Y/fhiAI4vgA6Fqn9xNfnZFz4Ivj7G3UImaIw+Q2oS0OIPIoPWr8LVxBYVf0nJ/SOUj76gPaJNDc7Rf78NsjZSwr8lYK/aQV6nV2iRDQjUjasDmLCnbTGrfAQdfRE+crv41URBptfgxqnlbloME7Vkuf4W/ylVjgBGytkzzx0wu0jiHAcV3cPpjXBLnN2kAcHYSn4UoXJPgeXYz4ycnbntI9HQ6k5Mw9hrIAVXTt7vFN5ZyrGYwLOAWXAktezAqRct+YLUZV2FS3HxhFlmL2MGvsXJ3GNz9geF1JXYeilGM8Tay1FYQ2wfwI6oc+a9nDbH9DjnKE7HqLr3XJ5kxMU47KiXjJu+hyX1TedaZLMg5Aot3zFf/nvz9QwF71Z268ZX9FlTJmm2tAYC0zFlU8Dz70IacyvsiFcpHSsGwrt+16yk/hNkZyamYM6c1SyxOoju+lNvUTuAyuW4uyLvUQ5WQWE5C93ccebJnn~-1~-1~1766176771~AAQAAAAE%2f%2f%2f%2f%2f8oOA3tv34D2V7ONQZtmPYLejQtQVkGWs%2fvwofeeythQgyIiBizjnFUkvIb9LGyVvOYCw3xNHy+m86G3W50LV006WFXInvjS6rNE~-1
|
||||
.bestbuy.com TRUE / FALSE 1800337196 cto_bundle xqwVuV94ejhqbTA4bHB4b0JiNVVxcFclMkZPRUdwWUhuUTFUbGx6ZE9UWXZmVTczaDNSNSUyQkJ0TW90T3glMkJQODgycmpFR3hTN0REZWVJSUV0OHRxNUJxNXp4RW5xclNOdGQ2cXlMQTk1NlpVSFhybGFHYU1nbFNVYUx5MUdlUWZUcjA0bmZmbm1jbFZNOXFPaXE3NkxMQndoZiUyQjd6S2w0S1dBNTQyNEtjMEJxdGhFZTZybTl5VWZiUjAySndURDFjME1SYTh2VQ
|
||||
.bestbuy.com TRUE / FALSE 1797709214 isGridViewEnabled true
|
||||
.bestbuy.com TRUE / FALSE 1799869219 __gsas ID=78bad4b99391fe9a:T=1766173219:RT=1766173219:S=ALNI_MZ7PtsAhyKgA-RaJ1wnpzOGAacHDQ
|
||||
.bestbuy.com TRUE / FALSE 1800337186 _cs_id 178c060b-ebba-a692-ebdd-0fc62bd54d08.1766173186.1.1766173232.1766173186.1645469968.1800337186882.1
|
||||
.bestbuy.com TRUE / FALSE 1797709247 locDestZip 37024
|
||||
.thingiverse.com TRUE / TRUE 1801782464 __gads ID=c274a2d1d73e5390:T=1768086464:RT=1768086464:S=ALNI_Mb0CVH9vFv-SqEXC4e1YO_sN8flTw
|
||||
.thingiverse.com TRUE / TRUE 1801782464 __gpi UID=00001324f4cc2db5:T=1768086464:RT=1768086464:S=ALNI_MaewbhT_k00lySbZa5mbAYBf7rQdw
|
||||
.thingiverse.com TRUE / TRUE 1783638464 __eoi ID=6378ed39ac9fb0ea:T=1768086464:RT=1768086464:S=AA-Afja98XhAT6jufaqPVnOucN-S
|
||||
.thingiverse.com TRUE / FALSE 1791414465 _cc_id 3cf9e41188988b992aee1510314411e1
|
||||
.thingiverse.com TRUE / FALSE 1801760863 cto_bundle abRd819vSkMzOGd0M2dGSk5FSnpOd25ydFd4dDhpNEMlMkJXQjVBNjF6bXVpaURpYTBJbEFvWEU1V3NWUjhOMiUyRnZoZ1ZxY0QwJTJCc1ZBRTV4Vk1ZMkFERmslMkZXSnhSTVAzQXlmMUxwWEp0S3JYRmtjc1U2NnR0M1Q5RlB4N2VZN0VrNTk5YzB2Nm9TWld4RWNJRE9kNGFJaHN6eFlheDk1Ynd0QXdsN0xWckxYMDhMamRKSlRIS1hHM2pxMkhLcUJic0ZIUHFNYg
|
||||
.thingiverse.com TRUE / FALSE 1801760863 cto_bidid mnWdDV9nbG84Y0Z1Zm1lRU9vMEVnJTJGdWQyODhXQjM0WTZlRSUyRkRaOUI2dFdKZWVac1hDcnJmZiUyRjA5NzNDcTFXUEdXekFiMXRwTm9IYmJiZDJ6ZHJvUnNjJTJCUWE0VldzdFhtc041MlBCdU5mWjNNYVZXUm0lMkZiWTFReXFWeTJhektHUCUyQm16UmhMJTJGQ29ZeTVTR1g0ZSUyRnh0dldib0x3JTNEJTNE
|
||||
.thingiverse.com TRUE / TRUE 1799622465 cf_clearance hwUXmBceIFvRDfbB2BtWlQCpzcjHbmiSqo5cz6aGdEc-1768086465-1.2.1.1-VX.Q4T4aESaY6xi9IRJfTIQDfpvdaoxXMckW7vY8vmQkx7u4vX3Bdu5s_u0xmy89GTnveSCQ0SJlfHk7pQu3UaXVaPtbCRViQF9Oyu4u64vlE1o1XegeKxhhNX95YA7bSOfsRrr1UrxF1_8QTgKwoEDJhXOchZj2r_3TfpfHGgz02di9c5oXG_3SqG93ctZEyEEHU4SucDaTIPUEElZ1pZ81RwmbGGX2uHkTFWremag
|
||||
.ipredictive.com TRUE / TRUE 1799622470 cu 066d8e19-5c26-450d-a649-863f2909f959|1768086465860
|
||||
.csync.loopme.me TRUE / TRUE 1775862478 viewer_token 2ca038bf-483c-4eaa-b684-a7905f94dc41
|
||||
.33across.com TRUE / TRUE 1799622479 33x_ps u%3D212328208636113%3As1%3D1698785496328%3Ats%3D1768086466187
|
||||
.opera.com TRUE / TRUE 1799622478 OAU OPUb598e447ff4b444484a3e5b793c7de50
|
||||
.xplosion.de TRUE / TRUE 1799622467 pid BSRCEif3WD_CWDa0WSRAEibkWSRsWD_8WiRkWi_ABifABfrr
|
||||
.xplosion.de TRUE / TRUE 1799622467 pid_short 52I3odHeaUJ3xI5D_DbFH5_8__rr
|
||||
.xplosion.de TRUE / TRUE 1799622467 pid_signature wSulwSjDBSU0wDjdwSBsWDU-wCHbWDwZWq7kBStIEiujwqUAEiU0Hfrr
|
||||
.xplosion.de TRUE / TRUE 1799622467 ep aWLbwkDbaJugE4bcG2i-
|
||||
.trustedstack.com TRUE / TRUE 1802387267 visitor-id 4110880664639125000V10
|
||||
.adfarm1.adition.com TRUE / TRUE 1775862469 UserID1 7593873548017072489
|
||||
.bttrack.com TRUE / TRUE 1775862468 GLOBALID 2uKlc8-sIBd984cSkD72DtzNhbXQZOzWrDrHRMWhaK7Tgu7vGY-DLeTd831jGxj6xBOn4fvgsrMC4Q2
|
||||
.simpli.fi TRUE / TRUE 1799708868 suid F807DADE2E214302BF3C6F3D58A4CEC8
|
||||
sync.srv.stackadapt.com FALSE / TRUE 1799622468 sa-user-id s%3A0-4629e297-6daa-5181-62e1-392785235d6f.PXNulmzJegZOvvWPXXg3lwleRwh%2F0%2B5MSVe1ySIMGEM
|
||||
sync.srv.stackadapt.com FALSE / TRUE 1799622468 sa-user-id-v2 s%3ARinil22qUYFi4TknhSNdb2vCVQw.ezE7srbmqWgoMYYglaxkS%2BF8s6cNlNz0M9lOXWpG0ek
|
||||
sync.srv.stackadapt.com FALSE / TRUE 1799622468 sa-user-id-v3 s%3AAQAKIOnhejbOhrV9-RM6AiFzDEwt3Ih8NIHekYWJsIGp7nzbEAMYAyDEt4vLBjABOgQTDjy1QgR2YepB.2F4w74GAZt5vjDhOMoMUFticPP20T51xHtlrhiwpK%2Bo
|
||||
.srv.stackadapt.com TRUE / TRUE 1799622468 sa-user-id s%3A0-4629e297-6daa-5181-62e1-392785235d6f.PXNulmzJegZOvvWPXXg3lwleRwh%2F0%2B5MSVe1ySIMGEM
|
||||
.srv.stackadapt.com TRUE / TRUE 1799622468 sa-user-id-v2 s%3ARinil22qUYFi4TknhSNdb2vCVQw.ezE7srbmqWgoMYYglaxkS%2BF8s6cNlNz0M9lOXWpG0ek
|
||||
.srv.stackadapt.com TRUE / TRUE 1799622468 sa-user-id-v3 s%3AAQAKIOnhejbOhrV9-RM6AiFzDEwt3Ih8NIHekYWJsIGp7nzbEAMYAyDEt4vLBjABOgQTDjy1QgR2YepB.2F4w74GAZt5vjDhOMoMUFticPP20T51xHtlrhiwpK%2Bo
|
||||
www.thingiverse.com FALSE / TRUE 1799622468 CookieConsent {stamp:%27dcCI10BHlJQetn3alh3J9z8IPiGNGQgScRgD0Fmt+ZeQiYr0g1z1WQ==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cmethod:%27explicit%27%2Cver:3%2Cutc:1768086468362%2Cregion:%27us-47%27}
|
||||
.presage.io TRUE / TRUE 1783897669 presage-ssp %7B%22uuid%22%3A%2239a603b9-8fc8-42ed-b4f9-05588b5ca524%22%7D
|
||||
.use3-sync.a-mo.net TRUE / TRUE 1799622469 sd_amuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.use3-sync.a-mo.net TRUE / TRUE 1799622469 psd_amuid2 6694de3f-a4e4-4153-be90-38070f322c76
|
||||
.dxtech.ai TRUE / TRUE 1783638469 mtuid 2f39f687-b1fe-4539-85b6-f60b92005b47
|
||||
.media6degrees.com TRUE / TRUE 1783638469 clid 2smn2ow011718bshr4m678srm15k500i0a010201101
|
||||
.media6degrees.com TRUE / TRUE 1783638469 sglst 453a3mcu03o9yj21fo95q21f1liw0q607mu018dl30q6
|
||||
.media6degrees.com TRUE / TRUE 1783638469 rdrlst 5515401j084uk20q64ujk0q64uim0q64sz80q64hkx0q649pl0q62dc00q629zg0q6
|
||||
.media6degrees.com TRUE / TRUE 1783638469 acs 015020i0j0k0o1smn2owxzt1m15k5xzt1m15k5xzt1m15k5xzt1m15k5
|
||||
.resetdigital.co TRUE / TRUE 1775862469 ckbk 000011CF9CE959F2
|
||||
.advolve.io TRUE / TRUE 1799622469 x 6962dbc575e323aa0d79326e
|
||||
.driftpixel.live TRUE / TRUE 1799622469 xedpxl 12261041.8bc2613b-cb1c-4e2c-9de0-564b760c7596
|
||||
cookies.nextmillmedia.com FALSE / TRUE 1799622478 NMUID c268b853-3d21-4300-bbd4-17f459389def
|
||||
.quantserve.com TRUE / TRUE 1775862470 sp CgkI6KsGEgMQghEKCQiCrQMSAxCCEQoICNllEgMQghEKCQjRpgISAxCCEQoICIkNEgMQmREKCAieDRIDEIIRCgcICxIDEIIRCggIknESAxCZEQoJCLmKAxIDEJkRCgkIhf8CEgMQghE=
|
||||
.pmbmonetize.live TRUE / TRUE 1799622470 xeadiu 12090783.fd98a23a-b3d0-487c-8fc9-8f1b434f671e
|
||||
.rbstsystems.live TRUE / TRUE 1799622475 xeroid 11690718.a267af1d-fded-4805-82f1-a40eb66eb5a4
|
||||
.nextmillmedia.com TRUE / TRUE 1799536070 adkernel eyJVSUQiOiJBNjc4MTQ0ODEzNTM2NDc0NjgxMCIsIkV4cGlyZXMiOiIyMDI3LTAxLTA5VDIzOjA3OjQ5Ljk5MzczMjk0WiJ9
|
||||
.nextmillmedia.com TRUE / TRUE 1799536070 rubicon eyJVSUQiOiJNNkNGNFRHOC1XLUo3MFMiLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1MC40MDk1MjQ5NDVaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 adnxs eyJVSUQiOiIxNDQ2NjY0NjMzMDYyNDU1NzM4IiwiRXhwaXJlcyI6IjIwMjctMDEtMDlUMjM6MDc6NTUuMzMwNDU3NVoifQ==
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 openx eyJVSUQiOiJkNjk4MWFlNy00ZWE1LTQ5NjUtODVjNi0zNDUzYTdjMDE0NTYiLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1NS4zNDAxNjk2OTVaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 pubmatic eyJVSUQiOiI3RUE1NURBRC01RUQxLTQyREQtQUZGNy05NkIwMTg3RTJGRTgiLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1NS4zNDYxNjA1MzlaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 yieldmo eyJVSUQiOiIzZUhQdkhIYm1QSHhTdE1vSVhPUiIsIkV4cGlyZXMiOiIyMDI3LTAxLTA5VDIzOjA3OjU1LjM1NzU5ODM1NloifQ==
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 sovrn eyJVSUQiOiJIbEhDVVJaSG1uR1lfaHR3UnkycWRTN1giLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1NS4zODQyMDE3MDlaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 verve1 eyJVSUQiOiJiYzY3MTE5MTY4IiwiRXhwaXJlcyI6IjIwMjctMDEtMDlUMjM6MDc6NTUuNDgzMDc1Mzk2WiJ9
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 medianet eyJVSUQiOiIzODA3NTAzNzc5Nzg2ODU1MDAwVjEwIiwiRXhwaXJlcyI6IjIwMjctMDEtMDlUMjM6MDc6NTUuNDg0Mzk4OTI1WiJ9
|
||||
.nextmillmedia.com TRUE / TRUE 1799536075 robustApps eyJVSUQiOiJkNi42ODBiMWE3ZjE1OWE0YzQ4OTIxZjM1NmI2NDJmNDdhOSIsIkV4cGlyZXMiOiIyMDI3LTAxLTA5VDIzOjA3OjU1LjU0MDYzNzYzM1oifQ==
|
||||
.nextmillmedia.com TRUE / TRUE 1799536076 zeta_global_ssp eyJVSUQiOiIxcnA2bHYyNjI3ZGIzIiwiRXhwaXJlcyI6IjIwMjctMDEtMDlUMjM6MDc6NTUuNjgwMTEwODIzWiJ9
|
||||
.nextmillmedia.com TRUE / TRUE 1799536076 unruly eyJVSUQiOiJSWC1mNjA3OTNiMS0wZWVmLTRjYTQtODJjNS1lZThhM2I1Y2RhYjktMDA1IiwiRXhwaXJlcyI6IjIwMjctMDEtMDlUMjM6MDc6NTUuODk3ODc3NjE4WiJ9
|
||||
.nextmillmedia.com TRUE / TRUE 1799536077 vidazoo eyJVSUQiOiIwNWYwNTVmNS0zY2E5LTUzNjctZjZkYS00ZWI3YTk5MTMzYzciLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1Ni42MTk3MzI3MzdaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536077 seedtag eyJVSUQiOiIwMTk3ZTgwOS0xZDI4LTc2ZmUtYmQyZS0yYTgzNjdhOWIxOTEiLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1Ni42MzA3NDc3MDZaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536077 insticator eyJVSUQiOiJjNjJkZDY0Ny0yNzNiLTQ0OTQtYmE2OS1kN2Y3NjAwNTI0YzQiLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1Ni42ODk4OTI2MjRaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536077 minutemedia eyJVSUQiOiIzOXJSX0FhY0NwX21tIiwiRXhwaXJlcyI6IjIwMjctMDEtMDlUMjM6MDc6NTYuNzg4MTA4OTIxWiJ9
|
||||
.nextmillmedia.com TRUE / TRUE 1799536077 rise eyJVSUQiOiJxN1lMVkFhY0NwX3MiLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1Ni43ODgzOTc3NzFaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536077 33across eyJVSUQiOiIyMTIzMjgyMDg2MzYxMTMiLCJFeHBpcmVzIjoiMjAyNy0wMS0wOVQyMzowNzo1Ni45MTkyNjk2NjdaIn0=
|
||||
.nextmillmedia.com TRUE / TRUE 1799536078 madopi eyJVSUQiOiJBNjc4MTQ0ODEzNTM2NDc0NjgxMCIsIkV4cGlyZXMiOiIyMDI3LTAxLTA5VDIzOjA3OjU3Ljk2NjAyMTY2MloifQ==
|
||||
.ingage.tech TRUE / TRUE 1775862476 instUid c62dd647-273b-4494-ba69-d7f7600524c4
|
||||
.ingage.tech TRUE / TRUE 1799622477 cf_clearance NCjJzyjOSrpPr8FbdKFfAf0KK4lm9mMYq9jvHHH0MR8-1768086477-1.2.1.1-mmrYg1gmneJqjVHHw9suApOL5Fp1OZNa_Www1ZEGXAncFrx3aCK6kyxZYt1wp1EWTPHaqANS5AZyQo1DXpBjc3fhm0NsRuAid0KJpU488XSwzWu1llU1Ggcb44as6HwlO.Img6iuRCK8kuU4i0G29GL0DeK3b0JrJdH26Q5oa1_wKK6IoBpZRKyoeE_kBAwsa4dnmkOWC7iAFyTiTQR2WJ2DICH4QBY3a4rvFj4PqpY
|
||||
usync.ingage.tech FALSE / TRUE 1775862470 instUid c62dd647-273b-4494-ba69-d7f7600524c4
|
||||
.admatic.de TRUE / TRUE 1802646470 uid a6d006be-43ed-4d42-8378-04d11dbce1bb
|
||||
.admatic.de TRUE / TRUE 1775862476 uids eyJ0ZW1wVUlEcyI6eyJwdWJtYXRpYyI6eyJleHBpcmVzIjoiMjAyNi0wMi0wOVQyMzowNzo1MC41NzNaIiwidWlkIjoiN0VBNTVEQUQtNUVEMS00MkRELUFGRjctOTZCMDE4N0UyRkU4In0sImFkZm9ybSI6eyJleHBpcmVzIjoiMjAyNi0wMi0wOVQyMzowNzo1MC41ODBaIiwidWlkIjoiMzg0NTk1MDc0ODA5NDc5ODMwMCJ9LCJibXRtIjp7ImV4cGlyZXMiOiIyMDI2LTAyLTA5VDIzOjA3OjUwLjU4OVoiLCJ1aWQiOiI3YjNhMGQ1Ni05Njg3LTRiNTktYjk1OS1iN2JjODJiNjhkMmEifSwibWVtYnJhbmEiOnsiZXhwaXJlcyI6IjIwMjYtMDItMDlUMjM6MDc6NTYuNzQ5WiIsInVpZCI6IjViMTk3ZDM2LWU4MjUtNGJmNi04ZTNiLWU5ZDc4NzI3OGQ5YiJ9fX0=
|
||||
pool.liftdsp.com FALSE / TRUE 1799622470 tuuid 0f4e815b-c839-410d-a8b9-575d7cbe1645
|
||||
pool.liftdsp.com FALSE / TRUE 1799622470 c 1768086470
|
||||
pool.liftdsp.com FALSE / TRUE 1799622470 tuuid_lu 1768086470
|
||||
.ib.mookie1.com TRUE / TRUE 1799622470 ibkukiuno s=89d7a4bb-c402-4bc2-a3fb-d3d8d85f8a74&h=&c=0&l=-8584335204150487532&op=&hl=0&clu=0&tcs=5&dcc=-8584638581107438250
|
||||
.id5-sync.com TRUE / TRUE 1775862470 id5 b93ab64a-793a-7025-9261-742e7bb32ab5#1766084685808#6
|
||||
.id5-sync.com TRUE / TRUE 1775862472 3pi 2#1768086471140#-1461159667|264#1768086467127#-156260550#248c815d-b156-4db9-8a92-46c25764dd54|203#1768086469599#-1560233741#6f78483e-d4c5-431a-8711-a497d0431bf7|108#1768086466841#-206138872|429#1768086472009#-1443568634#7EA55DAD-5ED1-42DD-AFF7-96B0187E2FE8|846#1768086468514#-492225994|369#1768086470445#-912217451|434#1768086470823#878225581|821#1768086471855#1728330091|441#1768086471401#1885834214#u_03cf1816-47b2-4472-8e12-9f751667d0a4|122#1768086470085#-1735519148|1242#1768086472318#-1301550593|155#1768086472167#2740117#AADBck7KgtEAABjN509uAw|796#1768086468798#-255155510|124#1768086470620#1433885824|1245#1768086470976#-1301550593
|
||||
.tribalfusion.com TRUE / TRUE 1775862472 ANON_ID aenv7yu4YUdmqcn62NoCUTPUQYIVjZapqCBi8xWYWMildQOVZcnA0qvpwhTxqqnCNZauofeoJwyZcKFRZaMIjyOkWGZas8T9INjnEbgyPV3dRPJuISMl49Zdvea
|
||||
.emxdgt.com TRUE / TRUE 1775862470 uid 47441768086470553925a6
|
||||
aorta.clickagy.com FALSE / TRUE 1802646470 chs [{"ch":"4","t":"2026-01-10 23:07:50"},{"ch":"185","t":"2025-01-24 19:59:38"},{"ch":"120","t":"2024-02-22 15:35:14"},{"ch":"8","t":"2025-07-08 03:16:33"},{"ch":"5","t":"2025-01-24 20:05:11"},{"ch":"114","t":"2025-07-08 03:16:33"},{"ch":"128","t":"2025-01-24 20:21:49"},{"ch":"124","t":"2025-01-24 20:21:49"},{"ch":"130","t":"2025-01-24 20:05:31"},{"ch":"150","t":"2025-07-08 03:16:33"}]
|
||||
.mxptint.net TRUE / TRUE 1802646471 mxpim R35CA5_10B6EE5CA_9A1777C8.1.6793F432000000006962DBC46793F20F000000006962DBC60000000000000000000000006793F51B000000006793F3FD
|
||||
.1rx.io TRUE / TRUE 1799622475 _rxuuid %7B%22rx_uuid%22%3A%22RX-f60793b1-0eef-4ca4-82c5-ee8a3b5cdab9-005%22%2C%22lastinit%22%3A%7B%222069.82%22%3A1737749787463%2C%222069.24%22%3A1737749787463%2C%222069.44%22%3A1737749787463%2C%222069.103%22%3A1737749787463%2C%222069.5%22%3A1737749787463%2C%222069.29%22%3A1737749787463%2C%222069.47%22%3A1737749787463%2C%222069.105%22%3A1737749787463%2C%222069.85%22%3A1737749787463%2C%222069.108%22%3A1737749787463%2C%222069.27%22%3A1737749787463%2C%222069.89%22%3A1737749787463%2C%222069.64%22%3A1737749787463%2C%222069.111%22%3A1737749787463%2C%222069.71%22%3A1737749787463%2C%222069.97%22%3A1737749787463%2C%222069.39%22%3A1737749787463%2C%222069.26%22%3A1737749787463%2C%222069.65%22%3A1737749787463%2C%222069.96%22%3A1737749787463%2C%222069.101%22%3A1737749787463%2C%222069.116%22%3A1737749787463%2C%222069.83%22%3A1737749787463%2C%222069.38%22%3A1737749787463%2C%222069.1%22%3A1737749787463%2C%222069.32%22%3A1737749787463%2C%222069.86%22%3A1737749787463%2C%222069.95%22%3A1737749787463%2C%222069.56%22%3A1737749787463%2C%222069.50%22%3A1737749787463%2C%222069.79%22%3A1737749787463%2C%222069.25%22%3A1737749787463%2C%222069.48%22%3A1737749787463%2C%222069.60%22%3A1737749787463%2C%222069.41%22%3A1737749787463%2C%222069.58%22%3A1737749787463%2C%222069.106%22%3A1737749787463%2C%222069.73%22%3A1737749787463%2C%222069.78%22%3A1737749787463%2C%222069.55%22%3A1737749787463%2C%222069.98%22%3A1737749787463%2C%222069.36%22%3A1737749787463%2C%222069.54%22%3A1737749787463%2C%222069.90%22%3A1737749787463%2C%222069.112%22%3A1737749787463%2C%222069.74%22%3A1737749787463%2C%222069.49%22%3A1737749787463%2C%222069.43%22%3A1737749787463%2C%222069.10%22%3A1737749787463%2C%222069.104%22%3A1737749787463%2C%222069.102%22%3A1737749787463%2C%222069.61%22%3A1737749787463%2C%222069.113%22%3A1737749787463%2C%222069.87%22%3A1737749787463%2C%222069.66%22%3A1737749787463%2C%222069.80%22%3A1737749787463%2C%222069.46%22%3A1737749787463%2C%222069.6%22%3A1737749787463%2C%222069.31%22%3A1737749787463%2C%222069.59%22%3A1737749787463%2C%222069.109%22%3A1737749787463%2C%222069.114%22%3A1737749787463%2C%222069.115%22%3A1737749787463%2C%222069.72%22%3A1737749787463%2C%222069.63%22%3A1737749787463%2C%222069.91%22%3A1737749787463%2C%222069.42%22%3A1737749787463%2C%222069.35%22%3A1737749787463%2C%222069.110%22%3A1737749787463%2C%222069.34%22%3A1737749787463%2C%222069.57%22%3A1737749787463%2C%222069.88%22%3A1737749787463%2C%222069.28%22%3A1737749787463%2C%222069.92%22%3A1737749787463%7D%2C%22lastsyncall%22%3A1737749787463%7D
|
||||
.admixer.net TRUE / TRUE 1775862476 am-uid 0f98d334640144d587d3b02ce049917a
|
||||
.adtelligent.com TRUE / TRUE 1776121677 vmuid 83a3bef4fbbf98a8
|
||||
server.smartytech.io FALSE / TRUE 1799622477 uid 3178aeee-de98-4209-9cd5-4a93fc663b44
|
||||
.rlcdn.com TRUE / TRUE 1799622478 rlas3 e/vV39S5p8q+XmmJZtjqYThcGcHi7BVfVwyFmwHekVdZZsLG1/Iz4RUpjoH9aGnHLho0Dk8C2wraaHJYoyT7csQFaIEszxKbBusappoLnpWJwynr2TGyGHQiET1cZU4aVRk0gAef9x0l3cL68Ah80sfJhjGRHbtBQemD/FHkryqMyA9jEACu5w==
|
||||
.ow.pubmatic.com TRUE / TRUE 1775862477 uids eyJ0ZW1wVUlEcyI6eyJhbXgiOnsidWlkIjoiNjY5NGRlM2YtYTRlNC00MTUzLWJlOTAtMzgwNzBmMzIyYzc2IiwiZXhwaXJlcyI6IjIwMjYtMDEtMjRUMjM6MDc6NTcuMzM1NTgwNjkzWiJ9fX0=
|
||||
.tynt.com TRUE / TRUE 1775862477 pids %5B%7B%22p%22%3A%22797f54a72d%22%2C%22f%22%3A1%2C%22ts%22%3A1768086466511%7D%2C%7B%22p%22%3A%22f46c881bee%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469697%7D%2C%7B%22p%22%3A%224bee518595%22%2C%22f%22%3A1%2C%22ts%22%3A1768086468893%7D%2C%7B%22p%22%3A%226f27415d53%22%2C%22f%22%3A1%2C%22ts%22%3A1768086468893%7D%2C%7B%22p%22%3A%227daaa56bb0%22%2C%22f%22%3A1%2C%22ts%22%3A1768086468893%7D%2C%7B%22p%22%3A%227912d88d74%22%2C%22f%22%3A1%2C%22ts%22%3A1768086468893%7D%2C%7B%22p%22%3A%22d9fe068602%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469809%7D%2C%7B%22p%22%3A%22f5b8438f72%22%2C%22f%22%3A1%2C%22ts%22%3A1768086468893%7D%2C%7B%22p%22%3A%22fcb82aaae3%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469809%7D%2C%7B%22p%22%3A%22607295b4a4%22%2C%22f%22%3A2%2C%22ts%22%3A1768086474638%7D%2C%7B%22p%22%3A%223bfd58deb3%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469697%7D%2C%7B%22p%22%3A%22baebe6454b%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469697%7D%2C%7B%22p%22%3A%220f90caf3cf%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469697%7D%2C%7B%22p%22%3A%22162dbd77b3%22%2C%22f%22%3A1%2C%22ts%22%3A1768086466511%7D%2C%7B%22p%22%3A%226db3fb8a85%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469697%7D%2C%7B%22p%22%3A%22b32ef6f991%22%2C%22f%22%3A23%2C%22ts%22%3A1768086477380%7D%2C%7B%22p%22%3A%22002f98d420%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469809%7D%2C%7B%22p%22%3A%2224c05c7b76%22%2C%22f%22%3A1%2C%22ts%22%3A1768086466511%7D%2C%7B%22p%22%3A%22cf4d6e49b5%22%2C%22f%22%3A1%2C%22ts%22%3A1768086466511%7D%2C%7B%22p%22%3A%2222833ea406%22%2C%22f%22%3A1%2C%22ts%22%3A1768086468893%7D%2C%7B%22p%22%3A%225cb91279ed%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469697%7D%2C%7B%22p%22%3A%22f9a4a8fd15%22%2C%22f%22%3A1%2C%22ts%22%3A1768086466511%7D%2C%7B%22p%22%3A%22008c314e8f%22%2C%22f%22%3A1%2C%22ts%22%3A1768086466511%7D%2C%7B%22p%22%3A%22729ff3013e%22%2C%22f%22%3A1%2C%22ts%22%3A1768086469809%7D%5D
|
||||
.exelator.com TRUE / TRUE 1778454480 EE "cc59f09a65a8447a5cf59d53c2ba71ef"
|
||||
.exelator.com TRUE / TRUE 1778454480 ud "eJxrXxzq6XKLQSE52dQyzcAy0cw00cLExDzRNDnN1DLF1DjZKCnR3DA1bXFZatGCpaXFqSlJh5ZU5JTkNK0uiw91jHdz9PX0iVzmnFGUn5u6AiwU5hq02NDEeEl%252BUWb6otDgxUUpaQyLSopPBZ%252B9KgoAxksq1g%253D%253D"
|
||||
.exelator.com TRUE / TRUE 1778454480 hsk_7144 "gAAAAAQAAABqKLUv%252FSBqUQMAiKRidWlkuWYzcmRoYm1xdHptYmdmYzg2NnEwNjg0a2mjaHNrpDY5NDmoZGVsaXZlcnmkNjk0OaN2ZXIBpWJuYW1lqFMyMDRVMjJWpXRzZWdzpzI4NTU5ODKlYmNvZGXNG%252BiidHPKU83VFQ%253D%253D"
|
||||
.e-planning.net TRUE / TRUE 1802646477 E ALz5R0Mx61BTzRqq
|
||||
pbs-publift-us.ay.delivery FALSE / TRUE 1775862477 uids eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIyMTIzMjgyMDg2MzYxMTMiLCJleHBpcmVzIjoiMjAyNi0wMS0yNFQyMzowNzo0Ni42Nzk0Mzc1MjZaIn0sImFkbnhzIjp7InVpZCI6IjE0NDY2NjQ2MzMwNjI0NTU3MzgiLCJleHBpcmVzIjoiMjAyNi0wMS0yNFQyMzowNzo1MC42MjIxNDgwNjZaIn0sImFteCI6eyJ1aWQiOiI2Njk0ZGUzZi1hNGU0LTQxNTMtYmU5MC0zODA3MGYzMjJjNzYiLCJleHBpcmVzIjoiMjAyNi0wMS0yNFQyMzowNzo1My42Mjg5OTY4OVoifSwia3VlZXpydGIiOnsidWlkIjoiNjZlODI5MjEtMjViOS1kZGM4LWM2MjYtZTU0ZjY3MTA2Mjk5IiwiZXhwaXJlcyI6IjIwMjYtMDEtMjRUMjM6MDc6NTIuNzE1MDkxNDA5WiJ9LCJtZWRpYW5ldCI6eyJ1aWQiOiIzODA3NTAzNzc5Nzg2ODU1MDAwVjEwIiwiZXhwaXJlcyI6IjIwMjYtMDEtMjRUMjM6MDc6NTcuNDExNTg0NzY2WiJ9LCJvcGVueCI6eyJ1aWQiOiJkNjk4MWFlNy00ZWE1LTQ5NjUtODVjNi0zNDUzYTdjMDE0NTYiLCJleHBpcmVzIjoiMjAyNi0wMS0yNFQyMzowNzo1MS42MTUyNzc0NTVaIn0sInB1Ym1hdGljIjp7InVpZCI6IjdFQTU1REFELTVFRDEtNDJERC1BRkY3LTk2QjAxODdFMkZFOCIsImV4cGlyZXMiOiIyMDI2LTAxLTI0VDIzOjA3OjUzLjg1NDE5NTExM1oifSwic2VlZHRhZyI6eyJ1aWQiOiIwMTk3ZTgwOS0xZDI4LTc2ZmUtYmQyZS0yYTgzNjdhOWIxOTEiLCJleHBpcmVzIjoiMjAyNi0wMS0yNFQyMzowNzo0NS41NjkzNTkwMzVaIn0sInZpZGF6b28iOnsidWlkIjoiMDVmMDU1ZjUtM2NhOS01MzY3LWY2ZGEtNGViN2E5OTEzM2M3IiwiZXhwaXJlcyI6IjIwMjYtMDEtMjRUMjM6MDc6NTQuNjM3ODUwNjk3WiJ9fX0=
|
||||
.brand-display.com TRUE / TRUE 1798600578 _knxq_ 0c3b7d5a-56ff-e9a3-fb4fc63f.1751944578.2.1768086477.1751944662
|
||||
.adtarget.biz TRUE / TRUE 1775862477 uids eyJ0ZW1wVUlEcyI6eyJwdWJtYXRpYyI6eyJleHBpcmVzIjoiMjAyNi0wMi0wOVQyMzowNzo1Ny41NDhaIiwidWlkIjoiN0VBNTVEQUQtNUVEMS00MkRELUFGRjctOTZCMDE4N0UyRkU4In0sImFkZm9ybSI6eyJleHBpcmVzIjoiMjAyNi0wMi0wOVQyMzowNzo1Ny41NTRaIiwidWlkIjoiMzg0NTk1MDc0ODA5NDc5ODMwMCJ9LCJibXRtIjp7ImV4cGlyZXMiOiIyMDI2LTAyLTA5VDIzOjA3OjU3LjU4NVoiLCJ1aWQiOiI3YjNhMGQ1Ni05Njg3LTRiNTktYjk1OS1iN2JjODJiNjhkMmEifX19
|
||||
.adtarget.biz TRUE / TRUE 1802646478 uid 03f75d14-552e-4ca8-8bfd-041dbccd6bbb
|
||||
.analytics.yahoo.com TRUE / TRUE 1799622478 IDSYNC "173n~2n5s:175s~2n57:175w~2hsn:1769~2el6:176l~2n5c:18vk~2g2j:18xa~2hsn:18y3~2gwf:18yi~2n57:18yl~2q6r:18yo~2g2j:18z8~2q6r:18z9~2hx1:18za~2hsn:190u~2n57:191q~2gwf:1929~2hx1:193k~2gwf:194o~2gwf:195g~2gce:1967~2hsn:1969~2tnb:196y~2hsn:199z~2nh7:19ab~2t7v:19ac~2n58:19aj~2lpr:19b9~2nf8:19bh~2gwf:19bk~2n5f:19bn~2lpr:19bu~2lpr:19bx~2f7r:19cg~2n58:19cu~2lpr:19cw~2lpr:19cx~2hsn:19e0~2lpr:19e3~2g2j:19e7~2lpr:19ea~2lpr:19ai~2n57:19cl~2tnb:19ba~2n58:17mv~2n58:173h~2n58:19h2~2tnb:19gl~2tnb:19h3~2tnb:19h0~2tnb"
|
||||
.pro-market.net TRUE / TRUE 1783638478 anProfile "jivjmz9he9gc+1+4=2m3+1f=1+1g=1+1j=57:1+rs=s+rt=26001700477AB01084F88A9E2C958264+s0=(a)+s2=(t7j7y3)+vm=47-87fd5a53-dc6b-4dcd-a287-48e386f3b8f1:67-64651016430244522174023048184620348074"
|
||||
.pbs.yahoo.com TRUE / TRUE 1783811278 uids eyJ0ZW1wVUlEcyI6eyJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjEwOTU5MTc5MDk3OTUxNjg5NjcxNzAiLCJleHBpcmVzIjoiMjAyNi0wMS0yNFQyMzowNzo1OC4yNzcwMzY3NDJaIn19fQ==
|
||||
.thrtle.com TRUE / TRUE 1785366478 mc eyJpZCI6IjdmNzFiNjVlLWQ5NjUtNDJjNC1hOWMyLTI3NTdmY2NjNWU2MCIsImwiOjE3NjgwODY0NzgzNTMsInQiOjI2OH0=
|
||||
.thrtle.com TRUE / TRUE 1785366478 sc eyJpIjoiZDg4NjkzNjAtZDlhZS00ODdkLTg2OGQtMDhjYmM1ZTg4YTMyIiwic2lkIjoic2lkLTM0M2ZjMzcxLWVlNzktMTFmMC1hOWJiLTAyNDIwYWZmMTViOSIsIm1zIjoyLCJwcyI6MTMsImxwIjo1MDQ3LCJzcCI6NTA0NCwicHAiOjEzLCJ0c2UiOjMxLCJsdHNlIjoxNzY4MDg2NDc4MTY4fQ==
|
||||
.clawi.ai TRUE / TRUE 1801951338 __stripe_mid 90fabc42-c1f3-4d66-9c87-5c4f03ef73f5cf7b18
|
||||
getalby.com FALSE / FALSE 1778527919 login_method otp
|
||||
claude.ai FALSE / TRUE 1797201511 anthropic-device-id 534e1c34-4f23-40c6-8112-a7903ca8884e
|
||||
claude.ai FALSE / FALSE 1805841472 _fbp fb.1.1771281471505.62904287848319980
|
||||
claude.ai FALSE / FALSE 1786833474 g_state {"i_l":0,"i_ll":1771281474848,"i_b":"m99XCDLAoL8P11crnbjIezvOZZdvtUX0Hgfq8LNtsLM","i_e":{"enable_itp_optimization":14}}
|
||||
.claude.ai TRUE / FALSE 1802817511 ajs_anonymous_id claudeai.v1.bbd65f09-a89f-4ba0-8d3a-765e242e7b59
|
||||
.claude.ai TRUE / TRUE 1802817511 CH-prefers-color-scheme light
|
||||
.claude.ai TRUE / FALSE 1805841473 __ssid 7aeefe83-85be-4b98-b9c0-b4f73844ccb6
|
||||
.claude.ai TRUE / TRUE 1802817511 lastActiveOrg 3a9dd5ca-f997-4fac-928b-86c0d8256273
|
||||
.claude.ai TRUE / TRUE 1794611513 intercom-device-id-lupk8zyo b53ff69c-4e9f-484c-8824-5fedc4acb6ee
|
||||
.claude.ai TRUE / FALSE 1802817511 ajs_user_id d578b8e1-704f-49a3-a691-67538a600e48
|
||||
.claude.ai TRUE / TRUE 1802817511 cf_clearance Rhus4wvo7dTBFd9IdHj04VerNdQBvsLf4BSmR14P62M-1771281511-1.2.1.1-ApqfCOEl1m3Oi1wan95HTIr95afvB2d_xsRrruPB22RIc5RprR8gAv6Nsr1j.CKzzXLHNCX5UbsVvPnMYeEBQl6UszA8_k3Osf4PV.fXVu.2OYBHufWxS1iSWDBMRVsp_Ela_O._hctK7IGx9oWnoDAHaybY3SIDZx3bk_yjcy0.vURxhX0roinh8l37XhgxYYtxH77yryXASwc.z0ZEWi94Oks0TpNiRDNAQDQzyIg
|
||||
immense-voyage.local FALSE / FALSE 1807808010 session jt3ax4vz67rvx4hwcccryo2bna
|
||||
5.161.191.254 FALSE / FALSE 1807828861 session mrnehylzg5pod7utyx7ykyitgy
|
||||
secure.chase.com FALSE / FALSE 1804898020 ktlvDW7IG5ClOcxYTbmY a
|
||||
chromewebstore.google.com FALSE / TRUE 1776279448 OTZ 8523057_76_80_104160_76_446820
|
||||
ogs.google.com FALSE / TRUE 1776279489 OTZ 8523058_76_80_104160_76_446820
|
||||
accounts.google.com FALSE / TRUE 1776285616 OTZ 8523160_76_80_104160_76_446820
|
||||
accounts.google.com FALSE / TRUE 1808253661 ACCOUNT_CHOOSER AFx_qI64Fyd3WOgqzqEwirImDYclCyKBdomLnpImNfCbXy-ToyYOZLOQM6BhSmSlh7UFNCDPjszdP-z3uKjvrdOfLvtfp1F6Kg2wUiy8bAxCN7gdW25-ySdXdN2M4W1pBJex8Gf5FSrR
|
||||
accounts.google.com FALSE / TRUE 1808253661 __Host-GAPS 1:RmYuBFSvtOYIoZSDApEcQTWRxTxxAOhQ7SWZ8-9OvRL405mb07I9hfesu8FtVAqHJcpnT5c1Yg0gdUg3wuOLqIyLqhKkJg:PNT2SoEsKh2Qqkj0
|
||||
accounts.google.com FALSE / TRUE 1808253661 SMSV ADHTe-DkkvBH6NujmTV1-jzXXvTKEoeb5sPPlOpFf60Ao3D66REzOEbHCQR1fWFuMkepvsaFR9tW8bz_UbR-t5hVK1ef7KuL3vlIIU9rx8ybr1mBFe0XlwQ
|
||||
accounts.google.com FALSE / TRUE 1808253661 LSID s.youtube:g.a0007whSQ6uWt7O_xuWCIoHK_EdxOWsKkMphXjhgD52Z_0oaIFM-FJpJ_APy6iex2KGtKZTfMAACgYKAfASARMSFQHGX2MipyYBdH9B1sm4C82pTF10MhoVAUF8yKqMdmN7-ALswzn7C2ZHpz0g0076
|
||||
accounts.google.com FALSE / TRUE 1808253661 __Host-1PLSID s.youtube:g.a0007whSQ6uWt7O_xuWCIoHK_EdxOWsKkMphXjhgD52Z_0oaIFM-xChahLJ-oWsMFMsal1AN-gACgYKAeYSARMSFQHGX2Mi6WdZ1kGkd18xVbcWRxA_mxoVAUF8yKoV_PIQM3B0ISl_lr7KTTmu0076
|
||||
accounts.google.com FALSE / TRUE 1808253661 __Host-3PLSID s.youtube:g.a0007whSQ6uWt7O_xuWCIoHK_EdxOWsKkMphXjhgD52Z_0oaIFM-U7S3MQ5V_ZeYOlCxJ_8x2wACgYKAf8SARMSFQHGX2Mi6DaJTk2e9IEPdfrMYUlALhoVAUF8yKoUhbRHHVSlePzvMBFRGxc40076
|
||||
.youtube.com TRUE / FALSE 1808253662 HSID ASVnILepE6lKhaVKT
|
||||
.youtube.com TRUE / TRUE 1808253662 SSID AOW01b5NEUFzESSgG
|
||||
.youtube.com TRUE / FALSE 1808253662 APISID MdMQfFFo_4b0Cw2d/Au_xxzGsUXCUZnFia
|
||||
.youtube.com TRUE / TRUE 1808253662 SAPISID gAA5ERfoktTnNYkF/A7x17szT_7WqXM9A1
|
||||
.youtube.com TRUE / TRUE 1808253662 __Secure-1PAPISID gAA5ERfoktTnNYkF/A7x17szT_7WqXM9A1
|
||||
.youtube.com TRUE / TRUE 1808253662 __Secure-3PAPISID gAA5ERfoktTnNYkF/A7x17szT_7WqXM9A1
|
||||
.youtube.com TRUE / FALSE 1808253662 SID g.a0007whSQ2FTtN3oaH2z5zAUg7rUNnJXuBC-FtNcmf_q4VZxCH6I5yCVYxA-LmPOHRXLgQktwAACgYKASYSARMSFQHGX2MitoydJ2nBzhSIkt8ceGNDPRoVAUF8yKpvGQGXHR2ixssAhIZ7Tlhg0076
|
||||
.youtube.com TRUE / TRUE 1808253662 __Secure-1PSID g.a0007whSQ2FTtN3oaH2z5zAUg7rUNnJXuBC-FtNcmf_q4VZxCH6IIWYz6SgNFiNwCFav4fBlxgACgYKAZgSARMSFQHGX2MiUhFl0FoKqeIalNYwK9r2kRoVAUF8yKquv1J4_ggvLRhNHGom3rco0076
|
||||
.youtube.com TRUE / TRUE 1808253662 __Secure-3PSID g.a0007whSQ2FTtN3oaH2z5zAUg7rUNnJXuBC-FtNcmf_q4VZxCH6Ip1KVQpXe0-3wXj_r0kABhgACgYKAVESARMSFQHGX2MiaYklqIL3lhBMrkEcEBXS5RoVAUF8yKpcc98ulcMv04cCcRloUdNL0076
|
||||
.youtube.com TRUE / TRUE 1808253662 LOGIN_INFO AFmmF2swRQIgDa8cIvI1fxFdd3GEA2GvmAmbS0amlSptrhn2SsGlEa0CIQCaL131LVEaQj7oJl3R9r7ONED30oCInRpvUDrLrbviyQ:QUQ3MjNmd3gxTkhRRmdRbC1Bb19lVDB2Znh0NzN0S2NmaGF5b0ZiNGlrdGpXOF8teVF1Q1ZzdHVuMmZtMVJNb1N4OVZVX0Y4MTRmbXZDTGdDaVpPRVo5YmJxSHVjT2FHOG01NkhzYmJsbTlSdEFZc0s4bFB5SWk5WlhNTWZzTmNPcEd4a01vY04tcVVuaEVWUjFWcTdveGVURDhldGtYU1Vn
|
||||
.youtube.com TRUE / FALSE 0 PREF f6=80&f7=4100&hl=en&tz=UTC
|
||||
.youtube.com TRUE / TRUE 1806760505 __Secure-1PSIDTS sidts-CjQBWhotCRA5YMKYuDyG5wumZa4VFuY3F7Fh2duwXD0UmuF6N7mEL_UYQOvz_1NTpoI1B-f4EAA
|
||||
.youtube.com TRUE / TRUE 1806760505 __Secure-3PSIDTS sidts-CjQBWhotCRA5YMKYuDyG5wumZa4VFuY3F7Fh2duwXD0UmuF6N7mEL_UYQOvz_1NTpoI1B-f4EAA
|
||||
.youtube.com TRUE / FALSE 1807301085 SIDCC AKEyXzXgdAiG1XmsO6K4iH5FtGh7LIwOuNXfcpr5EGRc9-ui2HNWRgT22phg7UqdZ7T0IbHkAQ
|
||||
.youtube.com TRUE / TRUE 1807301085 __Secure-1PSIDCC AKEyXzWuJrc5O2Tj22mrqnWkZHm7OWgSdYcSpgQzFBUdNkN59NPPP5dvoYyklnoIcjYBRJ6fig
|
||||
.youtube.com TRUE / TRUE 1807301085 __Secure-3PSIDCC AKEyXzWOuhBtJ1lUFiMkfoaPFM9tv_48nUP_moR0NXf3Z29M7YN2J8XV973XZiE1Pgea4WIHud8
|
||||
.youtube.com TRUE / TRUE 1791317085 VISITOR_INFO1_LIVE ENb1E2-qMSc
|
||||
.youtube.com TRUE / TRUE 1791317085 VISITOR_PRIVACY_METADATA CgJVUxIEGgAgUg%3D%3D
|
||||
.youtube.com TRUE / TRUE 1791313395 __Secure-ROLLOUT_TOKEN CLav7oDA64mAEBDv_MPOo6WTAxiar9XZuuGTAw%3D%3D
|
||||
.youtube.com TRUE / TRUE 1780182149 __Secure-YNID 13.YT=uPn267RhS6_DPP8X1r3X0mUsAq8X0L4zeknD1-ypQ02nK4yp2NT8iig-SVdbf_A0GP18-P4CgEglD8GsSkPjJVXW0IPFBWBykHuwczveAI30caCVgCUue4RQg2a9nLaU0s1BA2YJRygITXiXP7DGUp4lUDUhfK8-AEdzSeF_NcrFxnhs1lSzfO-og_isBQuS1O3Mv-kcOPgdkhUGWl4ZbIazInJka_fj45KQYi0IpoyMATQrN21FBdpCB-elz4s0Jk7cSB265FSf9McS8oQf0jIEW3pRbBJ2gTYIv5CDgJBGde-A43F8W2QnG2FLgc8huPihY9duf8fWyL6P9a5YLw
|
||||
.youtube.com TRUE / TRUE 0 YSC JFh3yO0TPqY
|
||||
.youtube.com TRUE / TRUE 1791138263 __Secure-BUCKET CLwC
|
||||
.evenue.net TRUE / FALSE 1805835847 _pxvid d4560d49-26fb-11f1-b9e0-54cb44a244a8
|
||||
.evenue.net TRUE / FALSE 1782075847 _gcl_au 1.1.1393764598.1774299847.623268785.1774299856.1774299860
|
||||
.evenue.net TRUE / FALSE 1808859865 _ga GA1.1.1833572337.1774299847
|
||||
.evenue.net TRUE / FALSE 1808859904 _ga_PXLJRLJD73 GS2.2.s1774299863$o1$g1$t1774299903$j20$l0$h0
|
||||
.evenue.net TRUE / FALSE 1808859919 _ga_VKEY0CCQPT GS2.1.s1774299847$o1$g1$t1774299918$j60$l0$h0
|
||||
goduke.evenue.net FALSE / TRUE 1805835891 WTPERSIST
|
||||
.adserver.inflightinternet.com TRUE / TRUE 1806510262 _ab_csid I0uhQm5%2BD3xbp97Js5KciY23Ok%2BiFXpu1ON3j7FZpOeWyNm2FpUYDmDCZnGw9%2F2K
|
||||
www.aa.com FALSE / TRUE 0 XSRF-TOKEN d26ece12-58b2-44b5-a96d-845c65813ef1
|
||||
www.aa.com FALSE / TRUE 0 JSESSIONID 567F2ABD5E672A0A33951C9664299FD8
|
||||
www.aa.com FALSE / TRUE 0 aka_state_code VA
|
||||
www.aa.com FALSE / TRUE 0 aka_cr_code US-VA
|
||||
www.aa.com FALSE / TRUE 0 sessionLocale en_US
|
||||
www.aa.com FALSE / TRUE 1777566385 al 0
|
||||
www.aa.com FALSE / TRUE 0 akavpau_www_aahomepage 1774974685~id=fc154d22d03c3f02c71c83c7d63a71fd
|
||||
www.aa.com FALSE / FALSE 0 aka_lc_code ML
|
||||
www.aa.com FALSE / TRUE 0 KROUTEID c4386c20c7a71c8b98ea571950b9023a|b1f37ed7f54333e7fed5541bf6553ced
|
||||
www.aa.com FALSE / TRUE 0 akavpau_www_aafullsite 1775224801~id=f1411d92597cd0576b28f85835e74fcd
|
||||
.www.aa.com TRUE / TRUE 1809534305 UAC 9660f397662842c9a5749fba1d2f34e8
|
||||
.aa.com TRUE / FALSE 0 rxVisitor 1774974462693TUVBMAUPSSLQG9LF1C8ENSS7P6ILAHIQ
|
||||
.aa.com TRUE / FALSE 0 dtSa -
|
||||
.aa.com TRUE / FALSE 0 dtCookie v_4_srv_16_sn_A6ED5A1920AD6C3AD36860C6EDA12B81_perc_100000_ol_0_mul_1_app-3A8b2a7e44ceb3fcd4_1_rcs-3Acss_0
|
||||
.aa.com TRUE / TRUE 1806759886 _abck 67380BEE96080DB117362BC3756BEA8C~-1~YAAQ5z3XF5a6iU2dAQAAKdCWUw/lBEKoHIIuWNYVmSiEDSpN5SyK9zGRg/U8PWg/ZCWVoBmFWJrfI94Yl876rAcDEOa7TXBbFPn93vnqoctznmL8dY8WdEdvJnIkZEGSDR92SLMD5eI4WbkLfGj6/ddv1TUZwKb7scYA7fCMvdE/Siib2YMIljMZgIKRDZA8w9bffKSTVntv8Tkiuq/DArqM+FnRvPKmI1qckwPbAmFc1zF67Nb7OOYkV36UZZmt7dJyKi0v+Zz9w58AWPXnoFLUKGuM34+O6yCs6qSRGxv+XrIe60ZUfmv1ent01YraNiGhC8sE1S4NOtt02nP6QU2sCg8cm/02tX7KX2jG85xqs/AW6u+QUmSgVqKfFTjksp6L0Ii2bBbY/Ru9YdlNeeZutmAQmns5al7+BPrEdCIqL4yimCyCU2y6IFbNYyUYgtz4ibjVqlFjad1HUAR1LInf~-1~-1~-1~-1~-1
|
||||
.aa.com TRUE / FALSE 0 dtsrVID 1775147472723
|
||||
.aa.com TRUE / FALSE 0 rxvt 1775149281085|1775146868389
|
||||
.aa.com TRUE / FALSE 0 dtPC 16$174462690_847h-vFJCRQWUFVIVJAVFKCRWLMPIAARUNADFH-0e0
|
||||
.doubleclick.net TRUE / TRUE 1809784503 IDE AHWqTUlisU2hmqPtDHgMSndCiEYb0PgfGDl4xaG6WQuM9aCEwjWFA-s7tqw9EZDh
|
||||
.doubleclick.net TRUE / TRUE 1783638469 APC AfxxVi6hNvQV29SLhzAFSpJ9ITzn0e3Wg50VHJGNpgewSpGfGZILqQ
|
||||
.protechts.net TRUE / TRUE 1781649266 _pxvid 0ed20029-4b02-11f0-942d-350be9935237
|
||||
.protechts.net TRUE / TRUE 1784385137 _px3 ff2e7f344e23124464181eb32a6547e8885afa2278f627d84c416eaa11765b2a:tEM1lQYPDIr7NIsSjXWJ26RKR1lKMP7Hq3mYq1PoOBjAT5hIQrofQYfUdbwKqECGvlfP42qmFno1iQBvlNuJbQ==:1000:7qGM1VmYIh4dHQ/0zkiQ11NC3Hly4knCGG1cM8RvU+GQcTtlRjayPpoTnJjspPvW0lgpzFNEUMkS2aDaxgVk5kQK+9HLuNcZXsxQtS5nZFLa7IWj7oGoPC4tlzi5XkBhgo8jZY+kvV7+WjKQoe4bzoJl0Si26iMlzeh3OtCpxkqFsOzlZ7i9j0COaEouvXsP8f06JnJwdn/6jfE8CJZbb2Bk0ivQkw61FEKzJAKH/lE=
|
||||
.criteo.com TRUE / TRUE 1785640562 receive-cookie-deprecation 1
|
||||
.criteo.com TRUE / TRUE 1801782514 cto_bundle BW3AuV84ODVyMXZkYTg2T2R5YjRwTVI0Y3lRT1pqTU44RUVoWWcxQ29xczVWYm5wVkhodktxQzJDd0V1eGhtOGxrZ2N1Y0ZSVkdLTVh1WDNVd1ZMTjByNGZDQktQQjVLdkxKVTc1ZE1MbDB6RjB0U1VoZ0Fwc3VSSVlTTHkzWmpFRDVTNmNmVlNEVFBiVk9haTJwQlRDMEsyZEElM0QlM0Q
|
||||
top-fwz1.mail.ru FALSE / TRUE 1787328588 PVID 24Phk500X52Y0026Kk1Q01oY:::0-0-0-cae53fc-0-dc04c0c:CAASEKxMyii6AT78_-3NUPqD0UIaYHtsqvds-0kSklpezGsuCNzWPVpfXllbLL1vqX4f428ZW2vUxODue-givca9Uz03dLH8ZLOAeFiLjUWT2D7xIKjFSSw1mNF8GXEjOnpIeFEPmwLXqAyKewzaUgOzLS9SFg
|
||||
prebid.media.net FALSE / TRUE 1783638514 receive-cookie-deprecation 1
|
||||
.tracookiepixel.xyz TRUE / TRUE 1799622469 server_tracking_bdsp_uid AADBck7KgtEAABjN509uAw
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Creates "YouTube Summarizer.app" — a proper macOS app bundle
|
||||
# that you can put in your Dock, Desktop, or Applications folder.
|
||||
#
|
||||
# Usage: Run this once from the project folder:
|
||||
# chmod +x create-app.sh && ./create-app.sh
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
APP_NAME="YouTube Summarizer"
|
||||
APP_PATH="$PROJECT_DIR/$APP_NAME.app"
|
||||
|
||||
echo ""
|
||||
echo "Creating $APP_NAME.app..."
|
||||
echo ""
|
||||
|
||||
# ── Build the .app bundle structure ──────────────────────────
|
||||
rm -rf "$APP_PATH"
|
||||
mkdir -p "$APP_PATH/Contents/MacOS"
|
||||
mkdir -p "$APP_PATH/Contents/Resources"
|
||||
|
||||
# ── Create the launcher script ───────────────────────────────
|
||||
cat > "$APP_PATH/Contents/MacOS/launcher" << 'LAUNCHER'
|
||||
#!/bin/bash
|
||||
|
||||
# Resolve the real project directory (the .app lives inside it)
|
||||
APP_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
# If the app has been moved (e.g. to /Applications), check for a saved path
|
||||
SAVED_PATH="$HOME/.config/youtube-summarizer/project-path"
|
||||
if [ ! -f "$APP_DIR/server/index.js" ]; then
|
||||
if [ -f "$SAVED_PATH" ]; then
|
||||
APP_DIR="$(cat "$SAVED_PATH")"
|
||||
else
|
||||
# Ask the user to locate the project folder
|
||||
APP_DIR=$(osascript -e 'POSIX path of (choose folder with prompt "Locate your YouTube Summarizer project folder:")')
|
||||
APP_DIR="${APP_DIR%/}"
|
||||
if [ -z "$APP_DIR" ] || [ ! -f "$APP_DIR/server/index.js" ]; then
|
||||
osascript -e 'display alert "YouTube Summarizer" message "Could not find the project folder. Make sure server/index.js exists." as critical'
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$(dirname "$SAVED_PATH")"
|
||||
echo "$APP_DIR" > "$SAVED_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
# Add common Homebrew paths (node, yt-dlp)
|
||||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||||
|
||||
# Check for Node.js
|
||||
if ! command -v node &> /dev/null; then
|
||||
osascript -e 'display alert "YouTube Summarizer" message "Node.js is not installed. Install it with: brew install node" as critical'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for yt-dlp
|
||||
if ! command -v yt-dlp &> /dev/null; then
|
||||
if command -v brew &> /dev/null; then
|
||||
brew install yt-dlp 2>&1
|
||||
else
|
||||
osascript -e 'display alert "YouTube Summarizer" message "yt-dlp is not installed. Install it with: brew install yt-dlp" as critical'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install npm dependencies if needed
|
||||
if [ ! -d "server/node_modules" ]; then
|
||||
cd server && npm install && cd ..
|
||||
fi
|
||||
|
||||
# Ask user: Home or Traveling?
|
||||
ICON_PATH="$APP_DIR/assets/icon.png"
|
||||
NETWORK_CHOICE=$(osascript -e "
|
||||
set iconFile to POSIX file \"$ICON_PATH\"
|
||||
set theChoice to button returned of (display dialog \"Where are you right now?\" & return & return & \"Home — allows your phone and other devices on your Wi-Fi to use the app.\" & return & return & \"Traveling — locks access to just this laptop for safety on public Wi-Fi.\" buttons {\"Traveling\", \"Home\"} default button \"Traveling\" with title \"YouTube Summarizer\" with icon iconFile)
|
||||
return theChoice
|
||||
" 2>/dev/null)
|
||||
|
||||
# Default to traveling (safe) if dialog was cancelled
|
||||
if [ "$NETWORK_CHOICE" = "Home" ]; then
|
||||
export LAN_MODE=true
|
||||
else
|
||||
export LAN_MODE=false
|
||||
fi
|
||||
|
||||
# Kill any existing instance on port 3001
|
||||
lsof -ti:3001 | xargs kill -9 2>/dev/null
|
||||
sleep 0.3
|
||||
|
||||
# Start the server
|
||||
cd server
|
||||
node index.js &
|
||||
SERVER_PID=$!
|
||||
cd ..
|
||||
|
||||
# Wait for server to be ready (up to 10 seconds)
|
||||
for i in {1..20}; do
|
||||
if curl -s http://localhost:3001/api/health > /dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
# Open browser
|
||||
open http://localhost:3001
|
||||
|
||||
# Keep alive — when the app is quit, stop the server
|
||||
trap "kill $SERVER_PID 2>/dev/null; exit 0" INT TERM
|
||||
wait $SERVER_PID
|
||||
LAUNCHER
|
||||
|
||||
chmod +x "$APP_PATH/Contents/MacOS/launcher"
|
||||
|
||||
# ── Create Info.plist ────────────────────────────────────────
|
||||
cat > "$APP_PATH/Contents/Info.plist" << PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>launcher</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>YouTube Summarizer</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.local.youtube-summarizer</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
# ── Create the app icon from the pre-made PNG ───────────────
|
||||
ICON_PNG="$PROJECT_DIR/assets/icon.png"
|
||||
ICON_DIR="$APP_PATH/Contents/Resources"
|
||||
|
||||
if [ -f "$ICON_PNG" ]; then
|
||||
echo "Building app icon..."
|
||||
|
||||
# Create iconset with all required sizes using sips (built into macOS)
|
||||
ICONSET="$ICON_DIR/AppIcon.iconset"
|
||||
mkdir -p "$ICONSET"
|
||||
|
||||
for size in 16 32 64 128 256 512; do
|
||||
sips -z $size $size "$ICON_PNG" --out "$ICONSET/icon_${size}x${size}.png" > /dev/null 2>&1
|
||||
done
|
||||
# @2x variants
|
||||
for size in 16 32 128 256 512; do
|
||||
double=$((size * 2))
|
||||
sips -z $double $double "$ICON_PNG" --out "$ICONSET/icon_${size}x${size}@2x.png" > /dev/null 2>&1
|
||||
done
|
||||
|
||||
# Convert iconset → .icns using iconutil (built into macOS)
|
||||
iconutil -c icns "$ICONSET" -o "$ICON_DIR/AppIcon.icns" 2>/dev/null
|
||||
|
||||
# Clean up the iconset folder
|
||||
rm -rf "$ICONSET"
|
||||
|
||||
if [ -f "$ICON_DIR/AppIcon.icns" ]; then
|
||||
echo " Icon created successfully."
|
||||
else
|
||||
echo " Warning: Could not create .icns icon. The app will use a generic icon."
|
||||
fi
|
||||
else
|
||||
echo " Warning: assets/icon.png not found. The app will use a generic icon."
|
||||
fi
|
||||
|
||||
# ── Remove quarantine so macOS doesn't block it ─────────────
|
||||
xattr -d com.apple.quarantine "$APP_PATH" 2>/dev/null
|
||||
|
||||
# ── Done! ────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Created: $APP_NAME.app"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "You can now:"
|
||||
echo " 1. Double-click it to launch the app"
|
||||
echo " 2. Drag it to your Dock for quick access"
|
||||
echo " 3. Move it to /Applications if you prefer"
|
||||
echo ""
|
||||
echo "If you move it outside this folder, it will"
|
||||
echo "ask you to locate the project folder once."
|
||||
echo ""
|
||||
echo "Done!"
|
||||
echo ""
|
||||
Vendored
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"folders": [],
|
||||
"uncategorized": [
|
||||
"1775679261151-MPAsUZEJHiM",
|
||||
"1775678510294-Mv4cF13glBo",
|
||||
"1775672308695-kwSVtQ7dziU",
|
||||
"1775603102882-yLmM3M7d9v8",
|
||||
"1775599688621-Tmw2dW0a9DY",
|
||||
"1775589261845-wO5iES4qjwE",
|
||||
"1775589068370-UmLdukkI0IU",
|
||||
"1775588771329-NBu8G-5YpGI",
|
||||
"1775588353507-mjNP5IOzCbA",
|
||||
"1775587377519-Rc9lTSs7wgI",
|
||||
"1775477439435-HMTrd9Hn3e8",
|
||||
"1775430672822-lclDwnfnOGc",
|
||||
"1775430216753-Axpnkqd-Agk",
|
||||
"1775275709956-d01H-T3-frg",
|
||||
"1775274863397-N-pust8qtGI",
|
||||
"1775246692426-m3uwf5BZy1Y",
|
||||
"1775224371563-Qc6hjJ9Zze0",
|
||||
"1775224185561-C0DMdUqF73Q",
|
||||
"1775223585124-NDE0NjE5NDgtNDFi",
|
||||
"1775223278105-ovih9Xotmxg",
|
||||
"1775223130960-kjQZexuV_AE",
|
||||
"1775222710862-gwW8GKwHB3I",
|
||||
"1775222522683-lTuS7Ns8cZg",
|
||||
"1775222175607-91up0VxYeNw",
|
||||
"1775221783211-YTVSwOY19Qs",
|
||||
"1775221563723-9ZbbxSgrjhw",
|
||||
"1774302030087--JBhTBu9ZbA",
|
||||
"1774289546791-rngx_PsPxkg",
|
||||
"1773858934218--dgsZ0O-lZQ",
|
||||
"1773775610426-xaNl2EY8b-c",
|
||||
"1773695002747-hlOA8ObQJXo",
|
||||
"1773694605993-Yzg3YjFlODQtYWNl",
|
||||
"1773185576199-YThlOTAyMWYtOTU4",
|
||||
"1773021528170-MjczYmIzZjgtZjg1",
|
||||
"1772740338135-2JiMmye2ezg"
|
||||
]
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "auto-1775757891625-_5-lc6c0e2Y",
|
||||
"videoId": "_5-lc6c0e2Y",
|
||||
"url": "https://www.youtube.com/watch?v=_5-lc6c0e2Y",
|
||||
"title": "MacroVoices #527 Adam Rozencwajg & Jim Bianco: What Comes Next After The Iran Crisis",
|
||||
"uploadDate": "20260409",
|
||||
"subscriptionId": "sub-1772423650145",
|
||||
"subscriptionName": "Macro Voices",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"id": "auto-1775764996750-LPDabHeg7co",
|
||||
"videoId": "LPDabHeg7co",
|
||||
"url": "https://www.youtube.com/watch?v=LPDabHeg7co",
|
||||
"title": "RABBIT HOLE RECAP #404: THE RISE OF THE PETROSAT WITH ODELL AND MARTY BENT",
|
||||
"uploadDate": "20260409",
|
||||
"subscriptionId": "sub-1771016441117",
|
||||
"subscriptionName": "TFTC",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"id": "auto-1775765083579-EaAtMvvZrrg",
|
||||
"videoId": "EaAtMvvZrrg",
|
||||
"url": "https://www.youtube.com/watch?v=EaAtMvvZrrg",
|
||||
"title": "MacroVoices #527 Adam Rozencwajg: What Comes Next After The Iran Crisis",
|
||||
"uploadDate": "20260409",
|
||||
"subscriptionId": "sub-1772423650145",
|
||||
"subscriptionName": "Macro Voices",
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"videoIds":["KX6q6lvoYtM","s7-l7gzm_PE","WovyQkSdONI","KKNGEr7Mc38","bzzFBvzONBo","ENW3PL50yPw","WTQwpuroPjQ","wwX0IgJYejc","RA2cvfdwy0I","dFknx-mRmKE","fenXCK0NO_w","BBKVBFrlnfw","B9udzeRK-vM","i61PKpgE3Ag","rAbJC-Qaw7Q","NEkUNahduWY","CnaegIpkenA","-ahp5X0VchM","n1E9IZfvGMA","pnn_rknhmcg","b7d71665-c736-4bc4-8072-93030cb13995","Zph8_udOKJ0","mMUNkgjwlio","mGohMkkxVj0","f2OyRTu8x3A","5mtuEf3zxTE","s0uiUWsichE","B5dZgGeXA2o","Nq3qLlcbFK0","U1oHRqUkI1E","q-sClVMYY4w","jA8ZQfq_Hzs","Gnl833wXRz0","wPy063-vd1Y","OZDFAvRq7Gc","9ZM9sCfmAGc","3hptKYix4X8","_Q4XT82yd-Q","sEXe-T-5Gn0","iDNlOGn1bTw","5kkIJNUGFho","_Fs5E5pQkLQ","X7ua58iwcd4","1aa14fcc-dd7f-45f6-bcd5-1a956b6383bb","BFt82dw0ci8","pWx_Kp6kliA","CQq3Ocm98sY","POASm17QnRk","9GzlbLIU5dU","8aUBS6z2dSI","5V-q3Sc0UIg","PSDXLvaswTI","2SNyah7nbE8","8JedAJA5AbQ","g81zFbnCZfc","NjdKMaeRBoU","m6kbpZ6JilI","mI30_ueZ7CU","jZfUANjHMF8","ie3wxnhEmYg","EVND2wqKBFE","0D8FwiFj7NM","eAKNfYJB8_c","ZZclhh9Keq8","l1qvUpHzKII","saGaeaMORA4","iCBpYwTxNiw","cf7T0TrgUUU","RUmJxQ3Hi8I","u-vMNzHgSHI","mBE_9vGJBUM","zjcYlEiwnKI","Hom5OMMzOQ0","uhpECa_XaBA","S1rQngjpUdI","9G2MRFs4vac","bdasraPo1Ak","6Uzi0OvFixw","kzWbCF_IkHY","7UeojKTzeHE","Ew0-Lg_Evj0","SaWSPZoPX34","PJh0ygZpQt8","3CZXzp1t9KE","489ca7d9-9a93-4ea4-9c0d-9ee82366f385","VPi_eWiaqdg","JsICN9ZiSjA","cru_4P6qVYQ","PF4dSKK_kww","boJRkecvyCM","xlzThNBFbk8","OnZv124nZqY","idb2PIVLLMs","4e58baFX6Go","XTnYVh7K6xQ","7RHwE_M68BY","rPaWW0IDurI","gaQwwT8Gjx0","vFTEUwYha0A","k8ZpHslhM_c","msygul3QpfU","HiVej9_fvl8","iuPQmw4Ax00","v5T1Q_5C_1w","hTY0ffRlmBQ","el6ObB60Fb4","frhd5xHQ_5Y","AVEZBy1uAk8","dnAMN9Kn8zI","ZJEnQOsMtsU","2wdsNwPUCgQ","6ctOnY1WmBw","MIKej1HCRW0","R8YC8OCnDxM","Httl6Xtf1eQ","G8Tp-3gKvu8","lxiXJKzbcp8","hPPq7NBAOvQ","yAHfG6jU2qA","zy8tpiJCEwk","3KsjuNcxTYA","u4VTFb4awrQ","elhGYEiZ7fw","gzwRflcLPAA","j95uk2BMkug","StQktHtUDbE","3bJHVYa4yMM","DMv1WLvLmQc","kC1NtDZPHuk","9y7TTU1jIvk","RAEW2KlyTy4","3ZVqcW6Wwmg","zA6gyFnlFWc","LioKxZatex0","QPkfVhYlxhE","YG7yFsXctv0","5klhxOKZ_ag","yJUk_pVN9m8","blBlD-mrIoE","L5dPkdmquNk","37LFbhjVNy0","Qbza16Y7owk","e357d703-6660-4bbb-ba40-bb7c401db429","jN6PmHUwrF0","EhEUtqEyM-8","UweigmKvoLs","vqa9qgqhT7E","LCLzgK03CQ0","wsISKLApBbk","6-mEiwe2NhE","c4tvVKDhpiY","vn_kFxqM_gw","rQ21FHpcOnY","0VSOoMuStvg","okEujapROIw","F6i7P-ZP6CY","9FEwl31hGKk","keinLNWVQXM","hnzrPKvRBD8","bHKyg77Cbs4","rjQ1L7gjCxE","lh1ij0ScKfA","dhhVs05qidI","Wh9318EAriE","9cMci6FRdMo","SSya123u9Yk","n2Mf3OotGrQ","2LRzttI1kbI","Y60lUXB8Wd8","4Gmd5UTF4rk","f7481641-a904-4d73-830a-40571b815f7d","IMAxsLAgTSQ","dcTdmQgGLDk","Bf2FIxtiWV8","77CdVSpnUX4","nxEhzsopyxY","cUngseNueP8","xZ4I2aE8zQA","GTI2ObvXMpo"]}
|
||||
@@ -1,127 +0,0 @@
|
||||
{
|
||||
"subscriptions": [
|
||||
{
|
||||
"id": "sub-1771016441117",
|
||||
"url": "https://www.youtube.com/@TFTC/streams",
|
||||
"name": "TFTC",
|
||||
"createdAt": "2026-02-06T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:03:16.751Z",
|
||||
"paused": false,
|
||||
"channelId": "UCtdbWsnfA08KhSUO4amVLaQ"
|
||||
},
|
||||
{
|
||||
"id": "sub-1771016769660",
|
||||
"url": "https://www.youtube.com/@MartyBentTFTC/videos",
|
||||
"name": "Marty Bent",
|
||||
"createdAt": "2026-02-08T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:03:25.378Z",
|
||||
"paused": false,
|
||||
"channelId": "UCUQcW3jxfQfEUS8kqR5pJtQ"
|
||||
},
|
||||
{
|
||||
"id": "sub-1771017860588",
|
||||
"url": "https://www.youtube.com/@thejackmallersshow/streams",
|
||||
"name": "THE JACK MALLERS SHOW",
|
||||
"createdAt": "2026-02-01T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:03:26.490Z",
|
||||
"paused": false,
|
||||
"channelId": "UC3ol9RQbQHqle_Uly6w9LfA"
|
||||
},
|
||||
{
|
||||
"id": "sub-1771020687575",
|
||||
"url": "https://www.youtube.com/@Bg2Pod/videos",
|
||||
"name": "@Bg2Pod",
|
||||
"createdAt": "2025-10-01T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:03:27.651Z",
|
||||
"paused": false,
|
||||
"channelId": "UC-yRDvpR99LUc5l7i7jLzew"
|
||||
},
|
||||
{
|
||||
"id": "sub-1771020804519",
|
||||
"url": "https://www.youtube.com/@PrestonPysh/videos",
|
||||
"name": "@PrestonPysh",
|
||||
"createdAt": "2025-10-01T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:24.347Z",
|
||||
"paused": false,
|
||||
"channelId": "UCLTdCY-fNXc1GqzIuflK-OQ"
|
||||
},
|
||||
{
|
||||
"id": "sub-1771028164809",
|
||||
"url": "https://www.youtube.com/@hubermanlab/videos",
|
||||
"name": "Andrew Huberman",
|
||||
"channelId": "UC2D2CMWXMOVWx7giW1n3LIg",
|
||||
"createdAt": "2026-02-01T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:25.512Z",
|
||||
"paused": false
|
||||
},
|
||||
{
|
||||
"id": "sub-1771028433890",
|
||||
"url": "https://www.youtube.com/@FindingMastery/videos",
|
||||
"name": "Finding Mastery",
|
||||
"channelId": "UCSZrR-KiOLjUPbhPoPYCxNQ",
|
||||
"createdAt": "2026-02-14T00:20:33.890Z",
|
||||
"lastChecked": "2026-04-09T20:04:33.639Z",
|
||||
"paused": false
|
||||
},
|
||||
{
|
||||
"id": "sub-1771030240503",
|
||||
"url": "https://www.youtube.com/@tetragrammaton_now/videos",
|
||||
"name": "Tetragrammaton with Rick Rubin",
|
||||
"type": "youtube",
|
||||
"channelId": "UC5Gat6FdyiG5ydUUHqPTAEQ",
|
||||
"createdAt": "2026-02-10T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:40.387Z",
|
||||
"paused": false
|
||||
},
|
||||
{
|
||||
"id": "sub-1771030543882",
|
||||
"url": "https://serve.podhome.fm/rss/c90e609a-df1e-596a-bd5e-57bcc8aad6cc",
|
||||
"name": "Citadel Dispatch",
|
||||
"type": "podcast",
|
||||
"channelId": null,
|
||||
"createdAt": "2026-02-08T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:40.777Z",
|
||||
"paused": false
|
||||
},
|
||||
{
|
||||
"id": "sub-1771045539000",
|
||||
"url": "https://www.youtube.com/@allin/videos",
|
||||
"name": "All-In Podcast",
|
||||
"type": "youtube",
|
||||
"channelId": "UCESLZhusAkFfsNsApnjF_Cg",
|
||||
"createdAt": "2026-02-13T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:41.995Z",
|
||||
"paused": false
|
||||
},
|
||||
{
|
||||
"id": "sub-1772423650145",
|
||||
"url": "https://www.youtube.com/@macrovoices7508/videos",
|
||||
"name": "Macro Voices",
|
||||
"type": "youtube",
|
||||
"channelId": "UCICRehoZjq3ZtAWgRJX118A",
|
||||
"createdAt": "2026-02-02T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:43.579Z",
|
||||
"paused": false
|
||||
},
|
||||
{
|
||||
"id": "sub-1772462897521",
|
||||
"url": "https://www.youtube.com/@20VC/videos",
|
||||
"name": "20VC with Harry Stebbings",
|
||||
"type": "youtube",
|
||||
"channelId": "UCf0PBRjhf0rF8fWBIxTuoWA",
|
||||
"createdAt": "2026-02-20T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:44.528Z",
|
||||
"paused": false
|
||||
},
|
||||
{
|
||||
"id": "sub-1773418202833",
|
||||
"url": "https://www.youtube.com/@ForwardGuidanceBW/videos",
|
||||
"name": "Forward Guidance",
|
||||
"type": "youtube",
|
||||
"channelId": "UCkrwgzhIBKccuDsi_SvZtnQ",
|
||||
"createdAt": "2026-03-06T00:00:00.000Z",
|
||||
"lastChecked": "2026-04-09T20:04:45.373Z",
|
||||
"paused": false
|
||||
}
|
||||
]
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
../fast-xml-parser/src/cli/cli.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../mime/bin/cli.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../@vercel/ncc/dist/ncc/cli.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../prettier/bin/prettier.cjs
|
||||
-1
@@ -1 +0,0 @@
|
||||
../typescript/bin/tsc
|
||||
-1
@@ -1 +0,0 @@
|
||||
../typescript/bin/tsserver
|
||||
-1
@@ -1 +0,0 @@
|
||||
../yaml/bin.mjs
|
||||
-318
@@ -1,318 +0,0 @@
|
||||
{
|
||||
"name": "youtube-summarizer-startos",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@iarna/toml": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz",
|
||||
"integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz",
|
||||
"integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@start9labs/start-sdk": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-1.0.0.tgz",
|
||||
"integrity": "sha512-rtAfumVbMy90iw2WRbWH7fGcuwAvvuFfR4YwgSsh5R2Bz9MXtcEfmznwhnrp+ntQ6BOUSQ0wLzePbfsS6kUagg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
"@noble/curves": "^1.8.2",
|
||||
"@noble/hashes": "^1.7.2",
|
||||
"@types/ini": "^4.1.1",
|
||||
"deep-equality-data-structures": "^2.0.0",
|
||||
"fast-xml-parser": "^5.5.6",
|
||||
"ini": "^5.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"mime": "^4.0.7",
|
||||
"yaml": "^2.7.1",
|
||||
"zod": "^4.3.6",
|
||||
"zod-deep-partial": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ini": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz",
|
||||
"integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
||||
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/ncc": {
|
||||
"version": "0.38.4",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz",
|
||||
"integrity": "sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"ncc": "dist/ncc/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-equality-data-structures": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-equality-data-structures/-/deep-equality-data-structures-2.0.0.tgz",
|
||||
"integrity": "sha512-qgrUr7MKXq7VRN+WUpQ48QlXVGL0KdibAoTX8KRg18lgOgqbEKMAW1WZsVCtakY4+XX42pbAJzTz/DlXEFM2Fg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-hash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
|
||||
"integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-expression-matcher": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.5.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.11.tgz",
|
||||
"integrity": "sha512-QL0eb0YbSTVWF6tTf1+LEMSgtCEjBYPpnAjoLC8SscESlAjXEIRJ7cHtLG0pLeDFaZLa4VKZLArtA/60ZS7vyA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-builder": "^1.1.4",
|
||||
"path-expression-matcher": "^1.4.0",
|
||||
"strnum": "^2.2.3"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz",
|
||||
"integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.1",
|
||||
"whatwg-fetch": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz",
|
||||
"integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/object-hash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-expression-matcher": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz",
|
||||
"integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
|
||||
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz",
|
||||
"integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
|
||||
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-deep-partial": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/zod-deep-partial/-/zod-deep-partial-1.4.4.tgz",
|
||||
"integrity": "sha512-aWkPl7hVStgE01WzbbSxCgX4O+sSpgt8JOjvFUtMTF75VgL6MhWQbiZi+AWGN85SfSTtI9gsOtL1vInoqfDVaA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"zod": "^4.1.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-291
@@ -1,291 +0,0 @@
|
||||
# 3.0.0
|
||||
|
||||
[TOML v1.0.0-rc.1](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v1.0.0-rc.1.md)
|
||||
has been released! This update brings support for it! It's not a huge
|
||||
update, an evening of tests and an evening of parser updates, but the
|
||||
changes as a consumer of TOML are actually pretty impactful:
|
||||
|
||||
* Inline arrays can have mixed types now!!
|
||||
* Inline tables MAY NOT have trailing commas. (Inline arrays are still allowed to have trailing commas.)
|
||||
* Control chars are no longer allowed in comments. Only tabs may be used in comments.
|
||||
* Multiline basic and literal strings can have quotes nestled up next to the closing triplet, that is:
|
||||
`""""Hello," she said, "this is a thing.""""` is the equivalent of the JSON `"\"Hello,\" she said, \"this is a thing.\""`.
|
||||
* Subtables may not extend tables created via dotted keys, that is, the following is invalid:```
|
||||
[a]
|
||||
x.y = 1
|
||||
[a.x]
|
||||
z = 2
|
||||
```
|
||||
|
||||
# 2.2.4
|
||||
|
||||
* Bug fix: Plain date literals (not datetime) immediately followed by another statement (no whitespace or blank line) would crash. Fixes [#19](https://github.com/iarna/iarna-toml/issues/19) and [#23](https://github.com/iarna/iarna-toml/issues/23), thank you [@arnau](https://github.com/arnau) and [@jschaf](https://github.com/jschaf) for reporting this!
|
||||
* Bug fix: Hex literals with lowercase Es would throw errors. (Thank you [@DaeCatt](https://github.com/DaeCatt) for this fix!) Fixed [#20](https://github.com/iarna/iarna-toml/issues/20)
|
||||
* Some minor doc tweaks
|
||||
* Added Node 12 and 13 to Travis. (Node 6 is failing there now, mysteriously. It works on my machine™, shipping anyway. 🙃)
|
||||
|
||||
# 2.2.3
|
||||
|
||||
This release just updates the spec compliance tests and benchmark data to
|
||||
better represent @ltd/j-toml.
|
||||
|
||||
# 2.2.2
|
||||
|
||||
## Fixes
|
||||
|
||||
* Support parsing and stringifying objects with `__proto__` properties. ([@LongTengDao](https://github.com/LongTengDao))
|
||||
|
||||
## Misc
|
||||
|
||||
* Updates for spec compliance and benchmarking:
|
||||
* @sgarciac/bombadil -> 2.1.0
|
||||
* toml -> 3.0.0
|
||||
* Added spec compliance and benchmarking for:
|
||||
* @ltd/j-toml
|
||||
|
||||
# 2.2.1
|
||||
|
||||
## Fixes
|
||||
|
||||
* Fix bug where keys with names matching javascript Object methods would
|
||||
error. Thanks [@LongTengDao](https://github.com/LongTengDao) for finding this!
|
||||
* Fix bug where a bundled version would fail if `util.inspect` wasn't
|
||||
provided. This was supposed to be guarded against, but there was a bug in
|
||||
the guard. Thanks [@agriffis](https://github.com/agriffis) for finding and fixing this!
|
||||
|
||||
## Misc
|
||||
|
||||
* Update the version of bombadil for spec compliance and benchmarking purposes to 2.0.0
|
||||
|
||||
## Did you know?
|
||||
|
||||
Node 6 and 8 are measurably slower than Node 6, 10 and 11, at least when it comes to parsing TOML!
|
||||
|
||||

|
||||
|
||||
# 2.2.0
|
||||
|
||||
## Features
|
||||
|
||||
* Typescript: Lots of improvements to our type definitions, many many to
|
||||
[@jorgegonzalez](https://github.com/jorgegonzalez) and [@momocow](https://github.com/momocow) for working through these.
|
||||
|
||||
## Fixes
|
||||
|
||||
* Very large integers (>52bit) are stored as BigInts on runtimes that
|
||||
support them. BigInts are 128bits, but the TOML spec limits its integers
|
||||
to 64bits. We now limit our integers to 64bits
|
||||
as well.
|
||||
* Fix a bug in stringify where control characters were being emitted as unicode chars and not escape sequences.
|
||||
|
||||
## Misc
|
||||
|
||||
* Moved our spec tests out to an external repo
|
||||
* Improved the styling of the spec compliance comparison
|
||||
|
||||
# 2.1.1
|
||||
|
||||
## Fixes
|
||||
|
||||
* Oops, type defs didn't end up in the tarball, ty [@jorgegonzalez](https://github.com/jorgegonzalez)‼
|
||||
|
||||
# 2.1.0
|
||||
|
||||
## Features
|
||||
|
||||
* Types for typescript support, thank you [@momocow](https://github.com/momocow)!
|
||||
|
||||
## Fixes
|
||||
|
||||
* stringify: always strip invalid dates. This fixes a bug where an
|
||||
invalid date in an inline array would not be removed and would instead
|
||||
result in an error.
|
||||
* stringify: if an invalid type is found make sure it's thrown as an
|
||||
error object. Previously the type name was, unhelpfully, being thrown.
|
||||
* stringify: Multiline strings ending in a quote would generate invalid TOML.
|
||||
* parse: Error if a signed integer has a leading zero, eg, `-01` or `+01`.
|
||||
* parse: Error if \_ appears at the end of the integer part of a float, eg `1_.0`. \_ is only valid between _digits_.
|
||||
|
||||
## Fun
|
||||
|
||||
* BurntSushi's comprehensive TOML 0.4.0 test suite is now used in addition to our existing test suite.
|
||||
* You can see exactly how the other JS TOML libraries stack up in testing
|
||||
against both BurntSushi's tests and my own in the new
|
||||
[TOML-SPEC-SUPPORT](TOML-SPEC-SUPPORT.md) doc.
|
||||
|
||||
# 2.0.0
|
||||
|
||||
With 2.0.0, @iarna/toml supports the TOML v0.5.0 specification. TOML 0.5.0
|
||||
brings some changes:
|
||||
|
||||
* Delete characters (U+007F) are not allowed in plain strings. You can include them with
|
||||
escaped unicode characters, eg `\u007f`.
|
||||
* Integers are specified as being 64bit unsigned values. These are
|
||||
supported using `BigInt`s if you are using Node 10 or later.
|
||||
* Keys may be literal strings, that is, you can use single quoted strings to
|
||||
quote key names, so the following is now valid:
|
||||
'a"b"c' = 123
|
||||
* The floating point values `nan`, `inf` and `-inf` are supported. The stringifier will no
|
||||
longer strip NaN, Infinity and -Infinity, instead serializing them as these new values..
|
||||
* Datetimes can separate the date and time with a space instead of a T, so
|
||||
`2017-12-01T00:00:00Z` can be written as `2017-12-01 00:00:00Z`.
|
||||
* Datetimes can be floating, that is, they can be represented without a timezone.
|
||||
These are represented in javascript as Date objects whose `isFloating` property is true and
|
||||
whose `toISOString` method will return a representation without a timezone.
|
||||
* Dates without times are now supported. Dates do not have timezones. Dates
|
||||
are represented in javascript as a Date object whose `isDate` property is true and
|
||||
whose `toISOString` method returns just the date.
|
||||
* Times without dates are now supported. Times do not have timezones. Times
|
||||
are represented in javascript as a Date object whose `isTime` property is true and
|
||||
whose `toISOString` method returns just the time.
|
||||
* Keys can now include dots to directly address deeper structures, so `a.b = 23` is
|
||||
the equivalent of `a = {b = 23}` or ```[a]
|
||||
b = 23```. These can be used both as keys to regular tables and inline tables.
|
||||
* Integers can now be specified in binary, octal and hexadecimal by prefixing the
|
||||
number with `0b`, `0o` and `0x` respectively. It is now illegal to left
|
||||
pad a decimal value with zeros.
|
||||
|
||||
Some parser details were also fixed:
|
||||
|
||||
* Negative zero (`-0.0`) and positive zero (`0.0`) are distinct floating point values.
|
||||
* Negative integer zero (`-0`) is not distinguished from positive zero (`0`).
|
||||
|
||||
# 1.7.1
|
||||
|
||||
Another 18% speed boost on our overall benchmarks! This time it came from
|
||||
switching from string comparisons to integer by converting each character to
|
||||
its respective code point. This also necessitated rewriting the boolean
|
||||
parser to actually parse character-by-character as it should. End-of-stream
|
||||
is now marked with a numeric value outside of the Unicode range, rather than
|
||||
a Symbol, meaning that the parser's char property is now monomorphic.
|
||||
|
||||
Bug fix, previously, `'abc''def'''` was accepted (as the value: `abcdef`).
|
||||
Now it will correctly raise an error.
|
||||
|
||||
Spec tests now run against bombadil as well (it fails some, which is unsurprising
|
||||
given its incomplete state).
|
||||
|
||||
# 1.7.0
|
||||
|
||||
This release features an overall 15% speed boost on our benchmarks. This
|
||||
came from a few things:
|
||||
|
||||
* Date parsing was rewritten to not use regexps, resulting in a huge speed increase.
|
||||
* Strings of all kinds and bare keywords now use tight loops to collect characters when this will help.
|
||||
* Regexps in general were mostly removed. This didn't result in a speed
|
||||
change, but it did allow refactoring the parser to be a lot easier to
|
||||
follow.
|
||||
* The internal state tracking now uses a class and is constructed with a
|
||||
fixed set of properties, allowing v8's optimizer to be more effective.
|
||||
|
||||
In the land of new features:
|
||||
|
||||
* Errors in the syntax of your TOML will now have the `fromTOML` property
|
||||
set to true. This is in addition to the `line`, `col` and `pos`
|
||||
properties they already have.
|
||||
|
||||
The main use of this is to make it possible to distinguish between errors
|
||||
in the TOML and errors in the parser code itself. This is of particular utility
|
||||
when testing parse errors.
|
||||
|
||||
# 1.6.0
|
||||
|
||||
**FIXES**
|
||||
|
||||
* TOML.stringify: Allow toJSON properties that aren't functions, to align with JSON.stringify's behavior.
|
||||
* TOML.stringify: Don't use ever render keys as literal strings.
|
||||
* TOML.stringify: Don't try to escape control characters in literal strings.
|
||||
|
||||
**FEATURES**
|
||||
|
||||
* New Export: TOML.stringify.value, for encoding a stand alone inline value as TOML would. This produces
|
||||
a TOML fragment, not a complete valid document.
|
||||
|
||||
# 1.5.6
|
||||
|
||||
* String literals are NOT supported as key names.
|
||||
* Accessing a shallower table after accessing it more deeply is ok and no longer crashes, eg:
|
||||
```toml
|
||||
[a.b]
|
||||
[a]
|
||||
```
|
||||
* Unicode characters in the reserved range now crash.
|
||||
* Empty bare keys, eg `[.abc]` or `[]` now crash.
|
||||
* Multiline backslash trimming supports CRs.
|
||||
* Multiline post quote trimming supports CRs.
|
||||
* Strings may not contain bare control chars (0x00-0x1f), except for \n, \r and \t.
|
||||
|
||||
# 1.5.5
|
||||
|
||||
* Yet MORE README fixes. 🙃
|
||||
|
||||
# 1.5.4
|
||||
|
||||
* README fix
|
||||
|
||||
# 1.5.3
|
||||
|
||||
* Benchmarks!
|
||||
* More tests!
|
||||
* More complete LICENSE information (some dev files are from other, MIT
|
||||
licensed, projects, this is now more explicitly documented.)
|
||||
|
||||
# 1.5.2
|
||||
|
||||
* parse: Arrays with mixed types now throw errors, per the spec.
|
||||
* parse: Fix a parser bug that would result in errors when trying to parse arrays of numbers or dates
|
||||
that were not separated by a space from the closing ].
|
||||
* parse: Fix a bug in the error pretty printer that resulted in errors on
|
||||
the first line not getting the pretty print treatment.
|
||||
* stringify: Fix long standing bug where an array of Numbers, some of which required
|
||||
decimals, would be emitted in a way that parsers would treat as mixed
|
||||
Integer and Float values. Now if any Numbers in an array must be
|
||||
represented with a decimal then all will be emitted such that parsers will
|
||||
understand them to be Float.
|
||||
|
||||
# 1.5.1
|
||||
|
||||
* README fix
|
||||
|
||||
# 1.5.0
|
||||
|
||||
* A brand new TOML parser, from scratch, that performs like `toml-j0.4`
|
||||
without the crashes and with vastly better error messages.
|
||||
* 100% test coverage for both the new parser and the existing stringifier. Some subtle bugs squashed!
|
||||
|
||||
# v1.4.2
|
||||
|
||||
* Revert fallback due to its having issues with the same files. (New plan
|
||||
will be to write my own.)
|
||||
|
||||
# v1.4.1
|
||||
|
||||
* Depend on both `toml` and `toml-j0.4` with fallback from the latter to the
|
||||
former when the latter crashes.
|
||||
|
||||
# v1.4.0
|
||||
|
||||
* Ducktype dates to make them compatible with `moment` and other `Date` replacements.
|
||||
|
||||
# v1.3.1
|
||||
|
||||
* Update docs with new toml module.
|
||||
|
||||
# v1.3.0
|
||||
|
||||
* Switch from `toml` to `toml-j0.4`, which is between 20x and 200x faster.
|
||||
(The larger the input, the faster it is compared to `toml`).
|
||||
|
||||
# v1.2.0
|
||||
|
||||
* Return null when passed in null as the top level object.
|
||||
* Detect and skip invalid dates and numbers
|
||||
|
||||
# v1.1.0
|
||||
|
||||
* toJSON transformations are now honored (for everything except Date objects, as JSON represents them as strings).
|
||||
* Undefined/null values no longer result in exceptions, they now just result in the associated key being elided.
|
||||
|
||||
# v1.0.1
|
||||
|
||||
* Initial release
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
Copyright (c) 2016, Rebecca Turner <me@re-becca.org>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
-299
@@ -1,299 +0,0 @@
|
||||
# @iarna/toml
|
||||
|
||||
Better TOML parsing and stringifying all in that familiar JSON interface.
|
||||
|
||||
[](https://coveralls.io/github/iarna/iarna-toml)
|
||||
|
||||
# ** TOML 1.0.0-rc.1 **
|
||||
|
||||
### TOML Spec Support
|
||||
|
||||
The most recent version as of 2019-04-21: [1.0.0-rc.1](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v1.0.0-rc.1.md)
|
||||
|
||||
### Other Versions
|
||||
|
||||
1.0.0-rc.1 parsers can load almost any TOML 0.4 and TOML 0.5 document, but
|
||||
TOML 1.0.0-rc.1 docs are not always compatible with TOML 0.4 and TOML 0.5
|
||||
parsers. If you're using this to generate TOML documents and you want an
|
||||
older parser to be able to read them you may want to use the
|
||||
[latest TOML 0.5 version of this module](https://www.npmjs.com/package/@iarna/toml/v/toml-0.5).
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
const TOML = require('@iarna/toml')
|
||||
const obj = TOML.parse(`[abc]
|
||||
foo = 123
|
||||
bar = [1,2,3]`)
|
||||
/* obj =
|
||||
{abc: {foo: 123, bar: [1,2,3]}}
|
||||
*/
|
||||
const str = TOML.stringify(obj)
|
||||
/* str =
|
||||
[abc]
|
||||
foo = 123
|
||||
bar = [ 1, 2, 3 ]
|
||||
*/
|
||||
```
|
||||
|
||||
Visit the project github [for more examples](https://github.com/iarna/iarna-toml/tree/latest/examples)!
|
||||
|
||||
|
||||
## Why @iarna/toml
|
||||
|
||||
* Support for TOML 1.0.0-rc.1!
|
||||
* Highly correct! Careful adherence to spec.
|
||||
* See [TOML-SPEC-SUPPORT](https://shared.by.re-becca.org/misc/TOML-SPEC-SUPPORT-v1.html) for a comparison of which TOML features
|
||||
are supported by the various Node.js TOML parsers.
|
||||
* Speedy! See benchmarks at end.
|
||||
* BigInt support on Node 10!
|
||||
* 100% test coverage.
|
||||
* Small parser bundle (if you use `@iarna/toml/parse-string`).
|
||||
* No deps.
|
||||
* Detailed and easy to read error messages‼
|
||||
|
||||
```console
|
||||
> TOML.parse(src)
|
||||
Error: Unexpected character, expecting string, number, datetime, boolean, inline array or inline table at row 6, col 5, pos 87:
|
||||
5: "abc\"" = { abc=123,def="abc" }
|
||||
6> foo=sdkfj
|
||||
^
|
||||
7:
|
||||
```
|
||||
|
||||
## TOML.parse(str) → Object [(example)](https://github.com/iarna/iarna-toml/blob/latest/examples/parse.js)
|
||||
|
||||
Also available with: `require('@iarna/toml/parse-string')`
|
||||
|
||||
Synchronously parse a TOML string and return an object.
|
||||
|
||||
|
||||
## TOML.stringify(obj) → String [(example)](https://github.com/iarna/iarna-toml/blob/latest/examples/stringify.js)
|
||||
|
||||
Also available with: `require('@iarna/toml/stringify)`
|
||||
|
||||
Serialize an object as TOML.
|
||||
|
||||
## [your-object].toJSON
|
||||
|
||||
If an object `TOML.stringify` is serializing has a `toJSON` method then it
|
||||
will call it to transform the object before serializing it. This matches
|
||||
the behavior of `JSON.stringify`.
|
||||
|
||||
The one exception to this is that `toJSON` is not called for `Date` objects
|
||||
because `JSON` represents dates as strings and TOML can represent them natively.
|
||||
|
||||
[`moment`](https://www.npmjs.com/package/moment) objects are treated the
|
||||
same as native `Date` objects, in this respect.
|
||||
|
||||
## TOML.stringify.value(obj) -> String
|
||||
|
||||
Also available with: `require('@iarna/toml/stringify').value`
|
||||
|
||||
Serialize a value as TOML would. This is a fragment and not a complete
|
||||
valid TOML document.
|
||||
|
||||
## Promises and Streaming
|
||||
|
||||
The parser provides alternative async and streaming interfaces, for times
|
||||
that you're working with really absurdly big TOML files and don't want to
|
||||
tie-up the event loop while it parses.
|
||||
|
||||
### TOML.parse.async(str[, opts]) → Promise(Object) [(example)](https://github.com/iarna/iarna-toml/blob/latest/examples/parse-async.js)
|
||||
|
||||
Also available with: `require('@iarna/toml/parse-async')`
|
||||
|
||||
`opts.blocksize` is the amount text to parser per pass through the event loop. Defaults to 40kb.
|
||||
|
||||
Asynchronously parse a TOML string and return a promise of the resulting object.
|
||||
|
||||
### TOML.parse.stream(readable) → Promise(Object) [(example)](https://github.com/iarna/iarna-toml/blob/latest/examples/parse-stream-readable.js)
|
||||
|
||||
Also available with: `require('@iarna/toml/parse-stream')`
|
||||
|
||||
Given a readable stream, parse it as it feeds us data. Return a promise of the resulting object.
|
||||
|
||||
### readable.pipe(TOML.parse.stream()) → Transform [(example)](https://github.com/iarna/iarna-toml/blob/latest/examples/parse-stream-through.js)
|
||||
|
||||
Also available with: `require('@iarna/toml/parse-stream')`
|
||||
|
||||
Returns a transform stream in object mode. When it completes, emit the
|
||||
resulting object. Only one object will ever be emitted.
|
||||
|
||||
## Lowlevel Interface [(example)](https://github.com/iarna/iarna-toml/blob/latest/examples/parse-lowlevel.js) [(example w/ parser debugging)](https://github.com/iarna/iarna-toml/blob/latest/examples/parse-lowlevel-debug.js)
|
||||
|
||||
You construct a parser object, per TOML file you want to process:
|
||||
|
||||
```js
|
||||
const TOMLParser = require('@iarna/toml/lib/toml-parser.js')
|
||||
const parser = new TOMLParser()
|
||||
```
|
||||
|
||||
Then you call the `parse` method for each chunk as you read them, or in a
|
||||
single call:
|
||||
|
||||
```js
|
||||
parser.parse(`hello = 'world'`)
|
||||
```
|
||||
|
||||
And finally, you call the `finish` method to complete parsing and retrieve
|
||||
the resulting object.
|
||||
|
||||
```js
|
||||
const data = parser.finish()
|
||||
```
|
||||
|
||||
Both the `parse` method and `finish` method will throw if they find a
|
||||
problem with the string they were given. Error objects thrown from the
|
||||
parser have `pos`, `line` and `col` attributes. `TOML.parse` adds a visual
|
||||
summary of where in the source string there were issues using
|
||||
`parse-pretty-error` and you can too:
|
||||
|
||||
```js
|
||||
const prettyError = require('./parse-pretty-error.js')
|
||||
const newErr = prettyError(err, sourceString)
|
||||
```
|
||||
|
||||
## What's Different
|
||||
|
||||
Version 3 of this module supports TOML 1.0.0-rc.1. Please see the
|
||||
[CHANGELOG](CHANGELOG.md#3.0.0) for details on exactly whats changed.
|
||||
|
||||
## TOML we can't do
|
||||
|
||||
* `-nan` is a valid TOML value and is converted into `NaN`. There is no way to
|
||||
produce `-nan` when stringifying. Stringification will produce positive `nan`.
|
||||
* Detecting and erroring on invalid utf8 documents: This is because Node's
|
||||
UTF8 processing converts invalid sequences into the placeholder character
|
||||
and does not have facilities for reporting these as errors instead. We
|
||||
_can_ detect the placeholder character, but it's valid to intentionally
|
||||
include them in documents, so erroring on them is not great.
|
||||
* On versions of Node < 10, very large Integer values will lose precision.
|
||||
On Node >=10, bigints are used.
|
||||
* Floating/local dates and times are still represented by JavaScript Date
|
||||
objects, which don't actually support these concepts. The objects
|
||||
returned have been modified so that you can determine what kind of thing
|
||||
they are (with `isFloating`, `isDate`, `isTime` properties) and that
|
||||
their ISO representation (via `toISOString`) are representative of their
|
||||
TOML value. They will correctly round trip if you pass them to
|
||||
`TOML.stringify`.
|
||||
* Binary, hexadecimal and octal values are converted to ordinary integers and
|
||||
will be decimal if you stringify them.
|
||||
|
||||
## Changes
|
||||
|
||||
I write a by hand, honest-to-god,
|
||||
[CHANGELOG](https://github.com/iarna/iarna-toml/blob/latest/CHANGELOG.md)
|
||||
for this project. It's a description of what went into a release that you
|
||||
the consumer of the module could care about, not a list of git commits, so
|
||||
please check it out!
|
||||
|
||||
## Benchmarks
|
||||
|
||||
You can run them yourself with:
|
||||
|
||||
```console
|
||||
$ npm run benchmark
|
||||
```
|
||||
|
||||
The results below are from my desktop using Node 13.13.0. The library
|
||||
versions tested were `@iarna/toml@3.0.0`, `toml-j0.4@1.1.1`, `toml@3.0.0`,
|
||||
`@sgarciac/bombadil@2.3.0`, `@ltd/j-toml@0.5.107`, and `fast-toml@0.5.4`.
|
||||
The speed value is megabytes-per-second that the parser can process of that
|
||||
document type. Bigger is better. The percentage after average results is
|
||||
the margin of error.
|
||||
|
||||
New here is fast-toml. fast-toml is very fast, for some datatypes, but it
|
||||
also is missing most error checking demanded by the spec. For 0.4, it is
|
||||
complete except for detail of multiline strings caught by the compliance
|
||||
tests. Its support for 0.5 is incomplete. Check out the
|
||||
[spec compliance](https://shared.by.re-becca.org/misc/TOML-SPEC-SUPPORT.html) doc
|
||||
for details.
|
||||
|
||||
As this table is getting a little wide, with how npm and github display it,
|
||||
you can also view it seperately in the
|
||||
[BENCHMARK](https://shared.by.re-becca.org/misc/BENCHMARK.html) document.
|
||||
|
||||
| | @iarna/<wbr>toml | toml-j0.4 | toml | @sgarciac/<wbr>bombadil | @ltd/<wbr>j-toml | fast-toml |
|
||||
| - | :---------: | :-------: | :--: | :----------------: | :---------: | :-------: |
|
||||
| **Overall** | 28MB/sec<br><small>0.55%</small> | - | - | - | - | - |
|
||||
| **01-small-doc-mixed-type-inline-array** | 5.3MB/sec<br><small>0.48%</small> | - | - | - | - | 12MB/sec<br><small>0.13%</small> |
|
||||
| **Spec Example: v0.4.0** | 25MB/sec<br><small>0.40%</small> | 9.9MB/sec<br><small>0.15%</small> | 0.9MB/sec<br><small>0.37%</small> | 1.3MB/sec<br><small>1.02%</small> | 28MB/sec<br><small>0.33%</small> | - |
|
||||
| **Spec Example: Hard Unicode** | 63MB/sec<br><small>0.47%</small> | 17MB/sec<br><small>0.21%</small> | 2MB/sec<br><small>0.25%</small> | 0.6MB/sec<br><small>0.47%</small> | 65MB/sec<br><small>0.27%</small> | 79MB/sec<br><small>0.09%</small> |
|
||||
| **Types: Array, Inline** | 7.2MB/sec<br><small>0.53%</small> | 4.1MB/sec<br><small>0.09%</small> | 0.1MB/sec<br><small>0.69%</small> | 1.4MB/sec<br><small>0.86%</small> | 10MB/sec<br><small>0.33%</small> | 9MB/sec<br><small>0.16%</small> |
|
||||
| **Types: Array** | 6.8MB/sec<br><small>0.09%</small> | 6.8MB/sec<br><small>0.20%</small> | 0.2MB/sec<br><small>0.81%</small> | 1.3MB/sec<br><small>0.82%</small> | 8.9MB/sec<br><small>0.36%</small> | 29MB/sec<br><small>0.16%</small> |
|
||||
| **Types: Boolean,** | 20MB/sec<br><small>0.22%</small> | 9.3MB/sec<br><small>0.29%</small> | 0.2MB/sec<br><small>0.91%</small> | 1.9MB/sec<br><small>0.85%</small> | 16MB/sec<br><small>0.29%</small> | 8.6MB/sec<br><small>0.22%</small> |
|
||||
| **Types: Datetime** | 17MB/sec<br><small>0.09%</small> | 11MB/sec<br><small>0.17%</small> | 0.3MB/sec<br><small>0.75%</small> | 1.6MB/sec<br><small>0.42%</small> | 9.8MB/sec<br><small>0.40%</small> | 6.5MB/sec<br><small>0.11%</small> |
|
||||
| **Types: Float** | 8.5MB/sec<br><small>0.29%</small> | 5.8MB/sec<br><small>0.33%</small> | 0.2MB/sec<br><small>0.91%</small> | 2.2MB/sec<br><small>0.91%</small> | 14MB/sec<br><small>0.25%</small> | 7.9MB/sec<br><small>0.33%</small> |
|
||||
| **Types: Int** | 5.8MB/sec<br><small>0.13%</small> | 4.5MB/sec<br><small>0.14%</small> | 0.1MB/sec<br><small>0.63%</small> | 1.5MB/sec<br><small>0.73%</small> | 9.8MB/sec<br><small>0.14%</small> | 8.1MB/sec<br><small>0.16%</small> |
|
||||
| **Types: Literal String, 7 char** | 25MB/sec<br><small>0.15%</small> | 8.3MB/sec<br><small>0.38%</small> | 0.2MB/sec<br><small>0.71%</small> | 2.3MB/sec<br><small>1.04%</small> | 23MB/sec<br><small>0.28%</small> | 14MB/sec<br><small>0.21%</small> |
|
||||
| **Types: Literal String, 92 char** | 44MB/sec<br><small>0.23%</small> | 12MB/sec<br><small>0.14%</small> | 0.3MB/sec<br><small>0.63%</small> | 13MB/sec<br><small>1.12%</small> | 100MB/sec<br><small>0.14%</small> | 77MB/sec<br><small>0.15%</small> |
|
||||
| **Types: Literal String, Multiline, 1079 char** | 23MB/sec<br><small>0.35%</small> | 7.2MB/sec<br><small>0.34%</small> | 0.9MB/sec<br><small>0.86%</small> | 47MB/sec<br><small>1.07%</small> | 380MB/sec<br><small>0.13%</small> | 641MB/sec<br><small>0.14%</small> |
|
||||
| **Types: Basic String, 7 char** | 25MB/sec<br><small>0.09%</small> | 7MB/sec<br><small>0.08%</small> | 0.2MB/sec<br><small>0.82%</small> | 2.3MB/sec<br><small>1.02%</small> | 15MB/sec<br><small>0.12%</small> | 13MB/sec<br><small>0.14%</small> |
|
||||
| **Types: Basic String, 92 char** | 44MB/sec<br><small>0.15%</small> | 8MB/sec<br><small>0.39%</small> | 0.1MB/sec<br><small>1.52%</small> | 12MB/sec<br><small>1.53%</small> | 70MB/sec<br><small>0.17%</small> | 71MB/sec<br><small>0.16%</small> |
|
||||
| **Types: Basic String, 1079 char** | 24MB/sec<br><small>0.36%</small> | 5.7MB/sec<br><small>0.12%</small> | 0.1MB/sec<br><small>3.65%</small> | 42MB/sec<br><small>1.67%</small> | 93MB/sec<br><small>0.13%</small> | 617MB/sec<br><small>0.14%</small> |
|
||||
| **Types: Table, Inline** | 9.4MB/sec<br><small>0.21%</small> | 5.2MB/sec<br><small>0.23%</small> | 0.1MB/sec<br><small>1.18%</small> | 1.4MB/sec<br><small>1.20%</small> | 8.5MB/sec<br><small>0.68%</small> | 8.7MB/sec<br><small>0.30%</small> |
|
||||
| **Types: Table** | 6.8MB/sec<br><small>0.13%</small> | 5.5MB/sec<br><small>0.22%</small> | 0.1MB/sec<br><small>1.10%</small> | 1.5MB/sec<br><small>1.05%</small> | 7.3MB/sec<br><small>0.54%</small> | 19MB/sec<br><small>0.21%</small> |
|
||||
| **Scaling: Array, Inline, 1000 elements** | 40MB/sec<br><small>0.27%</small> | 2.4MB/sec<br><small>0.20%</small> | 0.1MB/sec<br><small>1.90%</small> | 1.6MB/sec<br><small>1.14%</small> | 18MB/sec<br><small>0.16%</small> | 32MB/sec<br><small>0.12%</small> |
|
||||
| **Scaling: Array, Nested, 1000 deep** | 2MB/sec<br><small>0.17%</small> | 1.6MB/sec<br><small>0.09%</small> | 0.3MB/sec<br><small>0.62%</small> | - | 1.8MB/sec<br><small>0.80%</small> | 13MB/sec<br><small>0.19%</small> |
|
||||
| **Scaling: Literal String, 40kb** | 59MB/sec<br><small>0.26%</small> | 10MB/sec<br><small>0.14%</small> | 3MB/sec<br><small>0.91%</small> | 13MB/sec<br><small>0.40%</small> | 479MB/sec<br><small>0.25%</small> | 19kMB/sec<br><small>0.20%</small> |
|
||||
| **Scaling: Literal String, Multiline, 40kb** | 61MB/sec<br><small>0.23%</small> | 5.3MB/sec<br><small>0.30%</small> | 0.2MB/sec<br><small>1.78%</small> | 12MB/sec<br><small>0.55%</small> | 276MB/sec<br><small>0.16%</small> | 21kMB/sec<br><small>0.10%</small> |
|
||||
| **Scaling: Basic String, Multiline, 40kb** | 61MB/sec<br><small>0.21%</small> | 6MB/sec<br><small>0.40%</small> | 2.8MB/sec<br><small>0.75%</small> | 12MB/sec<br><small>0.60%</small> | 1kMB/sec<br><small>0.13%</small> | 27kMB/sec<br><small>0.14%</small> |
|
||||
| **Scaling: Basic String, 40kb** | 60MB/sec<br><small>0.13%</small> | 6.6MB/sec<br><small>0.13%</small> | 0.2MB/sec<br><small>1.67%</small> | 13MB/sec<br><small>0.30%</small> | 504MB/sec<br><small>0.26%</small> | 19kMB/sec<br><small>0.22%</small> |
|
||||
| **Scaling: Table, Inline, 1000 elements** | 26MB/sec<br><small>0.17%</small> | 7.3MB/sec<br><small>0.83%</small> | 0.3MB/sec<br><small>0.95%</small> | 2.5MB/sec<br><small>1.24%</small> | 5.4MB/sec<br><small>0.22%</small> | 13MB/sec<br><small>0.22%</small> |
|
||||
| **Scaling: Table, Inline, Nested, 1000 deep** | 8MB/sec<br><small>0.10%</small> | 5.2MB/sec<br><small>0.25%</small> | 0.1MB/sec<br><small>0.45%</small> | - | 3.1MB/sec<br><small>0.58%</small> | 10MB/sec<br><small>0.19%</small> |
|
||||
|
||||
## Tests
|
||||
|
||||
The test suite is maintained at 100% coverage: [](https://coveralls.io/github/iarna/iarna-toml)
|
||||
|
||||
The spec was carefully hand converted into a series of test framework
|
||||
independent (and mostly language independent) assertions, as pairs of TOML
|
||||
and YAML files. You can find those files here:
|
||||
[spec-test](https://github.com/iarna/toml-spec-test/).
|
||||
|
||||
Further tests were written to increase coverage to 100%, these may be more
|
||||
implementation specific, but they can be found in [coverage](https://github.com/iarna/iarna-toml/blob/latest/test/coverage.js) and
|
||||
[coverage-error](https://github.com/iarna/iarna-toml/blob/latest/test/coverage-error.js).
|
||||
|
||||
I've also written some quality assurance style tests, which don't contribute
|
||||
to coverage but do cover scenarios that could easily be problematic for some
|
||||
implementations can be found in:
|
||||
[test/qa.js](https://github.com/iarna/iarna-toml/blob/latest/test/qa.js) and
|
||||
[test/qa-error.js](https://github.com/iarna/iarna-toml/blob/latest/test/qa-error.js).
|
||||
|
||||
All of the official example files from the TOML spec are run through this
|
||||
parser and compared to the official YAML files when available. These files are from the TOML spec as of:
|
||||
[357a4ba6](https://github.com/toml-lang/toml/tree/357a4ba6782e48ff26e646780bab11c90ed0a7bc)
|
||||
and specifically are:
|
||||
|
||||
* [github.com/toml-lang/toml/tree/357a4ba6/examples](https://github.com/toml-lang/toml/tree/357a4ba6782e48ff26e646780bab11c90ed0a7bc/examples)
|
||||
* [github.com/toml-lang/toml/tree/357a4ba6/tests](https://github.com/toml-lang/toml/tree/357a4ba6782e48ff26e646780bab11c90ed0a7bc/tests)
|
||||
|
||||
The stringifier is tested by round-tripping these same files, asserting that
|
||||
`TOML.parse(sourcefile)` deepEqual
|
||||
`TOML.parse(TOML.stringify(TOML.parse(sourcefile))`. This is done in
|
||||
[test/roundtrip-examples.js](https://github.com/iarna/iarna-toml/blob/latest/test/round-tripping.js)
|
||||
There are also some tests written to complete coverage from stringification in:
|
||||
[test/stringify.js](https://github.com/iarna/iarna-toml/blob/latest/test/stringify.js)
|
||||
|
||||
Tests for the async and streaming interfaces are in [test/async.js](https://github.com/iarna/iarna-toml/blob/latest/test/async.js) and [test/stream.js](https://github.com/iarna/iarna-toml/blob/latest/test/stream.js) respectively.
|
||||
|
||||
Tests for the parser's debugging mode live in [test/devel.js](https://github.com/iarna/iarna-toml/blob/latest/test/devel.js).
|
||||
|
||||
And finally, many more stringification tests were borrowed from [@othiym23](https://github.com/othiym23)'s
|
||||
[toml-stream](https://npmjs.com/package/toml-stream) module. They were fetched as of
|
||||
[b6f1e26b572d49742d49fa6a6d11524d003441fa](https://github.com/othiym23/toml-stream/tree/b6f1e26b572d49742d49fa6a6d11524d003441fa/test) and live in
|
||||
[test/toml-stream](https://github.com/iarna/iarna-toml/blob/latest/test/toml-stream/).
|
||||
|
||||
## Improvements to make
|
||||
|
||||
* In stringify:
|
||||
* Any way to produce comments. As a JSON stand-in I'm not too worried
|
||||
about this. That said, a document orientated fork is something I'd like
|
||||
to look at eventually…
|
||||
* Stringification could use some work on its error reporting. It reports
|
||||
_what's_ wrong, but not where in your data structure it was.
|
||||
* Further optimize the parser:
|
||||
* There are some debugging assertions left in the main parser, these should be moved to a subclass.
|
||||
* Make the whole debugging parser thing work as a mixin instead of as a superclass.
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
import { Transform } from "stream";
|
||||
|
||||
type JsonValue = boolean | number | string | JsonMap | JsonArray | Date
|
||||
type JsonArray = JsonValue[]
|
||||
type AnyJson = boolean | number | string | JsonMap | Date | JsonArray | JsonArray[]
|
||||
|
||||
interface JsonMap {
|
||||
[key: string]: AnyJson;
|
||||
}
|
||||
|
||||
interface ParseOptions {
|
||||
/**
|
||||
* The amount text to parser per pass through the event loop. Defaults to 40kb (`40000`).
|
||||
*/
|
||||
blocksize: number
|
||||
}
|
||||
|
||||
interface FuncParse {
|
||||
/**
|
||||
* Synchronously parse a TOML string and return an object.
|
||||
*/
|
||||
(toml: string): JsonMap
|
||||
|
||||
/**
|
||||
* Asynchronously parse a TOML string and return a promise of the resulting object.
|
||||
*/
|
||||
async (toml: string, options?: ParseOptions): Promise<JsonMap>
|
||||
|
||||
/**
|
||||
* Given a readable stream, parse it as it feeds us data. Return a promise of the resulting object.
|
||||
*/
|
||||
stream (readable: NodeJS.ReadableStream): Promise<JsonMap>
|
||||
stream (): Transform
|
||||
}
|
||||
|
||||
interface FuncStringify {
|
||||
/**
|
||||
* Serialize an object as TOML.
|
||||
*
|
||||
* If an object `TOML.stringify` is serializing has a `toJSON` method
|
||||
* then it will call it to transform the object before serializing it.
|
||||
* This matches the behavior of JSON.stringify.
|
||||
*
|
||||
* The one exception to this is that `toJSON` is not called for `Date` objects
|
||||
* because JSON represents dates as strings and TOML can represent them natively.
|
||||
*
|
||||
* `moment` objects are treated the same as native `Date` objects, in this respect.
|
||||
*/
|
||||
(obj: JsonMap): string
|
||||
|
||||
/**
|
||||
* Serialize a value as TOML would. This is a fragment and not a complete valid TOML document.
|
||||
*/
|
||||
value (any: AnyJson): string
|
||||
}
|
||||
|
||||
export const parse: FuncParse
|
||||
export const stringify: FuncStringify
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
'use strict'
|
||||
const f = require('./format-num.js')
|
||||
const DateTime = global.Date
|
||||
|
||||
class Date extends DateTime {
|
||||
constructor (value) {
|
||||
super(value)
|
||||
this.isDate = true
|
||||
}
|
||||
toISOString () {
|
||||
return `${this.getUTCFullYear()}-${f(2, this.getUTCMonth() + 1)}-${f(2, this.getUTCDate())}`
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = value => {
|
||||
const date = new Date(value)
|
||||
/* istanbul ignore if */
|
||||
if (isNaN(date)) {
|
||||
throw new TypeError('Invalid Datetime')
|
||||
} else {
|
||||
return date
|
||||
}
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
'use strict'
|
||||
const f = require('./format-num.js')
|
||||
|
||||
class FloatingDateTime extends Date {
|
||||
constructor (value) {
|
||||
super(value + 'Z')
|
||||
this.isFloating = true
|
||||
}
|
||||
toISOString () {
|
||||
const date = `${this.getUTCFullYear()}-${f(2, this.getUTCMonth() + 1)}-${f(2, this.getUTCDate())}`
|
||||
const time = `${f(2, this.getUTCHours())}:${f(2, this.getUTCMinutes())}:${f(2, this.getUTCSeconds())}.${f(3, this.getUTCMilliseconds())}`
|
||||
return `${date}T${time}`
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = value => {
|
||||
const date = new FloatingDateTime(value)
|
||||
/* istanbul ignore if */
|
||||
if (isNaN(date)) {
|
||||
throw new TypeError('Invalid Datetime')
|
||||
} else {
|
||||
return date
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = value => {
|
||||
const date = new Date(value)
|
||||
/* istanbul ignore if */
|
||||
if (isNaN(date)) {
|
||||
throw new TypeError('Invalid Datetime')
|
||||
} else {
|
||||
return date
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
'use strict'
|
||||
const f = require('./format-num.js')
|
||||
|
||||
class Time extends Date {
|
||||
constructor (value) {
|
||||
super(`0000-01-01T${value}Z`)
|
||||
this.isTime = true
|
||||
}
|
||||
toISOString () {
|
||||
return `${f(2, this.getUTCHours())}:${f(2, this.getUTCMinutes())}:${f(2, this.getUTCSeconds())}.${f(3, this.getUTCMilliseconds())}`
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = value => {
|
||||
const date = new Time(value)
|
||||
/* istanbul ignore if */
|
||||
if (isNaN(date)) {
|
||||
throw new TypeError('Invalid Datetime')
|
||||
} else {
|
||||
return date
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = (d, num) => {
|
||||
num = String(num)
|
||||
while (num.length < d) num = '0' + num
|
||||
return num
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
'use strict'
|
||||
const Parser = require('./parser.js')
|
||||
const util = require('util')
|
||||
|
||||
const dump = _ => util.inspect(_, {colors: true, depth: 10, breakLength: Infinity})
|
||||
class DebugParser extends Parser {
|
||||
stateName (state) {
|
||||
// istanbul ignore next
|
||||
return (state.parser && state.parser.name) || state.name || ('anonymous')
|
||||
}
|
||||
runOne () {
|
||||
const callStack = this.stack.concat(this.state).map(_ => this.stateName(_)).join(' <- ')
|
||||
console.log('RUN', callStack, dump({line: this.line, col: this.col, char: this.char, ret: this.state.returned}))
|
||||
return super.runOne()
|
||||
}
|
||||
finish () {
|
||||
const obj = super.finish()
|
||||
// istanbul ignore if
|
||||
if (this.stack.length !== 0) {
|
||||
throw new Parser.Error('All states did not return by end of stream')
|
||||
}
|
||||
return obj
|
||||
}
|
||||
callStack () {
|
||||
const callStack = this.stack.map(_ => this.stateName(_)).join(' ').replace(/\S/g, ' ')
|
||||
return callStack ? callStack + ' ' : ''
|
||||
}
|
||||
next (fn) {
|
||||
console.log(' ', this.callStack(), 'NEXT', this.stateName(fn))
|
||||
return super.next(fn)
|
||||
}
|
||||
goto (fn) {
|
||||
console.log(' ', this.callStack(), 'GOTO', this.stateName(fn))
|
||||
super.next(fn)
|
||||
return false
|
||||
}
|
||||
call (fn, returnWith) {
|
||||
console.log(' ', this.callStack(), 'CALL', fn.name, returnWith ? '-> ' + returnWith.name : '')
|
||||
if (returnWith) super.next(returnWith)
|
||||
this.stack.push(this.state)
|
||||
this.state = {parser: fn, buf: '', returned: null}
|
||||
}
|
||||
callNow (fn, returnWith) {
|
||||
console.log(' ', this.callStack(), 'CALLNOW', fn.name, returnWith ? '-> ' + returnWith.name : '')
|
||||
if (returnWith) super.next(returnWith)
|
||||
this.stack.push(this.state)
|
||||
this.state = {parser: fn, buf: '', returned: null}
|
||||
return false
|
||||
}
|
||||
return (value) {
|
||||
console.log(' ', this.callStack(), 'RETURN')
|
||||
return super.return(value)
|
||||
}
|
||||
returnNow (value) {
|
||||
console.log(' ', this.callStack(), 'RETURNNOW')
|
||||
super.return(value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
module.exports = DebugParser
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
'use strict'
|
||||
const ParserEND = 0x110000
|
||||
class ParserError extends Error {
|
||||
/* istanbul ignore next */
|
||||
constructor (msg, filename, linenumber) {
|
||||
super('[ParserError] ' + msg, filename, linenumber)
|
||||
this.name = 'ParserError'
|
||||
this.code = 'ParserError'
|
||||
if (Error.captureStackTrace) Error.captureStackTrace(this, ParserError)
|
||||
}
|
||||
}
|
||||
class State {
|
||||
constructor (parser) {
|
||||
this.parser = parser
|
||||
this.buf = ''
|
||||
this.returned = null
|
||||
this.result = null
|
||||
this.resultTable = null
|
||||
this.resultArr = null
|
||||
}
|
||||
}
|
||||
class Parser {
|
||||
constructor () {
|
||||
this.pos = 0
|
||||
this.col = 0
|
||||
this.line = 0
|
||||
this.obj = {}
|
||||
this.ctx = this.obj
|
||||
this.stack = []
|
||||
this._buf = ''
|
||||
this.char = null
|
||||
this.ii = 0
|
||||
this.state = new State(this.parseStart)
|
||||
}
|
||||
|
||||
parse (str) {
|
||||
/* istanbul ignore next */
|
||||
if (str.length === 0 || str.length == null) return
|
||||
|
||||
this._buf = String(str)
|
||||
this.ii = -1
|
||||
this.char = -1
|
||||
let getNext
|
||||
while (getNext === false || this.nextChar()) {
|
||||
getNext = this.runOne()
|
||||
}
|
||||
this._buf = null
|
||||
}
|
||||
nextChar () {
|
||||
if (this.char === 0x0A) {
|
||||
++this.line
|
||||
this.col = -1
|
||||
}
|
||||
++this.ii
|
||||
this.char = this._buf.codePointAt(this.ii)
|
||||
++this.pos
|
||||
++this.col
|
||||
return this.haveBuffer()
|
||||
}
|
||||
haveBuffer () {
|
||||
return this.ii < this._buf.length
|
||||
}
|
||||
runOne () {
|
||||
return this.state.parser.call(this, this.state.returned)
|
||||
}
|
||||
finish () {
|
||||
this.char = ParserEND
|
||||
let last
|
||||
do {
|
||||
last = this.state.parser
|
||||
this.runOne()
|
||||
} while (this.state.parser !== last)
|
||||
|
||||
this.ctx = null
|
||||
this.state = null
|
||||
this._buf = null
|
||||
|
||||
return this.obj
|
||||
}
|
||||
next (fn) {
|
||||
/* istanbul ignore next */
|
||||
if (typeof fn !== 'function') throw new ParserError('Tried to set state to non-existent state: ' + JSON.stringify(fn))
|
||||
this.state.parser = fn
|
||||
}
|
||||
goto (fn) {
|
||||
this.next(fn)
|
||||
return this.runOne()
|
||||
}
|
||||
call (fn, returnWith) {
|
||||
if (returnWith) this.next(returnWith)
|
||||
this.stack.push(this.state)
|
||||
this.state = new State(fn)
|
||||
}
|
||||
callNow (fn, returnWith) {
|
||||
this.call(fn, returnWith)
|
||||
return this.runOne()
|
||||
}
|
||||
return (value) {
|
||||
/* istanbul ignore next */
|
||||
if (this.stack.length === 0) throw this.error(new ParserError('Stack underflow'))
|
||||
if (value === undefined) value = this.state.buf
|
||||
this.state = this.stack.pop()
|
||||
this.state.returned = value
|
||||
}
|
||||
returnNow (value) {
|
||||
this.return(value)
|
||||
return this.runOne()
|
||||
}
|
||||
consume () {
|
||||
/* istanbul ignore next */
|
||||
if (this.char === ParserEND) throw this.error(new ParserError('Unexpected end-of-buffer'))
|
||||
this.state.buf += this._buf[this.ii]
|
||||
}
|
||||
error (err) {
|
||||
err.line = this.line
|
||||
err.col = this.col
|
||||
err.pos = this.pos
|
||||
return err
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
parseStart () {
|
||||
throw new ParserError('Must declare a parseStart method')
|
||||
}
|
||||
}
|
||||
Parser.END = ParserEND
|
||||
Parser.Error = ParserError
|
||||
module.exports = Parser
|
||||
-1421
File diff suppressed because it is too large
Load Diff
-82
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"name": "@iarna/toml",
|
||||
"version": "3.0.0",
|
||||
"main": "toml.js",
|
||||
"scripts": {
|
||||
"test": "tap -J --100 test/*.js test/toml-stream/*.js",
|
||||
"benchmark": "node benchmark.js && node benchmark-per-file.js && node results2table.js",
|
||||
"prerelease": "npm t",
|
||||
"prepack": "rm -f *~",
|
||||
"postpublish": "git push --follow-tags",
|
||||
"pretest": "iarna-standard",
|
||||
"update-coc": "weallbehave -o . && git add CODE_OF_CONDUCT.md && git commit -m 'docs(coc): updated CODE_OF_CONDUCT.md'",
|
||||
"update-contrib": "weallcontribute -o . && git add CONTRIBUTING.md && git commit -m 'docs(contributing): updated CONTRIBUTING.md'",
|
||||
"setup-burntsushi-toml-suite": "[ -d test/burntsushi-toml-test ] || (git clone https://github.com/BurntSushi/toml-test test/burntsushi-toml-test; rimraf test/burntsushi-toml-test/.git/hooks/*); cd test/burntsushi-toml-test; git pull",
|
||||
"setup-iarna-toml-suite": "[ -d test/spec-test ] || (git clone https://github.com/iarna/toml-spec-tests -b 1.0.0-rc.1 test/spec-test; rimraf test/spec-test/.git/hooks/*); cd test/spec-test; git pull",
|
||||
"prepare": "npm run setup-burntsushi-toml-suite && npm run setup-iarna-toml-suite"
|
||||
},
|
||||
"keywords": [
|
||||
"toml",
|
||||
"toml-parser",
|
||||
"toml-stringifier",
|
||||
"parser",
|
||||
"stringifer",
|
||||
"emitter",
|
||||
"ini",
|
||||
"tomlify",
|
||||
"encoder",
|
||||
"decoder"
|
||||
],
|
||||
"author": "Rebecca Turner <me@re-becca.org> (http://re-becca.org/)",
|
||||
"license": "ISC",
|
||||
"description": "Better TOML parsing and stringifying all in that familiar JSON interface.",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@iarna/standard": "^2.0.2",
|
||||
"@ltd/j-toml": "^0.5.107",
|
||||
"@perl/qx": "^1.0.2",
|
||||
"@sgarciac/bombadil": "^2.3.0",
|
||||
"ansi": "^0.3.1",
|
||||
"approximate-number": "^2.0.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"fast-toml": "^0.5.4",
|
||||
"funstream": "^3.0.0",
|
||||
"glob": "^7.1.2",
|
||||
"js-yaml": "^3.13.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"tap": "^12.0.1",
|
||||
"toml": "^3.0.0",
|
||||
"toml-j0.4": "^1.1.1",
|
||||
"weallbehave": "*",
|
||||
"weallcontribute": "*"
|
||||
},
|
||||
"files": [
|
||||
"toml.js",
|
||||
"stringify.js",
|
||||
"parse.js",
|
||||
"parse-string.js",
|
||||
"parse-stream.js",
|
||||
"parse-async.js",
|
||||
"parse-pretty-error.js",
|
||||
"lib/parser.js",
|
||||
"lib/parser-debug.js",
|
||||
"lib/toml-parser.js",
|
||||
"lib/create-datetime.js",
|
||||
"lib/create-date.js",
|
||||
"lib/create-datetime-float.js",
|
||||
"lib/create-time.js",
|
||||
"lib/format-num.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/iarna/iarna-toml.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/iarna/iarna-toml/issues"
|
||||
},
|
||||
"homepage": "https://github.com/iarna/iarna-toml#readme"
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = parseAsync
|
||||
|
||||
const TOMLParser = require('./lib/toml-parser.js')
|
||||
const prettyError = require('./parse-pretty-error.js')
|
||||
|
||||
function parseAsync (str, opts) {
|
||||
if (!opts) opts = {}
|
||||
const index = 0
|
||||
const blocksize = opts.blocksize || 40960
|
||||
const parser = new TOMLParser()
|
||||
return new Promise((resolve, reject) => {
|
||||
setImmediate(parseAsyncNext, index, blocksize, resolve, reject)
|
||||
})
|
||||
function parseAsyncNext (index, blocksize, resolve, reject) {
|
||||
if (index >= str.length) {
|
||||
try {
|
||||
return resolve(parser.finish())
|
||||
} catch (err) {
|
||||
return reject(prettyError(err, str))
|
||||
}
|
||||
}
|
||||
try {
|
||||
parser.parse(str.slice(index, index + blocksize))
|
||||
setImmediate(parseAsyncNext, index + blocksize, blocksize, resolve, reject)
|
||||
} catch (err) {
|
||||
reject(prettyError(err, str))
|
||||
}
|
||||
}
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = prettyError
|
||||
|
||||
function prettyError (err, buf) {
|
||||
/* istanbul ignore if */
|
||||
if (err.pos == null || err.line == null) return err
|
||||
let msg = err.message
|
||||
msg += ` at row ${err.line + 1}, col ${err.col + 1}, pos ${err.pos}:\n`
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (buf && buf.split) {
|
||||
const lines = buf.split(/\n/)
|
||||
const lineNumWidth = String(Math.min(lines.length, err.line + 3)).length
|
||||
let linePadding = ' '
|
||||
while (linePadding.length < lineNumWidth) linePadding += ' '
|
||||
for (let ii = Math.max(0, err.line - 1); ii < Math.min(lines.length, err.line + 2); ++ii) {
|
||||
let lineNum = String(ii + 1)
|
||||
if (lineNum.length < lineNumWidth) lineNum = ' ' + lineNum
|
||||
if (err.line === ii) {
|
||||
msg += lineNum + '> ' + lines[ii] + '\n'
|
||||
msg += linePadding + ' '
|
||||
for (let hh = 0; hh < err.col; ++hh) {
|
||||
msg += ' '
|
||||
}
|
||||
msg += '^\n'
|
||||
} else {
|
||||
msg += lineNum + ': ' + lines[ii] + '\n'
|
||||
}
|
||||
}
|
||||
}
|
||||
err.message = msg + '\n'
|
||||
return err
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = parseStream
|
||||
|
||||
const stream = require('stream')
|
||||
const TOMLParser = require('./lib/toml-parser.js')
|
||||
|
||||
function parseStream (stm) {
|
||||
if (stm) {
|
||||
return parseReadable(stm)
|
||||
} else {
|
||||
return parseTransform(stm)
|
||||
}
|
||||
}
|
||||
|
||||
function parseReadable (stm) {
|
||||
const parser = new TOMLParser()
|
||||
stm.setEncoding('utf8')
|
||||
return new Promise((resolve, reject) => {
|
||||
let readable
|
||||
let ended = false
|
||||
let errored = false
|
||||
function finish () {
|
||||
ended = true
|
||||
if (readable) return
|
||||
try {
|
||||
resolve(parser.finish())
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
}
|
||||
function error (err) {
|
||||
errored = true
|
||||
reject(err)
|
||||
}
|
||||
stm.once('end', finish)
|
||||
stm.once('error', error)
|
||||
readNext()
|
||||
|
||||
function readNext () {
|
||||
readable = true
|
||||
let data
|
||||
while ((data = stm.read()) !== null) {
|
||||
try {
|
||||
parser.parse(data)
|
||||
} catch (err) {
|
||||
return error(err)
|
||||
}
|
||||
}
|
||||
readable = false
|
||||
/* istanbul ignore if */
|
||||
if (ended) return finish()
|
||||
/* istanbul ignore if */
|
||||
if (errored) return
|
||||
stm.once('readable', readNext)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function parseTransform () {
|
||||
const parser = new TOMLParser()
|
||||
return new stream.Transform({
|
||||
objectMode: true,
|
||||
transform (chunk, encoding, cb) {
|
||||
try {
|
||||
parser.parse(chunk.toString(encoding))
|
||||
} catch (err) {
|
||||
this.emit('error', err)
|
||||
}
|
||||
cb()
|
||||
},
|
||||
flush (cb) {
|
||||
try {
|
||||
this.push(parser.finish())
|
||||
} catch (err) {
|
||||
this.emit('error', err)
|
||||
}
|
||||
cb()
|
||||
}
|
||||
})
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = parseString
|
||||
|
||||
const TOMLParser = require('./lib/toml-parser.js')
|
||||
const prettyError = require('./parse-pretty-error.js')
|
||||
|
||||
function parseString (str) {
|
||||
if (global.Buffer && global.Buffer.isBuffer(str)) {
|
||||
str = str.toString('utf8')
|
||||
}
|
||||
const parser = new TOMLParser()
|
||||
try {
|
||||
parser.parse(str)
|
||||
return parser.finish()
|
||||
} catch (err) {
|
||||
throw prettyError(err, str)
|
||||
}
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = require('./parse-string.js')
|
||||
module.exports.async = require('./parse-async.js')
|
||||
module.exports.stream = require('./parse-stream.js')
|
||||
module.exports.prettyError = require('./parse-pretty-error.js')
|
||||
-270
@@ -1,270 +0,0 @@
|
||||
'use strict'
|
||||
module.exports = stringify
|
||||
module.exports.value = stringifyInline
|
||||
|
||||
function stringify (obj) {
|
||||
if (obj === null) throw typeError('null')
|
||||
if (obj === void (0)) throw typeError('undefined')
|
||||
if (typeof obj !== 'object') throw typeError(typeof obj)
|
||||
|
||||
if (typeof obj.toJSON === 'function') obj = obj.toJSON()
|
||||
if (obj == null) return null
|
||||
const type = tomlType(obj)
|
||||
if (type !== 'table') throw typeError(type)
|
||||
return stringifyObject('', '', obj)
|
||||
}
|
||||
|
||||
function typeError (type) {
|
||||
return new Error('Can only stringify objects, not ' + type)
|
||||
}
|
||||
|
||||
function getInlineKeys (obj) {
|
||||
return Object.keys(obj).filter(key => isInline(obj[key]))
|
||||
}
|
||||
function getComplexKeys (obj) {
|
||||
return Object.keys(obj).filter(key => !isInline(obj[key]))
|
||||
}
|
||||
|
||||
function toJSON (obj) {
|
||||
let nobj = Array.isArray(obj) ? [] : Object.prototype.hasOwnProperty.call(obj, '__proto__') ? {['__proto__']: undefined} : {}
|
||||
for (let prop of Object.keys(obj)) {
|
||||
if (obj[prop] && typeof obj[prop].toJSON === 'function' && !('toISOString' in obj[prop])) {
|
||||
nobj[prop] = obj[prop].toJSON()
|
||||
} else {
|
||||
nobj[prop] = obj[prop]
|
||||
}
|
||||
}
|
||||
return nobj
|
||||
}
|
||||
|
||||
function stringifyObject (prefix, indent, obj) {
|
||||
obj = toJSON(obj)
|
||||
let inlineKeys
|
||||
let complexKeys
|
||||
inlineKeys = getInlineKeys(obj)
|
||||
complexKeys = getComplexKeys(obj)
|
||||
const result = []
|
||||
const inlineIndent = indent || ''
|
||||
inlineKeys.forEach(key => {
|
||||
var type = tomlType(obj[key])
|
||||
if (type !== 'undefined' && type !== 'null') {
|
||||
result.push(inlineIndent + stringifyKey(key) + ' = ' + stringifyAnyInline(obj[key], true))
|
||||
}
|
||||
})
|
||||
if (result.length > 0) result.push('')
|
||||
const complexIndent = prefix && inlineKeys.length > 0 ? indent + ' ' : ''
|
||||
complexKeys.forEach(key => {
|
||||
result.push(stringifyComplex(prefix, complexIndent, key, obj[key]))
|
||||
})
|
||||
return result.join('\n')
|
||||
}
|
||||
|
||||
function isInline (value) {
|
||||
switch (tomlType(value)) {
|
||||
case 'undefined':
|
||||
case 'null':
|
||||
case 'integer':
|
||||
case 'nan':
|
||||
case 'float':
|
||||
case 'boolean':
|
||||
case 'string':
|
||||
case 'datetime':
|
||||
return true
|
||||
case 'array':
|
||||
return value.length === 0 || tomlType(value[0]) !== 'table'
|
||||
case 'table':
|
||||
return Object.keys(value).length === 0
|
||||
/* istanbul ignore next */
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function tomlType (value) {
|
||||
if (value === undefined) {
|
||||
return 'undefined'
|
||||
} else if (value === null) {
|
||||
return 'null'
|
||||
/* eslint-disable valid-typeof */
|
||||
} else if (typeof value === 'bigint' || (Number.isInteger(value) && !Object.is(value, -0))) {
|
||||
return 'integer'
|
||||
} else if (typeof value === 'number') {
|
||||
return 'float'
|
||||
} else if (typeof value === 'boolean') {
|
||||
return 'boolean'
|
||||
} else if (typeof value === 'string') {
|
||||
return 'string'
|
||||
} else if ('toISOString' in value) {
|
||||
return isNaN(value) ? 'undefined' : 'datetime'
|
||||
} else if (Array.isArray(value)) {
|
||||
return 'array'
|
||||
} else {
|
||||
return 'table'
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyKey (key) {
|
||||
const keyStr = String(key)
|
||||
if (/^[-A-Za-z0-9_]+$/.test(keyStr)) {
|
||||
return keyStr
|
||||
} else {
|
||||
return stringifyBasicString(keyStr)
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyBasicString (str) {
|
||||
return '"' + escapeString(str).replace(/"/g, '\\"') + '"'
|
||||
}
|
||||
|
||||
function stringifyLiteralString (str) {
|
||||
return "'" + str + "'"
|
||||
}
|
||||
|
||||
function numpad (num, str) {
|
||||
while (str.length < num) str = '0' + str
|
||||
return str
|
||||
}
|
||||
|
||||
function escapeString (str) {
|
||||
return str.replace(/\\/g, '\\\\')
|
||||
.replace(/[\b]/g, '\\b')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\f/g, '\\f')
|
||||
.replace(/\r/g, '\\r')
|
||||
/* eslint-disable no-control-regex */
|
||||
.replace(/([\u0000-\u001f\u007f])/, c => '\\u' + numpad(4, c.codePointAt(0).toString(16)))
|
||||
/* eslint-enable no-control-regex */
|
||||
}
|
||||
|
||||
function stringifyMultilineString (str) {
|
||||
let escaped = str.split(/\n/).map(str => {
|
||||
return escapeString(str).replace(/"(?="")/g, '\\"')
|
||||
}).join('\n')
|
||||
if (escaped.slice(-1) === '"') escaped += '\\\n'
|
||||
return '"""\n' + escaped + '"""'
|
||||
}
|
||||
|
||||
function stringifyAnyInline (value, multilineOk) {
|
||||
let type = tomlType(value)
|
||||
if (type === 'string') {
|
||||
if (multilineOk && /\n/.test(value)) {
|
||||
type = 'string-multiline'
|
||||
} else if (!/[\b\t\n\f\r']/.test(value) && /"/.test(value)) {
|
||||
type = 'string-literal'
|
||||
}
|
||||
}
|
||||
return stringifyInline(value, type)
|
||||
}
|
||||
|
||||
function stringifyInline (value, type) {
|
||||
/* istanbul ignore if */
|
||||
if (!type) type = tomlType(value)
|
||||
switch (type) {
|
||||
case 'string-multiline':
|
||||
return stringifyMultilineString(value)
|
||||
case 'string':
|
||||
return stringifyBasicString(value)
|
||||
case 'string-literal':
|
||||
return stringifyLiteralString(value)
|
||||
case 'integer':
|
||||
return stringifyInteger(value)
|
||||
case 'float':
|
||||
return stringifyFloat(value)
|
||||
case 'boolean':
|
||||
return stringifyBoolean(value)
|
||||
case 'datetime':
|
||||
return stringifyDatetime(value)
|
||||
case 'array':
|
||||
return stringifyInlineArray(value.filter(_ => tomlType(_) !== 'null' && tomlType(_) !== 'undefined' && tomlType(_) !== 'nan'))
|
||||
case 'table':
|
||||
return stringifyInlineTable(value)
|
||||
/* istanbul ignore next */
|
||||
default:
|
||||
throw typeError(type)
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyInteger (value) {
|
||||
/* eslint-disable security/detect-unsafe-regex */
|
||||
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, '_')
|
||||
}
|
||||
|
||||
function stringifyFloat (value) {
|
||||
if (value === Infinity) {
|
||||
return 'inf'
|
||||
} else if (value === -Infinity) {
|
||||
return '-inf'
|
||||
} else if (Object.is(value, NaN)) {
|
||||
return 'nan'
|
||||
} else if (Object.is(value, -0)) {
|
||||
return '-0.0'
|
||||
}
|
||||
const [int, dec] = String(value).split('.')
|
||||
return stringifyInteger(int) + '.' + dec
|
||||
}
|
||||
|
||||
function stringifyBoolean (value) {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
function stringifyDatetime (value) {
|
||||
return value.toISOString()
|
||||
}
|
||||
|
||||
function stringifyInlineArray (values) {
|
||||
values = toJSON(values)
|
||||
let result = '['
|
||||
const stringified = values.map(_ => stringifyInline(_))
|
||||
if (stringified.join(', ').length > 60 || /\n/.test(stringified)) {
|
||||
result += '\n ' + stringified.join(',\n ') + '\n'
|
||||
} else {
|
||||
result += ' ' + stringified.join(', ') + (stringified.length > 0 ? ' ' : '')
|
||||
}
|
||||
return result + ']'
|
||||
}
|
||||
|
||||
function stringifyInlineTable (value) {
|
||||
value = toJSON(value)
|
||||
const result = []
|
||||
Object.keys(value).forEach(key => {
|
||||
result.push(stringifyKey(key) + ' = ' + stringifyAnyInline(value[key], false))
|
||||
})
|
||||
return '{ ' + result.join(', ') + (result.length > 0 ? ' ' : '') + '}'
|
||||
}
|
||||
|
||||
function stringifyComplex (prefix, indent, key, value) {
|
||||
const valueType = tomlType(value)
|
||||
/* istanbul ignore else */
|
||||
if (valueType === 'array') {
|
||||
return stringifyArrayOfTables(prefix, indent, key, value)
|
||||
} else if (valueType === 'table') {
|
||||
return stringifyComplexTable(prefix, indent, key, value)
|
||||
} else {
|
||||
throw typeError(valueType)
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyArrayOfTables (prefix, indent, key, values) {
|
||||
values = toJSON(values)
|
||||
const firstValueType = tomlType(values[0])
|
||||
/* istanbul ignore if */
|
||||
if (firstValueType !== 'table') throw typeError(firstValueType)
|
||||
const fullKey = prefix + stringifyKey(key)
|
||||
let result = ''
|
||||
values.forEach(table => {
|
||||
if (result.length > 0) result += '\n'
|
||||
result += indent + '[[' + fullKey + ']]\n'
|
||||
result += stringifyObject(fullKey + '.', indent, table)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function stringifyComplexTable (prefix, indent, key, value) {
|
||||
const fullKey = prefix + stringifyKey(key)
|
||||
let result = ''
|
||||
if (getInlineKeys(value).length > 0) {
|
||||
result += indent + '[' + fullKey + ']\n'
|
||||
}
|
||||
return result + stringifyObject(fullKey + '.', indent, value)
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
'use strict'
|
||||
exports.parse = require('./parse.js')
|
||||
exports.stringify = require('./stringify.js')
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 Paul Miller (https://paulmillr.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-1009
File diff suppressed because it is too large
Load Diff
-19
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Utilities for short weierstrass curves, combined with noble-hashes.
|
||||
* @module
|
||||
*/
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { type CurveFn, type CurveType } from './abstract/weierstrass.ts';
|
||||
import type { CHash } from './utils.ts';
|
||||
/** connects noble-curves to noble-hashes */
|
||||
export declare function getHash(hash: CHash): {
|
||||
hash: CHash;
|
||||
};
|
||||
/** Same API as @noble/hashes, with ability to create curve with custom hash */
|
||||
export type CurveDef = Readonly<Omit<CurveType, 'hash'>>;
|
||||
export type CurveFnWithCreate = CurveFn & {
|
||||
create: (hash: CHash) => CurveFn;
|
||||
};
|
||||
/** @deprecated use new `weierstrass()` and `ecdsa()` methods */
|
||||
export declare function createCurve(curveDef: CurveDef, defHash: CHash): CurveFnWithCreate;
|
||||
//# sourceMappingURL=_shortw_utils.d.ts.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"_shortw_utils.d.ts","sourceRoot":"","sources":["src/_shortw_utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,sEAAsE;AACtE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,2BAA2B,CAAC;AACtF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,4CAA4C;AAC5C,wBAAgB,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAEpD;AACD,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AACzD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG;IAAE,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAA;CAAE,CAAC;AAE/E,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,GAAG,iBAAiB,CAGjF"}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getHash = getHash;
|
||||
exports.createCurve = createCurve;
|
||||
/**
|
||||
* Utilities for short weierstrass curves, combined with noble-hashes.
|
||||
* @module
|
||||
*/
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
const weierstrass_ts_1 = require("./abstract/weierstrass.js");
|
||||
/** connects noble-curves to noble-hashes */
|
||||
function getHash(hash) {
|
||||
return { hash };
|
||||
}
|
||||
/** @deprecated use new `weierstrass()` and `ecdsa()` methods */
|
||||
function createCurve(curveDef, defHash) {
|
||||
const create = (hash) => (0, weierstrass_ts_1.weierstrass)({ ...curveDef, hash: hash });
|
||||
return { ...create(defHash), create };
|
||||
}
|
||||
//# sourceMappingURL=_shortw_utils.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"_shortw_utils.js","sourceRoot":"","sources":["src/_shortw_utils.ts"],"names":[],"mappings":";;AASA,0BAEC;AAMD,kCAGC;AApBD;;;GAGG;AACH,sEAAsE;AACtE,8DAAsF;AAGtF,4CAA4C;AAC5C,SAAgB,OAAO,CAAC,IAAW;IACjC,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC;AAKD,gEAAgE;AAChE,SAAgB,WAAW,CAAC,QAAkB,EAAE,OAAc;IAC5D,MAAM,MAAM,GAAG,CAAC,IAAW,EAAW,EAAE,CAAC,IAAA,4BAAW,EAAC,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAClF,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC"}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
/**
|
||||
* BLS != BLS.
|
||||
* The file implements BLS (Boneh-Lynn-Shacham) signatures.
|
||||
* Used in both BLS (Barreto-Lynn-Scott) and BN (Barreto-Naehrig)
|
||||
* families of pairing-friendly curves.
|
||||
* Consists of two curves: G1 and G2:
|
||||
* - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4.
|
||||
* - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1
|
||||
* - Gt, created by bilinear (ate) pairing e(G1, G2), consists of p-th roots of unity in
|
||||
* Fq^k where k is embedding degree. Only degree 12 is currently supported, 24 is not.
|
||||
* Pairing is used to aggregate and verify signatures.
|
||||
* There are two modes of operation:
|
||||
* - Long signatures: X-byte keys + 2X-byte sigs (G1 keys + G2 sigs).
|
||||
* - Short signatures: 2X-byte keys + X-byte sigs (G2 keys + G1 sigs).
|
||||
* @module
|
||||
**/
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { type CHash, type Hex, type PrivKey } from '../utils.ts';
|
||||
import { type H2CHasher, type H2CHashOpts, type H2COpts, type htfBasicOpts, type MapToCurve } from './hash-to-curve.ts';
|
||||
import { type IField } from './modular.ts';
|
||||
import type { Fp12, Fp12Bls, Fp2, Fp2Bls, Fp6Bls } from './tower.ts';
|
||||
import { type CurvePointsRes, type CurvePointsType, type WeierstrassPoint, type WeierstrassPointCons } from './weierstrass.ts';
|
||||
type Fp = bigint;
|
||||
export type TwistType = 'multiplicative' | 'divisive';
|
||||
export type ShortSignatureCoder<Fp> = {
|
||||
fromBytes(bytes: Uint8Array): WeierstrassPoint<Fp>;
|
||||
fromHex(hex: Hex): WeierstrassPoint<Fp>;
|
||||
toBytes(point: WeierstrassPoint<Fp>): Uint8Array;
|
||||
toHex(point: WeierstrassPoint<Fp>): string;
|
||||
/** @deprecated use `toBytes` */
|
||||
toRawBytes(point: WeierstrassPoint<Fp>): Uint8Array;
|
||||
};
|
||||
export type SignatureCoder<Fp> = {
|
||||
fromBytes(bytes: Uint8Array): WeierstrassPoint<Fp>;
|
||||
fromHex(hex: Hex): WeierstrassPoint<Fp>;
|
||||
toBytes(point: WeierstrassPoint<Fp>): Uint8Array;
|
||||
toHex(point: WeierstrassPoint<Fp>): string;
|
||||
/** @deprecated use `toBytes` */
|
||||
toRawBytes(point: WeierstrassPoint<Fp>): Uint8Array;
|
||||
};
|
||||
export type BlsFields = {
|
||||
Fp: IField<Fp>;
|
||||
Fr: IField<bigint>;
|
||||
Fp2: Fp2Bls;
|
||||
Fp6: Fp6Bls;
|
||||
Fp12: Fp12Bls;
|
||||
};
|
||||
export type PostPrecomputePointAddFn = (Rx: Fp2, Ry: Fp2, Rz: Fp2, Qx: Fp2, Qy: Fp2) => {
|
||||
Rx: Fp2;
|
||||
Ry: Fp2;
|
||||
Rz: Fp2;
|
||||
};
|
||||
export type PostPrecomputeFn = (Rx: Fp2, Ry: Fp2, Rz: Fp2, Qx: Fp2, Qy: Fp2, pointAdd: PostPrecomputePointAddFn) => void;
|
||||
export type BlsPairing = {
|
||||
Fp12: Fp12Bls;
|
||||
calcPairingPrecomputes: (p: WeierstrassPoint<Fp2>) => Precompute;
|
||||
millerLoopBatch: (pairs: [Precompute, Fp, Fp][]) => Fp12;
|
||||
pairing: (P: WeierstrassPoint<Fp>, Q: WeierstrassPoint<Fp2>, withFinalExponent?: boolean) => Fp12;
|
||||
pairingBatch: (pairs: {
|
||||
g1: WeierstrassPoint<Fp>;
|
||||
g2: WeierstrassPoint<Fp2>;
|
||||
}[], withFinalExponent?: boolean) => Fp12;
|
||||
};
|
||||
export type BlsPairingParams = {
|
||||
ateLoopSize: bigint;
|
||||
xNegative: boolean;
|
||||
twistType: TwistType;
|
||||
postPrecompute?: PostPrecomputeFn;
|
||||
};
|
||||
export type CurveType = {
|
||||
G1: CurvePointsType<Fp> & {
|
||||
ShortSignature: SignatureCoder<Fp>;
|
||||
mapToCurve: MapToCurve<Fp>;
|
||||
htfDefaults: H2COpts;
|
||||
};
|
||||
G2: CurvePointsType<Fp2> & {
|
||||
Signature: SignatureCoder<Fp2>;
|
||||
mapToCurve: MapToCurve<Fp2>;
|
||||
htfDefaults: H2COpts;
|
||||
};
|
||||
fields: BlsFields;
|
||||
params: {
|
||||
ateLoopSize: BlsPairingParams['ateLoopSize'];
|
||||
xNegative: BlsPairingParams['xNegative'];
|
||||
r: bigint;
|
||||
twistType: BlsPairingParams['twistType'];
|
||||
};
|
||||
htfDefaults: H2COpts;
|
||||
hash: CHash;
|
||||
randomBytes?: (bytesLength?: number) => Uint8Array;
|
||||
postPrecompute?: PostPrecomputeFn;
|
||||
};
|
||||
type PrecomputeSingle = [Fp2, Fp2, Fp2][];
|
||||
type Precompute = PrecomputeSingle[];
|
||||
/**
|
||||
* BLS consists of two curves: G1 and G2:
|
||||
* - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4.
|
||||
* - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1
|
||||
*/
|
||||
export interface BLSCurvePair {
|
||||
longSignatures: BLSSigs<bigint, Fp2>;
|
||||
shortSignatures: BLSSigs<Fp2, bigint>;
|
||||
millerLoopBatch: BlsPairing['millerLoopBatch'];
|
||||
pairing: BlsPairing['pairing'];
|
||||
pairingBatch: BlsPairing['pairingBatch'];
|
||||
G1: {
|
||||
Point: WeierstrassPointCons<bigint>;
|
||||
} & H2CHasher<Fp>;
|
||||
G2: {
|
||||
Point: WeierstrassPointCons<Fp2>;
|
||||
} & H2CHasher<Fp2>;
|
||||
fields: {
|
||||
Fp: IField<Fp>;
|
||||
Fp2: Fp2Bls;
|
||||
Fp6: Fp6Bls;
|
||||
Fp12: Fp12Bls;
|
||||
Fr: IField<bigint>;
|
||||
};
|
||||
utils: {
|
||||
randomSecretKey: () => Uint8Array;
|
||||
/** @deprecated use randomSecretKey */
|
||||
randomPrivateKey: () => Uint8Array;
|
||||
calcPairingPrecomputes: BlsPairing['calcPairingPrecomputes'];
|
||||
};
|
||||
}
|
||||
export type CurveFn = BLSCurvePair & {
|
||||
/** @deprecated use `longSignatures.getPublicKey` */
|
||||
getPublicKey: (secretKey: PrivKey) => Uint8Array;
|
||||
/** @deprecated use `shortSignatures.getPublicKey` */
|
||||
getPublicKeyForShortSignatures: (secretKey: PrivKey) => Uint8Array;
|
||||
/** @deprecated use `longSignatures.sign` */
|
||||
sign: {
|
||||
(message: Hex, secretKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
|
||||
(message: WeierstrassPoint<Fp2>, secretKey: PrivKey, htfOpts?: htfBasicOpts): WeierstrassPoint<Fp2>;
|
||||
};
|
||||
/** @deprecated use `shortSignatures.sign` */
|
||||
signShortSignature: {
|
||||
(message: Hex, secretKey: PrivKey, htfOpts?: htfBasicOpts): Uint8Array;
|
||||
(message: WeierstrassPoint<Fp>, secretKey: PrivKey, htfOpts?: htfBasicOpts): WeierstrassPoint<Fp>;
|
||||
};
|
||||
/** @deprecated use `longSignatures.verify` */
|
||||
verify: (signature: Hex | WeierstrassPoint<Fp2>, message: Hex | WeierstrassPoint<Fp2>, publicKey: Hex | WeierstrassPoint<Fp>, htfOpts?: htfBasicOpts) => boolean;
|
||||
/** @deprecated use `shortSignatures.verify` */
|
||||
verifyShortSignature: (signature: Hex | WeierstrassPoint<Fp>, message: Hex | WeierstrassPoint<Fp>, publicKey: Hex | WeierstrassPoint<Fp2>, htfOpts?: htfBasicOpts) => boolean;
|
||||
verifyBatch: (signature: Hex | WeierstrassPoint<Fp2>, messages: (Hex | WeierstrassPoint<Fp2>)[], publicKeys: (Hex | WeierstrassPoint<Fp>)[], htfOpts?: htfBasicOpts) => boolean;
|
||||
/** @deprecated use `longSignatures.aggregatePublicKeys` */
|
||||
aggregatePublicKeys: {
|
||||
(publicKeys: Hex[]): Uint8Array;
|
||||
(publicKeys: WeierstrassPoint<Fp>[]): WeierstrassPoint<Fp>;
|
||||
};
|
||||
/** @deprecated use `longSignatures.aggregateSignatures` */
|
||||
aggregateSignatures: {
|
||||
(signatures: Hex[]): Uint8Array;
|
||||
(signatures: WeierstrassPoint<Fp2>[]): WeierstrassPoint<Fp2>;
|
||||
};
|
||||
/** @deprecated use `shortSignatures.aggregateSignatures` */
|
||||
aggregateShortSignatures: {
|
||||
(signatures: Hex[]): Uint8Array;
|
||||
(signatures: WeierstrassPoint<Fp>[]): WeierstrassPoint<Fp>;
|
||||
};
|
||||
G1: CurvePointsRes<Fp> & H2CHasher<Fp>;
|
||||
G2: CurvePointsRes<Fp2> & H2CHasher<Fp2>;
|
||||
/** @deprecated use `longSignatures.Signature` */
|
||||
Signature: SignatureCoder<Fp2>;
|
||||
/** @deprecated use `shortSignatures.Signature` */
|
||||
ShortSignature: ShortSignatureCoder<Fp>;
|
||||
params: {
|
||||
ateLoopSize: bigint;
|
||||
r: bigint;
|
||||
twistType: TwistType;
|
||||
/** @deprecated */
|
||||
G1b: bigint;
|
||||
/** @deprecated */
|
||||
G2b: Fp2;
|
||||
};
|
||||
};
|
||||
type BLSInput = Hex | Uint8Array;
|
||||
export interface BLSSigs<P, S> {
|
||||
getPublicKey(secretKey: PrivKey): WeierstrassPoint<P>;
|
||||
sign(hashedMessage: WeierstrassPoint<S>, secretKey: PrivKey): WeierstrassPoint<S>;
|
||||
verify(signature: WeierstrassPoint<S> | BLSInput, message: WeierstrassPoint<S>, publicKey: WeierstrassPoint<P> | BLSInput): boolean;
|
||||
verifyBatch: (signature: WeierstrassPoint<S> | BLSInput, messages: WeierstrassPoint<S>[], publicKeys: (WeierstrassPoint<P> | BLSInput)[]) => boolean;
|
||||
aggregatePublicKeys(publicKeys: (WeierstrassPoint<P> | BLSInput)[]): WeierstrassPoint<P>;
|
||||
aggregateSignatures(signatures: (WeierstrassPoint<S> | BLSInput)[]): WeierstrassPoint<S>;
|
||||
hash(message: Uint8Array, DST?: string | Uint8Array, hashOpts?: H2CHashOpts): WeierstrassPoint<S>;
|
||||
Signature: SignatureCoder<S>;
|
||||
}
|
||||
export declare function bls(CURVE: CurveType): CurveFn;
|
||||
export {};
|
||||
//# sourceMappingURL=bls.d.ts.map
|
||||
-1
File diff suppressed because one or more lines are too long
-411
@@ -1,411 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.bls = bls;
|
||||
/**
|
||||
* BLS != BLS.
|
||||
* The file implements BLS (Boneh-Lynn-Shacham) signatures.
|
||||
* Used in both BLS (Barreto-Lynn-Scott) and BN (Barreto-Naehrig)
|
||||
* families of pairing-friendly curves.
|
||||
* Consists of two curves: G1 and G2:
|
||||
* - G1 is a subgroup of (x, y) E(Fq) over y² = x³ + 4.
|
||||
* - G2 is a subgroup of ((x₁, x₂+i), (y₁, y₂+i)) E(Fq²) over y² = x³ + 4(1 + i) where i is √-1
|
||||
* - Gt, created by bilinear (ate) pairing e(G1, G2), consists of p-th roots of unity in
|
||||
* Fq^k where k is embedding degree. Only degree 12 is currently supported, 24 is not.
|
||||
* Pairing is used to aggregate and verify signatures.
|
||||
* There are two modes of operation:
|
||||
* - Long signatures: X-byte keys + 2X-byte sigs (G1 keys + G2 sigs).
|
||||
* - Short signatures: 2X-byte keys + X-byte sigs (G2 keys + G1 sigs).
|
||||
* @module
|
||||
**/
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
const utils_ts_1 = require("../utils.js");
|
||||
const curve_ts_1 = require("./curve.js");
|
||||
const hash_to_curve_ts_1 = require("./hash-to-curve.js");
|
||||
const modular_ts_1 = require("./modular.js");
|
||||
const weierstrass_ts_1 = require("./weierstrass.js");
|
||||
// prettier-ignore
|
||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3);
|
||||
// Not used with BLS12-381 (no sequential `11` in X). Useful for other curves.
|
||||
function NAfDecomposition(a) {
|
||||
const res = [];
|
||||
// a>1 because of marker bit
|
||||
for (; a > _1n; a >>= _1n) {
|
||||
if ((a & _1n) === _0n)
|
||||
res.unshift(0);
|
||||
else if ((a & _3n) === _3n) {
|
||||
res.unshift(-1);
|
||||
a += _1n;
|
||||
}
|
||||
else
|
||||
res.unshift(1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
function aNonEmpty(arr) {
|
||||
if (!Array.isArray(arr) || arr.length === 0)
|
||||
throw new Error('expected non-empty array');
|
||||
}
|
||||
// This should be enough for bn254, no need to export full stuff?
|
||||
function createBlsPairing(fields, G1, G2, params) {
|
||||
const { Fp2, Fp12 } = fields;
|
||||
const { twistType, ateLoopSize, xNegative, postPrecompute } = params;
|
||||
// Applies sparse multiplication as line function
|
||||
let lineFunction;
|
||||
if (twistType === 'multiplicative') {
|
||||
lineFunction = (c0, c1, c2, f, Px, Py) => Fp12.mul014(f, c0, Fp2.mul(c1, Px), Fp2.mul(c2, Py));
|
||||
}
|
||||
else if (twistType === 'divisive') {
|
||||
// NOTE: it should be [c0, c1, c2], but we use different order here to reduce complexity of
|
||||
// precompute calculations.
|
||||
lineFunction = (c0, c1, c2, f, Px, Py) => Fp12.mul034(f, Fp2.mul(c2, Py), Fp2.mul(c1, Px), c0);
|
||||
}
|
||||
else
|
||||
throw new Error('bls: unknown twist type');
|
||||
const Fp2div2 = Fp2.div(Fp2.ONE, Fp2.mul(Fp2.ONE, _2n));
|
||||
function pointDouble(ell, Rx, Ry, Rz) {
|
||||
const t0 = Fp2.sqr(Ry); // Ry²
|
||||
const t1 = Fp2.sqr(Rz); // Rz²
|
||||
const t2 = Fp2.mulByB(Fp2.mul(t1, _3n)); // 3 * T1 * B
|
||||
const t3 = Fp2.mul(t2, _3n); // 3 * T2
|
||||
const t4 = Fp2.sub(Fp2.sub(Fp2.sqr(Fp2.add(Ry, Rz)), t1), t0); // (Ry + Rz)² - T1 - T0
|
||||
const c0 = Fp2.sub(t2, t0); // T2 - T0 (i)
|
||||
const c1 = Fp2.mul(Fp2.sqr(Rx), _3n); // 3 * Rx²
|
||||
const c2 = Fp2.neg(t4); // -T4 (-h)
|
||||
ell.push([c0, c1, c2]);
|
||||
Rx = Fp2.mul(Fp2.mul(Fp2.mul(Fp2.sub(t0, t3), Rx), Ry), Fp2div2); // ((T0 - T3) * Rx * Ry) / 2
|
||||
Ry = Fp2.sub(Fp2.sqr(Fp2.mul(Fp2.add(t0, t3), Fp2div2)), Fp2.mul(Fp2.sqr(t2), _3n)); // ((T0 + T3) / 2)² - 3 * T2²
|
||||
Rz = Fp2.mul(t0, t4); // T0 * T4
|
||||
return { Rx, Ry, Rz };
|
||||
}
|
||||
function pointAdd(ell, Rx, Ry, Rz, Qx, Qy) {
|
||||
// Addition
|
||||
const t0 = Fp2.sub(Ry, Fp2.mul(Qy, Rz)); // Ry - Qy * Rz
|
||||
const t1 = Fp2.sub(Rx, Fp2.mul(Qx, Rz)); // Rx - Qx * Rz
|
||||
const c0 = Fp2.sub(Fp2.mul(t0, Qx), Fp2.mul(t1, Qy)); // T0 * Qx - T1 * Qy == Ry * Qx - Rx * Qy
|
||||
const c1 = Fp2.neg(t0); // -T0 == Qy * Rz - Ry
|
||||
const c2 = t1; // == Rx - Qx * Rz
|
||||
ell.push([c0, c1, c2]);
|
||||
const t2 = Fp2.sqr(t1); // T1²
|
||||
const t3 = Fp2.mul(t2, t1); // T2 * T1
|
||||
const t4 = Fp2.mul(t2, Rx); // T2 * Rx
|
||||
const t5 = Fp2.add(Fp2.sub(t3, Fp2.mul(t4, _2n)), Fp2.mul(Fp2.sqr(t0), Rz)); // T3 - 2 * T4 + T0² * Rz
|
||||
Rx = Fp2.mul(t1, t5); // T1 * T5
|
||||
Ry = Fp2.sub(Fp2.mul(Fp2.sub(t4, t5), t0), Fp2.mul(t3, Ry)); // (T4 - T5) * T0 - T3 * Ry
|
||||
Rz = Fp2.mul(Rz, t3); // Rz * T3
|
||||
return { Rx, Ry, Rz };
|
||||
}
|
||||
// Pre-compute coefficients for sparse multiplication
|
||||
// Point addition and point double calculations is reused for coefficients
|
||||
// pointAdd happens only if bit set, so wNAF is reasonable. Unfortunately we cannot combine
|
||||
// add + double in windowed precomputes here, otherwise it would be single op (since X is static)
|
||||
const ATE_NAF = NAfDecomposition(ateLoopSize);
|
||||
const calcPairingPrecomputes = (0, utils_ts_1.memoized)((point) => {
|
||||
const p = point;
|
||||
const { x, y } = p.toAffine();
|
||||
// prettier-ignore
|
||||
const Qx = x, Qy = y, negQy = Fp2.neg(y);
|
||||
// prettier-ignore
|
||||
let Rx = Qx, Ry = Qy, Rz = Fp2.ONE;
|
||||
const ell = [];
|
||||
for (const bit of ATE_NAF) {
|
||||
const cur = [];
|
||||
({ Rx, Ry, Rz } = pointDouble(cur, Rx, Ry, Rz));
|
||||
if (bit)
|
||||
({ Rx, Ry, Rz } = pointAdd(cur, Rx, Ry, Rz, Qx, bit === -1 ? negQy : Qy));
|
||||
ell.push(cur);
|
||||
}
|
||||
if (postPrecompute) {
|
||||
const last = ell[ell.length - 1];
|
||||
postPrecompute(Rx, Ry, Rz, Qx, Qy, pointAdd.bind(null, last));
|
||||
}
|
||||
return ell;
|
||||
});
|
||||
function millerLoopBatch(pairs, withFinalExponent = false) {
|
||||
let f12 = Fp12.ONE;
|
||||
if (pairs.length) {
|
||||
const ellLen = pairs[0][0].length;
|
||||
for (let i = 0; i < ellLen; i++) {
|
||||
f12 = Fp12.sqr(f12); // This allows us to do sqr only one time for all pairings
|
||||
// NOTE: we apply multiple pairings in parallel here
|
||||
for (const [ell, Px, Py] of pairs) {
|
||||
for (const [c0, c1, c2] of ell[i])
|
||||
f12 = lineFunction(c0, c1, c2, f12, Px, Py);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xNegative)
|
||||
f12 = Fp12.conjugate(f12);
|
||||
return withFinalExponent ? Fp12.finalExponentiate(f12) : f12;
|
||||
}
|
||||
// Calculates product of multiple pairings
|
||||
// This up to x2 faster than just `map(({g1, g2})=>pairing({g1,g2}))`
|
||||
function pairingBatch(pairs, withFinalExponent = true) {
|
||||
const res = [];
|
||||
// Cache precomputed toAffine for all points
|
||||
(0, curve_ts_1.normalizeZ)(G1, pairs.map(({ g1 }) => g1));
|
||||
(0, curve_ts_1.normalizeZ)(G2, pairs.map(({ g2 }) => g2));
|
||||
for (const { g1, g2 } of pairs) {
|
||||
if (g1.is0() || g2.is0())
|
||||
throw new Error('pairing is not available for ZERO point');
|
||||
// This uses toAffine inside
|
||||
g1.assertValidity();
|
||||
g2.assertValidity();
|
||||
const Qa = g1.toAffine();
|
||||
res.push([calcPairingPrecomputes(g2), Qa.x, Qa.y]);
|
||||
}
|
||||
return millerLoopBatch(res, withFinalExponent);
|
||||
}
|
||||
// Calculates bilinear pairing
|
||||
function pairing(Q, P, withFinalExponent = true) {
|
||||
return pairingBatch([{ g1: Q, g2: P }], withFinalExponent);
|
||||
}
|
||||
return {
|
||||
Fp12, // NOTE: we re-export Fp12 here because pairing results are Fp12!
|
||||
millerLoopBatch,
|
||||
pairing,
|
||||
pairingBatch,
|
||||
calcPairingPrecomputes,
|
||||
};
|
||||
}
|
||||
function createBlsSig(blsPairing, PubCurve, SigCurve, SignatureCoder, isSigG1) {
|
||||
const { Fp12, pairingBatch } = blsPairing;
|
||||
function normPub(point) {
|
||||
return point instanceof PubCurve.Point ? point : PubCurve.Point.fromHex(point);
|
||||
}
|
||||
function normSig(point) {
|
||||
return point instanceof SigCurve.Point ? point : SigCurve.Point.fromHex(point);
|
||||
}
|
||||
function amsg(m) {
|
||||
if (!(m instanceof SigCurve.Point))
|
||||
throw new Error(`expected valid message hashed to ${!isSigG1 ? 'G2' : 'G1'} curve`);
|
||||
return m;
|
||||
}
|
||||
// What matters here is what point pairing API accepts as G1 or G2, not actual size or names
|
||||
const pair = !isSigG1
|
||||
? (a, b) => ({ g1: a, g2: b })
|
||||
: (a, b) => ({ g1: b, g2: a });
|
||||
return {
|
||||
// P = pk x G
|
||||
getPublicKey(secretKey) {
|
||||
// TODO: replace with
|
||||
// const sec = PubCurve.Point.Fn.fromBytes(secretKey);
|
||||
const sec = (0, weierstrass_ts_1._normFnElement)(PubCurve.Point.Fn, secretKey);
|
||||
return PubCurve.Point.BASE.multiply(sec);
|
||||
},
|
||||
// S = pk x H(m)
|
||||
sign(message, secretKey, unusedArg) {
|
||||
if (unusedArg != null)
|
||||
throw new Error('sign() expects 2 arguments');
|
||||
// TODO: replace with
|
||||
// PubCurve.Point.Fn.fromBytes(secretKey)
|
||||
const sec = (0, weierstrass_ts_1._normFnElement)(PubCurve.Point.Fn, secretKey);
|
||||
amsg(message).assertValidity();
|
||||
return message.multiply(sec);
|
||||
},
|
||||
// Checks if pairing of public key & hash is equal to pairing of generator & signature.
|
||||
// e(P, H(m)) == e(G, S)
|
||||
// e(S, G) == e(H(m), P)
|
||||
verify(signature, message, publicKey, unusedArg) {
|
||||
if (unusedArg != null)
|
||||
throw new Error('verify() expects 3 arguments');
|
||||
signature = normSig(signature);
|
||||
publicKey = normPub(publicKey);
|
||||
const P = publicKey.negate();
|
||||
const G = PubCurve.Point.BASE;
|
||||
const Hm = amsg(message);
|
||||
const S = signature;
|
||||
// This code was changed in 1.9.x:
|
||||
// Before it was G.negate() in G2, now it's always pubKey.negate
|
||||
// e(P, -Q)===e(-P, Q)==e(P, Q)^-1. Negate can be done anywhere (as long it is done once per pair).
|
||||
// We just moving sign, but since pairing is multiplicative, we doing X * X^-1 = 1
|
||||
const exp = pairingBatch([pair(P, Hm), pair(G, S)]);
|
||||
return Fp12.eql(exp, Fp12.ONE);
|
||||
},
|
||||
// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
||||
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
|
||||
// TODO: maybe `{message: G2Hex, publicKey: G1Hex}[]` instead?
|
||||
verifyBatch(signature, messages, publicKeys) {
|
||||
aNonEmpty(messages);
|
||||
if (publicKeys.length !== messages.length)
|
||||
throw new Error('amount of public keys and messages should be equal');
|
||||
const sig = normSig(signature);
|
||||
const nMessages = messages;
|
||||
const nPublicKeys = publicKeys.map(normPub);
|
||||
// NOTE: this works only for exact same object
|
||||
const messagePubKeyMap = new Map();
|
||||
for (let i = 0; i < nPublicKeys.length; i++) {
|
||||
const pub = nPublicKeys[i];
|
||||
const msg = nMessages[i];
|
||||
let keys = messagePubKeyMap.get(msg);
|
||||
if (keys === undefined) {
|
||||
keys = [];
|
||||
messagePubKeyMap.set(msg, keys);
|
||||
}
|
||||
keys.push(pub);
|
||||
}
|
||||
const paired = [];
|
||||
const G = PubCurve.Point.BASE;
|
||||
try {
|
||||
for (const [msg, keys] of messagePubKeyMap) {
|
||||
const groupPublicKey = keys.reduce((acc, msg) => acc.add(msg));
|
||||
paired.push(pair(groupPublicKey, msg));
|
||||
}
|
||||
paired.push(pair(G.negate(), sig));
|
||||
return Fp12.eql(pairingBatch(paired), Fp12.ONE);
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Adds a bunch of public key points together.
|
||||
// pk1 + pk2 + pk3 = pkA
|
||||
aggregatePublicKeys(publicKeys) {
|
||||
aNonEmpty(publicKeys);
|
||||
publicKeys = publicKeys.map((pub) => normPub(pub));
|
||||
const agg = publicKeys.reduce((sum, p) => sum.add(p), PubCurve.Point.ZERO);
|
||||
agg.assertValidity();
|
||||
return agg;
|
||||
},
|
||||
// Adds a bunch of signature points together.
|
||||
// pk1 + pk2 + pk3 = pkA
|
||||
aggregateSignatures(signatures) {
|
||||
aNonEmpty(signatures);
|
||||
signatures = signatures.map((sig) => normSig(sig));
|
||||
const agg = signatures.reduce((sum, s) => sum.add(s), SigCurve.Point.ZERO);
|
||||
agg.assertValidity();
|
||||
return agg;
|
||||
},
|
||||
hash(messageBytes, DST) {
|
||||
(0, utils_ts_1.abytes)(messageBytes);
|
||||
const opts = DST ? { DST } : undefined;
|
||||
return SigCurve.hashToCurve(messageBytes, opts);
|
||||
},
|
||||
Signature: SignatureCoder,
|
||||
};
|
||||
}
|
||||
// G1_Point: ProjConstructor<bigint>, G2_Point: ProjConstructor<Fp2>,
|
||||
function bls(CURVE) {
|
||||
// Fields are specific for curve, so for now we'll need to pass them with opts
|
||||
const { Fp, Fr, Fp2, Fp6, Fp12 } = CURVE.fields;
|
||||
// Point on G1 curve: (x, y)
|
||||
const G1_ = (0, weierstrass_ts_1.weierstrassPoints)(CURVE.G1);
|
||||
const G1 = Object.assign(G1_, (0, hash_to_curve_ts_1.createHasher)(G1_.Point, CURVE.G1.mapToCurve, {
|
||||
...CURVE.htfDefaults,
|
||||
...CURVE.G1.htfDefaults,
|
||||
}));
|
||||
// Point on G2 curve (complex numbers): (x₁, x₂+i), (y₁, y₂+i)
|
||||
const G2_ = (0, weierstrass_ts_1.weierstrassPoints)(CURVE.G2);
|
||||
const G2 = Object.assign(G2_, (0, hash_to_curve_ts_1.createHasher)(G2_.Point, CURVE.G2.mapToCurve, {
|
||||
...CURVE.htfDefaults,
|
||||
...CURVE.G2.htfDefaults,
|
||||
}));
|
||||
const pairingRes = createBlsPairing(CURVE.fields, G1.Point, G2.Point, {
|
||||
...CURVE.params,
|
||||
postPrecompute: CURVE.postPrecompute,
|
||||
});
|
||||
const { millerLoopBatch, pairing, pairingBatch, calcPairingPrecomputes } = pairingRes;
|
||||
const longSignatures = createBlsSig(pairingRes, G1, G2, CURVE.G2.Signature, false);
|
||||
const shortSignatures = createBlsSig(pairingRes, G2, G1, CURVE.G1.ShortSignature, true);
|
||||
const rand = CURVE.randomBytes || utils_ts_1.randomBytes;
|
||||
const randomSecretKey = () => {
|
||||
const length = (0, modular_ts_1.getMinHashLength)(Fr.ORDER);
|
||||
return (0, modular_ts_1.mapHashToField)(rand(length), Fr.ORDER);
|
||||
};
|
||||
const utils = {
|
||||
randomSecretKey,
|
||||
randomPrivateKey: randomSecretKey,
|
||||
calcPairingPrecomputes,
|
||||
};
|
||||
const { ShortSignature } = CURVE.G1;
|
||||
const { Signature } = CURVE.G2;
|
||||
function normP1Hash(point, htfOpts) {
|
||||
return point instanceof G1.Point
|
||||
? point
|
||||
: shortSignatures.hash((0, utils_ts_1.ensureBytes)('point', point), htfOpts?.DST);
|
||||
}
|
||||
function normP2Hash(point, htfOpts) {
|
||||
return point instanceof G2.Point
|
||||
? point
|
||||
: longSignatures.hash((0, utils_ts_1.ensureBytes)('point', point), htfOpts?.DST);
|
||||
}
|
||||
function getPublicKey(privateKey) {
|
||||
return longSignatures.getPublicKey(privateKey).toBytes(true);
|
||||
}
|
||||
function getPublicKeyForShortSignatures(privateKey) {
|
||||
return shortSignatures.getPublicKey(privateKey).toBytes(true);
|
||||
}
|
||||
function sign(message, privateKey, htfOpts) {
|
||||
const Hm = normP2Hash(message, htfOpts);
|
||||
const S = longSignatures.sign(Hm, privateKey);
|
||||
return message instanceof G2.Point ? S : Signature.toBytes(S);
|
||||
}
|
||||
function signShortSignature(message, privateKey, htfOpts) {
|
||||
const Hm = normP1Hash(message, htfOpts);
|
||||
const S = shortSignatures.sign(Hm, privateKey);
|
||||
return message instanceof G1.Point ? S : ShortSignature.toBytes(S);
|
||||
}
|
||||
function verify(signature, message, publicKey, htfOpts) {
|
||||
const Hm = normP2Hash(message, htfOpts);
|
||||
return longSignatures.verify(signature, Hm, publicKey);
|
||||
}
|
||||
function verifyShortSignature(signature, message, publicKey, htfOpts) {
|
||||
const Hm = normP1Hash(message, htfOpts);
|
||||
return shortSignatures.verify(signature, Hm, publicKey);
|
||||
}
|
||||
function aggregatePublicKeys(publicKeys) {
|
||||
const agg = longSignatures.aggregatePublicKeys(publicKeys);
|
||||
return publicKeys[0] instanceof G1.Point ? agg : agg.toBytes(true);
|
||||
}
|
||||
function aggregateSignatures(signatures) {
|
||||
const agg = longSignatures.aggregateSignatures(signatures);
|
||||
return signatures[0] instanceof G2.Point ? agg : Signature.toBytes(agg);
|
||||
}
|
||||
function aggregateShortSignatures(signatures) {
|
||||
const agg = shortSignatures.aggregateSignatures(signatures);
|
||||
return signatures[0] instanceof G1.Point ? agg : ShortSignature.toBytes(agg);
|
||||
}
|
||||
function verifyBatch(signature, messages, publicKeys, htfOpts) {
|
||||
const Hm = messages.map((m) => normP2Hash(m, htfOpts));
|
||||
return longSignatures.verifyBatch(signature, Hm, publicKeys);
|
||||
}
|
||||
G1.Point.BASE.precompute(4);
|
||||
return {
|
||||
longSignatures,
|
||||
shortSignatures,
|
||||
millerLoopBatch,
|
||||
pairing,
|
||||
pairingBatch,
|
||||
verifyBatch,
|
||||
fields: {
|
||||
Fr,
|
||||
Fp,
|
||||
Fp2,
|
||||
Fp6,
|
||||
Fp12,
|
||||
},
|
||||
params: {
|
||||
ateLoopSize: CURVE.params.ateLoopSize,
|
||||
twistType: CURVE.params.twistType,
|
||||
// deprecated
|
||||
r: CURVE.params.r,
|
||||
G1b: CURVE.G1.b,
|
||||
G2b: CURVE.G2.b,
|
||||
},
|
||||
utils,
|
||||
// deprecated
|
||||
getPublicKey,
|
||||
getPublicKeyForShortSignatures,
|
||||
sign,
|
||||
signShortSignature,
|
||||
verify,
|
||||
verifyShortSignature,
|
||||
aggregatePublicKeys,
|
||||
aggregateSignatures,
|
||||
aggregateShortSignatures,
|
||||
G1,
|
||||
G2,
|
||||
Signature,
|
||||
ShortSignature,
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=bls.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-231
@@ -1,231 +0,0 @@
|
||||
import { type IField } from './modular.ts';
|
||||
export type AffinePoint<T> = {
|
||||
x: T;
|
||||
y: T;
|
||||
} & {
|
||||
Z?: never;
|
||||
};
|
||||
export interface Group<T extends Group<T>> {
|
||||
double(): T;
|
||||
negate(): T;
|
||||
add(other: T): T;
|
||||
subtract(other: T): T;
|
||||
equals(other: T): boolean;
|
||||
multiply(scalar: bigint): T;
|
||||
toAffine?(invertedZ?: any): AffinePoint<any>;
|
||||
}
|
||||
/** Base interface for all elliptic curve Points. */
|
||||
export interface CurvePoint<F, P extends CurvePoint<F, P>> extends Group<P> {
|
||||
/** Affine x coordinate. Different from projective / extended X coordinate. */
|
||||
x: F;
|
||||
/** Affine y coordinate. Different from projective / extended Y coordinate. */
|
||||
y: F;
|
||||
Z?: F;
|
||||
double(): P;
|
||||
negate(): P;
|
||||
add(other: P): P;
|
||||
subtract(other: P): P;
|
||||
equals(other: P): boolean;
|
||||
multiply(scalar: bigint): P;
|
||||
assertValidity(): void;
|
||||
clearCofactor(): P;
|
||||
is0(): boolean;
|
||||
isTorsionFree(): boolean;
|
||||
isSmallOrder(): boolean;
|
||||
multiplyUnsafe(scalar: bigint): P;
|
||||
/**
|
||||
* Massively speeds up `p.multiply(n)` by using precompute tables (caching). See {@link wNAF}.
|
||||
* @param isLazy calculate cache now. Default (true) ensures it's deferred to first `multiply()`
|
||||
*/
|
||||
precompute(windowSize?: number, isLazy?: boolean): P;
|
||||
/** Converts point to 2D xy affine coordinates */
|
||||
toAffine(invertedZ?: F): AffinePoint<F>;
|
||||
toBytes(): Uint8Array;
|
||||
toHex(): string;
|
||||
}
|
||||
/** Base interface for all elliptic curve Point constructors. */
|
||||
export interface CurvePointCons<P extends CurvePoint<any, P>> {
|
||||
[Symbol.hasInstance]: (item: unknown) => boolean;
|
||||
BASE: P;
|
||||
ZERO: P;
|
||||
/** Field for basic curve math */
|
||||
Fp: IField<P_F<P>>;
|
||||
/** Scalar field, for scalars in multiply and others */
|
||||
Fn: IField<bigint>;
|
||||
/** Creates point from x, y. Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
||||
fromAffine(p: AffinePoint<P_F<P>>): P;
|
||||
fromBytes(bytes: Uint8Array): P;
|
||||
fromHex(hex: Uint8Array | string): P;
|
||||
}
|
||||
/** Returns Fp type from Point (P_F<P> == P.F) */
|
||||
export type P_F<P extends CurvePoint<any, P>> = P extends CurvePoint<infer F, P> ? F : never;
|
||||
/** Returns Fp type from PointCons (PC_F<PC> == PC.P.F) */
|
||||
export type PC_F<PC extends CurvePointCons<CurvePoint<any, any>>> = PC['Fp']['ZERO'];
|
||||
/** Returns Point type from PointCons (PC_P<PC> == PC.P) */
|
||||
export type PC_P<PC extends CurvePointCons<CurvePoint<any, any>>> = PC['ZERO'];
|
||||
export type PC_ANY = CurvePointCons<CurvePoint<any, CurvePoint<any, CurvePoint<any, CurvePoint<any, CurvePoint<any, CurvePoint<any, CurvePoint<any, CurvePoint<any, CurvePoint<any, CurvePoint<any, any>>>>>>>>>>>;
|
||||
export interface CurveLengths {
|
||||
secretKey?: number;
|
||||
publicKey?: number;
|
||||
publicKeyUncompressed?: number;
|
||||
publicKeyHasPrefix?: boolean;
|
||||
signature?: number;
|
||||
seed?: number;
|
||||
}
|
||||
export type GroupConstructor<T> = {
|
||||
BASE: T;
|
||||
ZERO: T;
|
||||
};
|
||||
/** @deprecated */
|
||||
export type ExtendedGroupConstructor<T> = GroupConstructor<T> & {
|
||||
Fp: IField<any>;
|
||||
Fn: IField<bigint>;
|
||||
fromAffine(ap: AffinePoint<any>): T;
|
||||
};
|
||||
export type Mapper<T> = (i: T[]) => T[];
|
||||
export declare function negateCt<T extends {
|
||||
negate: () => T;
|
||||
}>(condition: boolean, item: T): T;
|
||||
/**
|
||||
* Takes a bunch of Projective Points but executes only one
|
||||
* inversion on all of them. Inversion is very slow operation,
|
||||
* so this improves performance massively.
|
||||
* Optimization: converts a list of projective points to a list of identical points with Z=1.
|
||||
*/
|
||||
export declare function normalizeZ<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(c: PC, points: P[]): P[];
|
||||
/** Internal wNAF opts for specific W and scalarBits */
|
||||
export type WOpts = {
|
||||
windows: number;
|
||||
windowSize: number;
|
||||
mask: bigint;
|
||||
maxNumber: number;
|
||||
shiftBy: bigint;
|
||||
};
|
||||
/**
|
||||
* Elliptic curve multiplication of Point by scalar. Fragile.
|
||||
* Table generation takes **30MB of ram and 10ms on high-end CPU**,
|
||||
* but may take much longer on slow devices. Actual generation will happen on
|
||||
* first call of `multiply()`. By default, `BASE` point is precomputed.
|
||||
*
|
||||
* Scalars should always be less than curve order: this should be checked inside of a curve itself.
|
||||
* Creates precomputation tables for fast multiplication:
|
||||
* - private scalar is split by fixed size windows of W bits
|
||||
* - every window point is collected from window's table & added to accumulator
|
||||
* - since windows are different, same point inside tables won't be accessed more than once per calc
|
||||
* - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)
|
||||
* - +1 window is neccessary for wNAF
|
||||
* - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication
|
||||
*
|
||||
* @todo Research returning 2d JS array of windows, instead of a single window.
|
||||
* This would allow windows to be in different memory locations
|
||||
*/
|
||||
export declare class wNAF<PC extends PC_ANY> {
|
||||
private readonly BASE;
|
||||
private readonly ZERO;
|
||||
private readonly Fn;
|
||||
readonly bits: number;
|
||||
constructor(Point: PC, bits: number);
|
||||
_unsafeLadder(elm: PC_P<PC>, n: bigint, p?: PC_P<PC>): PC_P<PC>;
|
||||
/**
|
||||
* Creates a wNAF precomputation window. Used for caching.
|
||||
* Default window size is set by `utils.precompute()` and is equal to 8.
|
||||
* Number of precomputed points depends on the curve size:
|
||||
* 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
|
||||
* - 𝑊 is the window size
|
||||
* - 𝑛 is the bitlength of the curve order.
|
||||
* For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
|
||||
* @param point Point instance
|
||||
* @param W window size
|
||||
* @returns precomputed point tables flattened to a single array
|
||||
*/
|
||||
private precomputeWindow;
|
||||
/**
|
||||
* Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
|
||||
* More compact implementation:
|
||||
* https://github.com/paulmillr/noble-secp256k1/blob/47cb1669b6e506ad66b35fe7d76132ae97465da2/index.ts#L502-L541
|
||||
* @returns real and fake (for const-time) points
|
||||
*/
|
||||
private wNAF;
|
||||
/**
|
||||
* Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.
|
||||
* @param acc accumulator point to add result of multiplication
|
||||
* @returns point
|
||||
*/
|
||||
private wNAFUnsafe;
|
||||
private getPrecomputes;
|
||||
cached(point: PC_P<PC>, scalar: bigint, transform?: Mapper<PC_P<PC>>): {
|
||||
p: PC_P<PC>;
|
||||
f: PC_P<PC>;
|
||||
};
|
||||
unsafe(point: PC_P<PC>, scalar: bigint, transform?: Mapper<PC_P<PC>>, prev?: PC_P<PC>): PC_P<PC>;
|
||||
createCache(P: PC_P<PC>, W: number): void;
|
||||
hasCache(elm: PC_P<PC>): boolean;
|
||||
}
|
||||
/**
|
||||
* Endomorphism-specific multiplication for Koblitz curves.
|
||||
* Cost: 128 dbl, 0-256 adds.
|
||||
*/
|
||||
export declare function mulEndoUnsafe<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(Point: PC, point: P, k1: bigint, k2: bigint): {
|
||||
p1: P;
|
||||
p2: P;
|
||||
};
|
||||
/**
|
||||
* Pippenger algorithm for multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).
|
||||
* 30x faster vs naive addition on L=4096, 10x faster than precomputes.
|
||||
* For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL.
|
||||
* Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0.
|
||||
* @param c Curve Point constructor
|
||||
* @param fieldN field over CURVE.N - important that it's not over CURVE.P
|
||||
* @param points array of L curve points
|
||||
* @param scalars array of L scalars (aka secret keys / bigints)
|
||||
*/
|
||||
export declare function pippenger<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(c: PC, fieldN: IField<bigint>, points: P[], scalars: bigint[]): P;
|
||||
/**
|
||||
* Precomputed multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).
|
||||
* @param c Curve Point constructor
|
||||
* @param fieldN field over CURVE.N - important that it's not over CURVE.P
|
||||
* @param points array of L curve points
|
||||
* @returns function which multiplies points with scaars
|
||||
*/
|
||||
export declare function precomputeMSMUnsafe<P extends CurvePoint<any, P>, PC extends CurvePointCons<P>>(c: PC, fieldN: IField<bigint>, points: P[], windowSize: number): (scalars: bigint[]) => P;
|
||||
/**
|
||||
* Generic BasicCurve interface: works even for polynomial fields (BLS): P, n, h would be ok.
|
||||
* Though generator can be different (Fp2 / Fp6 for BLS).
|
||||
*/
|
||||
export type BasicCurve<T> = {
|
||||
Fp: IField<T>;
|
||||
n: bigint;
|
||||
nBitLength?: number;
|
||||
nByteLength?: number;
|
||||
h: bigint;
|
||||
hEff?: bigint;
|
||||
Gx: T;
|
||||
Gy: T;
|
||||
allowInfinityPoint?: boolean;
|
||||
};
|
||||
/** @deprecated */
|
||||
export declare function validateBasic<FP, T>(curve: BasicCurve<FP> & T): Readonly<{
|
||||
readonly nBitLength: number;
|
||||
readonly nByteLength: number;
|
||||
} & BasicCurve<FP> & T & {
|
||||
p: bigint;
|
||||
}>;
|
||||
export type ValidCurveParams<T> = {
|
||||
p: bigint;
|
||||
n: bigint;
|
||||
h: bigint;
|
||||
a: T;
|
||||
b?: T;
|
||||
d?: T;
|
||||
Gx: T;
|
||||
Gy: T;
|
||||
};
|
||||
export type FpFn<T> = {
|
||||
Fp: IField<T>;
|
||||
Fn: IField<bigint>;
|
||||
};
|
||||
/** Validates CURVE opts and creates fields */
|
||||
export declare function _createCurveFields<T>(type: 'weierstrass' | 'edwards', CURVE: ValidCurveParams<T>, curveOpts?: Partial<FpFn<T>>, FpFnLE?: boolean): FpFn<T> & {
|
||||
CURVE: ValidCurveParams<T>;
|
||||
};
|
||||
//# sourceMappingURL=curve.d.ts.map
|
||||
-1
File diff suppressed because one or more lines are too long
-476
@@ -1,476 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.wNAF = void 0;
|
||||
exports.negateCt = negateCt;
|
||||
exports.normalizeZ = normalizeZ;
|
||||
exports.mulEndoUnsafe = mulEndoUnsafe;
|
||||
exports.pippenger = pippenger;
|
||||
exports.precomputeMSMUnsafe = precomputeMSMUnsafe;
|
||||
exports.validateBasic = validateBasic;
|
||||
exports._createCurveFields = _createCurveFields;
|
||||
/**
|
||||
* Methods for elliptic curve multiplication by scalars.
|
||||
* Contains wNAF, pippenger.
|
||||
* @module
|
||||
*/
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
const utils_ts_1 = require("../utils.js");
|
||||
const modular_ts_1 = require("./modular.js");
|
||||
const _0n = BigInt(0);
|
||||
const _1n = BigInt(1);
|
||||
function negateCt(condition, item) {
|
||||
const neg = item.negate();
|
||||
return condition ? neg : item;
|
||||
}
|
||||
/**
|
||||
* Takes a bunch of Projective Points but executes only one
|
||||
* inversion on all of them. Inversion is very slow operation,
|
||||
* so this improves performance massively.
|
||||
* Optimization: converts a list of projective points to a list of identical points with Z=1.
|
||||
*/
|
||||
function normalizeZ(c, points) {
|
||||
const invertedZs = (0, modular_ts_1.FpInvertBatch)(c.Fp, points.map((p) => p.Z));
|
||||
return points.map((p, i) => c.fromAffine(p.toAffine(invertedZs[i])));
|
||||
}
|
||||
function validateW(W, bits) {
|
||||
if (!Number.isSafeInteger(W) || W <= 0 || W > bits)
|
||||
throw new Error('invalid window size, expected [1..' + bits + '], got W=' + W);
|
||||
}
|
||||
function calcWOpts(W, scalarBits) {
|
||||
validateW(W, scalarBits);
|
||||
const windows = Math.ceil(scalarBits / W) + 1; // W=8 33. Not 32, because we skip zero
|
||||
const windowSize = 2 ** (W - 1); // W=8 128. Not 256, because we skip zero
|
||||
const maxNumber = 2 ** W; // W=8 256
|
||||
const mask = (0, utils_ts_1.bitMask)(W); // W=8 255 == mask 0b11111111
|
||||
const shiftBy = BigInt(W); // W=8 8
|
||||
return { windows, windowSize, mask, maxNumber, shiftBy };
|
||||
}
|
||||
function calcOffsets(n, window, wOpts) {
|
||||
const { windowSize, mask, maxNumber, shiftBy } = wOpts;
|
||||
let wbits = Number(n & mask); // extract W bits.
|
||||
let nextN = n >> shiftBy; // shift number by W bits.
|
||||
// What actually happens here:
|
||||
// const highestBit = Number(mask ^ (mask >> 1n));
|
||||
// let wbits2 = wbits - 1; // skip zero
|
||||
// if (wbits2 & highestBit) { wbits2 ^= Number(mask); // (~);
|
||||
// split if bits > max: +224 => 256-32
|
||||
if (wbits > windowSize) {
|
||||
// we skip zero, which means instead of `>= size-1`, we do `> size`
|
||||
wbits -= maxNumber; // -32, can be maxNumber - wbits, but then we need to set isNeg here.
|
||||
nextN += _1n; // +256 (carry)
|
||||
}
|
||||
const offsetStart = window * windowSize;
|
||||
const offset = offsetStart + Math.abs(wbits) - 1; // -1 because we skip zero
|
||||
const isZero = wbits === 0; // is current window slice a 0?
|
||||
const isNeg = wbits < 0; // is current window slice negative?
|
||||
const isNegF = window % 2 !== 0; // fake random statement for noise
|
||||
const offsetF = offsetStart; // fake offset for noise
|
||||
return { nextN, offset, isZero, isNeg, isNegF, offsetF };
|
||||
}
|
||||
function validateMSMPoints(points, c) {
|
||||
if (!Array.isArray(points))
|
||||
throw new Error('array expected');
|
||||
points.forEach((p, i) => {
|
||||
if (!(p instanceof c))
|
||||
throw new Error('invalid point at index ' + i);
|
||||
});
|
||||
}
|
||||
function validateMSMScalars(scalars, field) {
|
||||
if (!Array.isArray(scalars))
|
||||
throw new Error('array of scalars expected');
|
||||
scalars.forEach((s, i) => {
|
||||
if (!field.isValid(s))
|
||||
throw new Error('invalid scalar at index ' + i);
|
||||
});
|
||||
}
|
||||
// Since points in different groups cannot be equal (different object constructor),
|
||||
// we can have single place to store precomputes.
|
||||
// Allows to make points frozen / immutable.
|
||||
const pointPrecomputes = new WeakMap();
|
||||
const pointWindowSizes = new WeakMap();
|
||||
function getW(P) {
|
||||
// To disable precomputes:
|
||||
// return 1;
|
||||
return pointWindowSizes.get(P) || 1;
|
||||
}
|
||||
function assert0(n) {
|
||||
if (n !== _0n)
|
||||
throw new Error('invalid wNAF');
|
||||
}
|
||||
/**
|
||||
* Elliptic curve multiplication of Point by scalar. Fragile.
|
||||
* Table generation takes **30MB of ram and 10ms on high-end CPU**,
|
||||
* but may take much longer on slow devices. Actual generation will happen on
|
||||
* first call of `multiply()`. By default, `BASE` point is precomputed.
|
||||
*
|
||||
* Scalars should always be less than curve order: this should be checked inside of a curve itself.
|
||||
* Creates precomputation tables for fast multiplication:
|
||||
* - private scalar is split by fixed size windows of W bits
|
||||
* - every window point is collected from window's table & added to accumulator
|
||||
* - since windows are different, same point inside tables won't be accessed more than once per calc
|
||||
* - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)
|
||||
* - +1 window is neccessary for wNAF
|
||||
* - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication
|
||||
*
|
||||
* @todo Research returning 2d JS array of windows, instead of a single window.
|
||||
* This would allow windows to be in different memory locations
|
||||
*/
|
||||
class wNAF {
|
||||
// Parametrized with a given Point class (not individual point)
|
||||
constructor(Point, bits) {
|
||||
this.BASE = Point.BASE;
|
||||
this.ZERO = Point.ZERO;
|
||||
this.Fn = Point.Fn;
|
||||
this.bits = bits;
|
||||
}
|
||||
// non-const time multiplication ladder
|
||||
_unsafeLadder(elm, n, p = this.ZERO) {
|
||||
let d = elm;
|
||||
while (n > _0n) {
|
||||
if (n & _1n)
|
||||
p = p.add(d);
|
||||
d = d.double();
|
||||
n >>= _1n;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
/**
|
||||
* Creates a wNAF precomputation window. Used for caching.
|
||||
* Default window size is set by `utils.precompute()` and is equal to 8.
|
||||
* Number of precomputed points depends on the curve size:
|
||||
* 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
|
||||
* - 𝑊 is the window size
|
||||
* - 𝑛 is the bitlength of the curve order.
|
||||
* For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
|
||||
* @param point Point instance
|
||||
* @param W window size
|
||||
* @returns precomputed point tables flattened to a single array
|
||||
*/
|
||||
precomputeWindow(point, W) {
|
||||
const { windows, windowSize } = calcWOpts(W, this.bits);
|
||||
const points = [];
|
||||
let p = point;
|
||||
let base = p;
|
||||
for (let window = 0; window < windows; window++) {
|
||||
base = p;
|
||||
points.push(base);
|
||||
// i=1, bc we skip 0
|
||||
for (let i = 1; i < windowSize; i++) {
|
||||
base = base.add(p);
|
||||
points.push(base);
|
||||
}
|
||||
p = base.double();
|
||||
}
|
||||
return points;
|
||||
}
|
||||
/**
|
||||
* Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
|
||||
* More compact implementation:
|
||||
* https://github.com/paulmillr/noble-secp256k1/blob/47cb1669b6e506ad66b35fe7d76132ae97465da2/index.ts#L502-L541
|
||||
* @returns real and fake (for const-time) points
|
||||
*/
|
||||
wNAF(W, precomputes, n) {
|
||||
// Scalar should be smaller than field order
|
||||
if (!this.Fn.isValid(n))
|
||||
throw new Error('invalid scalar');
|
||||
// Accumulators
|
||||
let p = this.ZERO;
|
||||
let f = this.BASE;
|
||||
// This code was first written with assumption that 'f' and 'p' will never be infinity point:
|
||||
// since each addition is multiplied by 2 ** W, it cannot cancel each other. However,
|
||||
// there is negate now: it is possible that negated element from low value
|
||||
// would be the same as high element, which will create carry into next window.
|
||||
// It's not obvious how this can fail, but still worth investigating later.
|
||||
const wo = calcWOpts(W, this.bits);
|
||||
for (let window = 0; window < wo.windows; window++) {
|
||||
// (n === _0n) is handled and not early-exited. isEven and offsetF are used for noise
|
||||
const { nextN, offset, isZero, isNeg, isNegF, offsetF } = calcOffsets(n, window, wo);
|
||||
n = nextN;
|
||||
if (isZero) {
|
||||
// bits are 0: add garbage to fake point
|
||||
// Important part for const-time getPublicKey: add random "noise" point to f.
|
||||
f = f.add(negateCt(isNegF, precomputes[offsetF]));
|
||||
}
|
||||
else {
|
||||
// bits are 1: add to result point
|
||||
p = p.add(negateCt(isNeg, precomputes[offset]));
|
||||
}
|
||||
}
|
||||
assert0(n);
|
||||
// Return both real and fake points: JIT won't eliminate f.
|
||||
// At this point there is a way to F be infinity-point even if p is not,
|
||||
// which makes it less const-time: around 1 bigint multiply.
|
||||
return { p, f };
|
||||
}
|
||||
/**
|
||||
* Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.
|
||||
* @param acc accumulator point to add result of multiplication
|
||||
* @returns point
|
||||
*/
|
||||
wNAFUnsafe(W, precomputes, n, acc = this.ZERO) {
|
||||
const wo = calcWOpts(W, this.bits);
|
||||
for (let window = 0; window < wo.windows; window++) {
|
||||
if (n === _0n)
|
||||
break; // Early-exit, skip 0 value
|
||||
const { nextN, offset, isZero, isNeg } = calcOffsets(n, window, wo);
|
||||
n = nextN;
|
||||
if (isZero) {
|
||||
// Window bits are 0: skip processing.
|
||||
// Move to next window.
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
const item = precomputes[offset];
|
||||
acc = acc.add(isNeg ? item.negate() : item); // Re-using acc allows to save adds in MSM
|
||||
}
|
||||
}
|
||||
assert0(n);
|
||||
return acc;
|
||||
}
|
||||
getPrecomputes(W, point, transform) {
|
||||
// Calculate precomputes on a first run, reuse them after
|
||||
let comp = pointPrecomputes.get(point);
|
||||
if (!comp) {
|
||||
comp = this.precomputeWindow(point, W);
|
||||
if (W !== 1) {
|
||||
// Doing transform outside of if brings 15% perf hit
|
||||
if (typeof transform === 'function')
|
||||
comp = transform(comp);
|
||||
pointPrecomputes.set(point, comp);
|
||||
}
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
cached(point, scalar, transform) {
|
||||
const W = getW(point);
|
||||
return this.wNAF(W, this.getPrecomputes(W, point, transform), scalar);
|
||||
}
|
||||
unsafe(point, scalar, transform, prev) {
|
||||
const W = getW(point);
|
||||
if (W === 1)
|
||||
return this._unsafeLadder(point, scalar, prev); // For W=1 ladder is ~x2 faster
|
||||
return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform), scalar, prev);
|
||||
}
|
||||
// We calculate precomputes for elliptic curve point multiplication
|
||||
// using windowed method. This specifies window size and
|
||||
// stores precomputed values. Usually only base point would be precomputed.
|
||||
createCache(P, W) {
|
||||
validateW(W, this.bits);
|
||||
pointWindowSizes.set(P, W);
|
||||
pointPrecomputes.delete(P);
|
||||
}
|
||||
hasCache(elm) {
|
||||
return getW(elm) !== 1;
|
||||
}
|
||||
}
|
||||
exports.wNAF = wNAF;
|
||||
/**
|
||||
* Endomorphism-specific multiplication for Koblitz curves.
|
||||
* Cost: 128 dbl, 0-256 adds.
|
||||
*/
|
||||
function mulEndoUnsafe(Point, point, k1, k2) {
|
||||
let acc = point;
|
||||
let p1 = Point.ZERO;
|
||||
let p2 = Point.ZERO;
|
||||
while (k1 > _0n || k2 > _0n) {
|
||||
if (k1 & _1n)
|
||||
p1 = p1.add(acc);
|
||||
if (k2 & _1n)
|
||||
p2 = p2.add(acc);
|
||||
acc = acc.double();
|
||||
k1 >>= _1n;
|
||||
k2 >>= _1n;
|
||||
}
|
||||
return { p1, p2 };
|
||||
}
|
||||
/**
|
||||
* Pippenger algorithm for multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).
|
||||
* 30x faster vs naive addition on L=4096, 10x faster than precomputes.
|
||||
* For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL.
|
||||
* Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0.
|
||||
* @param c Curve Point constructor
|
||||
* @param fieldN field over CURVE.N - important that it's not over CURVE.P
|
||||
* @param points array of L curve points
|
||||
* @param scalars array of L scalars (aka secret keys / bigints)
|
||||
*/
|
||||
function pippenger(c, fieldN, points, scalars) {
|
||||
// If we split scalars by some window (let's say 8 bits), every chunk will only
|
||||
// take 256 buckets even if there are 4096 scalars, also re-uses double.
|
||||
// TODO:
|
||||
// - https://eprint.iacr.org/2024/750.pdf
|
||||
// - https://tches.iacr.org/index.php/TCHES/article/view/10287
|
||||
// 0 is accepted in scalars
|
||||
validateMSMPoints(points, c);
|
||||
validateMSMScalars(scalars, fieldN);
|
||||
const plength = points.length;
|
||||
const slength = scalars.length;
|
||||
if (plength !== slength)
|
||||
throw new Error('arrays of points and scalars must have equal length');
|
||||
// if (plength === 0) throw new Error('array must be of length >= 2');
|
||||
const zero = c.ZERO;
|
||||
const wbits = (0, utils_ts_1.bitLen)(BigInt(plength));
|
||||
let windowSize = 1; // bits
|
||||
if (wbits > 12)
|
||||
windowSize = wbits - 3;
|
||||
else if (wbits > 4)
|
||||
windowSize = wbits - 2;
|
||||
else if (wbits > 0)
|
||||
windowSize = 2;
|
||||
const MASK = (0, utils_ts_1.bitMask)(windowSize);
|
||||
const buckets = new Array(Number(MASK) + 1).fill(zero); // +1 for zero array
|
||||
const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize;
|
||||
let sum = zero;
|
||||
for (let i = lastBits; i >= 0; i -= windowSize) {
|
||||
buckets.fill(zero);
|
||||
for (let j = 0; j < slength; j++) {
|
||||
const scalar = scalars[j];
|
||||
const wbits = Number((scalar >> BigInt(i)) & MASK);
|
||||
buckets[wbits] = buckets[wbits].add(points[j]);
|
||||
}
|
||||
let resI = zero; // not using this will do small speed-up, but will lose ct
|
||||
// Skip first bucket, because it is zero
|
||||
for (let j = buckets.length - 1, sumI = zero; j > 0; j--) {
|
||||
sumI = sumI.add(buckets[j]);
|
||||
resI = resI.add(sumI);
|
||||
}
|
||||
sum = sum.add(resI);
|
||||
if (i !== 0)
|
||||
for (let j = 0; j < windowSize; j++)
|
||||
sum = sum.double();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
/**
|
||||
* Precomputed multi-scalar multiplication (MSM, Pa + Qb + Rc + ...).
|
||||
* @param c Curve Point constructor
|
||||
* @param fieldN field over CURVE.N - important that it's not over CURVE.P
|
||||
* @param points array of L curve points
|
||||
* @returns function which multiplies points with scaars
|
||||
*/
|
||||
function precomputeMSMUnsafe(c, fieldN, points, windowSize) {
|
||||
/**
|
||||
* Performance Analysis of Window-based Precomputation
|
||||
*
|
||||
* Base Case (256-bit scalar, 8-bit window):
|
||||
* - Standard precomputation requires:
|
||||
* - 31 additions per scalar × 256 scalars = 7,936 ops
|
||||
* - Plus 255 summary additions = 8,191 total ops
|
||||
* Note: Summary additions can be optimized via accumulator
|
||||
*
|
||||
* Chunked Precomputation Analysis:
|
||||
* - Using 32 chunks requires:
|
||||
* - 255 additions per chunk
|
||||
* - 256 doublings
|
||||
* - Total: (255 × 32) + 256 = 8,416 ops
|
||||
*
|
||||
* Memory Usage Comparison:
|
||||
* Window Size | Standard Points | Chunked Points
|
||||
* ------------|-----------------|---------------
|
||||
* 4-bit | 520 | 15
|
||||
* 8-bit | 4,224 | 255
|
||||
* 10-bit | 13,824 | 1,023
|
||||
* 16-bit | 557,056 | 65,535
|
||||
*
|
||||
* Key Advantages:
|
||||
* 1. Enables larger window sizes due to reduced memory overhead
|
||||
* 2. More efficient for smaller scalar counts:
|
||||
* - 16 chunks: (16 × 255) + 256 = 4,336 ops
|
||||
* - ~2x faster than standard 8,191 ops
|
||||
*
|
||||
* Limitations:
|
||||
* - Not suitable for plain precomputes (requires 256 constant doublings)
|
||||
* - Performance degrades with larger scalar counts:
|
||||
* - Optimal for ~256 scalars
|
||||
* - Less efficient for 4096+ scalars (Pippenger preferred)
|
||||
*/
|
||||
validateW(windowSize, fieldN.BITS);
|
||||
validateMSMPoints(points, c);
|
||||
const zero = c.ZERO;
|
||||
const tableSize = 2 ** windowSize - 1; // table size (without zero)
|
||||
const chunks = Math.ceil(fieldN.BITS / windowSize); // chunks of item
|
||||
const MASK = (0, utils_ts_1.bitMask)(windowSize);
|
||||
const tables = points.map((p) => {
|
||||
const res = [];
|
||||
for (let i = 0, acc = p; i < tableSize; i++) {
|
||||
res.push(acc);
|
||||
acc = acc.add(p);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
return (scalars) => {
|
||||
validateMSMScalars(scalars, fieldN);
|
||||
if (scalars.length > points.length)
|
||||
throw new Error('array of scalars must be smaller than array of points');
|
||||
let res = zero;
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
// No need to double if accumulator is still zero.
|
||||
if (res !== zero)
|
||||
for (let j = 0; j < windowSize; j++)
|
||||
res = res.double();
|
||||
const shiftBy = BigInt(chunks * windowSize - (i + 1) * windowSize);
|
||||
for (let j = 0; j < scalars.length; j++) {
|
||||
const n = scalars[j];
|
||||
const curr = Number((n >> shiftBy) & MASK);
|
||||
if (!curr)
|
||||
continue; // skip zero scalars chunks
|
||||
res = res.add(tables[j][curr - 1]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
// TODO: remove
|
||||
/** @deprecated */
|
||||
function validateBasic(curve) {
|
||||
(0, modular_ts_1.validateField)(curve.Fp);
|
||||
(0, utils_ts_1.validateObject)(curve, {
|
||||
n: 'bigint',
|
||||
h: 'bigint',
|
||||
Gx: 'field',
|
||||
Gy: 'field',
|
||||
}, {
|
||||
nBitLength: 'isSafeInteger',
|
||||
nByteLength: 'isSafeInteger',
|
||||
});
|
||||
// Set defaults
|
||||
return Object.freeze({
|
||||
...(0, modular_ts_1.nLength)(curve.n, curve.nBitLength),
|
||||
...curve,
|
||||
...{ p: curve.Fp.ORDER },
|
||||
});
|
||||
}
|
||||
function createField(order, field, isLE) {
|
||||
if (field) {
|
||||
if (field.ORDER !== order)
|
||||
throw new Error('Field.ORDER must match order: Fp == p, Fn == n');
|
||||
(0, modular_ts_1.validateField)(field);
|
||||
return field;
|
||||
}
|
||||
else {
|
||||
return (0, modular_ts_1.Field)(order, { isLE });
|
||||
}
|
||||
}
|
||||
/** Validates CURVE opts and creates fields */
|
||||
function _createCurveFields(type, CURVE, curveOpts = {}, FpFnLE) {
|
||||
if (FpFnLE === undefined)
|
||||
FpFnLE = type === 'edwards';
|
||||
if (!CURVE || typeof CURVE !== 'object')
|
||||
throw new Error(`expected valid ${type} CURVE object`);
|
||||
for (const p of ['p', 'n', 'h']) {
|
||||
const val = CURVE[p];
|
||||
if (!(typeof val === 'bigint' && val > _0n))
|
||||
throw new Error(`CURVE.${p} must be positive bigint`);
|
||||
}
|
||||
const Fp = createField(CURVE.p, curveOpts.Fp, FpFnLE);
|
||||
const Fn = createField(CURVE.n, curveOpts.Fn, FpFnLE);
|
||||
const _b = type === 'weierstrass' ? 'b' : 'd';
|
||||
const params = ['Gx', 'Gy', 'a', _b];
|
||||
for (const p of params) {
|
||||
// @ts-ignore
|
||||
if (!Fp.isValid(CURVE[p]))
|
||||
throw new Error(`CURVE.${p} must be valid field element of CURVE.Fp`);
|
||||
}
|
||||
CURVE = Object.freeze(Object.assign({}, CURVE));
|
||||
return { CURVE, Fp, Fn };
|
||||
}
|
||||
//# sourceMappingURL=curve.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-243
@@ -1,243 +0,0 @@
|
||||
/**
|
||||
* Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y².
|
||||
* For design rationale of types / exports, see weierstrass module documentation.
|
||||
* Untwisted Edwards curves exist, but they aren't used in real-world protocols.
|
||||
* @module
|
||||
*/
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
import { type FHash, type Hex } from '../utils.ts';
|
||||
import { type AffinePoint, type BasicCurve, type CurveLengths, type CurvePoint, type CurvePointCons } from './curve.ts';
|
||||
import { type IField, type NLength } from './modular.ts';
|
||||
export type UVRatio = (u: bigint, v: bigint) => {
|
||||
isValid: boolean;
|
||||
value: bigint;
|
||||
};
|
||||
/** Instance of Extended Point with coordinates in X, Y, Z, T. */
|
||||
export interface EdwardsPoint extends CurvePoint<bigint, EdwardsPoint> {
|
||||
/** extended X coordinate. Different from affine x. */
|
||||
readonly X: bigint;
|
||||
/** extended Y coordinate. Different from affine y. */
|
||||
readonly Y: bigint;
|
||||
/** extended Z coordinate */
|
||||
readonly Z: bigint;
|
||||
/** extended T coordinate */
|
||||
readonly T: bigint;
|
||||
/** @deprecated use `toBytes` */
|
||||
toRawBytes(): Uint8Array;
|
||||
/** @deprecated use `p.precompute(windowSize)` */
|
||||
_setWindowSize(windowSize: number): void;
|
||||
/** @deprecated use .X */
|
||||
readonly ex: bigint;
|
||||
/** @deprecated use .Y */
|
||||
readonly ey: bigint;
|
||||
/** @deprecated use .Z */
|
||||
readonly ez: bigint;
|
||||
/** @deprecated use .T */
|
||||
readonly et: bigint;
|
||||
}
|
||||
/** Static methods of Extended Point with coordinates in X, Y, Z, T. */
|
||||
export interface EdwardsPointCons extends CurvePointCons<EdwardsPoint> {
|
||||
new (X: bigint, Y: bigint, Z: bigint, T: bigint): EdwardsPoint;
|
||||
CURVE(): EdwardsOpts;
|
||||
fromBytes(bytes: Uint8Array, zip215?: boolean): EdwardsPoint;
|
||||
fromHex(hex: Hex, zip215?: boolean): EdwardsPoint;
|
||||
/** @deprecated use `import { pippenger } from '@noble/curves/abstract/curve.js';` */
|
||||
msm(points: EdwardsPoint[], scalars: bigint[]): EdwardsPoint;
|
||||
}
|
||||
/** @deprecated use EdwardsPoint */
|
||||
export type ExtPointType = EdwardsPoint;
|
||||
/** @deprecated use EdwardsPointCons */
|
||||
export type ExtPointConstructor = EdwardsPointCons;
|
||||
/**
|
||||
* Twisted Edwards curve options.
|
||||
*
|
||||
* * a: formula param
|
||||
* * d: formula param
|
||||
* * p: prime characteristic (order) of finite field, in which arithmetics is done
|
||||
* * n: order of prime subgroup a.k.a total amount of valid curve points
|
||||
* * h: cofactor. h*n is group order; n is subgroup order
|
||||
* * Gx: x coordinate of generator point a.k.a. base point
|
||||
* * Gy: y coordinate of generator point
|
||||
*/
|
||||
export type EdwardsOpts = Readonly<{
|
||||
p: bigint;
|
||||
n: bigint;
|
||||
h: bigint;
|
||||
a: bigint;
|
||||
d: bigint;
|
||||
Gx: bigint;
|
||||
Gy: bigint;
|
||||
}>;
|
||||
/**
|
||||
* Extra curve options for Twisted Edwards.
|
||||
*
|
||||
* * Fp: redefined Field over curve.p
|
||||
* * Fn: redefined Field over curve.n
|
||||
* * uvRatio: helper function for decompression, calculating √(u/v)
|
||||
*/
|
||||
export type EdwardsExtraOpts = Partial<{
|
||||
Fp: IField<bigint>;
|
||||
Fn: IField<bigint>;
|
||||
FpFnLE: boolean;
|
||||
uvRatio: (u: bigint, v: bigint) => {
|
||||
isValid: boolean;
|
||||
value: bigint;
|
||||
};
|
||||
}>;
|
||||
/**
|
||||
* EdDSA (Edwards Digital Signature algorithm) options.
|
||||
*
|
||||
* * hash: hash function used to hash secret keys and messages
|
||||
* * adjustScalarBytes: clears bits to get valid field element
|
||||
* * domain: Used for hashing
|
||||
* * mapToCurve: for hash-to-curve standard
|
||||
* * prehash: RFC 8032 pre-hashing of messages to sign() / verify()
|
||||
* * randomBytes: function generating random bytes, used for randomSecretKey
|
||||
*/
|
||||
export type EdDSAOpts = Partial<{
|
||||
adjustScalarBytes: (bytes: Uint8Array) => Uint8Array;
|
||||
domain: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
|
||||
mapToCurve: (scalar: bigint[]) => AffinePoint<bigint>;
|
||||
prehash: FHash;
|
||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||
}>;
|
||||
/**
|
||||
* EdDSA (Edwards Digital Signature algorithm) interface.
|
||||
*
|
||||
* Allows to create and verify signatures, create public and secret keys.
|
||||
*/
|
||||
export interface EdDSA {
|
||||
keygen: (seed?: Uint8Array) => {
|
||||
secretKey: Uint8Array;
|
||||
publicKey: Uint8Array;
|
||||
};
|
||||
getPublicKey: (secretKey: Hex) => Uint8Array;
|
||||
sign: (message: Hex, secretKey: Hex, options?: {
|
||||
context?: Hex;
|
||||
}) => Uint8Array;
|
||||
verify: (sig: Hex, message: Hex, publicKey: Hex, options?: {
|
||||
context?: Hex;
|
||||
zip215: boolean;
|
||||
}) => boolean;
|
||||
Point: EdwardsPointCons;
|
||||
utils: {
|
||||
randomSecretKey: (seed?: Uint8Array) => Uint8Array;
|
||||
isValidSecretKey: (secretKey: Uint8Array) => boolean;
|
||||
isValidPublicKey: (publicKey: Uint8Array, zip215?: boolean) => boolean;
|
||||
/**
|
||||
* Converts ed public key to x public key.
|
||||
*
|
||||
* There is NO `fromMontgomery`:
|
||||
* - There are 2 valid ed25519 points for every x25519, with flipped coordinate
|
||||
* - Sometimes there are 0 valid ed25519 points, because x25519 *additionally*
|
||||
* accepts inputs on the quadratic twist, which can't be moved to ed25519
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const someonesPub = ed25519.getPublicKey(ed25519.utils.randomSecretKey());
|
||||
* const aPriv = x25519.utils.randomSecretKey();
|
||||
* x25519.getSharedSecret(aPriv, ed25519.utils.toMontgomery(someonesPub))
|
||||
* ```
|
||||
*/
|
||||
toMontgomery: (publicKey: Uint8Array) => Uint8Array;
|
||||
/**
|
||||
* Converts ed secret key to x secret key.
|
||||
* @example
|
||||
* ```js
|
||||
* const someonesPub = x25519.getPublicKey(x25519.utils.randomSecretKey());
|
||||
* const aPriv = ed25519.utils.randomSecretKey();
|
||||
* x25519.getSharedSecret(ed25519.utils.toMontgomerySecret(aPriv), someonesPub)
|
||||
* ```
|
||||
*/
|
||||
toMontgomerySecret: (privateKey: Uint8Array) => Uint8Array;
|
||||
getExtendedPublicKey: (key: Hex) => {
|
||||
head: Uint8Array;
|
||||
prefix: Uint8Array;
|
||||
scalar: bigint;
|
||||
point: EdwardsPoint;
|
||||
pointBytes: Uint8Array;
|
||||
};
|
||||
/** @deprecated use `randomSecretKey` */
|
||||
randomPrivateKey: (seed?: Uint8Array) => Uint8Array;
|
||||
/** @deprecated use `point.precompute()` */
|
||||
precompute: (windowSize?: number, point?: EdwardsPoint) => EdwardsPoint;
|
||||
};
|
||||
lengths: CurveLengths;
|
||||
}
|
||||
export declare function edwards(params: EdwardsOpts, extraOpts?: EdwardsExtraOpts): EdwardsPointCons;
|
||||
/**
|
||||
* Base class for prime-order points like Ristretto255 and Decaf448.
|
||||
* These points eliminate cofactor issues by representing equivalence classes
|
||||
* of Edwards curve points.
|
||||
*/
|
||||
export declare abstract class PrimeEdwardsPoint<T extends PrimeEdwardsPoint<T>> implements CurvePoint<bigint, T> {
|
||||
static BASE: PrimeEdwardsPoint<any>;
|
||||
static ZERO: PrimeEdwardsPoint<any>;
|
||||
static Fp: IField<bigint>;
|
||||
static Fn: IField<bigint>;
|
||||
protected readonly ep: EdwardsPoint;
|
||||
constructor(ep: EdwardsPoint);
|
||||
abstract toBytes(): Uint8Array;
|
||||
abstract equals(other: T): boolean;
|
||||
static fromBytes(_bytes: Uint8Array): any;
|
||||
static fromHex(_hex: Hex): any;
|
||||
get x(): bigint;
|
||||
get y(): bigint;
|
||||
clearCofactor(): T;
|
||||
assertValidity(): void;
|
||||
toAffine(invertedZ?: bigint): AffinePoint<bigint>;
|
||||
toHex(): string;
|
||||
toString(): string;
|
||||
isTorsionFree(): boolean;
|
||||
isSmallOrder(): boolean;
|
||||
add(other: T): T;
|
||||
subtract(other: T): T;
|
||||
multiply(scalar: bigint): T;
|
||||
multiplyUnsafe(scalar: bigint): T;
|
||||
double(): T;
|
||||
negate(): T;
|
||||
precompute(windowSize?: number, isLazy?: boolean): T;
|
||||
abstract is0(): boolean;
|
||||
protected abstract assertSame(other: T): void;
|
||||
protected abstract init(ep: EdwardsPoint): T;
|
||||
/** @deprecated use `toBytes` */
|
||||
toRawBytes(): Uint8Array;
|
||||
}
|
||||
/**
|
||||
* Initializes EdDSA signatures over given Edwards curve.
|
||||
*/
|
||||
export declare function eddsa(Point: EdwardsPointCons, cHash: FHash, eddsaOpts?: EdDSAOpts): EdDSA;
|
||||
export type CurveType = BasicCurve<bigint> & {
|
||||
a: bigint;
|
||||
d: bigint;
|
||||
/** @deprecated the property will be removed in next release */
|
||||
hash: FHash;
|
||||
randomBytes?: (bytesLength?: number) => Uint8Array;
|
||||
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
|
||||
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
|
||||
uvRatio?: UVRatio;
|
||||
prehash?: FHash;
|
||||
mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>;
|
||||
};
|
||||
export type CurveTypeWithLength = Readonly<CurveType & Partial<NLength>>;
|
||||
export type CurveFn = {
|
||||
/** @deprecated the property will be removed in next release */
|
||||
CURVE: CurveType;
|
||||
keygen: EdDSA['keygen'];
|
||||
getPublicKey: EdDSA['getPublicKey'];
|
||||
sign: EdDSA['sign'];
|
||||
verify: EdDSA['verify'];
|
||||
Point: EdwardsPointCons;
|
||||
/** @deprecated use `Point` */
|
||||
ExtendedPoint: EdwardsPointCons;
|
||||
utils: EdDSA['utils'];
|
||||
lengths: CurveLengths;
|
||||
};
|
||||
export type EdComposed = {
|
||||
CURVE: EdwardsOpts;
|
||||
curveOpts: EdwardsExtraOpts;
|
||||
hash: FHash;
|
||||
eddsaOpts: EdDSAOpts;
|
||||
};
|
||||
export declare function twistedEdwards(c: CurveTypeWithLength): CurveFn;
|
||||
//# sourceMappingURL=edwards.d.ts.map
|
||||
-1
File diff suppressed because one or more lines are too long
-634
@@ -1,634 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.PrimeEdwardsPoint = void 0;
|
||||
exports.edwards = edwards;
|
||||
exports.eddsa = eddsa;
|
||||
exports.twistedEdwards = twistedEdwards;
|
||||
/**
|
||||
* Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y².
|
||||
* For design rationale of types / exports, see weierstrass module documentation.
|
||||
* Untwisted Edwards curves exist, but they aren't used in real-world protocols.
|
||||
* @module
|
||||
*/
|
||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||
const utils_ts_1 = require("../utils.js");
|
||||
const curve_ts_1 = require("./curve.js");
|
||||
const modular_ts_1 = require("./modular.js");
|
||||
// Be friendly to bad ECMAScript parsers by not using bigint literals
|
||||
// prettier-ignore
|
||||
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _8n = BigInt(8);
|
||||
function isEdValidXY(Fp, CURVE, x, y) {
|
||||
const x2 = Fp.sqr(x);
|
||||
const y2 = Fp.sqr(y);
|
||||
const left = Fp.add(Fp.mul(CURVE.a, x2), y2);
|
||||
const right = Fp.add(Fp.ONE, Fp.mul(CURVE.d, Fp.mul(x2, y2)));
|
||||
return Fp.eql(left, right);
|
||||
}
|
||||
function edwards(params, extraOpts = {}) {
|
||||
const validated = (0, curve_ts_1._createCurveFields)('edwards', params, extraOpts, extraOpts.FpFnLE);
|
||||
const { Fp, Fn } = validated;
|
||||
let CURVE = validated.CURVE;
|
||||
const { h: cofactor } = CURVE;
|
||||
(0, utils_ts_1._validateObject)(extraOpts, {}, { uvRatio: 'function' });
|
||||
// Important:
|
||||
// There are some places where Fp.BYTES is used instead of nByteLength.
|
||||
// So far, everything has been tested with curves of Fp.BYTES == nByteLength.
|
||||
// TODO: test and find curves which behave otherwise.
|
||||
const MASK = _2n << (BigInt(Fn.BYTES * 8) - _1n);
|
||||
const modP = (n) => Fp.create(n); // Function overrides
|
||||
// sqrt(u/v)
|
||||
const uvRatio = extraOpts.uvRatio ||
|
||||
((u, v) => {
|
||||
try {
|
||||
return { isValid: true, value: Fp.sqrt(Fp.div(u, v)) };
|
||||
}
|
||||
catch (e) {
|
||||
return { isValid: false, value: _0n };
|
||||
}
|
||||
});
|
||||
// Validate whether the passed curve params are valid.
|
||||
// equation ax² + y² = 1 + dx²y² should work for generator point.
|
||||
if (!isEdValidXY(Fp, CURVE, CURVE.Gx, CURVE.Gy))
|
||||
throw new Error('bad curve params: generator point');
|
||||
/**
|
||||
* Asserts coordinate is valid: 0 <= n < MASK.
|
||||
* Coordinates >= Fp.ORDER are allowed for zip215.
|
||||
*/
|
||||
function acoord(title, n, banZero = false) {
|
||||
const min = banZero ? _1n : _0n;
|
||||
(0, utils_ts_1.aInRange)('coordinate ' + title, n, min, MASK);
|
||||
return n;
|
||||
}
|
||||
function aextpoint(other) {
|
||||
if (!(other instanceof Point))
|
||||
throw new Error('ExtendedPoint expected');
|
||||
}
|
||||
// Converts Extended point to default (x, y) coordinates.
|
||||
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
||||
const toAffineMemo = (0, utils_ts_1.memoized)((p, iz) => {
|
||||
const { X, Y, Z } = p;
|
||||
const is0 = p.is0();
|
||||
if (iz == null)
|
||||
iz = is0 ? _8n : Fp.inv(Z); // 8 was chosen arbitrarily
|
||||
const x = modP(X * iz);
|
||||
const y = modP(Y * iz);
|
||||
const zz = Fp.mul(Z, iz);
|
||||
if (is0)
|
||||
return { x: _0n, y: _1n };
|
||||
if (zz !== _1n)
|
||||
throw new Error('invZ was invalid');
|
||||
return { x, y };
|
||||
});
|
||||
const assertValidMemo = (0, utils_ts_1.memoized)((p) => {
|
||||
const { a, d } = CURVE;
|
||||
if (p.is0())
|
||||
throw new Error('bad point: ZERO'); // TODO: optimize, with vars below?
|
||||
// Equation in affine coordinates: ax² + y² = 1 + dx²y²
|
||||
// Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
|
||||
const { X, Y, Z, T } = p;
|
||||
const X2 = modP(X * X); // X²
|
||||
const Y2 = modP(Y * Y); // Y²
|
||||
const Z2 = modP(Z * Z); // Z²
|
||||
const Z4 = modP(Z2 * Z2); // Z⁴
|
||||
const aX2 = modP(X2 * a); // aX²
|
||||
const left = modP(Z2 * modP(aX2 + Y2)); // (aX² + Y²)Z²
|
||||
const right = modP(Z4 + modP(d * modP(X2 * Y2))); // Z⁴ + dX²Y²
|
||||
if (left !== right)
|
||||
throw new Error('bad point: equation left != right (1)');
|
||||
// In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
|
||||
const XY = modP(X * Y);
|
||||
const ZT = modP(Z * T);
|
||||
if (XY !== ZT)
|
||||
throw new Error('bad point: equation left != right (2)');
|
||||
return true;
|
||||
});
|
||||
// Extended Point works in extended coordinates: (X, Y, Z, T) ∋ (x=X/Z, y=Y/Z, T=xy).
|
||||
// https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
|
||||
class Point {
|
||||
constructor(X, Y, Z, T) {
|
||||
this.X = acoord('x', X);
|
||||
this.Y = acoord('y', Y);
|
||||
this.Z = acoord('z', Z, true);
|
||||
this.T = acoord('t', T);
|
||||
Object.freeze(this);
|
||||
}
|
||||
static CURVE() {
|
||||
return CURVE;
|
||||
}
|
||||
static fromAffine(p) {
|
||||
if (p instanceof Point)
|
||||
throw new Error('extended point not allowed');
|
||||
const { x, y } = p || {};
|
||||
acoord('x', x);
|
||||
acoord('y', y);
|
||||
return new Point(x, y, _1n, modP(x * y));
|
||||
}
|
||||
// Uses algo from RFC8032 5.1.3.
|
||||
static fromBytes(bytes, zip215 = false) {
|
||||
const len = Fp.BYTES;
|
||||
const { a, d } = CURVE;
|
||||
bytes = (0, utils_ts_1.copyBytes)((0, utils_ts_1._abytes2)(bytes, len, 'point'));
|
||||
(0, utils_ts_1._abool2)(zip215, 'zip215');
|
||||
const normed = (0, utils_ts_1.copyBytes)(bytes); // copy again, we'll manipulate it
|
||||
const lastByte = bytes[len - 1]; // select last byte
|
||||
normed[len - 1] = lastByte & ~0x80; // clear last bit
|
||||
const y = (0, utils_ts_1.bytesToNumberLE)(normed);
|
||||
// zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5.
|
||||
// RFC8032 prohibits >= p, but ZIP215 doesn't
|
||||
// zip215=true: 0 <= y < MASK (2^256 for ed25519)
|
||||
// zip215=false: 0 <= y < P (2^255-19 for ed25519)
|
||||
const max = zip215 ? MASK : Fp.ORDER;
|
||||
(0, utils_ts_1.aInRange)('point.y', y, _0n, max);
|
||||
// Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case:
|
||||
// ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a)
|
||||
const y2 = modP(y * y); // denominator is always non-0 mod p.
|
||||
const u = modP(y2 - _1n); // u = y² - 1
|
||||
const v = modP(d * y2 - a); // v = d y² + 1.
|
||||
let { isValid, value: x } = uvRatio(u, v); // √(u/v)
|
||||
if (!isValid)
|
||||
throw new Error('bad point: invalid y coordinate');
|
||||
const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper
|
||||
const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit
|
||||
if (!zip215 && x === _0n && isLastByteOdd)
|
||||
// if x=0 and x_0 = 1, fail
|
||||
throw new Error('bad point: x=0 and x_0=1');
|
||||
if (isLastByteOdd !== isXOdd)
|
||||
x = modP(-x); // if x_0 != x mod 2, set x = p-x
|
||||
return Point.fromAffine({ x, y });
|
||||
}
|
||||
static fromHex(bytes, zip215 = false) {
|
||||
return Point.fromBytes((0, utils_ts_1.ensureBytes)('point', bytes), zip215);
|
||||
}
|
||||
get x() {
|
||||
return this.toAffine().x;
|
||||
}
|
||||
get y() {
|
||||
return this.toAffine().y;
|
||||
}
|
||||
precompute(windowSize = 8, isLazy = true) {
|
||||
wnaf.createCache(this, windowSize);
|
||||
if (!isLazy)
|
||||
this.multiply(_2n); // random number
|
||||
return this;
|
||||
}
|
||||
// Useful in fromAffine() - not for fromBytes(), which always created valid points.
|
||||
assertValidity() {
|
||||
assertValidMemo(this);
|
||||
}
|
||||
// Compare one point to another.
|
||||
equals(other) {
|
||||
aextpoint(other);
|
||||
const { X: X1, Y: Y1, Z: Z1 } = this;
|
||||
const { X: X2, Y: Y2, Z: Z2 } = other;
|
||||
const X1Z2 = modP(X1 * Z2);
|
||||
const X2Z1 = modP(X2 * Z1);
|
||||
const Y1Z2 = modP(Y1 * Z2);
|
||||
const Y2Z1 = modP(Y2 * Z1);
|
||||
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
||||
}
|
||||
is0() {
|
||||
return this.equals(Point.ZERO);
|
||||
}
|
||||
negate() {
|
||||
// Flips point sign to a negative one (-x, y in affine coords)
|
||||
return new Point(modP(-this.X), this.Y, this.Z, modP(-this.T));
|
||||
}
|
||||
// Fast algo for doubling Extended Point.
|
||||
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
|
||||
// Cost: 4M + 4S + 1*a + 6add + 1*2.
|
||||
double() {
|
||||
const { a } = CURVE;
|
||||
const { X: X1, Y: Y1, Z: Z1 } = this;
|
||||
const A = modP(X1 * X1); // A = X12
|
||||
const B = modP(Y1 * Y1); // B = Y12
|
||||
const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12
|
||||
const D = modP(a * A); // D = a*A
|
||||
const x1y1 = X1 + Y1;
|
||||
const E = modP(modP(x1y1 * x1y1) - A - B); // E = (X1+Y1)2-A-B
|
||||
const G = D + B; // G = D+B
|
||||
const F = G - C; // F = G-C
|
||||
const H = D - B; // H = D-B
|
||||
const X3 = modP(E * F); // X3 = E*F
|
||||
const Y3 = modP(G * H); // Y3 = G*H
|
||||
const T3 = modP(E * H); // T3 = E*H
|
||||
const Z3 = modP(F * G); // Z3 = F*G
|
||||
return new Point(X3, Y3, Z3, T3);
|
||||
}
|
||||
// Fast algo for adding 2 Extended Points.
|
||||
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
|
||||
// Cost: 9M + 1*a + 1*d + 7add.
|
||||
add(other) {
|
||||
aextpoint(other);
|
||||
const { a, d } = CURVE;
|
||||
const { X: X1, Y: Y1, Z: Z1, T: T1 } = this;
|
||||
const { X: X2, Y: Y2, Z: Z2, T: T2 } = other;
|
||||
const A = modP(X1 * X2); // A = X1*X2
|
||||
const B = modP(Y1 * Y2); // B = Y1*Y2
|
||||
const C = modP(T1 * d * T2); // C = T1*d*T2
|
||||
const D = modP(Z1 * Z2); // D = Z1*Z2
|
||||
const E = modP((X1 + Y1) * (X2 + Y2) - A - B); // E = (X1+Y1)*(X2+Y2)-A-B
|
||||
const F = D - C; // F = D-C
|
||||
const G = D + C; // G = D+C
|
||||
const H = modP(B - a * A); // H = B-a*A
|
||||
const X3 = modP(E * F); // X3 = E*F
|
||||
const Y3 = modP(G * H); // Y3 = G*H
|
||||
const T3 = modP(E * H); // T3 = E*H
|
||||
const Z3 = modP(F * G); // Z3 = F*G
|
||||
return new Point(X3, Y3, Z3, T3);
|
||||
}
|
||||
subtract(other) {
|
||||
return this.add(other.negate());
|
||||
}
|
||||
// Constant-time multiplication.
|
||||
multiply(scalar) {
|
||||
// 1 <= scalar < L
|
||||
if (!Fn.isValidNot0(scalar))
|
||||
throw new Error('invalid scalar: expected 1 <= sc < curve.n');
|
||||
const { p, f } = wnaf.cached(this, scalar, (p) => (0, curve_ts_1.normalizeZ)(Point, p));
|
||||
return (0, curve_ts_1.normalizeZ)(Point, [p, f])[0];
|
||||
}
|
||||
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
||||
// It's faster, but should only be used when you don't care about
|
||||
// an exposed private key e.g. sig verification.
|
||||
// Does NOT allow scalars higher than CURVE.n.
|
||||
// Accepts optional accumulator to merge with multiply (important for sparse scalars)
|
||||
multiplyUnsafe(scalar, acc = Point.ZERO) {
|
||||
// 0 <= scalar < L
|
||||
if (!Fn.isValid(scalar))
|
||||
throw new Error('invalid scalar: expected 0 <= sc < curve.n');
|
||||
if (scalar === _0n)
|
||||
return Point.ZERO;
|
||||
if (this.is0() || scalar === _1n)
|
||||
return this;
|
||||
return wnaf.unsafe(this, scalar, (p) => (0, curve_ts_1.normalizeZ)(Point, p), acc);
|
||||
}
|
||||
// Checks if point is of small order.
|
||||
// If you add something to small order point, you will have "dirty"
|
||||
// point with torsion component.
|
||||
// Multiplies point by cofactor and checks if the result is 0.
|
||||
isSmallOrder() {
|
||||
return this.multiplyUnsafe(cofactor).is0();
|
||||
}
|
||||
// Multiplies point by curve order and checks if the result is 0.
|
||||
// Returns `false` is the point is dirty.
|
||||
isTorsionFree() {
|
||||
return wnaf.unsafe(this, CURVE.n).is0();
|
||||
}
|
||||
// Converts Extended point to default (x, y) coordinates.
|
||||
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
||||
toAffine(invertedZ) {
|
||||
return toAffineMemo(this, invertedZ);
|
||||
}
|
||||
clearCofactor() {
|
||||
if (cofactor === _1n)
|
||||
return this;
|
||||
return this.multiplyUnsafe(cofactor);
|
||||
}
|
||||
toBytes() {
|
||||
const { x, y } = this.toAffine();
|
||||
// Fp.toBytes() allows non-canonical encoding of y (>= p).
|
||||
const bytes = Fp.toBytes(y);
|
||||
// Each y has 2 valid points: (x, y), (x,-y).
|
||||
// When compressing, it's enough to store y and use the last byte to encode sign of x
|
||||
bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0;
|
||||
return bytes;
|
||||
}
|
||||
toHex() {
|
||||
return (0, utils_ts_1.bytesToHex)(this.toBytes());
|
||||
}
|
||||
toString() {
|
||||
return `<Point ${this.is0() ? 'ZERO' : this.toHex()}>`;
|
||||
}
|
||||
// TODO: remove
|
||||
get ex() {
|
||||
return this.X;
|
||||
}
|
||||
get ey() {
|
||||
return this.Y;
|
||||
}
|
||||
get ez() {
|
||||
return this.Z;
|
||||
}
|
||||
get et() {
|
||||
return this.T;
|
||||
}
|
||||
static normalizeZ(points) {
|
||||
return (0, curve_ts_1.normalizeZ)(Point, points);
|
||||
}
|
||||
static msm(points, scalars) {
|
||||
return (0, curve_ts_1.pippenger)(Point, Fn, points, scalars);
|
||||
}
|
||||
_setWindowSize(windowSize) {
|
||||
this.precompute(windowSize);
|
||||
}
|
||||
toRawBytes() {
|
||||
return this.toBytes();
|
||||
}
|
||||
}
|
||||
// base / generator point
|
||||
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
|
||||
// zero / infinity / identity point
|
||||
Point.ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0
|
||||
// math field
|
||||
Point.Fp = Fp;
|
||||
// scalar field
|
||||
Point.Fn = Fn;
|
||||
const wnaf = new curve_ts_1.wNAF(Point, Fn.BITS);
|
||||
Point.BASE.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||
return Point;
|
||||
}
|
||||
/**
|
||||
* Base class for prime-order points like Ristretto255 and Decaf448.
|
||||
* These points eliminate cofactor issues by representing equivalence classes
|
||||
* of Edwards curve points.
|
||||
*/
|
||||
class PrimeEdwardsPoint {
|
||||
constructor(ep) {
|
||||
this.ep = ep;
|
||||
}
|
||||
// Static methods that must be implemented by subclasses
|
||||
static fromBytes(_bytes) {
|
||||
(0, utils_ts_1.notImplemented)();
|
||||
}
|
||||
static fromHex(_hex) {
|
||||
(0, utils_ts_1.notImplemented)();
|
||||
}
|
||||
get x() {
|
||||
return this.toAffine().x;
|
||||
}
|
||||
get y() {
|
||||
return this.toAffine().y;
|
||||
}
|
||||
// Common implementations
|
||||
clearCofactor() {
|
||||
// no-op for prime-order groups
|
||||
return this;
|
||||
}
|
||||
assertValidity() {
|
||||
this.ep.assertValidity();
|
||||
}
|
||||
toAffine(invertedZ) {
|
||||
return this.ep.toAffine(invertedZ);
|
||||
}
|
||||
toHex() {
|
||||
return (0, utils_ts_1.bytesToHex)(this.toBytes());
|
||||
}
|
||||
toString() {
|
||||
return this.toHex();
|
||||
}
|
||||
isTorsionFree() {
|
||||
return true;
|
||||
}
|
||||
isSmallOrder() {
|
||||
return false;
|
||||
}
|
||||
add(other) {
|
||||
this.assertSame(other);
|
||||
return this.init(this.ep.add(other.ep));
|
||||
}
|
||||
subtract(other) {
|
||||
this.assertSame(other);
|
||||
return this.init(this.ep.subtract(other.ep));
|
||||
}
|
||||
multiply(scalar) {
|
||||
return this.init(this.ep.multiply(scalar));
|
||||
}
|
||||
multiplyUnsafe(scalar) {
|
||||
return this.init(this.ep.multiplyUnsafe(scalar));
|
||||
}
|
||||
double() {
|
||||
return this.init(this.ep.double());
|
||||
}
|
||||
negate() {
|
||||
return this.init(this.ep.negate());
|
||||
}
|
||||
precompute(windowSize, isLazy) {
|
||||
return this.init(this.ep.precompute(windowSize, isLazy));
|
||||
}
|
||||
/** @deprecated use `toBytes` */
|
||||
toRawBytes() {
|
||||
return this.toBytes();
|
||||
}
|
||||
}
|
||||
exports.PrimeEdwardsPoint = PrimeEdwardsPoint;
|
||||
/**
|
||||
* Initializes EdDSA signatures over given Edwards curve.
|
||||
*/
|
||||
function eddsa(Point, cHash, eddsaOpts = {}) {
|
||||
if (typeof cHash !== 'function')
|
||||
throw new Error('"hash" function param is required');
|
||||
(0, utils_ts_1._validateObject)(eddsaOpts, {}, {
|
||||
adjustScalarBytes: 'function',
|
||||
randomBytes: 'function',
|
||||
domain: 'function',
|
||||
prehash: 'function',
|
||||
mapToCurve: 'function',
|
||||
});
|
||||
const { prehash } = eddsaOpts;
|
||||
const { BASE, Fp, Fn } = Point;
|
||||
const randomBytes = eddsaOpts.randomBytes || utils_ts_1.randomBytes;
|
||||
const adjustScalarBytes = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
|
||||
const domain = eddsaOpts.domain ||
|
||||
((data, ctx, phflag) => {
|
||||
(0, utils_ts_1._abool2)(phflag, 'phflag');
|
||||
if (ctx.length || phflag)
|
||||
throw new Error('Contexts/pre-hash are not supported');
|
||||
return data;
|
||||
}); // NOOP
|
||||
// Little-endian SHA512 with modulo n
|
||||
function modN_LE(hash) {
|
||||
return Fn.create((0, utils_ts_1.bytesToNumberLE)(hash)); // Not Fn.fromBytes: it has length limit
|
||||
}
|
||||
// Get the hashed private scalar per RFC8032 5.1.5
|
||||
function getPrivateScalar(key) {
|
||||
const len = lengths.secretKey;
|
||||
key = (0, utils_ts_1.ensureBytes)('private key', key, len);
|
||||
// Hash private key with curve's hash function to produce uniformingly random input
|
||||
// Check byte lengths: ensure(64, h(ensure(32, key)))
|
||||
const hashed = (0, utils_ts_1.ensureBytes)('hashed private key', cHash(key), 2 * len);
|
||||
const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE
|
||||
const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6)
|
||||
const scalar = modN_LE(head); // The actual private scalar
|
||||
return { head, prefix, scalar };
|
||||
}
|
||||
/** Convenience method that creates public key from scalar. RFC8032 5.1.5 */
|
||||
function getExtendedPublicKey(secretKey) {
|
||||
const { head, prefix, scalar } = getPrivateScalar(secretKey);
|
||||
const point = BASE.multiply(scalar); // Point on Edwards curve aka public key
|
||||
const pointBytes = point.toBytes();
|
||||
return { head, prefix, scalar, point, pointBytes };
|
||||
}
|
||||
/** Calculates EdDSA pub key. RFC8032 5.1.5. */
|
||||
function getPublicKey(secretKey) {
|
||||
return getExtendedPublicKey(secretKey).pointBytes;
|
||||
}
|
||||
// int('LE', SHA512(dom2(F, C) || msgs)) mod N
|
||||
function hashDomainToScalar(context = Uint8Array.of(), ...msgs) {
|
||||
const msg = (0, utils_ts_1.concatBytes)(...msgs);
|
||||
return modN_LE(cHash(domain(msg, (0, utils_ts_1.ensureBytes)('context', context), !!prehash)));
|
||||
}
|
||||
/** Signs message with privateKey. RFC8032 5.1.6 */
|
||||
function sign(msg, secretKey, options = {}) {
|
||||
msg = (0, utils_ts_1.ensureBytes)('message', msg);
|
||||
if (prehash)
|
||||
msg = prehash(msg); // for ed25519ph etc.
|
||||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(secretKey);
|
||||
const r = hashDomainToScalar(options.context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
|
||||
const R = BASE.multiply(r).toBytes(); // R = rG
|
||||
const k = hashDomainToScalar(options.context, R, pointBytes, msg); // R || A || PH(M)
|
||||
const s = Fn.create(r + k * scalar); // S = (r + k * s) mod L
|
||||
if (!Fn.isValid(s))
|
||||
throw new Error('sign failed: invalid s'); // 0 <= s < L
|
||||
const rs = (0, utils_ts_1.concatBytes)(R, Fn.toBytes(s));
|
||||
return (0, utils_ts_1._abytes2)(rs, lengths.signature, 'result');
|
||||
}
|
||||
// verification rule is either zip215 or rfc8032 / nist186-5. Consult fromHex:
|
||||
const verifyOpts = { zip215: true };
|
||||
/**
|
||||
* Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
|
||||
* An extended group equation is checked.
|
||||
*/
|
||||
function verify(sig, msg, publicKey, options = verifyOpts) {
|
||||
const { context, zip215 } = options;
|
||||
const len = lengths.signature;
|
||||
sig = (0, utils_ts_1.ensureBytes)('signature', sig, len);
|
||||
msg = (0, utils_ts_1.ensureBytes)('message', msg);
|
||||
publicKey = (0, utils_ts_1.ensureBytes)('publicKey', publicKey, lengths.publicKey);
|
||||
if (zip215 !== undefined)
|
||||
(0, utils_ts_1._abool2)(zip215, 'zip215');
|
||||
if (prehash)
|
||||
msg = prehash(msg); // for ed25519ph, etc
|
||||
const mid = len / 2;
|
||||
const r = sig.subarray(0, mid);
|
||||
const s = (0, utils_ts_1.bytesToNumberLE)(sig.subarray(mid, len));
|
||||
let A, R, SB;
|
||||
try {
|
||||
// zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5.
|
||||
// zip215=true: 0 <= y < MASK (2^256 for ed25519)
|
||||
// zip215=false: 0 <= y < P (2^255-19 for ed25519)
|
||||
A = Point.fromBytes(publicKey, zip215);
|
||||
R = Point.fromBytes(r, zip215);
|
||||
SB = BASE.multiplyUnsafe(s); // 0 <= s < l is done inside
|
||||
}
|
||||
catch (error) {
|
||||
return false;
|
||||
}
|
||||
if (!zip215 && A.isSmallOrder())
|
||||
return false; // zip215 allows public keys of small order
|
||||
const k = hashDomainToScalar(context, R.toBytes(), A.toBytes(), msg);
|
||||
const RkA = R.add(A.multiplyUnsafe(k));
|
||||
// Extended group equation
|
||||
// [8][S]B = [8]R + [8][k]A'
|
||||
return RkA.subtract(SB).clearCofactor().is0();
|
||||
}
|
||||
const _size = Fp.BYTES; // 32 for ed25519, 57 for ed448
|
||||
const lengths = {
|
||||
secretKey: _size,
|
||||
publicKey: _size,
|
||||
signature: 2 * _size,
|
||||
seed: _size,
|
||||
};
|
||||
function randomSecretKey(seed = randomBytes(lengths.seed)) {
|
||||
return (0, utils_ts_1._abytes2)(seed, lengths.seed, 'seed');
|
||||
}
|
||||
function keygen(seed) {
|
||||
const secretKey = utils.randomSecretKey(seed);
|
||||
return { secretKey, publicKey: getPublicKey(secretKey) };
|
||||
}
|
||||
function isValidSecretKey(key) {
|
||||
return (0, utils_ts_1.isBytes)(key) && key.length === Fn.BYTES;
|
||||
}
|
||||
function isValidPublicKey(key, zip215) {
|
||||
try {
|
||||
return !!Point.fromBytes(key, zip215);
|
||||
}
|
||||
catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const utils = {
|
||||
getExtendedPublicKey,
|
||||
randomSecretKey,
|
||||
isValidSecretKey,
|
||||
isValidPublicKey,
|
||||
/**
|
||||
* Converts ed public key to x public key. Uses formula:
|
||||
* - ed25519:
|
||||
* - `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
|
||||
* - `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
|
||||
* - ed448:
|
||||
* - `(u, v) = ((y-1)/(y+1), sqrt(156324)*u/x)`
|
||||
* - `(x, y) = (sqrt(156324)*u/v, (1+u)/(1-u))`
|
||||
*/
|
||||
toMontgomery(publicKey) {
|
||||
const { y } = Point.fromBytes(publicKey);
|
||||
const size = lengths.publicKey;
|
||||
const is25519 = size === 32;
|
||||
if (!is25519 && size !== 57)
|
||||
throw new Error('only defined for 25519 and 448');
|
||||
const u = is25519 ? Fp.div(_1n + y, _1n - y) : Fp.div(y - _1n, y + _1n);
|
||||
return Fp.toBytes(u);
|
||||
},
|
||||
toMontgomerySecret(secretKey) {
|
||||
const size = lengths.secretKey;
|
||||
(0, utils_ts_1._abytes2)(secretKey, size);
|
||||
const hashed = cHash(secretKey.subarray(0, size));
|
||||
return adjustScalarBytes(hashed).subarray(0, size);
|
||||
},
|
||||
/** @deprecated */
|
||||
randomPrivateKey: randomSecretKey,
|
||||
/** @deprecated */
|
||||
precompute(windowSize = 8, point = Point.BASE) {
|
||||
return point.precompute(windowSize, false);
|
||||
},
|
||||
};
|
||||
return Object.freeze({
|
||||
keygen,
|
||||
getPublicKey,
|
||||
sign,
|
||||
verify,
|
||||
utils,
|
||||
Point,
|
||||
lengths,
|
||||
});
|
||||
}
|
||||
function _eddsa_legacy_opts_to_new(c) {
|
||||
const CURVE = {
|
||||
a: c.a,
|
||||
d: c.d,
|
||||
p: c.Fp.ORDER,
|
||||
n: c.n,
|
||||
h: c.h,
|
||||
Gx: c.Gx,
|
||||
Gy: c.Gy,
|
||||
};
|
||||
const Fp = c.Fp;
|
||||
const Fn = (0, modular_ts_1.Field)(CURVE.n, c.nBitLength, true);
|
||||
const curveOpts = { Fp, Fn, uvRatio: c.uvRatio };
|
||||
const eddsaOpts = {
|
||||
randomBytes: c.randomBytes,
|
||||
adjustScalarBytes: c.adjustScalarBytes,
|
||||
domain: c.domain,
|
||||
prehash: c.prehash,
|
||||
mapToCurve: c.mapToCurve,
|
||||
};
|
||||
return { CURVE, curveOpts, hash: c.hash, eddsaOpts };
|
||||
}
|
||||
function _eddsa_new_output_to_legacy(c, eddsa) {
|
||||
const Point = eddsa.Point;
|
||||
const legacy = Object.assign({}, eddsa, {
|
||||
ExtendedPoint: Point,
|
||||
CURVE: c,
|
||||
nBitLength: Point.Fn.BITS,
|
||||
nByteLength: Point.Fn.BYTES,
|
||||
});
|
||||
return legacy;
|
||||
}
|
||||
// TODO: remove. Use eddsa
|
||||
function twistedEdwards(c) {
|
||||
const { CURVE, curveOpts, hash, eddsaOpts } = _eddsa_legacy_opts_to_new(c);
|
||||
const Point = edwards(CURVE, curveOpts);
|
||||
const EDDSA = eddsa(Point, hash, eddsaOpts);
|
||||
return _eddsa_new_output_to_legacy(c, EDDSA);
|
||||
}
|
||||
//# sourceMappingURL=edwards.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-122
@@ -1,122 +0,0 @@
|
||||
/**
|
||||
* Experimental implementation of NTT / FFT (Fast Fourier Transform) over finite fields.
|
||||
* API may change at any time. The code has not been audited. Feature requests are welcome.
|
||||
* @module
|
||||
*/
|
||||
import type { IField } from './modular.ts';
|
||||
export interface MutableArrayLike<T> {
|
||||
[index: number]: T;
|
||||
length: number;
|
||||
slice(start?: number, end?: number): this;
|
||||
[Symbol.iterator](): Iterator<T>;
|
||||
}
|
||||
/** Checks if integer is in form of `1 << X` */
|
||||
export declare function isPowerOfTwo(x: number): boolean;
|
||||
export declare function nextPowerOfTwo(n: number): number;
|
||||
export declare function reverseBits(n: number, bits: number): number;
|
||||
/** Similar to `bitLen(x)-1` but much faster for small integers, like indices */
|
||||
export declare function log2(n: number): number;
|
||||
/**
|
||||
* Moves lowest bit to highest position, which at first step splits
|
||||
* array on even and odd indices, then it applied again to each part,
|
||||
* which is core of fft
|
||||
*/
|
||||
export declare function bitReversalInplace<T extends MutableArrayLike<any>>(values: T): T;
|
||||
export declare function bitReversalPermutation<T>(values: T[]): T[];
|
||||
export type RootsOfUnity = {
|
||||
roots: (bits: number) => bigint[];
|
||||
brp(bits: number): bigint[];
|
||||
inverse(bits: number): bigint[];
|
||||
omega: (bits: number) => bigint;
|
||||
clear: () => void;
|
||||
};
|
||||
/** We limit roots up to 2**31, which is a lot: 2-billion polynomimal should be rare. */
|
||||
export declare function rootsOfUnity(field: IField<bigint>, generator?: bigint): RootsOfUnity;
|
||||
export type Polynomial<T> = MutableArrayLike<T>;
|
||||
/**
|
||||
* Maps great to Field<bigint>, but not to Group (EC points):
|
||||
* - inv from scalar field
|
||||
* - we need multiplyUnsafe here, instead of multiply for speed
|
||||
* - multiplyUnsafe is safe in the context: we do mul(rootsOfUnity), which are public and sparse
|
||||
*/
|
||||
export type FFTOpts<T, R> = {
|
||||
add: (a: T, b: T) => T;
|
||||
sub: (a: T, b: T) => T;
|
||||
mul: (a: T, scalar: R) => T;
|
||||
inv: (a: R) => R;
|
||||
};
|
||||
export type FFTCoreOpts<R> = {
|
||||
N: number;
|
||||
roots: Polynomial<R>;
|
||||
dit: boolean;
|
||||
invertButterflies?: boolean;
|
||||
skipStages?: number;
|
||||
brp?: boolean;
|
||||
};
|
||||
export type FFTCoreLoop<T> = <P extends Polynomial<T>>(values: P) => P;
|
||||
/**
|
||||
* Constructs different flavors of FFT. radix2 implementation of low level mutating API. Flavors:
|
||||
*
|
||||
* - DIT (Decimation-in-Time): Bottom-Up (leaves -> root), Cool-Turkey
|
||||
* - DIF (Decimation-in-Frequency): Top-Down (root -> leaves), Gentleman–Sande
|
||||
*
|
||||
* DIT takes brp input, returns natural output.
|
||||
* DIF takes natural input, returns brp output.
|
||||
*
|
||||
* The output is actually identical. Time / frequence distinction is not meaningful
|
||||
* for Polynomial multiplication in fields.
|
||||
* Which means if protocol supports/needs brp output/inputs, then we can skip this step.
|
||||
*
|
||||
* Cyclic NTT: Rq = Zq[x]/(x^n-1). butterfly_DIT+loop_DIT OR butterfly_DIF+loop_DIT, roots are omega
|
||||
* Negacyclic NTT: Rq = Zq[x]/(x^n+1). butterfly_DIT+loop_DIF, at least for mlkem / mldsa
|
||||
*/
|
||||
export declare const FFTCore: <T, R>(F: FFTOpts<T, R>, coreOpts: FFTCoreOpts<R>) => FFTCoreLoop<T>;
|
||||
export type FFTMethods<T> = {
|
||||
direct<P extends Polynomial<T>>(values: P, brpInput?: boolean, brpOutput?: boolean): P;
|
||||
inverse<P extends Polynomial<T>>(values: P, brpInput?: boolean, brpOutput?: boolean): P;
|
||||
};
|
||||
/**
|
||||
* NTT aka FFT over finite field (NOT over complex numbers).
|
||||
* Naming mirrors other libraries.
|
||||
*/
|
||||
export declare function FFT<T>(roots: RootsOfUnity, opts: FFTOpts<T, bigint>): FFTMethods<T>;
|
||||
export type CreatePolyFn<P extends Polynomial<T>, T> = (len: number, elm?: T) => P;
|
||||
export type PolyFn<P extends Polynomial<T>, T> = {
|
||||
roots: RootsOfUnity;
|
||||
create: CreatePolyFn<P, T>;
|
||||
length?: number;
|
||||
degree: (a: P) => number;
|
||||
extend: (a: P, len: number) => P;
|
||||
add: (a: P, b: P) => P;
|
||||
sub: (a: P, b: P) => P;
|
||||
mul: (a: P, b: P | T) => P;
|
||||
dot: (a: P, b: P) => P;
|
||||
convolve: (a: P, b: P) => P;
|
||||
shift: (p: P, factor: bigint) => P;
|
||||
clone: (a: P) => P;
|
||||
eval: (a: P, basis: P) => T;
|
||||
monomial: {
|
||||
basis: (x: T, n: number) => P;
|
||||
eval: (a: P, x: T) => T;
|
||||
};
|
||||
lagrange: {
|
||||
basis: (x: T, n: number, brp?: boolean) => P;
|
||||
eval: (a: P, x: T, brp?: boolean) => T;
|
||||
};
|
||||
vanishing: (roots: P) => P;
|
||||
};
|
||||
/**
|
||||
* Poly wants a cracker.
|
||||
*
|
||||
* Polynomials are functions like `y=f(x)`, which means when we multiply two polynomials, result is
|
||||
* function `f3(x) = f1(x) * f2(x)`, we don't multiply values. Key takeaways:
|
||||
*
|
||||
* - **Polynomial** is an array of coefficients: `f(x) = sum(coeff[i] * basis[i](x))`
|
||||
* - **Basis** is array of functions
|
||||
* - **Monominal** is Polynomial where `basis[i](x) == x**i` (powers)
|
||||
* - **Array size** is domain size
|
||||
* - **Lattice** is matrix (Polynomial of Polynomials)
|
||||
*/
|
||||
export declare function poly<T>(field: IField<T>, roots: RootsOfUnity, create?: undefined, fft?: FFTMethods<T>, length?: number): PolyFn<T[], T>;
|
||||
export declare function poly<T, P extends Polynomial<T>>(field: IField<T>, roots: RootsOfUnity, create: CreatePolyFn<P, T>, fft?: FFTMethods<T>, length?: number): PolyFn<P, T>;
|
||||
//# sourceMappingURL=fft.d.ts.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"fft.d.ts","sourceRoot":"","sources":["../src/abstract/fft.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;CAClC;AASD,+CAA+C;AAC/C,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAG/C;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK3D;AAED,gFAAgF;AAChF,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGtC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAchF;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAE1D;AASD,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IAClC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAChC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AACF,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAiEpF;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI;IAC1B,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IAC5B,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACrB,GAAG,EAAE,OAAO,CAAC;IACb,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,WAAW,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CA2CvF,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC1B,MAAM,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;IACvF,OAAO,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC;CACzF,CAAC;AAEF;;;GAGG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAoCnF;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;AAEnF,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI;IAC/C,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC;IACzB,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC,CAAC;IACjC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACvB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAC5B,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAEnB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;IAC5B,QAAQ,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC,CAAC;QAC9B,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;KACzB,CAAC;IACF,QAAQ,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;QAC7C,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;KACxC,CAAC;IAEF,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,IAAI,CAAC,CAAC,EACpB,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAChB,KAAK,EAAE,YAAY,EACnB,MAAM,CAAC,EAAE,SAAS,EAClB,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAClB,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAC7C,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAChB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user