421 |
422 | 3. **使用 `code` 和 `codeVerifier` 生成access_token**
423 |
424 | ```js
425 | POST https://${tenant_url}/token
426 |
427 | const data = {
428 | "grant_type": "authorization_code",
429 | "client_id": "augment-vscode-extension",
430 | "code_verifier": codeVerifier,
431 | "redirect_uri": `${ide === IDE.VSCode ? 'vscode' : 'cursor'}://augment.vscode-augment/auth/result`,
432 | "code": code
433 | }
434 |
435 | const response = {
436 | "access_token": "xxx",
437 | "expires_in": 0,
438 | "token_type": "Bearer"
439 | }
440 | ```
441 |
442 | 4. **整合sessions, 并加密成buffer数据**
443 |
444 | ```json
445 | {
446 | "accessToken": access_token,
447 | "tenantURL": tenant_url,
448 | "scopes":["email"] // 固定
449 | }
450 | ```
451 |
452 | 加密和解密数据有两种方案:
453 |
454 | 第一种: 做成编辑器插件的形式, 使用vscode库提供的方法:
455 |
456 | - 存入: `context.secrets.store('xxx', xxx)`
457 | - 读取: `context.secrets.get('xxx')`
458 |
459 | 这种方式可以延伸, 就是去魔改AugmentCode插件, 本地起一个node服务, 当做中间层, 把vscode库的存入和读取做成本地接口, 供你自己调用
460 |
461 | 第二种: 使用electron的 `safeStorage` 方法
462 |
463 | - 加密: `safeStorage.encryptString(json)` 先转成json, 在放入
464 | - 解密: `safeStorage.decryptString(Buffer)` 先把buffer字符串变成obj, 取.data后, 转成Buffer, 再放入
465 |
466 | 因为vscode就是拿electron写的, 通过读vscode的源码不难发现, 它的`context.secrets.store` `context.secrets.get('xxx')` 底层就是调用了electron的 `safeStorage`, 而electron底层, 用的是 `chromium` 的os_scrypt
467 |
468 | chromium源码参考: https://github.com/chromium/chromium/blob/main/components/os_crypt/sync/os_crypt.h
469 |
470 | vscode源码参考: https://github.com/microsoft/vscode/blob/main/src/vs/platform/encryption/electron-main/encryptionMainService.ts
471 |
472 | 使用electron的 `safeStorage`, 你随便加密, 插件能够正常登录正常使用, 但是想要把已有的buffer解密成json, 就要注意一下, 你的软件需要与编辑器保持同源, 这样调用 `safeStorage.decryptString(Buffer)` 才能成功, 不然就会报错, 两种方式:
473 |
474 | 第一种, electron项目启动时, 将`userData` path保持与ide一致
475 |
476 | ```js
477 | //
478 | const idePath = path.join(app.getPath('appData'), 'Code或者Cursor')
479 | app.setPath('userData', idePath)
480 | ```
481 |
482 | 第二种, electron项目启动时, 将对应ide的`userData` 下面的 `Local State`文件内容复制到你自己的electron项目的 `userData` 路径中, 这样也能保证你的项目和ide同源, 这样随意调用加密解密都不会出错
483 |
484 | 例如 `C:\Users\username\AppData\Roaming\Code\Local State`, 里面长这样
485 |
486 | ```
487 | {"os_crypt":{"audit_enabled":true,"encrypted_key":"RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAAAJzsAIpQ17QLdHVheQOg+oEAAAABIAAABDAGgAcgBvAG0AaQB1AG0AAAAQZgAAAAEAACAAAAA1hUVfeHlHOgJMJ6rhpcrVt9u/RZenstHBQEKB1ROhvwAAAAAOgAAAAAIAACAAAACYwgmTuw54juRI+l9BPmp5KnwkWEb3bk7rumqXw9HUBjAAAACFrXXo9RYGka1Vhjc5UG9g2SSzLutDq62H1gFDg0xoXr+mjGJ2fwJQT4d9xAuAXkVAAAAAnMAvyKUp/twEQoqKoptHAI9aHim6N09hRoWFycVa1QngALhd1crJXOb88GBi0/psQtU167UCMk6OvKo6CLfVww=="}}
488 | ```
489 |
490 | 你的项目的Local State文件在这: `C:\Users\username\AppData\Roaming\augment-assistant\Local State`, 将上面的内容或者整个文件复制到你的项目路径下即可
491 |
492 | 5. **替换state.vscdb**
493 |
494 | 上面的json加密完成后会变成这样:
495 |
496 | ```
497 | {"type":"Buffer","data":[118,49,48,18,46,39,138,161,249,210,72,171,160,193,67,1,223,224,160,204,123,177,3,214,42,214,10,5,79,197,108,175,156,72,240,233,211,203,183,190,107,163,115,74,176,68,134,106,109,165,50,145,209,120,36,209,171,80,220,162,166,233,101,138,37,242,91,245,235,157,214,191,106,140,213,130,59,31,199,251,56,49,129,148,210,93,122,246,114,138,73,81,11,43,87,220,153,58,178,210,254,93,58,217,251,230,179,188,122,73,148,90,199,20,201,13,10,60,230,201,30,3,203,31,183,253,194,25,119,46,135,152,24,57,101,153,16,230,131,11,50,74,240,233,90,190,144,128,56,70,122,15,120,244,54,130,126,125,209,168,214,215,59,182,114,137,232,77,124,18,38,2,220,25,203,203,99,192,6]}
498 | ```
499 |
500 | 存入state.vscdb中, key是 `secret://{"extensionId":"augment.vscode-augment","key":"augment.sessions"}`
501 |
502 | 这样就完成了切号
503 |
504 | ## 3. Windsurf
505 |
506 | (可选)grpc参考: https://github.com/yuxinle1996/windsurf-grpc, 我还原出了windsurf官网上所有的 `proto` 文件, 使用方式看[README.md](https://github.com/yuxinle1996/windsurf-grpc/blob/master/README.md)
507 |
508 | 两种发送请求方式:
509 |
510 | 1. 使用 `@connectrpc/connect` 系列库(简单)
511 |
512 | ```typescript
513 | import { createClient } from '@connectrpc/connect'
514 | import { SeatManagementService } from './gen/seat_management_pb_connect'
515 | import { createConnectTransport } from '@connectrpc/connect-web'
516 |
517 | const transport = createConnectTransport({
518 | baseUrl: 'https://web-backend.windsurf.com'
519 | })
520 | const client = createClient(SeatManagementService, transport)
521 | const res = await client.getOneTimeAuthToken({
522 | firebaseIdToken: webToken
523 | })
524 | console.log(res)
525 | ```
526 |
527 | 2. 使用传统 `axios/fetch` 发送请求
528 |
529 | ```typescript
530 | import axios from 'axios'
531 | import {
532 | GetOneTimeAuthTokenRequest,
533 | GetOneTimeAuthTokenResponse
534 | } from '../gen/seat_management_pb_pb'
535 |
536 | const data = new GetOneTimeAuthTokenRequest({
537 | firebaseIdToken: webToken
538 | })
539 | // 序列化请求数据
540 | const binaryData = data.toBinary()
541 | const response = await axios({
542 | method: 'POST',
543 | url: 'https://web-backend.windsurf.com/exa.seat_management_pb.SeatManagementService/GetOneTimeAuthToken',
544 | data: binaryData,
545 | headers: {
546 | 'Content-Type': 'application/proto'
547 | },
548 | responseType: 'arraybuffer'
549 | })
550 | // 反序列化响应数据
551 | const binaryresponse = new Uint8Array(response.data)
552 | const res = GetOneTimeAuthTokenResponse.fromBinary(binaryresponse)
553 | console.log(res)
554 | ```
555 |
556 | ### 3.1 重置机器
557 |
558 | 路径: `C:\Users\dami\AppData\Roaming\Windsurf\User\globalStorage\storage.json`
559 |
560 | 字段:
561 |
562 | - `telemetry.macMachineId` -- mac上的字段, 128位字符, 具体可找github开源项目
563 | - `telemetry.machineId` -- 64位字符, 具体可找github开源项目
564 | - `telemetry.sqmId` -- 拼接 `{` `随机uuid转大写` `}`
565 | - `telemetry.devDeviceId` - 随机uuid
566 |
567 | 注册表:
568 |
569 | path: `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`
570 |
571 | 字段: `MachineGuid` -- 随机uuid
572 |
573 | 参考代码同Cursor
574 |
575 | ### 3.2 state.vscdb
576 |
577 | 路径: `C:\Users\username\AppData\Roaming\Windsurf\User\globalStorage`
578 |
579 | ```json
580 | secret://{"extensionId":"codeium.windsurf","key":"windsurf_auth.sessions"}
581 | buffer解密后
582 | [
583 | {
584 | "id":"d4c224ed-ab9a-4a55-bbc8-a2573c13c764",
585 | "accessToken":"sk-ws-01-QD_mJNBAzYsm4mcecF2wLD8_9exXWGmV-cHz8Goixq_qmt-QxTeM-l9k3UzjaIlJQZHnlN9oB_Qww_WCd3B9y-a2pmNlFQ",
586 | "account":{
587 | "label":"da mi",
588 | "id":"da mi"
589 | },
590 | "scopes":[]
591 | }
592 | ]
593 | ```
594 |
595 | ### 3.3 切号流程
596 |
597 | 1. **使用 `refresh_token` 调接口得到 `access_token`**
598 |
599 | ```js
600 | POST https://securetoken.googleapis.com/v1/token
601 | const data = {
602 | grant_type: refresh_token,
603 | refresh_token: "xxx"
604 | }
605 | const headers = {
606 | "Content-Type": "application/x-www-form-urlencoded"
607 | }
608 |
609 | const response = {
610 | "access_token": "access_tokenxxx",
611 | "expires_in": "3600",
612 | "token_type": "Bearer",
613 | "refresh_token": "xxx",
614 | "id_token": "access_tokenxxx",
615 | "user_id": "jRkHw6jk3lW5ClWam4C33hPgufA3",
616 | "project_id": "957777847521"
617 | }
618 | ```
619 |
620 | 网页上的token默认时效只有1小时, 过期后就需要使用刷新token重新获取访问token
621 |
622 | 2. **获取 `api_key`**
623 |
624 | 这里有两种方式:
625 |
626 | 第一种: 正常方式, 完全按照ide的流程(需要懂点grpc)
627 |
628 | - 获取 `authToken`
629 |
630 | ```js
631 | POST https://web-backend.windsurf.com/exa.seat_management_pb.SeatManagementService/GetOneTimeAuthToken
632 |
633 | // 需要使用.proto文件将请求数据序列化为二进制数据
634 | const data = {
635 | firebase_id_token: access_token
636 | }
637 |
638 | const headers = {
639 | "Content-Type": "application/proto"
640 | }
641 |
642 | // 响应为二进制数据, 需要.proto文件反序列化
643 | // 反序列化之后的数据
644 | const response = {
645 | auth_token: "xxx"
646 | }
647 | ```
648 |
649 | 
650 |
651 | - 生成 `api_key`
652 |
653 | 两个url使用哪个都行, 并且它支持两种请求格式, 分别是 `application/json` 和 `application/proto`
654 |
655 | ```js
656 | POST https://register.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser
657 | POST https://web-backend.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser
658 | 以上url都可以
659 |
660 | const data = {
661 | firebase_id_token: auth_token
662 | }
663 |
664 | const headers = {
665 | "Content-Type": "application/json" 或 "application/proto"
666 | }
667 |
668 | const response = {
669 | "api_key": "sk-ws-xxx",
670 | "name": "username",
671 | "api_server_url": "https://server.self-serve.windsurf.com"
672 | }
673 | ```
674 |
675 | 第二种: 取巧方式
676 |
677 | 省略获取 `authToken` 这一步, 直接将网页上的 `access_token` 放入`https://register.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser` 请求的 `firebase_id_token` 参数上
678 |
679 | ```js
680 | POST https://register.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser
681 | POST https://web-backend.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser
682 | 以上url都可以
683 |
684 | const data = {
685 | firebase_id_token: access_token // 无需authToken
686 | }
687 |
688 | const headers = {
689 | "Content-Type": "application/json" 或 "application/proto"
690 | }
691 |
692 | const response = {
693 | "api_key": "sk-ws-xxx", // 有用的
694 | "name": "username", // 有用的
695 | "api_server_url": "https://server.self-serve.windsurf.com" // 可选
696 | }
697 | ```
698 |
699 | 3. **整合sessions, 并加密成buffer数据**
700 |
701 | ```json
702 | [
703 | {
704 | id: "d4c224ed-ab9a-4a55-bbc8-a2573c13c764", // 随机uuid或者写死
705 | accessToken: api_key,
706 | account: {
707 | label: username,
708 | id: username,
709 | },
710 | scopes: [],
711 | },
712 | ]
713 | ```
714 |
715 | 加密方式和AugmentCode一样, 这里略过
716 |
717 | 4. **替换state.vscdb**
718 |
719 | 上面的json加密完成后会变成这样:
720 |
721 | ```
722 | {"type":"Buffer","data":[118,49,48,208,189,49,183,252,20,5,81,99,217,37,25,223,3,150,194,233,19,68,101,170,123,7,2,58,60,108,31,213,151,244,177,31,181,216,190,147,171,18,50,92,251,214,238,116,201,56,205,106,87,94,223,199,190,39,159,71,194,84,20,40,64,50,229,92,60,1,214,124,138,2,176,255,13,70,251,132,52,177,30,2,97,212,118,225,251,126,244,251,3,2,94,133,173,181,5,101,182,57,206,139,242,170,205,158,178,194,187,202,54,170,4,220,118,108,215,138,108,135,41,122,171,149,27,103,63,98,204,240,102,133,74,100,4,135,87,112,115,11,88,170,102,163,60,244,12,198,174,9,29,202,83,56,201,102,97,204,31,191,77,100,158,114,25,157,43,245,115,79,112,34,120,57,65,225,12,41,195,121,30,241,120,8,75,28,179,214,72,222,19,233,234,140,3,225,164,140,50,73,226,16,239,216,65,39,117,34,142,144,27,145,161,139,236,153,218,58,215,238,38,147,219,194,4,93,228,32,135,42,171,71,221,120,28,248,77,34,32,55,172,215,201,251,212,77,64,148,238]}
723 | ```
724 |
725 | 存入state.vscdb中, key是 `secret://{"extensionId":"codeium.windsurf","key":"windsurf_auth.sessions"}`
726 |
727 | (可选): 替换 `api_server_url`
728 |
729 | 上面 **RegisterUser** 接口请求得到了 `api_server_url` 字段, 也可以选择性替换, 当然也可以不用管它
730 |
731 | key是 `codeium.windsurf`
732 |
733 | value是(将apiServerUrl替换掉):
734 |
735 | ```json
736 | {
737 | "codeium.installationId": "6af139ae-4338-4078-b493-eb5a50e7a676",
738 | "apiServerUrl": "https://server.self-serve.windsurf.com",
739 | "codeium.hasOneTimeUpdatedUnspecifiedMode": true
740 | }
741 | ```
742 |
743 | 这样就完成了切号
744 |
--------------------------------------------------------------------------------