Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLR - Fix replay session issue #3108

Merged
merged 5 commits into from
Jul 15, 2024
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
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
Loading