Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RUN test "$TARGETARCH" = "amd64" && \
CGO_ENABLED=0 GOOS="$TARGETOS" GOARCH="$TARGETARCH" \
go build -trimpath -ldflags="-s -w" -o /out/wrapper-manager .

FROM ubuntu:24.04
FROM debian:13.2

WORKDIR /root

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Support linux x86_64 and arm64 arch
## Features
- Multi-instance management
- Add accounts at runtime (support 2FA)
- Start multiple Wrapper instances for the same account by logging in again after each instance is ready
- Multi-connection decryption
- gRPC API
- Get lyrics without an account
Expand Down Expand Up @@ -54,4 +55,4 @@ This repository is forked to implement several performance optimizations, thread
- Adaptations for recent Apple token structure updates.
- Enhanced error handling when `checkAvailableOnRegion` fails due to network issues (returns clean errors instead of crashing).
- Fixed standard panics caused by nil interface conversions.
- Restored missing `SelectInstance` validations for music videos.
- Restored missing `SelectInstance` validations for music videos.
13 changes: 13 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
)

var LoginConnMap = sync.Map{}
var PendingLoginByAccount = sync.Map{}

func Login2FAHandler(id string) {
conn, _ := LoginConnMap.Load(id)
Expand All @@ -25,6 +26,7 @@ func Login2FAHandler(id string) {

func LoginDoneHandler(id string) {
SaveInstances()
clearPendingLogin(id)
conn, _ := LoginConnMap.LoadAndDelete(id)
if conn == nil {
return
Expand All @@ -43,6 +45,7 @@ func LoginDoneHandler(id string) {

func LoginFailedHandler(id string) {
RemoveWrapperData(id)
clearPendingLogin(id)
conn, _ := LoginConnMap.LoadAndDelete(id)
if conn == nil {
return
Expand All @@ -58,3 +61,13 @@ func LoginFailedHandler(id string) {
log.Println(err)
}
}

func clearPendingLogin(id string) {
PendingLoginByAccount.Range(func(account, pendingID any) bool {
if pendingID == id {
PendingLoginByAccount.Delete(account)
return false
}
return true
})
}
10 changes: 10 additions & 0 deletions instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,13 @@ func GetInstance(id string) *WrapperInstance {
}
return &WrapperInstance{}
}

func GetInstancesByAccount(account string) []*WrapperInstance {
var matches []*WrapperInstance
for _, instance := range Instances {
if instance.Account == account {
matches = append(matches, instance)
}
}
return matches
}
22 changes: 22 additions & 0 deletions instance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import "testing"

func TestGetInstancesByAccountReturnsAllMatchingWrappers(t *testing.T) {
original := Instances
t.Cleanup(func() { Instances = original })

Instances = []*WrapperInstance{
{Id: "first", Account: "same@example.com"},
{Id: "other", Account: "other@example.com"},
{Id: "second", Account: "same@example.com"},
}

matches := GetInstancesByAccount("same@example.com")
if len(matches) != 2 {
t.Fatalf("expected 2 wrappers, got %d", len(matches))
}
if matches[0].Id != "first" || matches[1].Id != "second" {
t.Fatalf("unexpected wrappers: %#v", matches)
}
}
58 changes: 30 additions & 28 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,30 @@ func (s *server) Login(stream grpc.BidiStreamingServer[pb.LoginRequest, pb.Login
if err != nil {
return err
}
id := uuid.NewV5(uuid.FromStringOrNil("77777777-7777-7777-7777-77777777"), req.Data.Username).String()
for _, instance := range Instances {
if instance.Id == id {
err = stream.Send(&pb.LoginReply{
Header: &pb.ReplyHeader{
Code: -1,
Msg: "already login",
},
})
if err != nil {
if req.Data.TwoStepCode != "" {
pendingID, ok := PendingLoginByAccount.Load(req.Data.Username)
if !ok {
if err = stream.Send(&pb.LoginReply{Header: &pb.ReplyHeader{Code: -1, Msg: "no pending login"}}); err != nil {
return err
}
continue
}
}
if req.Data.TwoStepCode != "" {
id := pendingID.(string)
provide2FACode(id, req.Data.TwoStepCode)
} else {
LoginConnMap.Store(id, stream)
go WrapperInitial(req.Data.Username, req.Data.Password)
id, idErr := uuid.NewV4()
if idErr != nil {
return idErr
}
idString := id.String()
if _, loaded := PendingLoginByAccount.LoadOrStore(req.Data.Username, idString); loaded {
if err = stream.Send(&pb.LoginReply{Header: &pb.ReplyHeader{Code: -1, Msg: "login already pending"}}); err != nil {
return err
}
continue
}
LoginConnMap.Store(idString, stream)
go WrapperInitial(id, req.Data.Username, req.Data.Password)
}
}
}
Expand All @@ -108,9 +113,8 @@ func (s *server) Logout(c context.Context, req *pb.LogoutRequest) (*pb.LogoutRep
} else {
log.Infof("logout request from unknown peer")
}
id := uuid.NewV5(uuid.FromStringOrNil("77777777-7777-7777-7777-77777777"), req.Data.Username).String()
instance := GetInstance(id)
if instance.Id == "" {
instances := GetInstancesByAccount(req.Data.Username)
if len(instances) == 0 {
return &pb.LogoutReply{
Header: &pb.ReplyHeader{
Code: -1,
Expand All @@ -119,18 +123,16 @@ func (s *server) Logout(c context.Context, req *pb.LogoutRequest) (*pb.LogoutRep
Data: &pb.LogoutData{Username: req.Data.Username},
}, nil
}
instance.NoRestart = true
err := KillWrapper(instance.Id)
if err != nil {
return &pb.LogoutReply{
Header: &pb.ReplyHeader{
Code: -1,
Msg: "failed to kill wrapper",
},
Data: &pb.LogoutData{Username: req.Data.Username},
}, nil
for _, instance := range instances {
instance.NoRestart = true
if err := KillWrapper(instance.Id); err != nil {
return &pb.LogoutReply{
Header: &pb.ReplyHeader{Code: -1, Msg: "failed to kill wrapper"},
Data: &pb.LogoutData{Username: req.Data.Username},
}, nil
}
RemoveWrapperData(instance.Id)
}
RemoveWrapperData(instance.Id)
return &pb.LogoutReply{
Header: &pb.ReplyHeader{
Code: 0,
Expand Down
3 changes: 1 addition & 2 deletions wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ func prepareWrapper(mirror bool, arch string) error {
return nil
}

func WrapperInitial(account string, password string) {
id := uuid.NewV5(uuid.FromStringOrNil("77777777-7777-7777-7777-77777777"), account)
func WrapperInitial(id uuid.UUID, account string, password string) {
err := os.MkdirAll("data/wrapper/rootfs/data/instances/"+id.String(), 0777)
if err != nil {
panic(err)
Expand Down
Loading