Skip to content

Commit 9e6e291

Browse files
committed
chore: code cleanup
1 parent 07a73fe commit 9e6e291

9 files changed

Lines changed: 272 additions & 344 deletions

File tree

db/push_tokens.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ func (d *Database) createSchema() error {
7373
return nil
7474
}
7575

76-
// SavePushToken saves or updates a push token record by selector.
76+
// SavePushToken saves or updates a push token record in the database by selector.
77+
// If a token with the same selector already exists, it will be updated with new values.
7778
func (d *Database) SavePushToken(selector, tokenMsgs, appIDMsgs, tokenCalls, appIDCalls string) error {
7879
d.mu.Lock()
7980
defer d.mu.Unlock()
@@ -100,7 +101,8 @@ func (d *Database) SavePushToken(selector, tokenMsgs, appIDMsgs, tokenCalls, app
100101
return nil
101102
}
102103

103-
// GetPushToken retrieves a push token by selector.
104+
// GetPushToken retrieves a push token from the database by selector.
105+
// Returns nil if the token is not found.
104106
func (d *Database) GetPushToken(selector string) (*PushToken, error) {
105107
d.mu.RLock()
106108
defer d.mu.RUnlock()
@@ -125,8 +127,8 @@ func (d *Database) GetPushToken(selector string) (*PushToken, error) {
125127
return &pt, nil
126128
}
127129

128-
// GetPushTokenByPushkey retrieves a push token by the actual device token (pushkey).
129-
// The pushkey can be either token_msgs or token_calls.
130+
// GetPushTokenByPushkey retrieves a push token from the database by the device token (pushkey).
131+
// The pushkey can be either token_msgs or token_calls. Returns nil if not found.
130132
func (d *Database) GetPushTokenByPushkey(pushkey string) (*PushToken, error) {
131133
d.mu.RLock()
132134
defer d.mu.RUnlock()
@@ -151,7 +153,7 @@ func (d *Database) GetPushTokenByPushkey(pushkey string) (*PushToken, error) {
151153
return &pt, nil
152154
}
153155

154-
// DeletePushToken removes a push token by selector.
156+
// DeletePushToken removes a push token from the database by selector.
155157
func (d *Database) DeletePushToken(selector string) error {
156158
d.mu.Lock()
157159
defer d.mu.Unlock()
@@ -166,7 +168,7 @@ func (d *Database) DeletePushToken(selector string) error {
166168
return nil
167169
}
168170

169-
// ListPushTokens returns all stored push tokens.
171+
// ListPushTokens returns all push tokens stored in the database.
170172
func (d *Database) ListPushTokens() ([]*PushToken, error) {
171173
d.mu.RLock()
172174
defer d.mu.RUnlock()
@@ -199,7 +201,8 @@ func (d *Database) ListPushTokens() ([]*PushToken, error) {
199201
return tokens, nil
200202
}
201203

202-
// ResetPushTokens deletes all push tokens from the database.
204+
// ResetPushTokens clears all push tokens from the database.
205+
// This is typically called when resetting the proxy state.
203206
func (d *Database) ResetPushTokens() error {
204207
d.mu.Lock()
205208
defer d.mu.Unlock()

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ require (
2222
github.com/ncruces/go-strftime v0.1.9 // indirect
2323
github.com/pmezard/go-difflib v1.0.0 // indirect
2424
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
25-
github.com/stretchr/objx v0.5.2 // indirect
2625
github.com/tidwall/gjson v1.18.0 // indirect
2726
github.com/tidwall/match v1.1.1 // indirect
2827
github.com/tidwall/pretty v1.2.1 // indirect

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
3333
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
3434
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
3535
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
36-
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
37-
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
3836
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
3937
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
4038
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=

matrix/client.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ type Config struct {
2525
}
2626

2727
// MatrixClient is a client wrapper for performing Application Service actions.
28-
// Note: The underlying mautrix client is stateful for impersonation in this version.
29-
// A mutex is used to make operations thread-safe.
28+
// It provides impersonation capabilities to act on behalf of different users.
29+
// All operations are thread-safe through internal mutex protection.
3030
type MatrixClient struct {
3131
cli *mautrix.Client
3232
homeserverURL string
@@ -120,6 +120,7 @@ func (mc *MatrixClient) Sync(ctx context.Context, userID id.UserID, batchToken s
120120
}
121121

122122
// CreateDirectRoom creates a new direct message room impersonating 'userID' and inviting 'targetUserID'.
123+
// Sets a room alias for future lookup and reuse between the same participants.
123124
func (mc *MatrixClient) CreateDirectRoom(ctx context.Context, userID id.UserID, targetUserID id.UserID, aliasKey string) (*mautrix.RespCreateRoom, error) {
124125
mc.mu.Lock()
125126
defer mc.mu.Unlock()
@@ -158,6 +159,7 @@ func (mc *MatrixClient) JoinRoom(ctx context.Context, userID id.UserID, roomID i
158159
}
159160

160161
// ResolveRoomAlias resolves a room alias to a room ID.
162+
// Automatically adds the '#' prefix and homeserver name if not present.
161163
func (mc *MatrixClient) ResolveRoomAlias(ctx context.Context, roomAlias string) string {
162164
roomAlias = strings.TrimSpace(roomAlias)
163165
if roomAlias == "" {
@@ -176,6 +178,8 @@ func (mc *MatrixClient) ResolveRoomAlias(ctx context.Context, roomAlias string)
176178
return string(resp.RoomID)
177179
}
178180

181+
// GetRoomAliases retrieves all aliases for a room from the Matrix homeserver.
182+
// Returns an empty slice if no aliases are found or if an error occurs.
179183
func (mc *MatrixClient) GetRoomAliases(ctx context.Context, roomID id.RoomID) []string {
180184
// This action does not require impersonation, so no lock is needed.
181185
logger.Debug().Str("room_id", roomID.String()).Msg("matrix: fetching room aliases")
@@ -196,6 +200,7 @@ func (mc *MatrixClient) GetRoomAliases(ctx context.Context, roomID id.RoomID) []
196200
return aliases
197201
}
198202

203+
// ListJoinedRooms retrieves the list of rooms the specified user has joined.
199204
func (mc *MatrixClient) ListJoinedRooms(ctx context.Context, userID id.UserID) ([]id.RoomID, error) {
200205
mc.mu.Lock()
201206
defer mc.mu.Unlock()

service/cache.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func (e *cacheEntry[T]) isExpired(now time.Time) bool {
1818

1919
// RoomAliasCache caches room alias to room ID mappings (e.g., "user1|user2" -> "!roomid:server").
2020
// This is used by ensureDirectRoom to avoid repeated ResolveRoomAlias calls.
21+
// It implements TTL-based expiration for cached entries.
2122
type RoomAliasCache struct {
2223
mu sync.RWMutex
2324
entries map[string]cacheEntry[string]
@@ -72,6 +73,7 @@ func (c *RoomAliasCache) Clear() {
7273

7374
// RoomAliasesCache caches room ID to room aliases mappings (e.g., "!roomid:server" -> ["user1|user2"]).
7475
// This is used by resolveRoomIDToOtherIdentifier to avoid repeated GetRoomAliases calls.
76+
// It implements TTL-based expiration for cached entries.
7577
type RoomAliasesCache struct {
7678
mu sync.RWMutex
7779
entries map[string]cacheEntry[[]string]

service/fetch_helpers.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package service
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
"time"
9+
10+
"github.com/nethesis/matrix2acrobits/logger"
11+
"github.com/nethesis/matrix2acrobits/models"
12+
"maunium.net/go/mautrix"
13+
"maunium.net/go/mautrix/event"
14+
"maunium.net/go/mautrix/id"
15+
)
16+
17+
// authenticateAndMapUser validates user credentials via external auth and returns the mapped Matrix user ID.
18+
// It persists the returned mapping for future reference.
19+
func (s *MessageService) authenticateAndMapUser(ctx context.Context, username, password string) (id.UserID, error) {
20+
userName := strings.TrimSpace(username)
21+
if userName == "" {
22+
logger.Warn().Msg("authentication: empty username")
23+
return "", ErrAuthentication
24+
}
25+
if strings.TrimSpace(password) == "" {
26+
logger.Warn().Msg("authentication: missing password")
27+
return "", ErrAuthentication
28+
}
29+
30+
mapReq, status, err := s.authClient.Validate(ctx, userName, strings.TrimSpace(password), s.homeserverHost)
31+
if err != nil {
32+
if status == http.StatusUnauthorized {
33+
logger.Warn().Str("username", userName).Msg("external auth failed: unauthorized")
34+
return "", ErrAuthentication
35+
}
36+
logger.Error().Err(err).Int("status", status).Msg("external auth request failed")
37+
return "", fmt.Errorf("external auth request failed: %w", err)
38+
}
39+
40+
// Persist mapping returned by auth
41+
if _, err := s.SaveMapping(mapReq); err != nil {
42+
logger.Error().Err(err).Msg("failed to save mapping from external auth response")
43+
return "", fmt.Errorf("failed to save mapping: %w", err)
44+
}
45+
46+
userID := id.UserID(strings.TrimSpace(mapReq.MatrixID))
47+
if userID == "" {
48+
logger.Warn().Str("username", userName).Msg("auth returned empty Matrix user ID")
49+
return "", ErrAuthentication
50+
}
51+
52+
return userID, nil
53+
}
54+
55+
// performSyncWithRetry performs a sync and retries with a full sync if the batch token is invalid.
56+
// This handles the case where the stored sync token expires or becomes invalid.
57+
func (s *MessageService) performSyncWithRetry(ctx context.Context, userID id.UserID, batchToken string) (*mautrix.RespSync, error) {
58+
resp, err := s.matrixClient.Sync(ctx, userID, batchToken)
59+
if err != nil {
60+
// If the token is invalid, retry with a full sync
61+
if strings.Contains(err.Error(), "Invalid stream token") || strings.Contains(err.Error(), "M_UNKNOWN") {
62+
logger.Warn().Err(err).Msg("invalid stream token, retrying with full sync")
63+
s.clearBatchToken(string(userID))
64+
resp, err = s.matrixClient.Sync(ctx, userID, "")
65+
}
66+
}
67+
if err != nil {
68+
logger.Error().Str("user_id", string(userID)).Err(err).Msg("matrix sync failed")
69+
return nil, fmt.Errorf("sync messages: %w", mapAuthErr(err))
70+
}
71+
return resp, nil
72+
}
73+
74+
// processSyncedMessages converts Matrix sync events to Acrobits SMS format.
75+
// It processes both received and sent messages, resolving Matrix IDs to phone numbers.
76+
func (s *MessageService) processSyncedMessages(ctx context.Context, resp *mautrix.RespSync, userID string) ([]models.SMS, []models.SMS) {
77+
received := make([]models.SMS, 0, 8)
78+
sent := make([]models.SMS, 0, 8)
79+
80+
// Resolve the caller's identifier
81+
callerIdentifier := s.resolveMatrixIDToIdentifier(userID)
82+
83+
for roomID, room := range resp.Rooms.Join {
84+
for _, evt := range room.Timeline.Events {
85+
if evt.Type != event.EventMessage {
86+
continue
87+
}
88+
89+
sms := s.convertMatrixEventToSMS(ctx, evt, roomID)
90+
senderMatrixID := string(evt.Sender)
91+
92+
// Determine if this message was sent by the user
93+
if isSentBy(senderMatrixID, userID) {
94+
// Message was sent by this user
95+
other := s.resolveRoomIDToOtherIdentifier(ctx, evt.RoomID, userID)
96+
sms.Recipient = other
97+
sent = append(sent, sms)
98+
logger.Debug().
99+
Str("sender", sms.Sender).
100+
Str("recipient", sms.Recipient).
101+
Msg("processed sent message from sync")
102+
} else {
103+
// Message was received by this user
104+
sms.Recipient = callerIdentifier
105+
received = append(received, sms)
106+
logger.Debug().
107+
Str("sender", sms.Sender).
108+
Str("recipient", sms.Recipient).
109+
Msg("processed received message from sync")
110+
}
111+
}
112+
}
113+
114+
logger.Debug().
115+
Int("received_count", len(received)).
116+
Int("sent_count", len(sent)).
117+
Msg("finished processing synced messages")
118+
119+
return received, sent
120+
}
121+
122+
// convertMatrixEventToSMS converts a Matrix message event to an Acrobits SMS format.
123+
// It extracts message metadata and resolves the sender to a phone number if available.
124+
func (s *MessageService) convertMatrixEventToSMS(ctx context.Context, evt *event.Event, roomID id.RoomID) models.SMS {
125+
eventRoomID := evt.RoomID
126+
if eventRoomID == "" {
127+
eventRoomID = roomID
128+
}
129+
130+
body := ""
131+
if b, ok := evt.Content.Raw["body"].(string); ok {
132+
body = b
133+
}
134+
135+
senderMatrixID := string(evt.Sender)
136+
logger.Debug().
137+
Str("event_id", string(evt.ID)).
138+
Str("room_id", string(eventRoomID)).
139+
Str("sender", senderMatrixID).
140+
Msg("converting matrix event to SMS")
141+
142+
return models.SMS{
143+
SMSID: string(evt.ID),
144+
SendingDate: time.UnixMilli(evt.Timestamp).UTC().Format(time.RFC3339),
145+
SMSText: body,
146+
ContentType: "text/plain",
147+
StreamID: string(roomID),
148+
Sender: string(s.resolveMatrixIDToIdentifier(senderMatrixID)),
149+
}
150+
}
151+
152+
// updateBatchToken stores the next batch token from a sync response for incremental syncing.
153+
func (s *MessageService) updateBatchToken(userID string, nextBatch string) {
154+
if nextBatch != "" {
155+
s.setBatchToken(userID, nextBatch)
156+
logger.Debug().Str("user_id", userID).Str("next_batch", nextBatch).Msg("stored next batch token")
157+
}
158+
}

0 commit comments

Comments
 (0)