Skip to content

Commit

Permalink
Merge pull request #3108 from the-events-calendar/slr/fix-session-replay
Browse files Browse the repository at this point in the history
SLR - Fix replay session issue
  • Loading branch information
lucatume authored Jul 15, 2024
2 parents 9bd0c8b + be2dce5 commit 648979b
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 101 deletions.
31 changes: 24 additions & 7 deletions src/Tickets/Seating/Frontend/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,23 +199,40 @@ public function remove_entry( int $post_id, string $token ): bool {
*
* @since TBD
*
* @param int $object_id The object ID to delete the sessions for.
* @param int $object_id The object ID to delete the sessions for.
* @param string $token The token to cancel the previous session for.
*
* @return bool Whether the previous sessions were deleted or not.
*/
public function cancel_previous_for_object( int $object_id ): bool {
public function cancel_previous_for_object( int $object_id, string $token ): bool {
if ( ! isset( $_COOKIE[ self::COOKIE_NAME ] ) ) {
return true;
}

foreach ( $this->get_entries() as $entry_object_id => $entry_token ) {
if ( $entry_object_id === $object_id ) {
$reservations = $this->sessions->get_reservations_for_token( $entry_token );
foreach ( $this->get_entries() as $entry_object_id => $cookie_token ) {
if ( $entry_object_id !== $object_id ) {
continue;
}

$reservations = $this->sessions->get_reservations_for_token( $cookie_token );

if ( $token === $cookie_token ) {
/*
* A new session with the same token, e.g. when the seat selection modal is opened again after
* closing it or cancelling the seat selection. Do not delete the token session, but cancel
* its previous reservations.
*/
return $this->reservations->cancel( $entry_object_id, $reservations )
&& $this->sessions->delete_token_session( $entry_token )
&& $this->remove_entry( $entry_object_id, $entry_token );
&& $this->sessions->clear_token_reservations( $token );
}

/*
* Start with a new token, e.g. on page reload where a new ephemeral token will be issued for the
* seat selection modal. Cancel the session and reservations for the previous token.
*/
return $this->reservations->cancel( $entry_object_id, $reservations )
&& $this->sessions->delete_token_session( $cookie_token )
&& $this->remove_entry( $entry_object_id, $cookie_token );
}

// Nothing to clear.
Expand Down
2 changes: 1 addition & 1 deletion src/Tickets/Seating/Frontend/Timer.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ public function ajax_start(): void {
$timeout = $this->get_timeout( $post_id );

// When starting a new session, we need to remove the previous sessions for the same post.
$this->session->cancel_previous_for_object( $post_id );
$this->session->cancel_previous_for_object( $post_id, $token );

// We're in the context of an XHR/AJAX request: the browser will set the cookie for us.
$now = microtime( true );
Expand Down
241 changes: 162 additions & 79 deletions src/Tickets/Seating/Tables/Sessions.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use TEC\Common\StellarWP\DB\DB;
use TEC\Common\StellarWP\Schema\Tables\Contracts\Table;
use TEC\Tickets\Seating\Logging;

/**
* Class Sessions.
Expand All @@ -21,6 +22,7 @@
*/
class Sessions extends Table {
use Truncate_Methods;
use Logging;

/**
* The schema version.
Expand Down Expand Up @@ -75,13 +77,26 @@ class Sessions extends Table {
* @return int The number of expired sessions removed.
*/
public static function remove_expired_sessions(): int {
$query = DB::prepare(
'DELETE FROM %i WHERE expiration < %d',
self::table_name(),
time()
);
try {
$query = DB::prepare(
'DELETE FROM %i WHERE expiration < %d',
self::table_name(),
time()
);

return (int) DB::query( $query );
} catch ( \Exception $e ) {
( new self() )->log_error(
'Failed to remove expired sessions.',
[
'source' => __METHOD__,
'code' => $e->getCode(),
'error' => $e->getMessage(),
]
);

return (int) DB::query( $query );
return 0;
}
}

/**
Expand Down Expand Up @@ -121,18 +136,32 @@ protected function get_definition() {
* @return bool|int The number of rows affected, or `false` on failure.
*/
public function upsert( string $token, int $object_id, int $expiration_timestamp ) {
$query = DB::prepare(
'INSERT INTO %i (token, object_id, expiration) VALUES (%s, %d, %d)
ON DUPLICATE KEY UPDATE object_id = %d, expiration = %d',
self::table_name(),
$token,
$object_id,
$expiration_timestamp,
$object_id,
$expiration_timestamp
);

return DB::query( $query ) !== false;

try {
$query = DB::prepare(
'INSERT INTO %i (token, object_id, expiration) VALUES (%s, %d, %d)
ON DUPLICATE KEY UPDATE object_id = %d',
self::table_name(),
$token,
$object_id,
$expiration_timestamp,
$object_id
);

return DB::query( $query ) !== false;
} catch ( \Exception $e ) {
$this->log_error(
'Failed to upsert the session.',
[
'source' => __METHOD__,
'code' => $e->getCode(),
'token' => $token,
'error' => $e->getMessage(),
]
);

return false;
}
}

/**
Expand All @@ -145,13 +174,27 @@ public function upsert( string $token, int $object_id, int $expiration_timestamp
* @return int The number of seconds left in the timer.
*/
public function get_seconds_left( $token ): int {
$query = DB::prepare(
'SELECT expiration FROM %i WHERE token = %s',
self::table_name(),
$token
);

$expiration = DB::get_var( $query );
try {
$query = DB::prepare(
'SELECT expiration FROM %i WHERE token = %s',
self::table_name(),
$token
);
$expiration = DB::get_var( $query );
} catch ( \Exception $e ) {
$this->log_error(
'Failed to get the seconds left for the token.',
[
'source' => __METHOD__,
'code' => $e->getCode(),
'token' => $token,
'error' => $e->getMessage(),
]
);

return 0;
}

if ( empty( $expiration ) ) {
// Either the token is not found or the session has expired and was removed.
Expand All @@ -171,13 +214,27 @@ public function get_seconds_left( $token ): int {
* @return string[] The list of reservations for the given object ID.
*/
public function get_reservations_for_token( string $token ) {
$query = DB::prepare(
'SELECT reservations FROM %i WHERE token = %s ',
self::table_name(),
$token
);

$reservations = DB::get_var( $query );
try {
$query = DB::prepare(
'SELECT reservations FROM %i WHERE token = %s ',
self::table_name(),
$token
);
$reservations = DB::get_var( $query );
} catch ( \Exception $e ) {
$this->log_error(
'Failed to get the reservations for the token.',
[
'source' => __METHOD__,
'code' => $e->getCode(),
'token' => $token,
'error' => $e->getMessage(),
]
);

return [];
}

if ( empty( $reservations ) ) {
return [];
Expand Down Expand Up @@ -207,34 +264,49 @@ public function update_reservations( string $token, array $reservations ): bool
return false;
}

/*
* The UPDATE operation will return the number of updated rows.
* A value of 0 means that the row was either not found, or it did not need to be updated.
* We want to fail the update if the row did not exist in the first place.
*/
$exists = DB::get_var(
DB::prepare(
'SELECT token FROM %i WHERE token = %s',
try {
/*
* The UPDATE operation will return the number of updated rows.
* A value of 0 means that the row was either not found, or it did not need to be updated.
* We want to fail the update if the row did not exist in the first place.
*/
$exists = DB::get_var(
DB::prepare(
'SELECT token FROM %i WHERE token = %s',
self::table_name(),
$token
)
);

if ( empty( $exists ) ) {
return false;
}

/*
* The result of this query might be 0 to indicate that the row was not updated.
* We want to fail the update if the row was not updated.
*/
return DB::update(
self::table_name(),
$token
)
);
[ 'reservations' => $reservations_json ],
[ 'token' => $token ],
[ '%s' ],
[ '%s' ]
) !== false;
} catch ( \Exception $e ) {
$this->log_error(
'Failed to update the reservations for the token.',
[
'source' => __METHOD__,
'code' => $e->getCode(),
'token' => $token,
'error' => $e->getMessage(),
]
);


if ( empty( $exists ) ) {
return false;
}

/*
* The result of this query might be 0 to indicate that the row was not updated.
* We want to fail the update if the row was not updated.
*/
return DB::update(
self::table_name(),
[ 'reservations' => $reservations_json ],
[ 'token' => $token ],
[ '%s' ],
[ '%s' ]
) !== false;
}

/**
Expand All @@ -247,13 +319,27 @@ public function update_reservations( string $token, array $reservations ): bool
* @return bool Whether the sessions werer deleted or not.
*/
public function delete_token_session( string $token ): bool {
$query = DB::prepare(
'DELETE FROM %i WHERE token = %s',
self::table_name(),
$token
);
try {
$query = DB::prepare(
'DELETE FROM %i WHERE token = %s',
self::table_name(),
$token
);

return DB::query( $query ) !== false;
} catch ( \Exception $e ) {
$this->log_error(
'Failed to delete the sessions for the token.',
[
'source' => __METHOD__,
'code' => $e->getCode(),
'token' => $token,
'error' => $e->getMessage(),
]
);

return DB::query( $query ) !== false;
return false;
}
}

/**
Expand All @@ -266,29 +352,26 @@ public function delete_token_session( string $token ): bool {
* @return bool Whether the reservations were cleared or not.
*/
public function clear_token_reservations( string $token ): bool {
/*
* The UPDATE operation will return the number of updated rows.
* A value of 0 means that the row was either not found, or it did not need to be updated.
* We want to fail the update if the row did not exist in the first place.
*/
$exists = DB::get_var(
DB::prepare(
'SELECT token FROM %i WHERE token = %s',
try {
$query = DB::prepare(
"UPDATE %i SET reservations = '' WHERE token = %s",
self::table_name(),
$token
)
);
);

return DB::query( $query ) !== false;
} catch ( \Exception $e ) {
$this->log_error(
'Failed to clear the reservations for the token.',
[
'source' => __METHOD__,
'code' => $e->getCode(),
'token' => $token,
'error' => $e->getMessage(),
]
);

if ( empty( $exists ) ) {
return false;
}

$query = DB::prepare(
"UPDATE %i SET reservations = '' WHERE token = %s",
self::table_name(),
$token
);

return DB::query( $query ) !== false;
}
}
5 changes: 4 additions & 1 deletion src/Tickets/Seating/app/frontend/session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ function startHealthCheckLoop() {
healthCheckLoopId = setTimeout(async () => {
await syncWithBackend();
startHealthCheckLoop();
}, 60 * 1000);
}, 3 * 1000);
}

/**
Expand Down Expand Up @@ -381,6 +381,9 @@ export async function syncWithBackend() {
}

startCountdownLoop(secondsLeft);
if (!healthCheckLoopId) {
startHealthCheckLoop();
}
}

/**
Expand Down
Loading

0 comments on commit 648979b

Please sign in to comment.