You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
15 KiB
464 lines
15 KiB
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package open_im_sdk
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/business"
|
|
conv "github.com/openimsdk/openim-sdk-core/v3/internal/conversation_msg"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/file"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/friend"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/full"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/group"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/interaction"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/third"
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/user"
|
|
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/ccontext"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/common"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/db"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
|
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
|
"github.com/openimsdk/protocol/push"
|
|
"github.com/openimsdk/protocol/sdkws"
|
|
"github.com/openimsdk/tools/log"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
LogoutStatus = iota + 1
|
|
Logging
|
|
Logged
|
|
)
|
|
|
|
const (
|
|
LogoutTips = "js sdk socket close"
|
|
)
|
|
|
|
var (
|
|
// UserForSDK Client-independent user class
|
|
UserForSDK *LoginMgr
|
|
)
|
|
|
|
// CheckResourceLoad checks the SDK is resource load status.
|
|
func CheckResourceLoad(uSDK *LoginMgr, funcName string) error {
|
|
if uSDK == nil {
|
|
return utils.Wrap(errors.New("CheckResourceLoad failed uSDK == nil "), "")
|
|
}
|
|
if funcName == "" {
|
|
return nil
|
|
}
|
|
parts := strings.Split(funcName, ".")
|
|
if parts[len(parts)-1] == "Login-fm" {
|
|
return nil
|
|
}
|
|
if uSDK.Friend() == nil || uSDK.User() == nil || uSDK.Group() == nil || uSDK.Conversation() == nil ||
|
|
uSDK.Full() == nil {
|
|
return utils.Wrap(errors.New("CheckResourceLoad failed, resource nil "), "")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type LoginMgr struct {
|
|
friend *friend.Friend
|
|
group *group.Group
|
|
conversation *conv.Conversation
|
|
user *user.User
|
|
file *file.File
|
|
business *business.Business
|
|
|
|
full *full.Full
|
|
db db_interface.DataBase
|
|
longConnMgr *interaction.LongConnMgr
|
|
msgSyncer *interaction.MsgSyncer
|
|
third *third.Third
|
|
token string
|
|
loginUserID string
|
|
connListener open_im_sdk_callback.OnConnListener
|
|
|
|
justOnceFlag bool
|
|
|
|
w sync.Mutex
|
|
loginStatus int
|
|
|
|
groupListener open_im_sdk_callback.OnGroupListener
|
|
friendListener open_im_sdk_callback.OnFriendshipListener
|
|
conversationListener open_im_sdk_callback.OnConversationListener
|
|
advancedMsgListener open_im_sdk_callback.OnAdvancedMsgListener
|
|
batchMsgListener open_im_sdk_callback.OnBatchMsgListener
|
|
userListener open_im_sdk_callback.OnUserListener
|
|
signalingListener open_im_sdk_callback.OnSignalingListener
|
|
businessListener open_im_sdk_callback.OnCustomBusinessListener
|
|
msgKvListener open_im_sdk_callback.OnMessageKvInfoListener
|
|
|
|
conversationCh chan common.Cmd2Value
|
|
cmdWsCh chan common.Cmd2Value
|
|
heartbeatCmdCh chan common.Cmd2Value
|
|
pushMsgAndMaxSeqCh chan common.Cmd2Value
|
|
loginMgrCh chan common.Cmd2Value
|
|
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
info *ccontext.GlobalConfig
|
|
id2MinSeq map[string]int64
|
|
}
|
|
|
|
func (u *LoginMgr) GroupListener() open_im_sdk_callback.OnGroupListener {
|
|
return u.groupListener
|
|
}
|
|
|
|
func (u *LoginMgr) FriendListener() open_im_sdk_callback.OnFriendshipListener {
|
|
return u.friendListener
|
|
}
|
|
|
|
func (u *LoginMgr) ConversationListener() open_im_sdk_callback.OnConversationListener {
|
|
return u.conversationListener
|
|
}
|
|
|
|
func (u *LoginMgr) AdvancedMsgListener() open_im_sdk_callback.OnAdvancedMsgListener {
|
|
return u.advancedMsgListener
|
|
}
|
|
|
|
func (u *LoginMgr) BatchMsgListener() open_im_sdk_callback.OnBatchMsgListener {
|
|
return u.batchMsgListener
|
|
}
|
|
|
|
func (u *LoginMgr) UserListener() open_im_sdk_callback.OnUserListener {
|
|
return u.userListener
|
|
}
|
|
|
|
func (u *LoginMgr) SignalingListener() open_im_sdk_callback.OnSignalingListener {
|
|
return u.signalingListener
|
|
}
|
|
|
|
func (u *LoginMgr) BusinessListener() open_im_sdk_callback.OnCustomBusinessListener {
|
|
return u.businessListener
|
|
}
|
|
|
|
func (u *LoginMgr) MsgKvListener() open_im_sdk_callback.OnMessageKvInfoListener {
|
|
return u.msgKvListener
|
|
}
|
|
|
|
func (u *LoginMgr) BaseCtx() context.Context {
|
|
return u.ctx
|
|
}
|
|
|
|
func (u *LoginMgr) Exit() {
|
|
u.cancel()
|
|
}
|
|
|
|
func (u *LoginMgr) GetToken() string {
|
|
return u.token
|
|
}
|
|
|
|
func (u *LoginMgr) Third() *third.Third {
|
|
return u.third
|
|
}
|
|
|
|
func (u *LoginMgr) ImConfig() sdk_struct.IMConfig {
|
|
return sdk_struct.IMConfig{
|
|
PlatformID: u.info.PlatformID,
|
|
ApiAddr: u.info.ApiAddr,
|
|
WsAddr: u.info.WsAddr,
|
|
DataDir: u.info.DataDir,
|
|
LogLevel: u.info.LogLevel,
|
|
IsExternalExtensions: u.info.IsExternalExtensions,
|
|
}
|
|
}
|
|
|
|
func (u *LoginMgr) Conversation() *conv.Conversation {
|
|
return u.conversation
|
|
}
|
|
|
|
func (u *LoginMgr) User() *user.User {
|
|
return u.user
|
|
}
|
|
|
|
func (u *LoginMgr) File() *file.File {
|
|
return u.file
|
|
}
|
|
|
|
func (u *LoginMgr) Full() *full.Full {
|
|
return u.full
|
|
}
|
|
|
|
func (u *LoginMgr) Group() *group.Group {
|
|
return u.group
|
|
}
|
|
|
|
func (u *LoginMgr) Friend() *friend.Friend {
|
|
return u.friend
|
|
}
|
|
|
|
func (u *LoginMgr) SetConversationListener(conversationListener open_im_sdk_callback.OnConversationListener) {
|
|
u.conversationListener = conversationListener
|
|
}
|
|
|
|
func (u *LoginMgr) SetAdvancedMsgListener(advancedMsgListener open_im_sdk_callback.OnAdvancedMsgListener) {
|
|
u.advancedMsgListener = advancedMsgListener
|
|
}
|
|
|
|
func (u *LoginMgr) SetMessageKvInfoListener(messageKvInfoListener open_im_sdk_callback.OnMessageKvInfoListener) {
|
|
u.msgKvListener = messageKvInfoListener
|
|
}
|
|
|
|
func (u *LoginMgr) SetBatchMsgListener(batchMsgListener open_im_sdk_callback.OnBatchMsgListener) {
|
|
u.batchMsgListener = batchMsgListener
|
|
}
|
|
|
|
func (u *LoginMgr) SetFriendListener(friendListener open_im_sdk_callback.OnFriendshipListener) {
|
|
u.friendListener = friendListener
|
|
}
|
|
|
|
func (u *LoginMgr) SetGroupListener(groupListener open_im_sdk_callback.OnGroupListener) {
|
|
u.groupListener = groupListener
|
|
}
|
|
|
|
func (u *LoginMgr) SetUserListener(userListener open_im_sdk_callback.OnUserListener) {
|
|
u.userListener = userListener
|
|
}
|
|
|
|
func (u *LoginMgr) SetCustomBusinessListener(listener open_im_sdk_callback.OnCustomBusinessListener) {
|
|
u.businessListener = listener
|
|
}
|
|
func (u *LoginMgr) GetLoginUserID() string {
|
|
return u.loginUserID
|
|
}
|
|
func (u *LoginMgr) logoutListener(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-u.loginMgrCh:
|
|
log.ZDebug(ctx, "logoutListener exit")
|
|
err := u.logout(ctx, true)
|
|
if err != nil {
|
|
log.ZError(ctx, "logout error", err)
|
|
}
|
|
case <-ctx.Done():
|
|
log.ZInfo(ctx, "logoutListener done sdk logout.....")
|
|
return
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func NewLoginMgr() *LoginMgr {
|
|
return &LoginMgr{
|
|
info: &ccontext.GlobalConfig{}, // 分配内存空间
|
|
}
|
|
}
|
|
func (u *LoginMgr) getLoginStatus(_ context.Context) int {
|
|
u.w.Lock()
|
|
defer u.w.Unlock()
|
|
return u.loginStatus
|
|
}
|
|
func (u *LoginMgr) setLoginStatus(status int) {
|
|
u.w.Lock()
|
|
defer u.w.Unlock()
|
|
u.loginStatus = status
|
|
}
|
|
func (u *LoginMgr) checkSendingMessage(ctx context.Context) {
|
|
sendingMessages, err := u.db.GetAllSendingMessages(ctx)
|
|
if err != nil {
|
|
log.ZError(ctx, "GetAllSendingMessages failed", err)
|
|
}
|
|
for _, message := range sendingMessages {
|
|
if err := u.handlerSendingMsg(ctx, message); err != nil {
|
|
log.ZError(ctx, "handlerSendingMsg failed", err, "message", message)
|
|
}
|
|
if err := u.db.DeleteSendingMessage(ctx, message.ConversationID, message.ClientMsgID); err != nil {
|
|
log.ZError(ctx, "DeleteSendingMessage failed", err, "conversationID", message.ConversationID, "clientMsgID", message.ClientMsgID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (u *LoginMgr) handlerSendingMsg(ctx context.Context, sendingMsg *model_struct.LocalSendingMessages) error {
|
|
tableMessage, err := u.db.GetMessage(ctx, sendingMsg.ConversationID, sendingMsg.ClientMsgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tableMessage.Status != constant.MsgStatusSending {
|
|
return nil
|
|
}
|
|
err = u.db.UpdateMessage(ctx, sendingMsg.ConversationID, &model_struct.LocalChatLog{ClientMsgID: sendingMsg.ClientMsgID, Status: constant.MsgStatusSendFailed})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conversation, err := u.db.GetConversation(ctx, sendingMsg.ConversationID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
latestMsg := &sdk_struct.MsgStruct{}
|
|
if err := json.Unmarshal([]byte(conversation.LatestMsg), &latestMsg); err != nil {
|
|
return err
|
|
}
|
|
if latestMsg.ClientMsgID == sendingMsg.ClientMsgID {
|
|
latestMsg.Status = constant.MsgStatusSendFailed
|
|
conversation.LatestMsg = utils.StructToJsonString(latestMsg)
|
|
return u.db.UpdateConversation(ctx, conversation)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *LoginMgr) login(ctx context.Context, userID, token string) error {
|
|
if u.getLoginStatus(ctx) == Logged {
|
|
return sdkerrs.ErrLoginRepeat
|
|
}
|
|
u.setLoginStatus(Logging)
|
|
u.info.UserID = userID
|
|
u.info.Token = token
|
|
log.ZDebug(ctx, "login start... ", "userID", userID, "token", token)
|
|
t1 := time.Now()
|
|
u.token = token
|
|
u.loginUserID = userID
|
|
var err error
|
|
u.db, err = db.NewDataBase(ctx, userID, u.info.DataDir, int(u.info.LogLevel))
|
|
if err != nil {
|
|
return sdkerrs.ErrSdkInternal.WrapMsg("init database " + err.Error())
|
|
}
|
|
u.checkSendingMessage(ctx)
|
|
log.ZDebug(ctx, "NewDataBase ok", "userID", userID, "dataDir", u.info.DataDir, "login cost time", time.Since(t1))
|
|
u.user = user.NewUser(u.db, u.loginUserID, u.conversationCh)
|
|
u.file = file.NewFile(u.db, u.loginUserID)
|
|
u.friend = friend.NewFriend(u.loginUserID, u.db, u.user, u.conversationCh)
|
|
|
|
u.group = group.NewGroup(u.loginUserID, u.db, u.conversationCh)
|
|
u.full = full.NewFull(u.user, u.friend, u.group, u.conversationCh, u.db)
|
|
u.business = business.NewBusiness(u.db)
|
|
u.third = third.NewThird(u.info.PlatformID, u.loginUserID, constant.SdkVersion, u.info.SystemType, u.info.LogFilePath, u.file)
|
|
log.ZDebug(ctx, "forcedSynchronization success...", "login cost time: ", time.Since(t1))
|
|
|
|
u.msgSyncer, _ = interaction.NewMsgSyncer(ctx, u.conversationCh, u.pushMsgAndMaxSeqCh, u.loginUserID, u.longConnMgr, u.db, 0)
|
|
u.conversation = conv.NewConversation(ctx, u.longConnMgr, u.db, u.conversationCh,
|
|
u.friend, u.group, u.user, u.business, u.full, u.file)
|
|
u.setListener(ctx)
|
|
u.run(ctx)
|
|
u.setLoginStatus(Logged)
|
|
log.ZDebug(ctx, "login success...", "login cost time: ", time.Since(t1))
|
|
return nil
|
|
}
|
|
|
|
func (u *LoginMgr) setListener(ctx context.Context) {
|
|
setListener(ctx, &u.userListener, u.UserListener, u.user.SetListener, newEmptyUserListener)
|
|
setListener(ctx, &u.friendListener, u.FriendListener, u.friend.SetListener, newEmptyFriendshipListener)
|
|
setListener(ctx, &u.groupListener, u.GroupListener, u.group.SetGroupListener, newEmptyGroupListener)
|
|
setListener(ctx, &u.conversationListener, u.ConversationListener, u.conversation.SetConversationListener, newEmptyConversationListener)
|
|
setListener(ctx, &u.advancedMsgListener, u.AdvancedMsgListener, u.conversation.SetMsgListener, newEmptyAdvancedMsgListener)
|
|
setListener(ctx, &u.batchMsgListener, u.BatchMsgListener, u.conversation.SetBatchMsgListener, nil)
|
|
setListener(ctx, &u.businessListener, u.BusinessListener, u.business.SetListener, newEmptyCustomBusinessListener)
|
|
}
|
|
|
|
func setListener[T any](ctx context.Context, listener *T, getter func() T, setFunc func(listener func() T), newFunc func(context.Context) T) {
|
|
if *(*unsafe.Pointer)(unsafe.Pointer(listener)) == nil && newFunc != nil {
|
|
*listener = newFunc(ctx)
|
|
}
|
|
setFunc(getter)
|
|
}
|
|
|
|
func (u *LoginMgr) run(ctx context.Context) {
|
|
u.longConnMgr.Run(ctx)
|
|
go u.msgSyncer.DoListener(ctx)
|
|
go common.DoListener(u.conversation, u.ctx)
|
|
go u.logoutListener(ctx)
|
|
}
|
|
|
|
func (u *LoginMgr) InitSDK(config sdk_struct.IMConfig, listener open_im_sdk_callback.OnConnListener) bool {
|
|
if listener == nil {
|
|
return false
|
|
}
|
|
u.info = &ccontext.GlobalConfig{}
|
|
u.info.IMConfig = config
|
|
u.connListener = listener
|
|
u.initResources()
|
|
return true
|
|
}
|
|
|
|
func (u *LoginMgr) Context() context.Context {
|
|
return u.ctx
|
|
}
|
|
|
|
func (u *LoginMgr) initResources() {
|
|
ctx := ccontext.WithInfo(context.Background(), u.info)
|
|
u.ctx, u.cancel = context.WithCancel(ctx)
|
|
u.conversationCh = make(chan common.Cmd2Value, 1000)
|
|
u.heartbeatCmdCh = make(chan common.Cmd2Value, 10)
|
|
u.pushMsgAndMaxSeqCh = make(chan common.Cmd2Value, 1000)
|
|
u.loginMgrCh = make(chan common.Cmd2Value, 1)
|
|
u.longConnMgr = interaction.NewLongConnMgr(u.ctx, u.connListener, u.heartbeatCmdCh, u.pushMsgAndMaxSeqCh, u.loginMgrCh)
|
|
u.ctx = ccontext.WithApiErrCode(u.ctx, &apiErrCallback{loginMgrCh: u.loginMgrCh, listener: u.connListener})
|
|
u.setLoginStatus(LogoutStatus)
|
|
}
|
|
|
|
func (u *LoginMgr) UnInitSDK() {
|
|
if u.getLoginStatus(context.Background()) == Logged {
|
|
fmt.Println("sdk not logout, please logout first")
|
|
return
|
|
}
|
|
u.info = nil
|
|
u.setLoginStatus(0)
|
|
}
|
|
|
|
// token error recycle recourse, kicked not recycle
|
|
func (u *LoginMgr) logout(ctx context.Context, isTokenValid bool) error {
|
|
if ccontext.Info(ctx).OperationID() == LogoutTips {
|
|
isTokenValid = true
|
|
}
|
|
if !isTokenValid {
|
|
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
|
defer cancel()
|
|
err := u.longConnMgr.SendReqWaitResp(ctx, &push.DelUserPushTokenReq{UserID: u.info.UserID, PlatformID: u.info.PlatformID}, constant.LogoutMsg, &push.DelUserPushTokenResp{})
|
|
if err != nil {
|
|
log.ZWarn(ctx, "TriggerCmdLogout server recycle resources failed...", err)
|
|
} else {
|
|
log.ZDebug(ctx, "TriggerCmdLogout server recycle resources success...")
|
|
}
|
|
}
|
|
u.Exit()
|
|
err := u.db.Close(u.ctx)
|
|
if err != nil {
|
|
log.ZWarn(ctx, "TriggerCmdLogout db recycle resources failed...", err)
|
|
}
|
|
// user object must be rest when user logout
|
|
u.initResources()
|
|
log.ZDebug(ctx, "TriggerCmdLogout client success...", "isTokenValid", isTokenValid)
|
|
return nil
|
|
}
|
|
|
|
func (u *LoginMgr) setAppBackgroundStatus(ctx context.Context, isBackground bool) error {
|
|
if u.longConnMgr.GetConnectionStatus() == 0 {
|
|
u.longConnMgr.SetBackground(isBackground)
|
|
return nil
|
|
}
|
|
var resp sdkws.SetAppBackgroundStatusResp
|
|
err := u.longConnMgr.SendReqWaitResp(ctx, &sdkws.SetAppBackgroundStatusReq{UserID: u.loginUserID, IsBackground: isBackground}, constant.SetBackgroundStatus, &resp)
|
|
if err != nil {
|
|
return err
|
|
} else {
|
|
u.longConnMgr.SetBackground(isBackground)
|
|
if isBackground == false {
|
|
_ = common.TriggerCmdWakeUp(u.heartbeatCmdCh)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|